6、uboot源码——内核启动分析

时间:2020-12-11 16:33:52

以下内容,源于朱友鹏《物联网大讲坛》课程,以及博客http://www.cnblogs.com/biaohc/p/6403863.html的学习和整理。如有侵权,请告知删除。



总结:uboot启动linux内核的整个流程

开机时会出现倒计时,当没有按键按下的时候,uboot会读取出bootcmd这个环境变量,并使用rum_command函数来执行这个命令;

  • 实质是执行了movi read kernel 30008000;bootm 30008000。
  • movi read kernel,把sd卡中的kernel分区赋值到30008000内存处;
  • bootm 30008000,真正的传参以及跳转到linux内核中执行(实际执行do_bootm()函数)。
  • bootm(do_bootm()函数)首先要做的事情是判断这个内核镜像类型(zImage、uImage、设备树);
  • 通过对镜像文件的头文件的验证,以确定是哪种内核镜像,然后再把必须的信息储存起来(linux操作系统、ep的值等)。
  • 确定好以后调用do_bootm_linux函数来对内核传参并且启动内核。

 


一、uboot作用简介

uboot的主要作用是用来启动linux内核。

  • 因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如时钟、串口、dram等;
  • 如要想让CPU启动linux内核,只能通过另外的程序,进行必要的初始化工作,在把linux内核中代码复制到内存中,并执行这块内存中的代码,即可启动linux内核;
  • 一般情况下,linux镜像储存在块设备中(SD卡、iNand、Nandflash等)。首先执行uboot代码,把块设备中的内核代码复制到内存地址0x30008000地址处,然后再执行bootm 0x30008000命令以启动内核代码。


 

二、基础知识 

vmlinuz、Image、zImage、uImage的区别与联系

(1)linux内核代码经过编译链接,生成一个elf格式的可执行文件,即vmlinuz或者vmlinux;

  • 此文件不能直接烧录。

(2)vmlinuz文件经过arm-linux-objcopy以后,生成一个Image镜像文件。

  • vmlinuz.elf文件大小为70M以上,而Image镜像文件为7M左右。

(3)Image文件在进一步经过压缩,并添加解压缩代码,形成zImage文件。

  • 当zImage文件作为启动镜像来启动时,首先要解压这个文件,这个解压过程可以由uboot解压或者zImage文件本身自解压。
  • zImage中除了linux内核的镜像以外,还有一些头文件以及这部分解压代码,所以内核实际上在addr地址中再加一个偏移量的位置。

(5)uImage是uboot自己专用的启动内核镜像,相对于zImage他们之间头文件有一定区别。

  • uImage现在基本上要属于过时的技术了,新一点的技术为设备树的启动方式;

 


三、UBOOT启动内核的代码分析


在启动UBOOT时,出现倒计时,如果没有按键按下,则会自动启动内核。

1、start_armboot()函数末尾的main_loop

 6、uboot源码——内核启动分析



下面这段代码在此main_loop函数中,作用是执行完倒计时函数之后,启动linux内核。

  • 启动方式是s = getenv ("bootcmd");;
  • 假定不使用HUAH_PARSER,则run_command (s, 0);实际上就是读取环境变量bootcmd,然后执行这个命令。

 6、uboot源码——内核启动分析

 

2、bootcmd命令

  • bootcmd=movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000
  • 这两个命令完成linux内核启动。
  • movi read kernel 30008000,把sd卡中kernel分区复制到30008000内存地址处;
  • bootm 30008000,到内存地址处执行代码;
  • 执行bootm命令,实际执行do_bootm函数。

  

3、do_bootm()函数

 6、uboot源码——内核启动分析

 6、uboot源码——内核启动分析

(1)因为bootm 0x30008000,参数为2,因此有addr = simple_strtoul(argv[1], NULL, 16)

  • 此时addr中的值为0x30008000。

(2)接着判断0x30008000偏移36字节以后的地址中的值

  • 如果为0x016f2818,说明启动镜像为zImage,则输出boot with zImage。

