系统移植(系统移植环境搭建及简单概念)

linux系统 = linux内核 + 根文件系统;
根文件系统 = 库(/lib和/usr/lib) + 命令( /bin和/usr/bin) + 配置文件( /etc) + GUI(图形化界面,如:QT)

1、为什么学习系统移植

为后边学习linux驱动开发打基础
linux驱动开发:基于linux内核提供的接口,完成硬件驱动的开发。

2、系统移植的目的

给开发板移植一个linux操作系统

3、如何学习系统移植

  1. 系统移植都是流程化的操作,重点掌握系统移植的流程
  2. 动手操作
  3. 不要深追代码

4、GNU组织相关命令的扩展(二进制工具)

  1. gcc / g++ (重点)
  2. gdb (重点)
  3. ld (重点)
    将.o文件链接生成.elf文件
  4. objdump (重点)
    将.elf文件进行反汇编生成.dis反汇编文件
arm-none-linux-gnueabi-gcc 1.c  //生成elf文件
arm-none-linux-gnueabi-objdump -D a.out >1.dis
  1. objcopy (重点)
    将.elf文件编译生成一个纯粹的二进制.bin文件
arm-none-linux-gnueabi-gcc 1.c  //生成elf文件
arm-none-linux-gnueabi-objcopy -O binary a.out >1.bin 
  1. nm (了解)
    查看elf文件符号表
  2. size (了解)
    查看文件各个段所占空间的大小
  3. readelf (了解)
    查看elf文件的头部信息
  4. strip (重点)
    对文件进行压缩,缩小文件的体积。
    注意:不可以对中间文件进行压缩(比如:***.o)
  5. addr2line (重点)
    根据地址信息,定位错误所在位置。

如果是交叉编译工具链(在命令的前边添加交叉编译工具链对应的前缀)
比如:arm-none-linux-gnueabi-

	以ARM阶段,汇编点灯的代码为例进行gnu命令的演示:
	//只编译不链接
	arm-none-linux-gnueabi-gcc -c led_asm.S -o led_asm.o
	//链接到代码段的0x43c00000这个地址上
	arm-none-linux-gnueabi-ld -Ttext=0x43c00000 led_asm.o -o led_asm.elf
	
	//生成反汇编文件,重定向到led_asm.dis(若不重定向则默认输出到终端)
	arm-none-linux-gnueabi-objdump -D led_asm.elf > led_asm.dis
	
	//生成.bin二进制文件
	arm-none-linux-gnueabi-objcopy -O binary led_asm.elf led_asm.bin
	
	//查看led_asm.elf文件符号表 (地址    loop等),查看有哪些函数名等
	arm-none-linux-gnueabi-nm led_asm.elf 
	
	//查看各个段的文件大小
	arm-none-linux-gnueabi-size led_asm.elf 
	
	//查看文件的头部信息(起始地址,ARM架构等等)
	arm-none-linux-gnueabi-readelf -h led_asm.elf 
	
	//压缩文件
	arm-none-linux-gnueabi-strip led_asm.elf 
	
	//在interface.elf中0x43c016f8这个地址对应的函数名和行号
	arm-none-linux-gnueabi-addr2line 0x43c016f8 -e interface.elf -f
	---->
	hal_gpio_init
	/home/arm/02led_c/src/led.c:11
	0x43c016f8 : 错误信息的地址 
	-e  ***.elf :  指令elf文件 
	-f  : 回显函数的名字和行号
	使用场合:常用于调试内核中段错误和野指针。(可以通过backtrace函数获取错误信息的地址)

5、ubuntu开发环境的搭建

5.1. 安装交叉编译工具链

注意:
1.在实际的开发中,不同的硬件平台,使用的交叉编译工具链的版本可能不一致。
2.uboot源码或linux内核源码的版本不一致,可能交叉编译工具链的版本也不一致。

5.2. 安装tftp服务

tftp:基于TCP/IP的简单文本传输协议。
Trivial File Transfer Protocol:
基于TCP/IP协议的一个用来在客户机与服务器之间进行简单文件传输的协议。
作用:基于网络,使用tftp服务下载程序到开发板。
tftp服务安装的具体步骤:

1. 检查ubuntu是否安装了tftp服务(可以忽略,因为大部分没有安装)
		sudo dpkg -s tftpd-hpa
		打印以下内容表示安装了tftp服务:
		Architecture: i386
		Source: tftp-hpa
		Version: 5.2-7ubuntu3.1
2. 安装tftp服务 (前提:ubuntu必须能连接外网)		
		安装tftp服务的命令:
		sudo apt-get install tftpd-hpa tftp-hpa 
		如果安装失败,可以更新源和依赖:
		sudo apt-get update    ---> 更新源
		sudo apt-get install -f  ---> 跟新依赖
	
3. 配置tftp服务
		1>. 在家目录下创建一个tftpboot文件夹
			mkdir tftpboot 
			目的:tftpboot目录下存放的是你要下载到
			开发板上的可执行文件
		2>. 修改tftpboot的权限
			chmod 777 tftpboot
		3>. 配置tftp服务的环境变量
			打开sudo vi /etc/default/tftpd-hpa 
			修改以下内容:
			  1 # /etc/default/tftpd-hpa
			  2 
			  3 TFTP_USERNAME="tftp" 
					# tftp用户名,不需要修改
			  4 TFTP_DIRECTORY="/home/linux/tftpboot"
					# tftp服务下载文件的存放的路径,需要修改
					# 改成自己的对应的tftpboot的路径
			  5 TFTP_ADDRESS="0.0.0.0:69"
					# tftp服务默认使用的69端口号
			  6 TFTP_OPTIONS="-c -s -l"  
					# tftp服务的参数,这个需要修改
					# -l:以standalone/listen模式启动TFTP服务
					# -c:可创建新文件。默认情况下,
					#	TFTP只允许覆盖原有文件,不能创建新文件。
					# -s:改变TFTP启动的根目录。
					#	加了-s后,客户端使用TFTP时,
					#	不再需要输入指定目录,填写文件的完整路径,
					#	而是使用配置文件中写好的目录。
					#	这样也可以增加安全性。
	
4. 重启tftp服务(只需要执行以下命令中的一个即可)
		1. sudo service tftpd-hpa start  启动TFTP服务
		2. sudo service tftpd-hpa restart  重启TFTP服务(默认执行重启)
		
		注意:
		1. 有时一旦重启系统或者长时间不使用tftp服务,都需要重启tftp服务
		2. 只需修改了tftp的配置文件就需要重启tftp服务
		
5. 本地测试tftp服务是否安装成功
		// 使用本地tftp客户端,连接tftp本地的服务器
		$ tftp 127.0.0.1  # 本地回环测试IP地址
		tftp> get 1.c   # 从tftpboot目录下,下载1.c文件到当前目录 
						  # 注:需要在tftpboot目录下创建1.c
		tftp> put 2.txt   # 把当前目录中的2.c文件,上传到tftpboot文件夹中
						  # 注:需要在当前目录下需要创建2.c
	
		tftp> q   <回车>  退出
	
6. 可能出现的问题
		下载或上传是,一直卡,
		原因:
			1. tftp服务安装成功,需要重启tftp服务
			2. tftp服务安装不成功,重新安装tftp服务
			3. tftp安装成功,检查/etc/default/tftpd-hpa环境变量修改是否正确,如果修改环境变量,必须重启tftp服务
			4. 关闭windows和ubuntu的防火墙(ubuntu的防火墙默认都是关闭的)sudo ufw disable
tftp服务的作用 参考下图 

系统移植(系统移植环境搭建及简单概念)_第1张图片

5.3.安装nfs服务

nfs:Network File System(网络文件系统)
作用:让开发板通过网络的方式远程从服务器端挂载根文件系统
ubuntu安装nfs服务器的步骤

1. 检查nfs服务是否安装
		sudo dpkg -s nfs-kernel-server
2. 安装nfs服务(前提:可以上网)
		sudo apt-get install nfs-kernel-server
3. 配置nfs服务
		1>在家目录下创建nfs文件夹
			mkdir nfs 
		2>设置文件夹的权限最大
			chmod 777 nfs 
		3>拷贝根文件系统到nfs目录下
			根文件系统一会发给你们(rootfs-ok.tar.bz2)
			cp /mnt/hgfs/share/rootfs-ok.tar.bz2 ~/nfs
		4>对根文件系统的压缩包进行解压缩
			cd ~/nfs
			tar -vxf rootfs-ok.tar.bz2
		5>配置nfs服务的环境变量
			sudo vi /etc/exports
			在文件的最后一行添加以下内容:
			/home/linux/nfs/rootfs/ *(rw,sync,no_subtree_check,no_root_squash) 
			
			解析:
			/home/linux/nfs/rootfs/:自己的根文件系统的路径
							需要修改为自己的路径
			*:所有的用户,
					注:*和后边的左括号"("之间不可以出现空格.
			rw:可读可写的权限
			sync:同步文件
			no_subtree_check:不对子目录检查文件的权限
			no_root_squash:如果客户端为root用户,那么他对整个文件具有root的权限
			
			注意:这段话前边不要加#,#号是这个文件中的注释符号
			
