uboot启动流程

一、预备知识

1.1 vmlinux、zImage和uImage文件

  • vmlinux:是编译出的最原始的内核文件,未被压缩。
  • zImage:是vmlinux经过gzip压缩过后的文件。
  • uImage:是uboot专用的映像文件,它是在zImage的前面加上了一个长度为0x40的头部标签,该标签说明了内核的版本、加载位置、生成时间以及大小等信息。

内核在编译(make)之后会生成两个文件,分别是vmLinux文件和zImage文件,而uImage的生成需要手动进行,首先在uboot的/tools目录下找到mkImage文件,将其拷贝到系统/usr/local/bin目录下,这样就完成了制作工具,然后在内核目录下运行命令make uImage,成功后便可以在arch/arm/boot/目录下生成uImage文件,其比zImage大64个字节。
由于uImage需要手动生成,所以多数情况下我们加载进DDR的为zImage。

1.2 bootm和bootz命令

原则上uboot引导、启动Linux内核时应该给他uImage格式的内核镜像,但是实际上uboot也可以支持zImage,且根据上面说到的,uImage需要手动生成,所以我们一般提供给uboot的是zImage。uboot在引导和启动Linux内核时会涉及到两个命令,分别是bootmbootz

  • bootm用于加载uImage和ramdisk
  • bootz用于加载zImage和ext4文件系统

我们需要在uboot中根据提供的Linux镜像格式来使用对应的命令启动Linux内核。

1.3 C语言程序划分

在C语言中,程序可以分为以下几个部分:

  • 代码段(.text)
  • 数据段(.data)
  • 只读数据(.rodata)
  • 未初始化的数据段(.bss)
  • 堆和栈
    其中,代码段、数据段和只读数据段在链接之后产生;堆和栈属于动态内存,在程序运行时分配和释放。

