内核编译(七):内核启动流程(二)

    上节课,我们调用了详细地分析了head.S这个文件,并知道,它的作用是进行一些内核引导的初始化,并知道了,CPU最终是跳到了start_kernel(没什么意外的话)去执行的。因此,本节课,我们就来分析start_kernel这个函数。

    我们先看一下start_kernel这个函数,在init文件夹里的main.c文件里:

我们看到,前面的做了一堆的初始化,比如有关死锁的,中断的,这里我们先不去分析,略过它们。经过这些初始化后,我们看到这句:


这里,它打印了一些信息,我们看一些它传入的参数Linux_banner是什么:


可以看到,它应该是打印内核相关的信息,如版本,编译器,编译目录编译日期等,我们可以修改里面的内容,然后编译生成烧录进去试试:


如图,我成功修改成了我的个人信息。我们继续往下看:


这两步,是处理我们UBOOT启动内核时传入的命令行参数。处理完参数之后,我们知道,我们内核最终的目的是要挂载根文件系统,所以我们接着往下看:


后面,它会执行rest_init这个函数,我们跳进这个函数里面去看看:

我们看到kernel_thread这个函数的第一个调用,这里我们暂且猜测它是调用kernel_init这个函数,我们跳进这个函数里面进去看看:


我们看到prepare_namespace这个函数,跳进去看看:


我们看到最后,会看到这样一个函数:


这就是开始给我们挂接根文件系统。这里,我们大致的把内核启动流程给总结一下:

head.S(内核引导的初始化)---->start_kernel(setup_arch和setup_command_line:解析UBOOT传进来的命令行参数,最终跳到rest_init)----->rest_init---->kernel_init---->prepare_namespace----->mount_root(挂接根文件系统,即识别根文件系统)----->init_post(检查是否挂接成功并进行其它操作等)。   

    接下来,我们就来分析它的命令行参数了。为什么要分析命令行参数呢?我们在之前分析U-Boot的时候知道,在命令行参数中,保存有根文件系统的信息,既然内核要挂接根文件系统,肯定需要知道根文件系统的位置,这样才能执行挂载操作。我们在mout_root这个函数往上看就会看到这部分代码:


ROOT_DEV也就是我们的根文件设备(系统),而ROOT_DEV这个值,在这里是由saved_root_name赋予的,那么这个saved_root_name这个值又是哪里得来的呢?我们继续往上看:

我们看到,saved_root_name这的值最终是通过调用root_dev_setup这个函数得到的。我们看到它的下面还有一个带参宏,而且第一个参数是“root=”,看到这里我们是否感到很熟悉?没有错,这就是我们命令行参数里面的部分内容:


通过调用函数root_dev_setup,就可以把"root="后面的内容取出来,然后保存到saved_root_name里面去。下面,我们就来看看这个宏是如何工作的。


可以看到,这个宏是被替换成:

static char __setup_str_root_dev_setup[] __initdata = "root=";	\ /*#define __initdata	__attribute__ ((__section__ (".init.data")))*/
static struct obs_kernel_param __setup_root_dev_setup	\
	__attribute_used__				\
	__attribute__((__section__(".init.setup")))	\
	__attribute__((aligned((sizeof(long)))))	\
	= { __setup_str_root_dev_setup, root_dev_setup, 0 }

可以看到,这里定义了一个首先定义了一个字符串,并把它强制的指定在.init.data这个段,然后再定义了一个结构体,同时把这个结构体强制的指定在.init.setup这个段,在这个结构体里面有三个值,一个就是我们之前定义的那个字符串,另一个就是我们需要调用到的函数,最后一个是不使用的。接下来,我们就在链接脚本这里看一下.init.setup这个段:

可以看到,这一段的起始地址是__setup_start,结束地址是__setup_end。对于这些结构体,后面肯定是通过某个函数去遍历他们的,所以我们就看看是那些函数使用了这两个地址:


第二个do_early_param我们不考虑,因为我们的第四个参数是0。这样我们就考虑第一个obsolete_checksetup。在这个函数中,它遍历了.init.setup这个段的所有结构体,并分别调用了他们的函数。这样,就可以调用我们之前传入的 root_dev_setup这个函数来获得我们的根文件设备了。对于这个分区的信息,我们在嵌入式系统中,一般是写死的(因为FLASH里面没有分区表),我们在arm/plat-s3c24xx/common_smdk.c这个文件里面就会找到它的信息:

这就是我们NANDFLASH上的默认分区信息。

以上就是内核启动流程的所有内容,后面将会讲解根文件系统的内容。

猜你喜欢

转载自blog.csdn.net/xiaokangdream/article/details/80283559