4. 重启nfs服务(任选其一)
		1. sudo service nfs-kernel-server start  启动nfs服务
		2. sudo service nfs-kernel-server restart  重启nfs服务
			
5. 本地测试nfs服务是否安装成功
		1>回到家目录下
			cd ~
		2>sudo mount -t nfs 本机IP地址:/home/linux/nfs/rootfs/ /mnt
		nfs:使用nfs服务,将本机IP地址:/home/linux/nfs/rootfs/文件挂载到/mnt目录下
		3>检查/mnt目录下是否挂载成功
			cd /mnt 
			ls 
		4>卸载挂载的文件
			sudo umount /mnt
			注意:不可以在/mnt目录下执行卸载的命令
			
nfs服务的作用参考图:如下

系统移植(系统移植环境搭建及简单概念)_第2张图片

6、 bootloader概念

boot : 引导
loader : 加载
bootloader : 一系列引导加载系统启动的程序的统称。
比如:
u-boot属于bootloader中的一种引导程序
BIOS属于bootloader中的一种引导程序
嵌入式开发中使用最多的是u-boot

7、u-boot特点

德国DENX软件工程中心负责维护u-boot源码。

  1. u-boot是一个开源的软件
  2. u-boot支持多种架构的硬件平台
    arm x86 mips powerPC …
  3. u-boot源码短小精悍
  4. u-boot是一个短命鬼
    u-boot的主要作用是引导加载linux内核或者应用程序启动。
    一旦内核或应用程序启动,uboot的使命就结束了。
  5. u-boot源码本质就是一个裸机程序
  6. u-boot启动时,主要完成部分硬件的初始化;比如:内存,EMMC, 时钟,串口等
  7. u-boot主要用于引导linux内核启动,并给内核传递必要的启动参数。

8、u-boot命令

8.1 help

help 查看uboot支持的所有的命令

	如:(列举部分)
	uboot命令 	uboot命令的描述
	loadb   	- load binary file over serial line (kermit mode)
	loadbmp 	- load bmpfile with command or 'bootlog' environment 
	loadx   	- load binary file over serial line (xmodem mode)
	loady   	- load binary file over serial line (ymodem mode)
查看u-boot命令的帮助手册 
	help  u-boot命令
	如:help loadb

8.2 loadb

作用: 通过串口的方式下载二进制文件到内存中、
用法: loadb+下载的地址+波特率(可省)

8.3 go

作用: 从内存的某个地址运行程序
用法: go+地址

8.4 printenv / print / pri

作用:打印u-boot默认的环境变量
u-boot解析命令时,是部分字符的比较,所以用printenv和print和pri都可以

	baudrate=115200    # 串口波特率
	bootdelay=3        # 倒计时的时间
	gatewayip=192.168.5.1  # 开发板的网关
	ipaddr=192.168.5.222   # 开发板的IP地址
	netmask=255.255.255.0  # 开发板的子网掩码
	serverip=192.168.5.250 # ubuntu系统的IP地址
	stderr=serial   # 标准错误
	stdin=serial    # 标准输入
	stdout=serial   # 标准输出

8.5 uboot的环境变量的 增 删 改

setenv : 设置环境变量,默认在内存中(断电就丢失)
saveenv :保存环境变量,将内存中的环境变量保存到EMMC中(断电不会丢失)

8.5.1 添加新的环境变量:

setenv 新的环境变量名 变量的值
saveenv
注意:

  1. 环境变量名和变量值之间的等号会自动填充
  2. 环境变量名之间不允许出现空格(setenv no zuo no die 4444----->no=zuo no die 4444)
eg: 
	setenv target s5p6818
	saveenv

8.5.2 修改环境变量

setenv 要修改的环境变量名 变量的值
saveenv

eg: 
	setenv bootdelay 5
	注: 不要将bootdelay的值改成0

8.5.3 删除环境变量

setenv 要删除的环境变量名
saveenv

eg: 
	setenv  target 

8.6 md

作用: 回显一块连续空间内存地址中的值
格式 : md mem_addr (内存地址)

eg: md 0xC001A000

8.7 nm

作用: 修改某个内存地址中的内容

案例: 熄灭红色LED灯
	GPIOA28 
	0xC001A024[25:24] = 00
	0xC001A004[28] = 1
	0xC001A000[28] = 0
------------------------------------------------
	FS6818# nm 0xc001a024
	  地址   地址中的值  输入的值
	c001a024: 74555555 ? 74555555   回车
	c001a024: 74555555 ? q          回车 :退出
	FS6818# nm 0xc001a004
	c001a004: 00000000 ? 10000000
	c001a004: 10000000 ? q
	FS6818# nm 0xc001a000
	c001a000: 10000000 ? 00000000
	c001a000: 00000000 ? 10000000
	c001a000: 10000000 ? 00000000

8.8 ping

用法: ping pingAddress(地址)
测试开发板是否可以和ubuntu的服务器ping通

8.9 tftpboot 命令

格式:tftpboot [loadAddress] [bootfilename]
作用:使用tftp服务下载程序bootfilename到内存loadAddress中

练习ping和tftp命令的使用

[准备工作]
	1>. ubuntu系统必须安装tftp服务
	2>. 关闭windows和ubuntu的防火墙
	3>. 设置有线网卡为百兆全双工
	控制面板->网络和Internet->网络和共享中心->
	更改适配器设置->以太网(USB转网卡)->属性->
	配置->高级->Speed & Duplex ->100M full Duplex
	每个人的电脑配置不同,可能略有差距。
[Target和PC端硬件连接]
		 pc					Target
	  ---------- 		   ----------
	 |          |         |          |
	 |          |   电源--|          |
	 |          |         |          |
	 |			|--串口线-|          |
	 |          |         |          |
	 |          |--网线---|          |
	 |          |         |          |
	  ----------           ----------
PC端和开发板进行网线连接的方式:参考下图

系统移植(系统移植环境搭建及简单概念)_第3张图片

[配置ubuntu的网络]
	ubuntu必须使用桥接的模式
	具体的配置过程参考下图
注意:
1.在虚拟网络编辑器中:
桥接到不能选择自动,因为若选择自动,则可能会选用wifi,无法与开发板ping通
桥接应该选择Realtek PCIe GBE Family Controller(本地网卡,在windows的网络共享中心-->以太网看),但是此时虚拟机会没有网,但是无关紧要,只要开发板可以ping通即可
2.开发板要和ubuntu链接,必须是桥接模式(同理主机间的通信也必须是桥接模式,net为网络共享模式,不可进行跨主机通信)
3.开发板和主机必须在同一局域网下,主机号不同
4.在日常使用桥接模式给ubuntu连网时,注意在虚拟网络编辑器中选择桥接到自动,且在虚拟机-->设置-->网络适配器-->桥接模式

系统移植(系统移植环境搭建及简单概念)_第4张图片

[配置开发板的网络]
	配置开发板的网络就是配置u-boot的以下几个环境变量。
	gatewayip=192.168.5.1  # 开发板的网关
	ipaddr=192.168.5.222   # 开发板的IP地址
	netmask=255.255.255.0  # 开发板的子网掩码
	serverip=192.168.5.250 # ubuntu系统的IP地址		 
IP地址的网段,和IP地址根据自己的需要进行配置
	
配置过程演示:(在串口工具上执行)
	FS6818# setenv gatewayip 192.168.1.1
	FS6818# setenv netmask 255.255.255.0
	FS6818# setenv ipaddr 192.168.1.222
	FS6818# setenv serverip 192.168.1.250
	FS6818# saveenv

使用ping命令测试开发板是否可以ping通ubuntu系统

FS6818# ping 192.168.1.250
出现以下信息,表示开发板可以ping通ubuntu系统:
	dwmac.c0060000 Waiting for PHY auto negotiation to complete..... done
	Speed: 100, full duplex
	Using dwmac.c0060000 device
	host 192.168.1.250 is alive
无法ping通的打印信息如下:
	FS6818# ping 192.168.1.250
	dwmac.c0060000 Waiting for PHY auto negotiation to complete......... TIMEOUT !
	Waiting for PHY realtime link...... TIMEOUT !
	done
	dwmac.c0060000: No link.
	ping failed; host 192.168.1.250 is not alive
注意:开发板无法ping通ubuntu的可能原因?
	1. windows和ubuntu的防火墙
	2. ubuntu网络的配置(桥接模式,网卡的选择,IP地址)
	3. 开发板的网络的配置(u-boot的环境变量)
	4. 检查网线(网线好坏,网线未插,网线松紧)

测试tftp命令下载程序

1. 拷贝ARM阶段某个interface.bin文件到~/tftpboot目录下
2. 使用tftp命令下载程序
		下载的命令:
		FS6818# tftp 0x43c00000 interface.bin
		出现以下信息表示下载成功:
		Speed: 100, full duplex
		Using dwmac.c0060000 device
		TFTP from server 192.168.1.250; our IP address is 192.168.1.222
		Filename 'interface.bin'.
		Load address: 0x43c00000
		Loading: #
				 1.1 MiB/s
		done
		Bytes transferred = 14412 (384c hex)
3. 运行程序 
		FS6818# go 0x43c00000