1.4 常用的uboot命令

   1.查询命令:bdinfo、printenv、 version
   		bdinfo:查询系统信息,用法【bdinfo】
   		printenv:查询当前系统所有环境变量,用法【printenv】
   		version:查询uboot版本,用法【version】

   2.环境变量操作命令:setenv、saveenv
   		setenv:新建/修改或删除环境变量,删除环境变量【setenv 环境变量名 变量值】
   		saveenv:保存环境变量,用法【saveenv】
   
   3.内存操作命令:md、nm、mm、mw、cp 、cmp
   		md:显示内存值,用法【md[.b, .w, .l] address [# of objects]】,.b表示以byte读取,.w表示以word读取,of objects表示长度
   		mm:命令用于修改指定地址的内存值,用法【nm [.b, .w, .l] address】
   		mw:命令 mw 用于使用一个指定的数据填充一段内存,用法【mw [.b, .w, .l] address value [count]cpcp 是数据拷贝命令,用于将 DRAM或falsh 中的数据或从一段内存拷贝到另一段内存中,用法cp [.b, .w, .l] source target count
   		cmp:用于比较两段内存的数据是否相等,【cmp [.b, .w, .l] addr1 addr2 count】
   
   4.网络操作命令:dhcp、ping、tftp、nfs
   		dhcp:自动获取ip,并通过tftp从网络启动内核,用法【dhcp】,(可通过?dhcp查看用法)
   		ping:网络连接测试,用法【ping 192.168.xxx.xxx】
   		nfs: 通过nfs从网络启动内核,用法【nfs 内存地址 主机IP:文件名】例如nfs 80800000 192.168.1.250:/home/nfs/zImage
   		tftp:通过tftp从网络启动内核,用法【tftp 80800000 192.168.1.250:/zImage】
   
   5.emmc和sd卡操作命令:mmc info、mmc read、mmc wirte、 mmc rescan、mmc part、mmc dev、mmc list
   (emmc和sd属于同一种类型设备)
   mmc info:查看当前设备信息,【mmcinfo】
   mmc rescan:扫码系统中存在的emmc设备,【mmcrescan】
   mmc list:列出系统中的所有emmc设备,即查看一共有个emmc【mmclist】
   mmc dev:切换到某个emmc设备,或切换到某个emmc设备的某个分区,【mmcdev [dev] [part],例如切换到emmc1的分区2:ccmdev 1 2
   mmc part:列出当前emmc的所有分区,【mmcpart】
   mmc read:读取mmc设备的数据,【mmc read addr blk cnt】,addr是数据读取到DRAM中的地址,blk是要读取的块起始地址,cnt是要读取的块数据量
   mmc write:将数据写到mmc设备里面,【mmc write addr blk cnt】

	6.FAT 文件系统操作命令:fatinfo、fatls、fstype、fatload、fatwrite、
	fatinfo:查询指定 MMC 设置指定分区的文件系统信息,【fatinfo  [[:part]>]】,例:fatinfo mmc 1:1
	fatls:于查询 FAT 格式设备的目录和文件信息,【fatls  [[:part]>] [directory]】,例:fatls mmc 1:1
	fstype:于查看 MMC 设备某个分区的文件系统格式,【fstype  :】,例:fstype mmc 1:0
	fatload:于将指定的文件读取到 DRAM 中,【fatload  [[:part]> [ [ [bytes [pos]]]]]】,例:fatload mmc 1:1 80800000 zImage
	fatwrite:将文件写入硬盘,【fatwrite  [:part]>   】

	7. EXT 格式文件系统操作命令:ext2load、ext2ls、ext4load、ext4ls
	   (操作方法与FAT一致)

	8.NAND 操作命令:
	    nand info:打印 NAND Flash 信息
		nand device:切换 NAND Flash,如果你的板子支持多片 NAND 的话就可以使用此命令来设置当前所使用的 NAND
		nand erase:擦除 NAND Flash,从指定地址开始(off)开始,擦除指定大小(size)的区域【nand erase[.spread] [clean] off size】,擦除指定的分区【nand erase.part [clean] partition】,【nand erase.chip [clean]】,
		例如:nand erase 0x4000000 0xA00000
		nand write:写入,【nand write addr off size】,例如:nand write 0x87800000 0x4000000 0xA00000
		nand read:读取,【nand read addr off size】,例如:nand read 0x83000000 0x6000000 0x19000
		
   9.boot操作命令:bootz、bootm、boot、
   		bootz:令用于启动 zImage 镜像文件,用法【bootz addr initrd fdt】addr 是 zImage 的内存地址,initrd 是 zImage 的地址 initrd 文件的内存地址,不使用 initrd 的话用‘-’代替,fdt 就是设备树文件的内存地址。
   		bootm:令用于启动 uImage 镜像文件,用法【bootz addr initrd fdt】addr 是 zImage 的内存地址,initrd 是 zImage 的地址 initrd 文件的内存地址,不使用 initrd 的话用‘-’代替,fdt 就是设备树文件的内存地址,bootm命令可以直接使用bootm addr 的方式启动uImage而不需要设备树文件。
   		boot:boot命令也使用来启动linux用来启动linux内核的,但是它不是直接去启动,而是间接的调用bootcmd
   		来启动内核,具体的启动内核行为是由bootcmd决定的。

二、uboot启动流程

在Linux主机中好编译uboot之后,通过专用软件将uboot.imx文件烧进SD卡或者EMMC,然后在开发板上选中对应的启动方式,上电后uboot启动。
uboot的启动流程可以分为两大部分:第一部分,arch级初始化,通常由汇编语言实现;第二部分,板级初始化,通常由C语言实现。以下分别是arch初始化和板级初始化的过程:
1.arch级初始化
(1)指定uboot的入口。在链接脚本uboot.lds中指定uboot的入口为start.S中的_start;
(2)关中断(FIQ和IRQ)设置CPU进入SVC32模式;
(3)重定向异常向量表,目的是将uboot加载到DDR上去;
(4)设置CP15寄存器、关闭Cache、MMU等;
(5)设置6ULL的内部RAM,在内存中预留出248B的gd,将SP地址保存在R9寄存器中;
(6)调用_main函数,进入板级初始化。
2. 板级初始化
(1)预留出早期的malloc和gd(global_data)内存区域;
(2)初始化gd,即清零处理;
(3)初始化DDR、串口、定时器等一系列外设,初始化gd的各个成员变量,uboot会将自己重定位到DDR最后面的区域,给kernel流出空间;
(4)拷贝uboot代码至重定向后地址的内存;
(5)重定向中断向量表;
(6)初始化C语言的运行环境;
(7)初始化外设;
(8)倒计时3秒,结束之前按下任意键进入uboot命令行,使用boot命令启动Linux内核;倒计时结束未进入命令行,uboot自动启动Linux内核。

补充:重定位就是在链接地址跟运行地址不同的情况下,执行一段位置无关码,这段位置无关码的作用就是将原来的那份代码全部复制到链接地址那里去,然后自己再长跳转到新的那份代码的刚刚执行的那个位置。

如下所示为uboot启动过程中的函数调用关系:

Uboot启动流程:
u-boot.lds
   |
   |--->_start  <arch/arm/lib/vectors.S>
	       |--->reset  <arch/arm/cpu/armv7/start.S>
	       |       |--->save_boot_params   <arch/arm/cpu/armv7/start.S>
	       |                      |--->save_boot_params_ret    <arch/arm/cpu/armv7/start.S>
	       |                                   |               // 禁止中断(FIQ和IRQ),设置cpu的模式为SVC模式
	       |                                   |               // 清楚SCTLR的bit13,允许向量重定位,同时
	                                           |               // 重定位向量表,把CP15的c12(VBAR)寄存器设置为
	                                           |               // 0x87800000(uboot的起始地址,也是向量表的起始地址)
	                                           |
	                                           |--->cpu_init_cp15      <arch/arm/cpu/armv7/start.S>
	                                           |                       // 设置cp15相关内容,比如关闭mmu,cache
	                                           |
	                                           |--->cpu_init_crit      <arch/arm/cpu/armv7/start.S>
	                                           |           |
	                                           |           |--->lowlevel_init  <arch/arm/cpu/armv7/lowlevel_init.S>
	                                           |           |                   // 设置栈指针sp = 0x0091FF00,属于MX6ULL
	                                           |           |                   // 的内部ram,同时(sp - GD_SIZE(248))-->sp
	                                           |           |                   // 留出global_data数据结构的位置sp = 0x0091FE08
	                                           |           |                   // 设置sp-->r9, sp==r9
	                                           |           |
	                                           |           |--->s_init     <arch/arm/cpu/armv7/mx6/soc.c>
	                                           |           |               // 空函数,直接返回
	                                           |
	                                           |--->_main      <arch/arm/lib/crt0.S>
	                                                   |       // 设置sp为0x0091ff00,调用函数
	                                                   |       // board_init_f_alloc_reserve(arg:0x0091FF00)后,把sp设为
	                                                   |       // 此函数的返回值:0x0091FA00, r9(gd)设为0x0091FA00;
	                                                   |       // 调用board_init_f_init_reserve(arg:0x0091FA00)后
	                                                   |       // 把gb的成员malloc_base设为0x0091FB00(early_malloc的起始地址)
	                                                   |       // 调用board_init_f函数:会初始化gd,返回之后重新设置环境(sp和gd)
	                                                   |       // 把gd的成员start_addr(0x9EF44E90)赋值给sp, 此时sp == 0x9EF44E90
	                                                   |       // 是外部DDR的地址,gd->bd赋给r9(gd),新的gd结构在bd结构下面,
	                                                   |       // 重新设置gd = r9 - sizeof(*gd); lr = here. gd指向新的区域(DDR内)时
	                                                   |       // lr = here + 68,这是为什么?uboot的拷贝目的地址:0x9FF47000
	                                                   |--->board_init_f_alloc_reserve(arg:0x0091FF)    <common/init/board_init.c>
	                                                   |                       // 在包含此函数的文件中有:DECLARE_GLOBAL_DATA_PTR;
	                                                   |                       // 是个宏定义:#define  DECLARE_GLOBAL_DATA_PTR \
	                                                   |                       //                 register volatile gd_t *gd asm("r9")
	                                                   |                       // 此函数设置留出早期malloc和global_data内存区域,
	                                                   |                       // 返回值:0x0091FA00
	                                                   |
	                                                   |--->board_init_f_init_reserve(arg:0x0091FA00)     <common/init/board_init.c>
	                                                   |                       // 此函数用于初始化gd所指向的结构(清零处理)
	                                                   |                       // 设置gd的成员malloc_base为0x91FB00
	                                                   |                       // 就是early_malloc的起始地
	                                                   |
	                                                   |--->board_init_f       <common/board_f.c>
	                                                   |                       // 主要做两个工作:初始化一系列外设(串口、定时器等)
	                                                   |                       // 初始化gd的各个成员变量(此时gd还保存在内部ocram中)。上面的工作都是通过在函数内运行
	                                                   |                       // initcall_sequence_f函数表中的一些函数来实现的,此函数表与
	                                                   |                       // board_init_f函数定义在相同的文件,是static属性的静态表
	                                                   |                       // 表中的函数执行完后会把gd->mon_len设为0xA8E74(__bss_end-_start),
	                                                   |                       // 也就是代码长度。gd->malloc_init设为0x400(malloc内存池的大小)
	                                                   |                       // gd->ram_size:0x20000000  gd->ram_top:0xA0000000  gd->relocaddr:0x9FF47000
	                                                   |                       // gd->arch.tlb_size:0x4000  gd->arch.tlb_addr:0x9FFF0000
	                                                   |
	                                                   |--->relocate_code(arg:0x9FF47000)      <arch/arm/lib/relocate.S>
	                                                   |                       // 代码拷贝。0x9FF47000是uboot拷贝目标首地址,offset=0x9FF47000-0x8780000,offset:0x18747000
	                                                   |                       // 拷贝源地址:__image_copy_start=0x87800000,结束地址:__image_copy_end =0x8785dd54
	                                                   |                       // 裸机程序运行需要链接地址与运行地址相同,uboot解决拷贝后的重定位问题是采用ld链接器
	                                                   |                       // 链接时使用选项'-pie'生成位置无关的可执行文件,使用此选项时会生成一个.rel.dyn段,
	                                                   |                       // uboot就是靠这个.rel.dyn来解决重定位问题的(.rel.dyn 段是存放.text 段中需要重定位地址的集合)
	                                                   |                       // 修改.rel.dyn中的label来重定位
	                                                   |
	                                                   |--->relocate_vectors       <arch/arm/lib/relocate.S>
	                                                   |                       // 重定位向量表,将CP15的VBAR寄存器的值设为0x9FF47000,uboot拷贝后的目标首地址
	                                                   |
	                                                   |--->c_runtime_cpu_setup      <arch/arm/cpu/armv7/start.S>
	                                                   |
	                                                   |--->board_init_r       <common/board_r.c>
	                                                   |           |           // 初始化一些在board_init_f函数中未初始化的一些外设,做些后续工作。
	                                                   |           |           // 是通过运行init_sequence_r函数集合中的函数来实现的,init_sequence_r与board_init_f函数
	                                                   |           |           // 在同一个文件。在函数集合中initr_reloc_global_data函数初始化重定位后的gd的一些成员变量
	                                                   |           |           // 集合中的其他函数:初始化了malloc、串口、电源芯片、emmc、环境变量、LCD、初始化跳转表、中断,使能中断
	                                                   |           |           // 初始化网络地址(获取MAC地址,通过读取环境变量ethaddr的值,环境变量保存在emmc中)、
	                                                   |           |           // 初始化网络设备,最后执行run_main_loop函数,主循环(处理命令)
	                                                   |           |
	                                                   |           |--->run_main_loop      <common/board_r.c>
	                                                                       |            // uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就
	                                                                       |            // 会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内
	                                                                       |            // 核,这个功能就是由run_main_loop函数来完成的.
	                                                                       |
	                                                                       |--->main_loop(void)    <common/main.c>
	                                                                       |           |        // 如果如果倒计时结束之前按下按键,那么就会执行cli_loop函数,这个就是
	                                                                       |           |        // 命令处理函数,负责接收好处理输入的命令。
	                                                                       |           |
	                                                                       |           |--->bootstage_mark_name
	                                                                       |           |                    // 打印处启动进度
	                                                                                   |
	                                                                                   |--->autoboot_command
	                                                                                   |		|			// 此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?如果没有被打断则调用函数run_command_list
	                                                                                   |		|--->run_command_list
	                                                                                   |		|			|		// 执行环境变量bootcmd中保存的启动linux指令							
	                                                                                   |
	                                                                                   |--->cli_loop       <common/cli.c>
	                                                                                   |       |     // cli_loop函数是uboot的命令行处理函数,我们在uboot中输入
	                                                                                   |       |     // 各种命令,进行各种操作就是由cli_loop来处理的
	                                                                                   |       |
	                                                                                   |       |--->parse_file_outer
	                                                                                                       |
	                                                                                                       |--->setup_file_in_str
	                                                                                                       |
	                                                                                                       |--->parse_stream_outer
	                                                                                                       |                   // 这个函数就是 hush shell 的命令解释器,负责接收命令行输入,解析并执行。
	                                                                                                       |                   // 负责接收命令行输入,然后解析并执行相应的命令

三、uboot启动Linux内核过程

上面最后提到来了uboot启动Linux,接下来分析uboot启动Linux内核的过程。uboot启动内核总体可以分为4个步骤:
1. 将Linux内核镜像、设备树文件搬进DDR中

  • 将Linux内核镜像、设备树文件搬进DDR中一般有两种方式,分别是:
    • 从SD卡、EMMC或NandFlash中将Linux镜像文件、设备树文件拷贝到DDR中。
    • 通过nfs或tftp将Linux镜像和设备树文件从Linux主机直接下载到DDR中。
  • uboot在main_loop()函数中读取环境变量bootcmd的值(该变量的值为几个uboot命令),uboot通过执行这些命令加载Linux镜像、设备树文件至DDR,并启动Linux内核。如下分别为从EMMC中加载Linux镜像从tftp网络中加载Linux镜像时,环境变量bootcmd值的设置:
# 从MMC启动
# bootcmd的值以';'为分割可以分为三段:
#第一段:将MMC分区1中的zImage文件读取到DDR中的80800000地址处;
#第二段:将MMC分区1中的imx6ull-14x14emmc-4.3-480x272-c.dtb文件读到DDR中的83000000地址处
#第三段:使用bootz命令启动内核和设备树
setenv bootcmd fatload mmc 1:1 80800000 zImage;fatload mmc 1:1 83000000 imx6ull-14x14emmc-4.3-480x272-c.dtb;bootz 80800000 - 83000000;
saveenv

# 通过tftp网络服务启动
# bootcmd的值以';'为分割可以分为三段:
#第一段:通过tftp网络服务下载zImage文件读取到DDR中的80800000地址处;
#第二段:通过tftp网络服务下载imx6ull-14x14emmc-4.3-480x272-c.dtb文件读到DDR中的83000000地址处
#第三段:使用bootz命令启动内核和设备树
setenv bootcmd tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000;
saveenv

注意:此处使用的Linux镜像格式为zImage,所以最后使用bootz来启动内核!!

2. 校验内核格式、CRC
3. 准备传参;
4. 跳转执行内核。

以下为uboot引导并启动Linux内核过程中的相关结构体及函数调用:

  • image全局变量
    不论是用bootz命令还是bootm命令启动,都会用到一个重要的全局变量images,该变量的类型为boom_headers_t,boom_headers_t是个boot头结构体,该结构体包含着各种与内核相关的参数信息,定义如下:
42 //定义了一个bootm_headers_t类型的结构体变量images		<
43 bootm_headers_t images; 


303 // bootm_headers_t结构体	
304 typedef struct bootm_headers {
305 /*
306 * Legacy os image header, if it is a multi component image
307 * then boot_get_ramdisk() and get_fdt() will attempt to get
308 * data from second and third component accordingly.
309 */
310 image_header_t *legacy_hdr_os; /* image header pointer */
311 image_header_t legacy_hdr_os_copy; /* header copy */
312 ulong legacy_hdr_valid;
313
......
333
334 #ifndef USE_HOSTCC
335 image_info_t os; 			/* OS 镜像信息,是一个image_info_t类型的 */
336 ulong ep; 					/* OS 入口点 */
337
338 ulong rd_start, rd_end; 	/* ramdisk 开始和结束位置 */
339
340 char *ft_addr; 				/* 设备树地址 */
341 ulong ft_len; 				/* 设备树长度 */
342
343 ulong initrd_start; 		/* initrd 开始位置 */
344 ulong initrd_end; 			/* initrd 结束位置 */
345 ulong cmdline_start; 		/* cmdline 开始位置 */
346 ulong cmdline_end; 			/* cmdline 结束位置 */
347 bd_t *kbd;
348 #endif
349
350 int verify;  /* getenv("verify")[0] != 'n' 如果环境变量"verify"的值为'n',那么此处的局部变量verify值为0*/
351 /* 下面宏表示 BOOT的不同阶段*/
352 #define BOOTM_STATE_START (0x00000001)
353 #define BOOTM_STATE_FINDOS (0x00000002)
354 #define BOOTM_STATE_FINDOTHER (0x00000004)
355 #define BOOTM_STATE_LOADOS (0x00000008)
356 #define BOOTM_STATE_RAMDISK (0x00000010)
357 #define BOOTM_STATE_FDT (0x00000020)
358 #define BOOTM_STATE_OS_CMDLINE (0x00000040)
359 #define BOOTM_STATE_OS_BD_T (0x00000080)
360 #define BOOTM_STATE_OS_PREP (0x00000100)
361 #define BOOTM_STATE_OS_FAKE_GO (0x00000200)/*'Almost' run the OS*/
362 #define BOOTM_STATE_OS_GO (0x00000400)
363 int state;
364
365 #ifdef CONFIG_LMB
366 struct lmb lmb; /* 内存管理相关,不深入研究 */
367 #endif
368 } bootm_headers_t;


//image_info_t结构体  
typedef struct image_info {
293 ulong start, end; 			  /* blob 开始和结束位置*/
294 ulong image_start, image_len; /* 镜像起始地址(包括 blob)和长度 */
295 ulong load; 				  /* 系统镜像加载地址*/
296 uint8_t comp, type, os; 	  /* 镜像压缩、类型, OS 类型 */
297 uint8_t arch; 				  /* CPU 架构 */
298 } image_info_t;
  • 使用booz命令启动
run_command_list/parse_stream_outer函数
			|		
			|-->bootz命令
			|		|		
			|		|-->do_bootz	<cmd/bootz.c>
			|		|		|
			|		|		|-->bootz_start		<cmd/bootz.c>
			|		|		|		|
			|		|		|		|-->do_bootm_states		<common/bootm.c>
			|		|		|		|		|		//根据BOOT状态执行不同的boot阶段
			|		|		|		|		|-->bootm_start		<common/bootm.c>
			|		|		|		|		|		|		//BOOT状态:BOOTM_STATE_START
			|		|		|		|		|		|		//1.清空image结构体
			|		|		|		|		|		|		//2.获取uboot的环境变量verify的值并赋给image结构体的verify成员
			|		|		|		|		|		|-->bootstage_mark_name
			|		|		|		|		|		|		|		//记录启动阶段的名字
			|		|		|		|-->images->ep=load_addr
			|		|		|		|		|		//获取Linux镜像(zImage),保存在images的成员变量ep中
			|		|		|		|-->bootz_setup		<cmd/bootz.c>
			|		|		|		|		|		
			|		|		|		|		|-->zi->zi_magic = LINUX_ARM_ZIMAGE_MAGIC
			|		|		|		|		|		|		//根据幻数判断zImage是否正确
			|		|		|		|-->bootm_find_images		<common/bootm.c>
			|		|		|		|		|		
			|		|		|		|		|-->boot_get_ramdisk		<>
			|		|		|		|		|		//查找ramdisk(虚拟磁盘)
			|		|		|		|		|-->boot_get_fdt		<>
			|		|		|		|		|		//查找设备树,将其首地址保存在全局变量images的成员变量ft_addr中,长度保存在ft_len中
			|		|		|		|		|-->set_working_fdt_addr		<>
			|		|		|		|		|		//设置设备树的工作地址
			|		|		|
			|		|		|-->bootm_disable_interrupts	<common/bootm.c>
			|		|		|		|		//关闭中断
			|		|		|-->do_bootm_states		<common/bootm.c>
			|		|		|		|		//根据BOOT状态执行不同的boot阶段
			|		|		|		|		//BOOT状态:BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO、BOOTM_STATE_OS_GO
			|		|		|		|-->bootm_os_get_boot_func		<common/bootm_os.c>	
			|		|		|		|		|		//查找Linux内核启动函数do_bootm_linux,赋值给函数boot_fn
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_RAMDISK
			|		|		|		|-->...
			|		|		|		|-->boot_selected_os		<common/bootm_os.c>
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_OS_GO
			|		|		|		|		|-->boot_fn
			|		|		|		|		|		|
			|		|		|		|		|		|-->do_bootm_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|
			|		|		|		|		|		|		|-->boot_prep_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		//启动Linux之前做一些其他处理, 比如在设备树的chosen节点下添加子节点bootargs,bootargs子节点存放bootargs环境变量
			|		|		|		|		|		|		|-->boot_jump_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		//machid = gd->bd->bi_arch_number;获取机器ID并保存在变量machid中,Linux内核会判断是否支持这个机器
			|		|		|		|		|		|		|		|		//kernel_entry=(void (*)(int, int, uint))images->ep;获取kernel_entry函数,该函数不是在uboot中定义的,
			|		|		|		|		|		|		|		|		//而是在Linux内核定义的,而Linux镜像的第一行代码就是kernel_entry函数,所以images->ep所保存的Linux镜像首地址就是函数kernel_entry的地址
			|		|		|		|		|		|		|		|-->announce_and_cleanup		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		|		//输出"Starting kernel"并做一些清理工作	
			|		|		|		|		|		|		|		|-->kernel_entry
			|		|		|		|		|		|		|		|		|		//进入内核函数,最终的大Boss,启动内核!!
  • 使用bootm命令启动
    上面是bootz命令启动Linux内核的函数调用过程,但是如果在环境变量bootcmd中使用uboot命令传入DDR中的Linux镜像如果为uImage,就像需要使用bootm命令启动。如下为bootm命令启动Linux内核的函数调用过程:
run_command_list/parse_stream_outer函数
			|
			|-->bootm命令
			|		|
			|		|-->do_bootm	<cmd/bootm.c>
			|		|		|		//1.重定位boot的功能表
			|		|		|		//2.确定是否拥有子命令,如果有,调用函数do_bootm_subcommand
			|		|		|-->do_bootm_subcommand		<cmd/bootm.c>
			|		|		|		|		//按照bootm的参数来指定运行某一个阶段
			|		|		|-->do_bootm_states		<common/bootm.c>
			|		|		|		|		//根据BOOT状态执行不同的boot阶段
			|		|		|		|-->bootm_start
			|		|		|		|		|		//1.清空image结构体
			|		|		|		|		|		//2.获取uboot的环境变量verify的值并赋给image结构体的verify成员
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_START
			|		|		|		|		|-->bootstage_mark_name
			|		|		|		|		|		|		//记录启动阶段的名字
			|		|		|		|-->bootm_find_os
			|		|		|		|		|		//解析Image的基本信息并保存到全局变量images的成员变量中
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_FINDOS
			|		|		|		|		|-->boot_get_kernel
			|		|		|		|		|		|		//根据bootm传递的参数获取uImage的存储地址
			|		|		|		|		|		|-->image_get_kernel
			|		|		|		|		|		|		|		//进行kernel格式校验
			|		|		|		|		|-->genimg_get_format
			|		|		|		|		|		|		//检查镜像类型
			|		|		|		|		|-->image_get_type
			|		|		|		|		|		|		//获取内核的类型(zImage还是uImage)
			|		|		|		|		|-->image_get_comp
			|		|		|		|		|		|		//获取内核的压缩方式
			|		|		|		|		|-->image_get_os
			|		|		|		|		|		|		//获取操作系统的类型(Linux)
			|		|		|		|		|-->image_get_load
			|		|		|		|		|		|		//获取内核的加载地址
			|		|		|		|		|-->image_get_arch
			|		|		|		|		|		|		//获取芯片架构
			|		|		|		|-->bootm_find_other		<common/bootm.c>
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_FINDOTHER
			|		|		|		|		|-->boot_get_ramdisk
			|		|		|		|		|		//获取虚拟磁盘
			|		|		|		|		|-->boot_get_fdt
			|		|		|		|		|		//获取设备树
			|		|		|		|-->bootm_disable_interrupts
			|		|		|		|		|		//禁用中断
			|		|		|		|-->bootm_load_os
			|		|		|		|		|		//调用函数image_decomp来解压内核
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_LOADOS
			|		|		|		|		|-->image_decomp
			|		|		|		|		|		|		//解压内核
			|		|		|		|-->bootm_os_get_boot_func
			|		|		|		|		|		//查找Linux内核启动函数do_bootm_linux,赋值给函数boot_fn
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_RAMDISK
			|		|		|		|-->boot_selected_os		<common/bootm_os.c>
			|		|		|		|		|		//BOOT状态:BOOTM_STATE_OS_GO
			|		|		|		|		|-->boot_fn
			|		|		|		|		|		|
			|		|		|		|		|		|-->do_bootm_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|
			|		|		|		|		|		|		|-->boot_prep_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		//启动Linux之前做一些其他处理, 比如在设备树的chosen节点下添加子节点bootargs,bootargs子节点存放bootargs环境变量
			|		|		|		|		|		|		|-->boot_jump_linux		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		//machid = gd->bd->bi_arch_number;获取机器ID并保存在变量machid中,Linux内核会判断是否支持这个机器
			|		|		|		|		|		|		|		|		//kernel_entry=(void (*)(int, int, uint))images->ep;获取kernel_entry函数,该函数不是在uboot中定义的,
			|		|		|		|		|		|		|		|		//而是在Linux内核定义的,而Linux镜像的第一行代码就是kernel_entry函数,所以images->ep所保存的Linux镜像首地址就是函数kernel_entry的地址
			|		|		|		|		|		|		|		|-->announce_and_cleanup		<arch/arm/lib/bootm.c>
			|		|		|		|		|		|		|		|		|		//输出"Starting kernel"并做一些清理工作	
			|		|		|		|		|		|		|		|-->kernel_entry
			|		|		|		|		|		|		|		|		|		//进入内核函数,最终的大Boss,启动内核!!

四、最后捋一遍烧写uboot和内核的过程

  1. 使用烧写工具烧写uboot至SD卡。
  2. 开发板上电,运行uboot。
  3. 倒计时开始,按下任意键进入uboot命令行。
  4. 设置环境变量bootcmd为从tftp网络服务下载内核镜像zImage及设备树文件等;通过bootz命令启动。
  5. 系统运行

你可能感兴趣的:(Linux驱动学习笔记,linux)