历史上的今天
今天是:2025年08月20日(星期三)
2021年08月20日 | 1_5.3.4_内核配置裁剪及启动流程_内核启动流程分析之内核启
2021-08-20 来源:eefocus
内核的最终目的:运行应用程序(在根文件系统里面,需要挂接根文件系统)。
一下运行应用程序前要做什么事情?
1.处理u-boot传入的参数
从第一个文件(arch/arm/kernel/head.S)开始分析。
查找head.S,发现还有一个bootcompressed目录下的head.S文件,这是什么文件呢?

我们编译出来的内核可能会很大,有没有办法让它变小一点呢?
答:有的,使用压缩将文件变小。
在压缩后的文件前面加上一个自解压代码。本来代码是从原来的文件开始运行,现在使用压缩后的文件,代码就从自解压代码开始运行。自解压代码会将后面的压缩文件解压出来,然后再从解压缩后的代码开始运行。可以看出,这是一种时间换空间的方法。
打开kerenl中的head.S文件,从上往下看。
开始这边会检查处理器ID,查看内核是否支持这种处理器,如果不支持就跳转到error_p中去,支持就继续往下执行。

然后还要检查机器ID,这个机器ID是由u-boot在启动内核时传入的。

在启动内核时,uboot将机器ID传过来,对于2440的开发板,启动ID是MACH_TYPE_S3C2440,也就是362。



点入__lookup_machine_type查看具体的实现过程,其中r4的点(".")是虚拟地址。


其中,__arch_info_begin和__arch_info_end是在链接脚本中定义的,可以看到,他们分别表示(.arch.info.init)段的起始地址和结束地址,是从(0xc0000000) + 0x00008000开始增长的,所以他们也是虚拟地址。

那么,(.arch.info.init)段中包含了什么?
使用grep “(.arch.info.init)” * -nR搜索,可以看到在include/asm-arm/mach/arch.h文件下有定义。

打开该文件查看,可以看到这是一个宏定义,传入_type和_name,然后会定义一个属性为(".arch.info.init")段的结构体。其中 __uesd的意思是告诉编译器,这一段有用,这样即使这一段没有用到,编译器也不会报警告 。

在archarmmach-s3c2440的mach-smdk2440.c文件中有如下定义:

将它们综合起来就是定义了一个只读的静态machine_desc结构体,结构体名字为__mach_desc_S3C2440,然后是它的一些参数。

查看machine_desc结构体的定义,可以看到nr,这个就是机器ID,还可以看到boot_params,这是启动参数的入口地址,都是和机器相关的描述。显然,内核支持多少单板,里面就应该有多少个这样的结构体。

通过这个结构体,内核的机器ID就和之前的u-boot传入的机器ID联系起来了。2440的机器ID,2410的机器ID还有很多其他的机器ID都存在这里,启动时逐一比较,找到相同的机器ID就说明内核有支持,否则就不支持。
之后就是逐个比较,直到全部比较完或者找到一致的机器ID了, 如果全部找完都没有找到一致的,那么说明不支持,那就让r5等于0,然后退出调用。(blo表示(无符号数)小于跳转)

接下来是建立列表,为什么要建立列表?
答:我们的SDRAM是从0x30000000开始的,但是链接脚本的起始地址却是(0xc0000000) + 0x00008000,显然,这个地址并不对应一个真实存在的内存,事实上这是一个虚拟地址,所以需要建立一个页表,这个页表会提供给MMU使用,用于虚拟地址和物理地址之间的转换。


接着向下看,通过注释可以知道,后续会使能MMU,然后跳转__mmap_switched,最后跳转到start_kernel。其中,start_kernel是内核的第一个C函数。



下面两幅图就是ARM处理器的Linux内核启动过程。


启动内核查看,可以看到看到自解压的调试信息和输出内核打印信息。

其中内核的打印信息就是通过printk(linux_banner)输出的。


我们在u-boot中还设置了启动参数,刚刚分析了这么多,都没有关于启动参数,那么启动参数要在哪里使用呢?
答:在内核的第一个程序,也就是start_kernel中使用。所以我们还要继续分析start_kernel。
查看start_kernel,这两个函数就是用来处理启动参数的。