注意:tftp下载不成功的可能原因?
	1. 开发板无法ping通ubuntu 
	2. tftp安装成功,需要重启tftp服务
		sudo service tftpd-hpa restart 
	3. tftp服务安装不成功
		检查你的配置文件中,存放下载文件的路径。
		一旦修改配置文件,必须重启tftp服务

9、开发板系统的部署的方式

ubootpak.bin : u-boot的镜像文件(ubuntu)
win_ubootpak.bin : u-boot的镜像文件(windows)
uImage : linux内核的镜像
rootfs : 根文件系统
ramdisk.img : 根文件系统的镜像;对rootfs进行打包压缩得到ramdisk.img

9.1 系统的启动过程

开发板上电->启动u-boot->加载内核启动内核->内核启动成功,挂载根文件系统

9.2 开发阶段系统的部署方式

u-boot镜像-->烧写到-> SD/EMMC 
uImage镜像-->使用tftp服务下载到-> 内存
根文件系统 -->使用nfs服务挂载-> ubuntu服务器
(rootfs)

9.3 产品阶段系统的部署方式

u-boot镜像-->烧写到->  SD/EMMC 
uImage镜像-->烧写到->  EMMC
根文件系统 -->烧写到->  EMMC
(ramdisk.img)	
参考如下图

系统移植(系统移植环境搭建及简单概念)_第5张图片

10、烧写u-boot镜像到SD卡/EMMC

10.1 通过windows的工具烧写

通过windows的工具烧写win_ubootpan.bin到SD卡中
打开win32DiskImager.exe—>选中win_uboottpak.bin—>write
参考:"烧写uboot到SD卡中"目录下的文档

10.2 通过ubuntu的sdtool工具烧写

通过ubuntu的sdtool工具制作sd卡启动盘
注意: 必须使用读卡器,不可以使用电脑自带的sd卡卡槽
1> 拷贝sdtool工具到ubuntu中
2> 将sd卡被ubuntu识别(默认是被windows识别)
先格式化sd卡
虚拟机–>设置–>USB控制器–>USB兼容性:USB3.0
虚拟机–>可移动设备–>选择自己的SD卡的名字–>连接
3> 烧写ubootpak.bin到sd卡中
1) 进入sdtool目录下
cd sdtool
2) sdtool目录下的文件分析
s5p6818-sdmmc.sh : 烧写的脚本文件
ubootpak.bin : uboot的镜像文件
3) 烧写ubootpak.bin到sd卡中
sudo ./s5p6818-sdmmc.sh /dev/sdb ubootpak.bin
解释:使用root的方式执行脚本文件
/dev/sdb : SD卡对应的设备文件
ubootpak.bin : uboot的镜像文件
将ubootpak.bin烧写到/dev/sdb设备对应的sd卡中
4> 以sd卡的方式重新启动,进行测试。

10.3. 使用update_mmc命令烧写

使用update_mmc命令将ubootpak.bin烧写到EMMC中
前提:
1> 开发板必须有能够启动的uboot
2> uboot必须支持tftp的命令
步骤:

1> 切换到sd卡的方式启动uboot,进入uboot的交互界面 
2> 将ubootpak.bin拷贝到ubuntu的~/tftpboot目录下 
3> 使用tftp命令烧写ubootpak.bin到内存的0x48000000地址中
		tftp 0x48000000 ubootpak.bin
		如果下载的过程中报权限不够的错误:
		在ubuntu中修改ubootpak.bin文件的权限为777
		chmod 777 ubootpak.bin
4> 将内存中的ubootpak.bin使用update_mmc命令搬移到EMMC中
		update_mmc 2 2ndboot  0x48000000 0x200 0x78000
		解析:
		update_mmc <dev no> <type> <mem> <addr> <length>
			- type :  2ndboot | boot | raw | part 
		<dev no> : 设备号: EMMC的设备号为2
		<type> : 设备分区的类型  2ndboot
		flash=mmc,2:ubootpak:2nd:0x200,0x78000;
		flash=mmc,2:2ndboot:2nd:0x200,0x4000;
		<mem> : 内存的起始地址
		<addr> : EMMC的起始地址,以字节为单位
		<length> : 搬移数据的长度,以字节为单位(必须大于文件的字节数)		
5> 切换到EMMC的启动方式进行测试

思路如下图:
系统移植(系统移植环境搭建及简单概念)_第6张图片

11、完成开发阶段的系统的部署

u-boot镜像  -烧写到->  			   SD/EMMC 
uImage镜像  -使用tftp服务下载到->  内存
根文件系统  -使用nfs服务挂载->     ubuntu服务器
(rootfs)
  1. 启动开发板,进入uboot的交互模式
    FS6818#
  2. 拷贝uImage的镜像文件到ubuntu的~/tftpboot目录下
  3. 使用tftp命令下载uImage到内存的0x41000000地址中
    FS6818# tftp 0x41000000 uImage
  4. 设置uboot中环境变量中的bootargs参数
    (只需要设置一次)
    setenv bootargs root=/dev/nfs nfsroot=192.168.1.250:/home/hqyj/nfs/rootfs tcp,v4 console=/dev/ttySAC0, 115200 init=/linuxrc ip=192.168.1.222
    saveenv
    bootargs环境变量的作用?
bootargs:自启动参数, 给内核传递参数
root=/dev/nfs : 使用nfs的方式挂载根文件系统
nfsroot=192.168.1.250:/home/hqyj/nfs/rootfs 
				: 根文件系统的服务器的IP地址和路径
				: 需要修改位自己的IP地址和路径
tcp,v4 : nfs服务基于tcp协议,使用v4版本
console=/dev/ttySAC0,115200 
			    : 指定使用开发板的串口0,波特率115200
init=/linuxrc 
			    : 系统启动之后运行的1号进行 
ip=192.168.1.222
			    : 开发板的IP地址
  1. 启动内核
    FS6818# bootm 0x41000000
    系统启动成功之后,会自动通过nfs服务的方式从ubuntu中挂载
    根文件系统。

    启动内核的命令:bootm

  2. 编译一个helloworld程序,使用交叉编译工具链进行编译,在开发板上运行应用程序。

在 ubuntu上编写代码,
	使用交叉编译工具链进行编译 
	arm-none-linux-gnueabi-gcc hello.c -o hello
	或者
	arm-none-linux-gnueabi-gcc hello.c
	拷贝可执行文件到根文件系统(~/nfs/rootfs)中。
目标机放在nfs/rootfs中的文件在开发板上也会同步显示
在开发板新建文件也会在nfs/rootfs中新建
ubuntu系统是18.04以上版本需要修改ubuntu中的配置文件:
参考文档:
https://www.jianshu.com/p/10e3245f15f3?tdsourcetag=s_pctim_aiomsg	

12、uboot的启动模式(重点掌握)

  1. 交互模式
    uboot启动之后,在倒计时减到0之前,按下任意键,既可以进入uboot的交互模式,此时可以输入各种uboot命令,和uboot进行交互。
  2. 自启动模式
    uboot启动之后,在倒计时减到0之前,不要按下任何按键,此时,uboot会进入到自启动模式。进入自启动模式之后,会依次执行uboot环境变量中bootcmd参数后边的命令。
    ==bootcmd:自启动命令的环境变量 ==
    设置的格式:
    setenv bootcmd uboot命令1;uboot命令2;…
  3. 设置开发板为自启动的方式通过tftp下载uImage,
    bootm启动uImage。
    setenv bootcmd tftp 0x41000000 uImage;bootm 0x41000000
    saveenv
    开发板重新上电,进入自启动模式,观察视同是否可以启动成功(reboot 开发板重启)

13、产品阶段系统的部署

u-boot镜像  -烧写到->  SD/EMMC 
uImage镜像  -烧写到->  EMMC
根文件系统  -烧写到->  EMMC
(ramdisk.img)	

13.1 uboot中mmc命令的学习

	//mmc info显示当前的MMC设备的信息
	mmc info   	- display info of the current MMC device
	mmc read    addr blk# cnt
	mmc write   addr blk# cnt
	mmc erase   blk# cnt
  1. mmc read addr blk# cnt
	mmc: EMMC读写命令 
	read: 将EMMC中的内容读到内存中
	addr: 读到内存的起始地址
	blk#: EMMC设备的起始块号,读的起始块号
	   flash读写是以块为单位的,一块的大小是512字节
	cnt: 读多少块
	addr,blk#,cnt:都用十六进制数表示
  1. mmc write addr blk# cnt
	mmc: EMMC读写命令 
	write: 将内存中的内容写到EMMC中
	addr: 写到EMMC中数据在内存的起始地址
	blk#: EMMC设备的起始块号,写的起始块号
	   flash读写是以块为单位的,一块的大小是512字节
	cnt: 写多少块
	addr,blk#,cnt:都用十六进制数表示
  1. mmc erase blk# cnt
	mmc: EMMC命令 
	erase: 擦除EMMC中的数据
	blk#: 擦除EMMC中数据的起始块号
	   flash读写是以块为单位的,一块的大小是512字节
	cnt: 擦除多少块
	blk#,cnt:都用十六进制数表示