(3)内核的操作系统类型,和内核真正的入口

  • hdr->ih_os = IH_OS_LINUX;zImage header中ih_os赋值为 IH_OS_LINUX;
  • hdr->ih_ep = ntohl(addr);ih_ep中存放的是point address,这个值是真正内核代码的地址(起始执行入口地址);

(4)image变量

  • static bootm_headers_t images
  • images为uboot内定义的一个bootm_header_t格式的全局变量。
  • bootm_header_t类型为一个结构体,如下图:
  • 包含一个image_header_t类型的指针,这个指针最后指向了0x30008000处的zImage header。
  • 包含一个image_header_t类型的结构体,后面会把0x30008000处的zImage header复制一份到此结构体;
  • 包含一个标志位 legacy_hdr_valid。如果上面两个赋值以后,把legacy_hdr_valid赋值为1;

 6、uboot源码——内核启动分析

(5)memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

  • 把hdr中的值复制一份到 image.legacy_hdr_os_copy中,即把内存地址0x30008000处设置好的zImage头复制一份到uboot的data段。

(6)直接跳转到after_header_check处,os为IH_OS_LINUX。


4、after_header_check

  • 判断操作系统,然后调用do_bootm_linux函数

 6、uboot源码——内核启动分析


5、do_bootm_linux()函数

 6、uboot源码——内核启动分析

 6、uboot源码——内核启动分析

(1)获取环境变量bootargs;

(2)判断全局变量images中的legacy_hdr_valid是否为1,如果为1获取ep值,如果不为1则error。

(3)把ep强制类型换换为函数指针类型赋值给thekernel;

(4)从环境变量中读取machid的值,赋值给s,如果s不空则machid = 环境变量中machid的值,并打印machid;



----------------------------------uboot给内核传参-----------------------------------------------

6、uboot给内核传参

  • 传参主要是uboot把与硬件有关的信息传给linux内核,如memory信息(多少bank、size、起始地址)、命令行信息、lcd 串口、initrd、MTD等信息。
  • 如要定义了任意一个CONFIG_XXXXX,则执行setup_start_tag(bd)

一、setup_start_tag函数

  • 初始化第一块参数tag

6、uboot源码——内核启动分析  

6、uboot源码——内核启动分析


(1)params = (struct tag *) bd->bi_boot_params; 

  • gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
  • 这句代码,把uboot中的全局变量(即gd)的bi_boot_params内存地址,强制转换为stuct tag* 类型,然后赋值给params;
  • 把PHYS_SDRAM_1+0x100这个地址设置为传参的起始地址;  

(2)struct tag结构体

6、uboot源码——内核启动分析6、uboot源码——内核启动分析

  • 此结构体由一个stuct tag_header类型的结构体,加上一个由一系列结构体组成的union联合体组成;
  • 这一系列结构体中存放的就是与board有关的参数;

(3)hdr.tag 与hdr.size赋值

  • params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size (tag_core);
(4)对联合体中的结构体参数赋值
  • params->u.core.flags = 0;
  • params->u.core.pagesize = 0;
  • params->u.core.rootdev = 0;
(5)对下一个params进行初始化
  • params = tag_next (params); 
  • //#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)),把params移动sizeof(tag_core)大小继续赋值



二、传递内存参数,setup_memory_tags函数

  • 把内存每个bank的信息放到这里:第一个扇区的起始地址和大小,第二个扇区的起始地址和大小……

 6、uboot源码——内核启动分析


三、传递命令行参数,setup_commandline_tag函数

  • commandline是一个char *类型,指向环境变量中的bootargs的值,即:
  • #define CONFIG_BOOTARGS  "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC0,115200"

 6、uboot源码——内核启动分析



四、setup_end_tag (bd):结束传参

6、uboot源码——内核启动分析


-----------------------------------------uboot给内核传参结束-----------------------------------------------



 7、uboot中最后一句代码

  • 通过执行thekernel函数直接启动linux内核,传递三个参数:0、machid、传参的首地址;
  • 这三个参数是通过r0、r1、r2三个寄存器来传递,r0传递0、r1传递machid、r2传递传参的首地址。
  • 这样就把内核启动起来了。

 6、uboot源码——内核启动分析