再回过头来看下,启动参数有哪些东西,之前分析的笔记如下,其中命令行参数是由getenv(“bootargs”)获得的,这些参数都存在内存起始地址为0x30000100的地方。


启动内核的最终目的是执行应用程序,而应用程序是在根文件系统里面的,所以需要先挂接根文件系统,梳理了一下调用关系。

挂接根文件系统,那么需要挂接到哪一个根文件系统呢?
答:挂接的根文件系统是根据传入的启动参数决定的,在u-boot中查看,可以看到挂接的是第四个分区上面(从0开始)。

内核在mount_root中挂载根文件系统,那么在prepare_namespace中就要决定挂接的是哪个根文件系统。我们以此入手,来分析一下这些参数怎么处理。
从变量名可以看出,ROOT_DEV就是根文件系统,它的设置与saved_root_name有关,可以猜测一下,这个变量保存的应该就是根文件系统的名字。

查看saved_root_name的调用关系,可以看到root_dev_setup和__setup,作用是在解析命令行参数时,如果遇到root=,那就会调用root_dev_setup函数,这个函数就将这个root=后面的数据保存到了saved_root_name里去。

查看saved_root_name定义,可以看到,saved_root_name是一个最多64字节的字符型数组。

根据之前的经验,可以推测这个__setup应该也是一个宏,而且这个宏应该在某一个头文件中定义,搜索__setup,可以看到,在init.h文件中定义了这个宏。

进入init.h,可以看到如下定义。

和__setup(“root=”, root_dev_setup);一起展开,结果如红框所示,也就是定义了一个char型的数组和一个结构体,结构体里面有三个成员,一个char型的数组,一个函数,还有一个参数early,同时结构体的段属性被强制定义为了.init.setup。


可以在链接脚本中找到这个段(.init.setup),再来搜索一下__setup_start和__setup_end,这样我们就知道这些命令行是被怎样调用的了。

搜索发现,在两个函数中有用到,分别是obsolete_checksetup和do_early_param,可以猜测,那么early会在do_early_param中用到。

显然,不是do_early_param函数。

应该是在obsolete_checksetup中使用。


所以,u-boot传入的命令行参数,在内核的(.init.setup)段都会一一对应,等u-boot传入后再来一条一条的处理。
内核的启动流程就是这样,回过头来再说一个问题,现在是root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,我们说过在flash中无分区表,这些分区都是在代码中写死的,那么写在哪里呢?

答:启动内核的时候会打印分区的信息,我们可以搜索分区名来查找在哪里设置这些分区。

可以看到,应该是在common-smdk.c文件中定义的。

打开common-smdk.c文件查看一下,这就是它的分区,在代码里面写死的。
史海拾趣
|
我的PIC单片机调试心得,给像我一样曾经苦苦寻觅的初学者一点启示 我是一位初学者,pic初学者,但是在单片机行当已经混迹多年了。说句实话,c开发环境都大同小异,只不过烧写和在线仿真大同小异。钻研了两天,收到了很多回应和启发。 下面应soso的请求,把最近的调试心得和大家分享 1、 先说说我要做的东西 说来 ...… 查看全部问答> |
|
9. TI协议栈所用系统框架探讨。 51的系统往往不是太大,但是几十K的程序,也足以让一个初学者望而却步。我们首先忽略C语言本身的难度,光是系统框架也让生手读起来很吃力,再加上这种到处是API跟\"define\"的程序,还没有正式学习协议部分就已经让 ...… 查看全部问答> |
|
问题是这样的:从CPU过来的数据总线,接到FPGA了,FPGA后面又接了一个CPLD,等于说,FPGA既要将数据总线自己用,又要输出给CPLD用,该怎么实现了? 难题是数据总向是双向的不好办! 因为两片FPGA之间的连线资源较多,把它们之间的数据总结分为送 ...… 查看全部问答> |
|
请问各位大虾,现在我用ARM模块作了一个模块,需要计算机把它识别成一个人体学输入设备,该模块现在用的是windows ce系统,请问是否可以做一个驱动程序?大概应该怎么做?… 查看全部问答> |
|
从CWnd继承一个类CInputInfoWnd,在CInputInfoWnd用Create动态创建Edit控件m_editTest,此Edit控件上可以看到输入光标,但不能输入任何东西?… 查看全部问答> |