13.2 步骤

  1. 启动开发板,进入uboot的交互模式
  2. 拷贝uImage镜像和ramdisk.img镜像到~/tftpboot目录下
  3. 下载uImage镜像到EMMC中
    先下载到内存中再搬移到EMMC中
    tftp 0x48000000 uImage
    mmc erase 0x800 0x4000 (先给EMMC的内容擦除,再写入)
    mmc write 0x48000000 0x800 0x4000
  4. 下载ramdisk.img镜像到EMMC中
    tftp 0x48000000 ramdisk.img
    mmc erase 0x20800 0x20800
    mmc write 0x48000000 0x20800 0x20800
  5. 修改bootargs参数,从ram(内存)中挂载根文件系统【之前是从nfs中,但是已经下载下来了也需要告知从哪里挂载】
    setenv bootargs root=/dev/ram rw initrd=0x49000040,0x1000000 rootfstype=ext4 console=/dev/ttySAC0,115200 init=/linuxrc
    saveenv
解析: 
root=/dev/ram :从ram中挂载根文件系统
rw : 可读可写的权限
initrd=0x49000040,0x10000000x49000000 : 根文件系统镜像的起始地址
		:0x40 : 偏移64字节,跳过镜像文件的文件头
		:0x1000000 : 根文件系统的大小
rootfstype=ext4 
		:根文件系统的类型
console=/dev/ttySAC0,115200 
init=/linuxrc 
  1. 从EMMC中启动内核,从ram中挂载根文件系统
    mmc read 0x48000000 0x800 0x4000
    mmc read 0x49000000 0x20800 0x20800
    bootm 0x48000000 0x49000000
    内核的地址 跟文件系统的地址
  2. 设置开发板为自启动的模式,设置bootcmd
    setenv bootcmd mmc read 0x48000000 0x800 0x4000;mmc read 0x49000000 0x20800 0x20800; bootm 0x48000000 0x49000000
    saveenv

面试题

uboot中bootcmd和bootargs环境变量的作用?

bootcmd:自启动命令
		uboot倒计时减到0之前,不按下任意键,uboot会自动解析bootcmd环境变量后边的uboot命令,命令会被依次的执行
bootargs:自启动参数
		用于uboot给内核传递参数,告诉内核一些重要的信息,使用哪种方式挂载根文件系统,从哪里挂载根文件系统,使用哪个串口,串口的波特率等。

14、uboot移植

14.1 uboot源码的获取

1> u-boot官方获取
https://ftp.denx.de/pub/u-boot/
2> 芯片厂家
3> 开发板厂家
4> 公司主管

本次使用的u-boot-2014.07版本,
注: 不可以从uboot官方去下载uboot源码使用,samsung公司没有将对s5p6818芯片的硬件支持,开源到u-boot官方源码中。

14.2 u-boot版本命名方式

u-boot-2021.01-rc2.tar.bz2  测试版
u-boot-2021.01.tar.bz2      稳定版
名字    年月
rc : 测试版

14.3 移植的准备工作

了解移植硬件平台的相关硬件配置信息:
cpu : Cortex-A53 * 8
Arch : arm
Vender : Samsung
SOC : S5P6818
Board : FS6818

14.4 移植uboot源码

  1. 准备u-boot源码
	1>. 拷贝u-boot源码到ubuntu中
		u-boot-2014.07-netok.tar.bz2
	2>. 解压缩
		tar -vxf u-boot-2014.07-netok.tar.bz2
	3>. 进入到u-boot源码目录
		cd u-boot-2014.07
	注:u-boot源码不要在共享目录或者windows下解压,因为在源码中有很多软链接文件,而windows不支持软链接文件
  1. 分析uboot源码的目录结构,大概了解每个目录下放的是什么.
平台相关代码:跟硬件相关的代码
	arch: 架构 --> 不同架构相关的代码
	board: 板子 --> 处理器相关的代码
	
平台无关代码:跟硬件无关的代码
	common: uboot相关的命令
	drivers: 驱动
	fs : 文件系统
	include :头文件
	lib : 库
	net : 网络
	tools : 工具 
	api : 接口
	disk : 存储相关
	doc : 文档
	dts : 设备树
	.....
  1. 对uboot源码进行配置和编译
    配置的目的:让源码支持S5p6818,只编译S5p6818平台相关的代码
    编译的目的:根据配置信息生成ubootpak.bin
    uboot源码的工程的配置和管理使用的是Makefile
1>. 修改uboot源码顶层目录的Makefile,使用交叉编译工具链对uboot源码进行编译。
	 将以下信息:
	 198 ifeq ($(HOSTARCH),$(ARCH))
	 199 CROSS_COMPILE ?= 
	 200 endif
	 修改为:
	 198 ifeq (arm,arm)
	 199 CROSS_COMPILE ?= arm-none-linux-gnueabi-
	 200 endif
	 注:CROSS_COMPILE是指定交叉编译工具的前缀的
	
2> 获取make编译源码时的帮助信息
		/********************
		规则:(Makefile文件中的规则)
		目标:依赖 
			命令
		make  目标(去找目标,如果依赖不存在,则去找依赖如何合成的;如果依赖存在则执行下面的命令)
		*********************/
make help   或者make的帮助信息
		清除目标文件:(从上往下一个比一个清理得干净)
		  clean  eg:make clean
		  clobber	eg:make clobber  
		  mrproper	 ....
		  distclean		....
		编译uboot源码
		  all:根据配置信息编译uboot源码
		For further info see the ./README file(更多信息可以查看readme文件)

3> 读README 
	 //选择处理器的架构和板子的类型 
	 251 Selection of Processor Architecture and Board Type:
	 252 ---------------------------------------------------
	 253 
	 //对于所有的板子我们可以使用默认的配置变量
	 254 For all supported boards there are ready-to-use default
	 //可以使用"make _config"命令来实现让u-boot源码支持板子
	 255 configurations available; just type "make _config".
	 256 
	 //举例:让u-boot支持TQM823L板子
	 //进入 u-boot源码目录,使用命令make TQM823L_config即可
	 257 Example: For a TQM823L module type:
	 258 
	 259     cd u-boot
	 260     make TQM823L_config    

4> 配置u-boot-2014.07源码支持fs6818开发板
		make fs6818_config
		打印以下信息,表示配置成功:
		Configuring for fs6818 board...
		注: 
		1). 配置只需要在编译源码的时候执行一次(最开始的时候执行一次)
		2). 如果执行了make distclean命令,需要重新执行make fs6818_config

5>. 编译u-boot源码 
		方法1:make
		方法2:make all
		方法3:make -j4 all   # 使用4个线程编译源码
		编译成功之后,会在uboot源码的顶层目录生成ubootpak.bin

6> 将自己编译的ubootpak.bin文件烧写到EMMC中,进行测试 

boot :执行bootcmd后边的命令
reset : 复位uboot重新启动
问题总结:
1.启动内核总是重启:
原因:内核启动成功,但是挂载根文件系统失败。
挂载根文件系统失败的原因:
1> 检查bootargs参数设置是否正确
2> 检查nfs的配置是否正确
配置的跟文件系统的路径和bootargs的路径是否一致
3> 重启nfs服务
4> ubuntu是18.04及以上版本,需要修改配置文件
linux内核使用的nfs的版本是v4的,
而ubuntu18.04及以上版本,默认使用的是v5版本。

14.4 移植loadb命令

uboot源码本身已经支持了大多数的uboot命令
1> 思考:uboot源码是否支持loadb命令

		//查找文件:find / -name 1.c(从根目录下查找1.c文件) 
		//使用greb命令搜索“loadb”字符串
		grep "loadb" * -nR
		获取以下信息:
		common/cmd_load.c:389: * loadb command (load binary) included
		common/cmd_load.c:1059:	loadb, 3, 0,	do_load_serial_bin,

2> 打开common/cmd_load.c文件定位到389和1059行

有以下信息:
		1057 #if defined(CONFIG_CMD_LOADB)   
		1058 U_BOOT_CMD(
		1059     loadb, 3, 0,    do_load_serial_bin,
		1060     "load binary file over serial line (kermit mode)",
		1061     "[ off ] [ baud ]\n"
		1062     "    - load binary file over serial line"
		1063     " with offset 'off' and baudrate 'baud'"
		1064 );
		说明uboot源码本事已经支持了loadb的命令

3> 思考2:有可能cmd_load.c没有被编译到ubootpak.bin

		打开common目录下的Makfile,得到以下信息:
		133 obj-y += cmd_load.o  
		all:$(obj-y)
		说明:cmd_loadb.o被编译到ubootpak.bin中

4> 通过对cmd_load.c源码的分析,可能是CONFIG_CMD_LOADB没有被定义,添加一个定义然后重新编译。

		打开include/configs/fs6818.h文件,
		在121行添加以下内容:
		#define CONFIG_CMD_LOADB

5>. 编译u-boot源码

		make
		make all
		make -j4 all   # 使用4个线程编译源码
		编译成功之后,会在uboot源码的顶层目录生成
		ubootpak.bin

6> 将自己编译的ubootpak.bin文件烧写到EMMC中,进行测试
启动开发板,测试loadb命令

14.5 分析make _config执行过长详解

