0
点赞
收藏
分享

微信扫一扫

uboot启动流程分析


uboot启动流程分析_示例代码

uboot启动流程分析_设备树_02

uboot启动流程分析_linux_03

uboot启动流程分析_设备树_04

uboot启动流程分析_linux_05

uboot启动流程分析_linux_06

uboot启动流程分析_设备树_07

第 14 行判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的

分支。比如设置 abort 为 1,设置 bootdelay 为 0 等,最后跳出倒计时循环。

第 26 行,返回 abort 的值,如果倒计时自然结束,没有被打断 abort 就为 0,否则的话 abort

的值就为 1。

回到示例代码 32.2.9.6 的 autoboot_command 函数中,如果倒计时自然结束那么就执行函数

run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,

bootcmd 里面保存着默认的启动命令,因此 linux 内核启动!这个就是 uboot 中倒计时结束以后

自动启动 linux 内核的原理。如果倒计时结束之前按下了键盘上的按键,那么 run_command_list

函数就不会执行,相当于 autoboot_command 是个空函数。

回到“遥远”的示例代码 32.2.9.2 中的 main_loop 函数中,如果倒计时结束之前按下按键,

那么就会执行第 74 行的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。


回到“遥远”的示例代码 32.2.9.2 中的 main_loop 函数中,如果倒计时结束之前按下按键,

那么就会执行第 74 行的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。

32.2.10 cli_loop 函数详解

cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就
是有 cli_loop 来处理的
,此函数定义在文件 common/cli.c 中,函数内容如下:

示例代码 32.2.10.1 cli.c 文件代码段

202 void cli_loop(void)

203 {

204 #ifdef CONFIG_SYS_HUSH_PARSER

205 parse_file_outer();

206 /* This point is never reached */

207 for (;;);

208 #else

209 cli_simple_loop();

210 #endif /*CONFIG_SYS_HUSH_PARSER*/

211 }

在文件 include/configs/mx6_common.h 中有定义宏 CONFIG_SYS_HUSH_PARSER,而正点

原子的 I.MX6ULL 开发板配置头文件 mx6ullevk.h 里面会引用 mx_common.h 这个头文件,因此

宏 CONFIG_SYS_HUSH_PARSER 有定义。

第 205 行调用函数 parse_file_outer。

第 207 行是个死循环,永远不会执行到这里。

函数 parse_file_outer 定义在文件 common/cli_hush.c 中,去掉条件编译内容以后的函数内容

如下:

示例代码 32.2.10.2 parse_file_outer 函数精简

1 int parse_file_outer(void)

2 {

3 int rcode;

4 struct in_str input;

5

6 setup_file_in_str(&input);

7 rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);

8 return rcode;

9 } 

原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com

823

I.MX6U 嵌入式 Linux 驱动开发指南

第 3 行调用函数 setup_file_in_str 初始化变量 input 的成员变量。

第 4 行调用函数 parse_stream_outer,这个函数就是 hush shell 的命令解释器,负责接收命

令行输入,然后解析并执行相应的命令,函数 parse_stream_outer 定义在文件 common/cli_hush.c

中,精简版的函数内容如下:

示例代码 32.2.10.3 parse_stream_outer 函数精简

1 static int parse_stream_outer(struct in_str *inp, int flag)

2 {

3 struct p_context ctx;

4 o_string temp=NULL_O_STRING;

5 int rcode;

6 int code = 1;

7 do {

8 ......

9 rcode = parse_stream(&temp, &ctx, inp,

10 flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');

11 ......

12 if (rcode != 1 && ctx.old_flag == 0) {

13 ......

14 run_list(ctx.list_head);

15 ......

16 } else {

17 ......

18 }

19 b_free(&temp);

20 /* loop on syntax errors, return on EOF */

21 } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&

22 (inp->peek != static_peek || b_peek(inp)));

23 return 0;

24 }

第 7~21 行中的 do-while 循环就是处理输入命令的。

第 9 行调用函数 parse_stream 进行命令解析。

第 14 行调用调用 run_list 函数来执行解析出来的命令。


uboot启动流程分析_linux_08

uboot启动流程分析_设备树_09

uboot启动流程分析_示例代码_10

uboot启动流程分析_设备树_11

uboot启动流程分析_设备树_12

u-boot为了做得通用,能启动所有的操作系统,所以它加了很多很多的通用化判断,配置,初值赋值,选择,等过程,使得代码非常非常复杂和庞大,各种封装,各种宏定义,跳转。我举得反而失去了易懂的初衷了。

uboot启动流程分析_uboot启动流程_13


uboot启动流程分析_设备树_14


第 295 行,函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也

就是最终的大 boos!!此函数有三个参数:zero,arch,params,第一个参数 zero 同样为 0;第

二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用

于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。

第 299 行,获取 kernel_entry 函数,函数 kernel_entry 并不是 uboot 定义的,而是 Linux 内

核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux

内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码!

uboot启动流程分析_设备树_15

继续回到示例代码 32.3.6.2 的函数 boot_jump_linux,第 315~318 行是设置寄存器 r2 的值?

为什么要设置 r2 的值呢?Linux 内核一开始是汇编代码,因此函数 kernel_entry 就是个汇编函

数。向汇编函数传递参数要使用 r0、r1 和 r2(参数数量不超过 3 个的时候),所以 r2 寄存器就是

函数 kernel_entry 的第三个参数。

第 316 行,如果使用设备树的话,r2 应该是设备树的起始地址,而设备树地址保存在 images

的 ftd_addr 成员变量中。

第 317 行,如果不使用设备树的话,r2 应该是 uboot 传递给 Linux 的参数起始地址,也就

是环境变量 bootargs 的值,

第 328 行,调用 kernel_entry 函数进入 Linux 内核,此行将一去不复返,uboot 的使命也就
完成了
,它可以安息了!


uboot启动流程分析_设备树_16

uboot启动流程分析_uboot启动流程_17


举报

相关推荐

0 条评论