需要具备的能力: Makefile shell脚本

  1. 打开uboot源码顶层目录下的Makefile文件, 搜索_config
	 //目标          依赖
	 467 %_config:: outputmakefile
	 //命令             
	 468     @$(MKCONFIG) -A $(@:_config=)
	 469     @cp net/x6818-eth.mk net/eth.o
解析:
	% :模式匹配(*多个字符匹配,?单个字符匹配)
	@ :命令不在终端回显
	$ : 使用某个变量
技巧:去掉命令前边的@符, 重新执行make fs6818_config
	$(MKCONFIG) : /home/hqyj/20010-20011/porting/u-boot-2014.07/mkconfig 
	$(@:_config=) : fs6818
解析的结果:
/home/hqyj/20010-20011/porting/u-boot-2014.07/mkconfig -A fs6818
使用file命令,查看mkconfig文件的属性,
file mkconfig 可知mkconfig是一个shell脚本文件。
-A fs6818 是给mkconfig传递的两个参数
  1. 打开mkconfig文件,进行分析
	//$#:参数的个数,\是转义字符
	//$1: 第一个参数
	//[ ]: test命令
	//如果参数个数等于2且第一个参数等于-A
	24 if [ \( $# -eq 2 \) -a \( "$1" = "-A" \) ] ; then
	 25     # Automatic mode
	 //``:命令置换符
	 //$srctree/boards.cfg:uboot源码的顶层目录下的板子配置文件
	 //给line变量赋值;
	 //awk命令的含义是:从boards.cfg中一行一行去拿信息(print中的$0(不是#号的),并且$7等于$2(拿到的第二个参数))
	 //最终line=Active arm slsiap s5p6818 
	 //			$1     $2   $3     $4  
//s5p6818 fs6818 fs6818 - 
// $5     $6     $7     $8
	 26     line=`awk '($0 !~ /^#/ && $7 ~ /^'"$2"'$/) { print $1, $2, $3, $4, $5, $6, $7, $8 }' $srctree/boards.cfg`
	 27     if [ -z "$line" ] ; then
	 28         echo "make: *** No rule to make target \`$2_config'.  Stop." >&2
	 29         exit 1
	 30     fi
	 31 
	 //set ${line}  : 重新设置执行mkconfig脚本文件时传递的参数
	 //最开始的参数:mkconfig -A fs6818
	 // 执行set ${line}之后变成
	 //mkconfig Active arm slsiap s5p6818 s5p6818 fs6818 fs6818 - 
	 32     set ${line}
	 33     # add default board name if needed
	 //$# !=3,则与后的条件不会执行
	 34     [ $# = 3 ] && set ${line} ${1}
	 35 fi 
	给几个特殊的变量赋值:
	 50 # Strip all options and/or _config suffixes
	 //给CONFIG_NAME赋值$7:fs6818
	 51 CONFIG_NAME="${7%_config}"
	 52 
	 //判断${BOARD_NAME}是否为空
	 53 [ "${BOARD_NAME}" ] || BOARD_NAME="${7%_config}"
	 54 
	 //$2:arm
	 55 arch="$2"
	 //$3: slsiap
	 56 cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'` 
	 57 spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'` 
	 58  
	 59 if [ "$cpu" = "-" ] ; then 
	 60     cpu= 
	 61 fi 
	 62  
	 63 [ "$6" != "-" ] && board="$6" 
	 64 [ "$5" != "-" ] && vendor="$5" 
	 65 [ "$4" != "-" ] && soc="$4"
	 这几个变量中的值,具体是什么可以通过echo输出变量的值。
	 84 if [ "${ARCH}" -a "${ARCH}" != "${arch}" ]; then
	 85     echo "Failed: \$ARCH=${ARCH}, should be '${arch}' for ${BOARD_NAME}" 1>&2
	 86     exit 1
	 87 fi  没有执行
	 解析: 
	 ARCH = arm 
	 arch = arm
	 96 if [ "$options" ] ; then
	 97     echo "Configuring for ${BOARD_NAME} - Board: ${CONFIG_NAME}, Options: ${options}" 
	 98 else  // 板子配置成功的打印信息
	 99     echo "Configuring for ${BOARD_NAME} board..."
	100 fi
	126 # 创建make时需要的头文件
	127 # Create include file for Make
	128 #
	129 ( echo "ARCH   = ${arch}"
	130     if [ ! -z "$spl_cpu" ] ; then      
	131     echo 'ifeq ($(CONFIG_SPL_BUILD),y)'
	132     echo "CPU    = ${spl_cpu}"
	133     echo "else"
	134     echo "CPU    = ${cpu}"
	135     echo "endif"
	136     else
	137     echo "CPU    = ${cpu}"
	138     fi
	139     echo "BOARD  = ${board}"
	140 
	141     [ "${vendor}" ] && echo "VENDOR = ${vendor}"
	142     [ "${soc}"    ] && echo "SOC    = ${soc}"
	143     exit 0 ) > config.mk
	//把以上很多信息重定向到config.mk文件中
config.mk是Makefile的头文件,
打开include/config.mk文件,有以下信息:
	  1 ARCH   = arm     
	  2 CPU    = slsiap
	  3 BOARD  = fs6818
	  4 VENDOR = s5p6818
	  5 SOC    = s5p6818
Makefile根据config.mk中的变量的信息,决定哪些文件编译,那些不编译。
	152 # 创建板子指定的头文件
	153 # Create board specific header file 
	154 # 
	155 if [ "$APPEND" = "yes" ]    # Append to existing config file 
	156 then 
	157     echo >> config.h 
	158 else 
	159     > config.h      # Create new config file 
	160 fi 
	161 echo "/* Automatically generated - do not edit */" >>config.h 
	162  
	163 for i in ${TARGETS} ; do 
	164     i="`echo ${i} | sed '/=/ {s/=/  /;q; } ; { s/$/ 1/; }'`" 
	165     echo "#define CONFIG_${i}" >>config.h ; 
	166 done 
	167  
	168 echo "#define CONFIG_SYS_ARCH  \"${arch}\""  >> config.h 
	169 echo "#define CONFIG_SYS_CPU   \"${cpu}\""   >> config.h 
	170 echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h 
	171  
	172 [ "${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h 
	173 
	174 [ "${soc}"    ] && echo "#define CONFIG_SYS_SOC    \"${soc}\""    >> config.h
	175 
	176 [ "${board}"  ] && echo "#define CONFIG_BOARDDIR board/$BOARDDIR" >> config.h
	177 cat << EOF >> config.h
	178 #include <config_cmd_defaults.h>
	179 #include <config_defaults.h>
	180 #include <configs/${CONFIG_NAME}.h>
	181 #include <asm/config.h>
	182 #include <config_fallbacks.h>
	183 #include <config_uncmd_spl.h>
	184 EOF
打开include/config.h文件,有以下信息:
	  2 #define CONFIG_SYS_ARCH  "arm"
	  3 #define CONFIG_SYS_CPU   "slsiap"
	  4 #define CONFIG_SYS_BOARD "fs6818"
	  5 #define CONFIG_SYS_VENDOR "s5p6818"
	  6 #define CONFIG_SYS_SOC    "s5p6818"
	  7 #define CONFIG_BOARDDIR board/s5p6818/fs6818
	  8 #include <config_cmd_defaults.h>
	  9 #include <config_defaults.h>
	 10 #include <configs/fs6818.h>  
	 11 #include <asm/config.h>
	 12 #include <config_fallbacks.h>
	 13 #include <config_uncmd_spl.h>
重点关注:configs/fs6818.h头文件,
板子的头文件, 还有一些其他的配置信息都在这个文件中,

14.6 分析make all执行过程详解

  1. 打开Makefile搜索all
 764 all:        $(ALL-y) 
  1. 搜索ALL-y
 712 ALL-y += u-boot.srec u-boot.bin System.map binary_size_check
  1. 搜索u-boot.bin
	 804 u-boot.bin: u-boot FORCE
	 //$@:目标  $<:依赖
	 805     $(call if_changed,objcopy)                                 
	 806     $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
	 807     $(BOARD_SIZE_CHECK)
	 808     ./tools/mk6818 ubootpak.bin nsih.txt 2ndboot u-boot.bin
	分析 ./tools/mk6818  	  ubootpak.bin nsih.txt 2ndboot u-boot.bin
				elf可执行文件  参数
	使用file命令查看mk6818文件的数据,file ./tools/mk6818, 可知mk6818是一个可执行文件
	mk6818可执行文件的功能:根据nsih.txt, 2ndboot和u-boot.bin文件合成ubootpak.bin 
	nsih.txt : 三星提供的代码,不开源,是机器码
	2ndboot : 三星提供的代码,不开源,是机器码
	u-boot.bin : u-boot源码编译生成的,开源

15 、u-boot的启动流程

参考如下图片

系统移植(系统移植环境搭建及简单概念)_第7张图片

16、linux内核的移植

16.1 linux内核的特点

  1. linux内核是开源的,
    作者:linus
    git : 版本管理工具
    源码由C语言和汇编实现
  2. linux内核支持多种硬件架构平台
  3. linux内核采用模块化方式编写,分层思想
  4. linux内核使用C和汇编代码实现,在内核中最好不要使用C++
  5. linux内核具有很好的移植和裁剪特性,通过图形化界面的配置方式就可以完成裁剪

16.2 linux内核源码的获取

  1. linux官方获取
    https://mirrors.edge.kernel.org/pub/linux/kernel/
  2. 芯片厂家获取
  3. 开发板厂家获取
  4. 公司主管获取
    由于三星没有将S5p6818芯片硬件支持开源到linux内核官方
    本次课程不使用linux内核官方的源码。
    本次课程使用:kernel-3.4.39-ok.tar.bz2

16.3 linux内核的版本命名方式

主版本号.次版本号.修订版本号
主版本号:内核源码有较大的改动才会更新主版本号
次版本号:修订版本号用完升级次版本号
偶数:稳定版
奇数:测试版
修订版本号:内核代码只要有更新就会修改修订版本号

16.4 linux内核的配置和编译

  1. 拷贝linux内核源码压缩包到ubuntu中
    1》拷贝内核源码到ubuntu目录中
    kernel-3.4.39.tar.xz
    2》解压缩内核源码
    tar -vxf kernel-3.4.39.tar.xz
    3》进入内核源码目录
    cd kernel-3.4.39
  2. 分析linux内核源码的目录结构
    平台相关代码
    arch
    平台无关代码
    include
    lib
    fs
    drivers
    tools
  3. 获取make的帮助信息
    执行make help ,得到以下帮助信息:
    1> 清除中间文件
    clean
    mrproper
    distclean
    2> 配置内核源码
    menuconfig 通过图形化界面菜单选项的方式配置内核源码
    3> 编译内核源码
    modules 采用模块化的方式编译驱动代码
    uImage 编译内核源码生成uImage镜像
    dtbs 编译设备树文件
    fs6818_defconfig 配置内核支持fs6818开发板
  4. 配置内核源码和编译内核源码
    1》 配置交叉编译工具链,
    打开内核源码顶层目录的Makefile:
    195 ARCH ?=
    196 CROSS_COMPILE ?=
    修改为:
    195 ARCH ?= arm
    196 CROSS_COMPILE ?= arm-none-linux-gnueabi-
    2》执行make distclean清除中间文件
    只需要在第一次编译内核源码是执行一次。
    3》配置linux内核源码支持fs6818硬件平台
    make fs6818_defconfig
    打印以下信息,表示成功:
    #
    # configuration written to .config
    #
    注意:
    如果执行了make distclean命令,
    会清除.config的配置文件,此时需要
    重新执行make fs6818_defconfig
    4》通过图形化界面的方式配置内核
    make menuconfig
	通过菜单图形化界面的方式,
	去掉研发中心添加的外设驱动代码
	Device Drivers  --->
		Character devices  --->
			[ ] FS6818 beep driver  去掉*
			FS6818 board device driver support  --->
				[ ] adc driver for farsight FS6818 all platform  
				[ ] pwm timer driver for farsight FS6818 
				[ ] DS18B20 driver for farsight FS6818  
			FS6818 extension device driver support  ---> 
				< > This is FS6818_LED! 
				[ ] zlg7290 driver support input device
enter是选择键
两次esc是退出键
?是帮助;/是搜索
y是包含,n是不包含,m是模块化
[*]编译到uImage中,[]不编译,<M>是模块化编译,<>不模块化编译
有箭头则可以用enter进入下一级菜单

5》保存退出,编译内核源码
time make -j4 uImage
time: 回显编译的时间

问题1:make menuconfig
		第一次使用make menuconfig 需要安装图形化界面的工具
		配置之前需要安装图形图(make meuconfig):
		sudo apt-get install libncurses5-dev
		sudo apt-get install lib32z1 (64)
问题2 :make menuconfig 
		scripts/kconfig/mconf Kconfig
		Your display is too small to run Menuconfig!
		It must be at least 19 lines by 80 columns.
		make[1]: *** [menuconfig] Error 1
		make: *** [menuconfig] Error 2	
		终端字体太大,缩小终端字体	
		
问题3:make uImage
		在编译的过程中可能出现如下错误:
		"mkimage" command not found - U-Boot images will not be built
		make[1]: *** [arch/arm/boot/uImage] Error 1
		make: *** [uImage] Error 2
		错误的原因:找不到mkimage命令,
			根据提示分析出来mkimage应该存在uboot源码目录中
			uboot源码必须进行编译之后才会有mkimage可执行程序		
		解决问题的方法:
			将uboot源码的tools目录下的mkimage,
			拷贝到到ubuntu的/usr/bin目录下:
			sudo cp ~/uboot源码目录/tools/mkimage   /usr/bin
				      uboot源码目录                ubuntu目录	  
		再次make uImage重新编译内核即可。	

6》 编译成功之后,测试uImage镜像是否可以使用
打印以下信息表示编译成功:

		Image Name:   Linux-3.4.39-farsight
		Created:      Mon Jul 20 15:09:28 2020
		Image Type:   ARM Linux Kernel Image (uncompressed)
		Data Size:    5391608 Bytes = 5265.24 kB = 5.14 MB
		Load Address: 40008000
		Entry Point:  40008000
		uImage内核镜像在以下目录中:
		Image arch/arm/boot/uImage is ready

拷贝uImage到tftpboot目录下
切换开发板的启动方式为开发阶段系统的部署方式
启动linux系统

16.5 分析make fs6818_defconfig执行过程详解

  1. 读内核源码中的README
		"make ${PLATFORM}_defconfig"                      
		Create a ./.config file by using the default
		symbol values from
		arch/$ARCH/configs/${PLATFORM}_defconfig.
		Use "make help" to get a list of all available
		platforms of your architecture.

make fs6818_defconfig命令根据arch/arm/configs/目录下的缺省配置文件fs6818_defconfig,在内核源码的顶层目录下生成.config配置文件。

  1. 打开内核源码的顶层目录下的Makefile文件,搜索config目标
	491 %config: scripts_basic outputmakefile FORCE
	492     $(Q)mkdir -p include/linux include/config
	493     $(Q)$(MAKE) $(build)=scripts/kconfig $@
	解析: 
	$(Q) --> @ 取消在终端的显示
	$@   --> 目标 
	$^   --> 所有的依赖
	$<   --> 第一个依赖
	去掉命令前边的$(Q),重新执行make fs6818_defconfig,
	得到以下执行结果:
	mkdir -p include/linux include/config
	//进入scripts/kconfig目录下继续执行make fs6818_defconfig
	make -f scripts/Makefile.build obj=scripts/kconfig fs6818_defconfig
	$(MAKE) --> make -f 
	$(build) --> scripts/Makefile.build obj
	-f :-f后边的文件当成一个Makefile文件
	obj=scripts/kconfig :执行make时给obj变量赋值
  1. scripts/kconfig 目录下的Makefile文件,搜索_defconfig, 得到以下信息:
	//conf可执行程序根据arch/arm/configs目录下的默认的缺省配置文件fs6818_defconfig和内核源码顶层目录下的Kconfig文件,在内核源码顶层目录下生成.config文件。
	95 %_defconfig: $(obj)/conf
	96     $(Q)$< --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
	解析: 
	$(obj) --> scripts/kconfig
	$(Q)   --> @
	$<     --> scripts/kconfig/conf
	$(SRCARCH) --> arm
	$@     --> fs6818_defconfig
	$(Kconfig) --> Kconfig
	去掉命令前边的$(Q),重新执行make fs6818_defconfig
	scripts/kconfig/conf --defconfig=arch/arm/configs/fs6818_defconfig Kconfig
	conf文件: 
		通过file conf命令可知,conf是一个可执行文件。
	fs6818_defconfig文件:
		缺省的fs6818的配置文件
	Kconfig文件:
		内核源码顶层目录下的Kconfig文件
	可以同时打开.config和fs6818_defconfig文件进行对比,一模一样

16.6 分析make menuconfig执行过程详解。

  1. 打开内核源码的顶层目录下的Makefile文件,搜索config
	491 %config: scripts_basic outputmakefile FORCE
	492     $(Q)mkdir -p include/linux include/config
	493     $(Q)$(MAKE) $(build)=scripts/kconfig $@
	解析: 
	$(Q) --> @
	$@   --> 目标 
	$^   --> 所有的依赖
	$<   --> 第一个依赖
	去掉命令前边的$(Q),重新执行make menuconfig,
	得到以下执行结果:
	mkdir -p include/linux include/config
	make -f scripts/Makefile.build obj=scripts/kconfig menuconfig
	$(MAKE) --> make -f 
	$(build) --> scripts/Makefile.build obj
	-f :-f后边的文件当成一个Makefile文件
	obj=scripts/kconfig :执行make时给obj变量赋值
	进入scripts/kconfig目录下继续执行make menuconfig
  1. scripts/kconfig 目录下的Makefile文件,搜索menuconfig, 得到以下信息:
	 20 menuconfig: $(obj)/mconf                                  
	 21     $< $(Kconfig)
	解析: 
	$(obj)/mconf  --> scripts/kconfig/mconf
	$< --> scripts/kconfig/mconf
	$(Kconfig) --> Kconfig
	解析命令的结果: 
	scripts/kconfig/mconf  Kconfig
	根据file mconf命令可知,mconf是一个可执行程序,
	mconf会调用ncurses库中的接口函数,根据Kconfig
	文件中的菜单选项的配置信息,生成图形化界面的
	菜单选项。
	ncurses : 这个库是一个图形化界面的库
  1. 打开内核源码顶层目录的Kconfig文件。
	  5 mainmenu "Linux/$ARCH $KERNELVERSION Kernel Configuration"
	  6 
	  7 config SRCARCH
	  8     string
	  9     option env="SRCARCH"
	 10 
	 11 source "arch/$SRCARCH/Kconfig"
	mainmenu : 修饰主菜单 
	语法格式: 
		mainmenu "主菜单的名字"

17、.config Kconfig Makfile三个文件的关系

参考下图

18、通过图形化界面菜单选项的方式配置自己的led灯驱动

APP (led灯的应用程序:fs6818led_test.c)
(代码逻辑)
--------------------接口----------

kernel (led灯驱动:fs6818_led.c)
(代码机制)


hal 硬件LED灯

  1. 去掉研发中心在内核源码中添加的外设驱动
	Device Drivers  --->
		Character devices  --->
			[ ] FS6818 beep driver  去掉*
			FS6818 board device driver support  --->
				[ ] adc driver for farsight FS6818 all platform  
				[ ] pwm timer driver for farsight FS6818 
				[ ] DS18B20 driver for farsight FS6818  
			FS6818 extension device driver support  ---> 
				< > This is FS6818_LED! 
				[ ] zlg7290 driver support input device
  1. 拷贝led灯的驱动代码fs6818_led.c和fs6818_led.h文件到内核的drivers/char目录下

  2. 修改drivers/char目录下的Kconfig文件,添加菜单选项。添加的内容如下

	在这个的下边menu "Character devices",
	  添加一下信息: 
	  config HQYJ_LED_DRIVER 
	      bool "20010-20011 led driver"
	      default y
	      help
	      20010-20011 led driver test!
  1. 修改drivers/char目录下的Makefile,添加一下信息:
    obj-$(CONFIG_HQYJ_LED_DRIVER) += fs6818_led.o

  2. 执行make menuconfig, 查看效果
    可以通过图形化界面的方式进行配置,
    查看.config文件中的配置信息。

  3. 执行make uImage编译源码,并进行测试
    1> 出现以下信息表示led灯驱动被编译到uImage中
    CC drivers/char/fs6818_led.o
    2> 拷贝uImage到~/tftpboot目录下
    cp arch/arm/boot/uImage ~/tftpboot
    3> 重启开发板,以开放阶段系统部署的方式启动linux系统,及通过tftp下载uImage,nfs挂载根文件系统
    4> 使用交叉编译工具链编译led灯的应用程序,并将可执行文件拷贝到根文件系统中拷贝day04/led-driver文件夹到ubuntu中,
    进入到led-driver: cd led-driver
    编译:
    arm-none-linux-gnueabi-gcc fs6818led_test.c -o led_test
    拷贝led_test到根文件系统中:
    cp led_test ~/nfs/rootfs

19、使用模块化的方式编译led灯的驱动

bool --> []

	[*]  --> 对应的驱动被编译到uImage中
	[ ]  --> 对应的驱动不被编译

tristate --> <> 三态

	<*>  --> 对应的驱动被编译到uImage中
	< >  --> 对应的驱动不被编译
	<M>  --> 对应的驱动采用模块化的方式进行编译,使用make modules命令编译模块化的驱动程序,将***.c编译生成***.ko 

模块化的驱动程序如何使用(***.ko)?
需要使用驱动时,通过insmod命令加载***.ko文件加载到内核中,
不需要使用驱动时,可以通过rmmod命令,将驱动文件从内核中进行卸载。

  1. 去掉研发中心在内核源码中添加的外设驱动
	Device Drivers  --->
		Character devices  --->
			[ ] FS6818 beep driver  去掉*
			FS6818 board device driver support  --->
				[ ] adc driver for farsight FS6818 all platform  
				[ ] pwm timer driver for farsight FS6818 
				[ ] DS18B20 driver for farsight FS6818  
			FS6818 extension device driver support  ---> 
				< > This is FS6818_LED! 
				[ ] zlg7290 driver support input device
  1. 拷贝led灯的驱动代码fs6818_led.c和fs6818_led.h
    文件到内核的drivers/char目录下

  2. 修改drivers/char目录下的Kconfig文件,添加菜单选项。添加的内容如下

	在这个的下边menu "Character devices",
	  添加一下信息: 
	  config HQYJ_LED_DRIVER 
	      tristate "20010-20011 led driver"
	      default y
	      help
	      20010-20011 led driver test!
  1. 修改drivers/char目录下的Makefile,添加一下信息:
    obj-$(CONFIG_HQYJ_LED_DRIVER) += fs6818_led.o
  2. 执行make menuconfig, 查看效果
    可以通过图形化界面的方式进行配置,查看.config文件中的配置信息。
	Device Drivers  --->
		Character devices  ---> 
			<M> 20010-20011 led driver (配置为M)
	查看.config文件中的配置信息:
	CONFIG_HQYJ_LED_DRIVER=m :表示采用模块化的方式编译驱动
	可以使用搜索的功能,搜索CONFIG_后边的内容
  1. 使用 make uImage编译内核源码重新生成uImage镜像文件
	目的:去掉上一次编译内核源码是uImage中的led灯驱动
	拷贝新生成的uImage到~/tftpboot目录下.
	cp arch/arm/boot/uImage ~/tftpboot 
  1. 使用模块化的命令编译驱动
    make modules
	出现以下信息表示编译成功:
	LD [M]  drivers/char/fs6818_led.ko
  1. 测试led灯的模块化程序
    1> 拷贝fs6818_led.ko到根文件系统中
    cp drivers/char/fs6818_led.ko ~/nfs/rootfs
    2> 启动开发板,
    3> 使用insmod命令加载驱动
    insmod fs6818_led.ko
    4> 使用lsmod查看加载的驱动
    5> 运行应用程序
    ./led_test
    6> 使用rmmod命令卸载驱动
    rmmod fs6818_led
    出现以下问题:
    rmmod: can’t change directory to ‘/lib/modules’: No such file or directory
    解决办法:
    在lib目录下创建modules目录
    mkdir modules
    再次卸载:rmmod fs6818_led
    出现以下问题:
    rmmod: can’t change directory to ‘3.4.39-farsight’: No such file or directory
    解决办法:
    在lib/modules目录下创建3.4.39-farsight目录
    mkdir 3.4.39-farsight
    再次卸载:rmmod fs6818_led 成功

20、总结:模块化开发的好处

1. 缩小uImage内核镜像的体积
2. 提高linux驱动开发的效率
3. 提高排错的能力
在实际linux驱动开发中,都是采用模块化的方式进行编译
insmod  加载驱动  
lsmod   查看驱动
rmmod 	删除驱动
mknod   在/dev创建设备节点
mknod  /dev/设备文件名  c/b  主设备号  次设备号
解析:
/dev/设备文件名  -->设备文件名
c -->字符设备文件
b -->块设备文件
主设备号 : 0-2^12
次设备号 : 0-2^20

21、内核的调试手段

1. printk 
2. kgdb 
3. addr2line  (GNU中的工具)
	实验13:内核调试.docx

面试题

linux内核启动过程主要干了那些事?
1> uboot通过tftp命令将uImage下载到内存中
2> uImage需要完成自解压
3> 获取cpu的ID号,创建页表,
初始化MMU,完成物理地址到虚拟地址的映射
4> 清除BSS段
5> 完成绝大多数硬件的初始化,进一步对硬件初始化
内存,时钟,串口,EMMC,nfs客户端…
5> 从u-boot环境变量的内存分区获取bootargs参数,
根据bootargs参数,决定从哪里挂载根文件系统。
6> 挂载根文件系统,
7> 执行根文件系统中的1号进程,linuxrc程序
8> 到此开发板的linux系统启动成功

22、根文件系统的移植

22.1 概念

根文件系统(rootfs):系统运行所必须依赖的一些文件(比如脚本、库、配置文件、命令…),本质就是目录和文件。
根文件系统镜像(ramdisk.img):将根文件系统按照某种格式进行打包压缩后生成的单个文件 rootfs-----> ramdisk.img
文件系统:一种管理和访问磁盘的软件机制,不同文件系统管理和访问磁盘的机制不同

22.2 移植根文件系统的工具 —> busybox

  1. 短小精悍
  2. 版本更新较快,版本之间差异不大

22.3 如何获取busybox

	https://busybox.net/downloads/
	选择busybox-1.33.0.tar.bz2版本

22.4 根文件系统中目录的介绍

注释:各文件功能解析
busybox生成的目录有:bin,sbin,usr,linuxrc
其他的目录都是手动添加

bin:    命令文件,如ls,cd等(通过busybox工具制作)
dev:    设备文件(被操作系统识别的设备才有对应的文件,即设备运行时)
etc:    配置文件,如bashrc,environment等(配置内核相关的一些信息)
lib:    库文件、比如C的标准库(从交叉编译工具链中复制的)
linuxrc:根文件系统被挂载后运行的第一个程序(通过busybox工具制作)
mnt:    共享目录(非必要)比如挂载SD卡等时将SD卡挂载在该目录
proc:   与进程相关的文件(当有进程运行时才会有文件)
root:   用户权限(板子本身就是以root用户运行)
sbin:   超级用户命令、一般用户不可用(板子本身是超级用户  通过busybox工具制作)
sys:    系统文件(系统运行时,系统加载后才会有文件)
tmp:    临时文件(比如插入新的设备时会产生临时文件)
usr:    用户文件(通过busybox工具制作)
var:    存放下载的文件和软件 (可有可无)
home:    家目录

22.5 更换交叉编译工具链

uboot源码,linux内核编译使用4.5.1版本的交叉编译工具链
根文件系统busybox使用4.6.4版本的交叉编译工具链
注意:编译源码之前先使用命令测试交叉编译工具链的版本,
如果版本不对进行切换。
arm-none-linux-gnueabi-gcc -v

22.6 使用busybox制作根文件系统

  1. 拷贝busybox-1.33.0.tar.bz2压缩包到ubuntu中
  2. 对busybox压缩包进行解压缩
  3. 进入到busybox-1.33.0目录下
  4. 分析busybox的目录结构
    读源码的README获取make的帮助信息,通过执行make help命令获取更多的帮助信息。
	清除中间目标:
		clean			
		distclean
	编译: 
		all 
	配置:
		menuconfig 
	安装: 
		install 
		uninstall
  1. 对busybox源码进行配置和编译
    1> 修改Makefile配置交叉编译工具链
	修改以下几个内容:
	 164 CROSS_COMPILE ?=
	 190 ARCH ?= $(SUBARCH)  
	修改为:
	 164 CROSS_COMPILE ?= arm-none-linux-gnueabi-
	 190 ARCH ?= arm

2> 清除中间文件
make distclean

3> 使用图形化界面的方式对busybox进行配置
make menuconfig
问题: 分析make menuconfig执行过程详解?

		a) 采用静态编译,不使用共享库
		 Settings  ---> 
			[*] Build static binary (no shared libs)
		b) 采用vi风格的命令行编译
		 Settings  ---> 
			[*]   vi-style line editing commands 
		c) 设置根文件系统的安装路径
		 Settings  --->
			(./_install) Destination path for 'make install' (NEW)
			配置为:
			 (/home/hqyj/rootfs) Destination path for 'make install' 
			 注意:每个人的家目录可能都不一样,修改为自己的。
		d) 配置支持所有的模块化的命令
		 Linux Module Utilities  ---> 
			 [ ] Simplified modutils 

4> 编译busybox源码
make all

5> 安装
make install

		根文件系统中的很多文件散落在busybox的各个目录下,
		可以通过make install命令将散落在buxybox目录的的各个文件,
		重定向到rootfs文件中的的各个目录下。

6> 测试自己新生成的rootfs根文件系统,将之前提供的可以使用的rootfs根文件系统进行备份

		cd ~/nfs 
		mv rootfs rootfs-ok 
		移动自己生产的rootfs到nfs目录下 
		mv ../rootfs  ./

7> 启动开发板,使用开发阶段的方式启动开发板
tftp下载uImage nfs挂载根文件系统
检查:bootcmd和bootargs两个参数。

		内核启动成功,并且成功挂载了根文件系统,
		只是有很多的错误信息,错误信息如下:
		can't run '/etc/init.d/rcS': No such file or directory
		can't open /dev/tty2: No such file or directory
		can't open /dev/tty3: No such file or directory
		can't open /dev/tty4: No such file or directory
		
		解决思路:<没有文件则创建对应的文件>
		在根文件系统中创建/etc/init.d目录和/dev目录,
		并在/etc/init.d目录下添加rcS文件,修改rcS的文件权限为777.
		
		$ cd ~/nfs/rootfs 
		$ mkdir -p etc/init.d dev 
		$ cd etc/init.d 
		$ touch rcS 
		$ chmod 777 rcS
		
		打开rcS文件,添加一下信息:
			#!/bin/sh 
			/bin/mount -a
			echo /sbin/mdev > /proc/sys/kernel/hotplug 
			/sbin/mdev -s

		解析:
		#!/bin/sh  ---> 使用sh脚本解析器
		/bin/mount -a  ---> 系统会自动解析fstab配置文件,根据fstab文件中的信息,实现一系列的挂载动作。
		echo /sbin/mdev > /proc/sys/kernel/hotplug  ---->进行重定向,告诉内核用于创建设备文件的程序是sbin/mdev 
		/sbin/mdev -s  ---->在dev目录下自动创建设备文件

8> 启动开饭,继续观察现场

	出现以下错误信息:
	mount: can't read '/etc/fstab': No such file or directory
	解决办法: 
	在etc/目录下创建fstab文件,
	$ cd ~/nfs/rootfs/etc 
	$ touch fstab 
	打开fstab文件,并添加一下信息:
		#device mount-point type options dump fsck orde
		proc   /proc  proc  defaults  0  0
		tmpfs  /tmp   tmpfs defaults  0  0
		sysfs  /sys   sysfs defaults  0  0
		tmpfs  /dev   tmpfs defaults  0  0
	
	解析: 
	第1列: 设备
	第2列: 挂载点
	第3:  设备类型
	其他列:权限相关,保持默认即可。
	/etc/init.d/rcS: line 3: can't create /proc/sys/kernel/hotplug: nonexistent directory
	解决办法: 在根文件系统中创建proc目录 
	$ cd ~/nfs/rootfs 
	$ mkdir proc 
	mdev: /sys/dev: No such file or directory
	解决办法: 在根文件系统中创建sys目录 
	$ cd ~/nfs/rootfs 
	$ mkdir sys

9> 重启开发,观察现象

	出现以下错误信息:
	mount: mounting tmpfs on /tmp failed: No such file or directory
	解决办法: 在根文件系统中创建tmp目录 
	$ cd ~/nfs/rootfs 
	$ mkdir tmp

10> 重启开发,观察现象

	出现以下错误信息:
	can't open /dev/tty2: No such file or directory
	can't open /dev/tty3: No such file or directory
	can't open /dev/tty4: No such file or directory
	
	解决办法: 
	在根文件系统内的etc目录下创建inittab文件,
	$ cd ~/nfs/rootfs/etc
	$ touch inittab 
	打开inittab文件,并添加以下信息:
		::sysinit:/etc/init.d/rcS 
		::askfirst:-/bin/sh
		::restart:/sbin/init 
		::ctrlaltdel:/sbin/reboot 
	
	解析: 
	::sysinit:/etc/init.d/rcS  ---》 系统启动之后运行linuxrc程序,执行/etc/init.d/rcS脚本文件。
	::askfirst:-/bin/sh   ---》按下任意键之后,开启一个shell中断
	::restart:/sbin/init  ---》重启系统自动执行/sbin/init程序
	::ctrlaltdel:/sbin/reboot  ---》 按下ctrl + alt + del 执行/sbin/rebot,重启系统

11> 重启开发板,观察现象

		到此根文件系统制作,基本完成,
		创建剩余的几个文件
		$ cd ~/nfs/rootfs
		$ mkdir lib home mnt root var 

12> 添加用户名:

		在根文件系统的etc目录下创建profile文件,
		并添加以下信息: 
		$ cd ~/nfs/rootfs/etc 
		$ vi profile 
		添加的信息如下:
		export HOSTNAME=20101-20111
		export USER=root
		export HOME=root
		#export PS1="\[\u@\h \W\ ]\$ "
		#cd root
		export PS1="[$USER@$HOSTNAME \W]\# "
		PATH=/bin:/sbin:/usr/bin:/usr/sbin:$PATH
		LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
		export PATH LD_LIBRARY_PATH

13> 编写一个hello world应用程序,使用交叉编译工具链进行编译,
将可执行程序拷贝到根文件系统中,并运行,观察现象。

		[root@20101-20111 ]# ./hello 
		运行时,出现以下错误信息:
		-/bin/sh: ./hello: not found
		使用以下命令查看hello依赖的动态库: 
		$ arm-none-linux-gnueabi-readelf -d hello
		0x00000001 (NEEDED)  Shared library: [libc.so.6]
		解决办法:移植共享库
		从交叉编译工具链中移植arm版本的共享库到根文件系统的lib目录下。
		$ cd ~/toolchain/gcc-4.6.4 
		$ find . -name libc.so.6
			./arm-arm1176jzfssf-linux-gnueabi/sysroot/lib/libc.so.6
		将交叉编译工具量./arm-arm1176jzfssf-linux-gnueabi/sysroot/lib/
		目录下的所有的库拷贝到根文件系统的lib目录下
		$ cd ./arm-arm1176jzfssf-linux-gnueabi/sysroot/lib/
		$ cp -raf *  ~/nfs/rootfs/lib/
		再次运行hello可执行程序,运行成功。

14> 测试linux驱动模块化相关的命令

		拷贝fs6818_led.ko到根文件系统中
		拷贝led_test到根文件系统中,
		以上几个文件在讲解linux内核的模块化编程时,
		进行讲解生成的。

【将rootfs打包生成ramdisk.img镜像文件】
根文件系统的制作实验手册.docx

你可能感兴趣的:(系统移植,内核,uboot,arm)