一:来源

前面分析,内核启动是从hesd.S开始的,在建立段式页表之后,

ldr r13, __switch_data 跳转到__switch_data

__switch_data:
.long__mmap_switched
.long__data_loc@ r4
.long_data@ r5
.long__bss_start@ r6
.long_end@ r7
.longprocessor_id@ r4
.long__machine_arch_type@ r5
.long__atags_pointer@ r6
.longcr_alignment@ r7
.longinit_thread_union + THREAD_START_SP @ sp


这个东西其实可以理解为一个函数指针数组,并且做了如下事情

(1)分析得知下一步要执行__mmap_switched函数,在这个函数中通过

b start_kernel跳转到C语言运行阶段。

(2)复制数据段、清除bss段(目的是构建C语言运行环境)

(3)保存起来cpu id号、机器码、tag传参的首地址。


二:C语言运行阶段分析

(1)函数名:start_kernel();这个和uboot启动第二阶段的函数名start_armboot很像,原因是uboot本身就是模仿kernel写出来的。

(2)smp_setup_processor_id();

SMP (对称多处理器)。对单核SOC来说,mpidr = 0;所以当只有一个CPU时这个函数就什么也不做,但如果有多个CPU的时候那么它就返回在启动的时候那个CPU的ID号

(3)lockdep_init();    

初始化lockdep hash表

lockdep: 死锁检测模块

死锁是指多个进程(线程)因为长久等待已被其他进程占有的的资源而陷入阻塞的一种状态。当等待的资源一直得不到释放,死锁会一直持续下去。死锁一旦发生,程序本身是解决不了的,只能依靠外部力量使得程序恢复运行,例如重启,开门狗复位等。

(4)debug_objects_early_init();  //初始化debug kernel相关

(5)boot_init_stack_canary();

stack_canary的是带防止栈溢出***保护的堆栈。详见http://www.cloud-sec.org/CC_STACKPROTECTOR_patch_Analysis

(6)cgroup_init_early();

Cgroup初始化,Cgroup是近代linux kernel出现的.它为进程和其后续的子进程提供了一种性能控制机制,详见:http://linux.chinaunix.net/techdoc/net/2008/12/23/1054425.shtml

(7)local_irq_disable();  

     关闭当前CPU的中断

(8)early_boot_irqs_off();  

也是和CPU中断相关的

(9)early_init_irq_lock_class();  

IRQ中断的初始化

(10)lock_kernel();

(11)tick_init();   //初始化 tick控制功能,注册clockevents的框架

(12)boot_cpu_init();

    对于CPU核的系统来说,设置第一个CPU核为活跃  CPU核。对于单CPU核系统来说,设置CPU核为活跃CPU核

(13)page_address_init();

    当定义了CONFIG_HIGHMEM 宏,并且没有定义   WANT_PAGE_VIRTUAL 宏时,非空函数。其他情况为空函数。注意:ARM9不支持高端地址(大于896M),一般的嵌入式产品也不会用高端地址,所以,在ARM体系结构下,此函数为空

(14)printk(KERN_NOTICE "%s", linux_banner);

    将linux_banner的内容打印到log_buf缓冲区中去,等到串口或者其它终端初始化之后,在一次性打印到终端上去 KERN_NOTICE这个宏表示打印的级别,只有打印级别高于(等于可能也行)控制台的打印级别的信息最终才能被打印出来

linux_banner是linux内核的信息,主要是版本,编译生成的时间等

(15)setup_arch(&command_line);

     实际上这个函数是用来确定我们当前内核的机器(arch、machine)的。

我们的linux内核会支持一种CPU的运行,CPU+开发板就确定了一个硬件平台,

然后我们当前配置的内核就在这个平台上可以运行。之前说过的机器码就是给这个硬件平台一个固定的编码,以表征这个平台。当前内核支持的机器码以及硬件平台相关的一些定义都在这个函数中处理。


这个函数里面的调用的函数分析

   setup_processor函数用来查找CPU信息,可以结合串口打印的信息来分析。


   setup_machine函数的传参是机器码编号,machine_arch_type符号在include/generated/mach-types.h的32039-32050行定义了。经过分析后确定这个传参值就是2456.函数的作用是通过传入的机器码编号,找到对应这个机器码的machine_desc描述符,并且返回这个描述符的指针。其实真正干活的函数是lookup_machine_type,找这个函数发现在head-common.S中,真正干活的函数是__lookup_machine_type。)__lookup_machine_type函数的工作原理:内核在建立的时候就把各种CPU架构的信息组织成一个一个的machine_desc结构体实例,然后都给一个段属性.arch.info.init,链接的时候会保证这些描述符会被连接在一起。__lookup_machine_type就去那个那些描述符所在处依次挨个遍历各个描述符,比对看机器码哪个相同。

setup_arch函数进行了基本的cmdline处理

(1)这里说的cmdline就是指的uboot给kernel传参时传递的命令行启动参数,也就是uboot的bootargs

(2)有几个相关的变量需要注意:

default_command_line:看名字是默认的命令行参数,实际是一个全局变量字符数组,这个字符数组可以用来存东西。

CONFIG_CMDLINE:在.config文件中定义的(可以在make menuconfig中去更改设置),这个表示内核的一个默认的命令行参数。

(3)内核对cmdline的处理思路是:内核中自己维护了一个默认的cmdline(就是.config中配置的这一个),然后uboot还可以通过tag给kernel再传递一个cmdline。如果uboot给内核传cmdline成功则内核会优先使用uboot传递的这一个;如果uboot没有给内核传cmdline或者传参失败,则内核会使用自己默认的这个cmdline。以上说的这个处理思路就是在setup_arch函数中实现的


实验验证内核的cmdline确定

(1)验证思路:首先给内核配置时配置一个基本的cmdline,然后在uboot启动内核时给uboot设置一个bootargs,然后启动内核看打印出来的cmdline和uboot传参时是否一样。

(2)在uboot中去掉bootargs,然后再次启动内核看打印出来的cmdline是否和内核中设置的默认的cmdline一样。


注意:uboot给内核传递的cmdline非常重要,会影响内核的运行,所以要谨慎。有时候内核启动有问题,可以分析下是不是uboot的bootargs设置不对。

总结:setup_arch中做的2件事情:机器码架构的查找并且执行架构相关的硬件的初始化、uboot给内核的传参cmdline。


(16)mm_init_owner(&init_mm, &init_task);  

内存管理相关的初始化

(17)setup_command_line(command_line);

command 相关的函数处理命令行先关的函数,给saved_command_line和static_command_line分配内存;复制boot_command_line和command_line,这两东西其实就是setup_machine中的default_command_line中的内容,或者是uboot传参时的ubootargs

(18)printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);

正式打印kernel command line的信息

(19)parse_early_param和parse_args

解析cmdline传参和其他传参,也就是把cmdline的信息以及细节解析出来。譬如cmdline :console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3,则解析出的内容就是就是一个字符串数组,数组中依次存放了一个设置项目信息。

console=ttySAC2,115200   一个

oot=/dev/mmcblk0p2 rw      一个

init=/linuxrc                              一个

rootfstype=ext3                           一个

也就是将cmdline解析成四个字符串,每个字符串将来都对应一个设置项,每个设置项都会对kernel的运行有所影响。这里只是进行了解析,并没有去处理。也就是说只是把长字符串解析成了短字符串,最多和内核里控制这个相应功能的变量挂钩了,但是并没有去执行。执行的代码在各自模块初始化的代码部分。


(20)trap_init       设置异常向量表

(21)mm_init 内存管理模块初始化

(22)sched_init         内核调度系统初始化

(23)early_irq_init&init_IRQ          中断初始化

(24)console_init   控制台初始化


总结:start_kernel函数中调用了很多的xx_init函数,全都是内核工作需要的模块的初始化函数。这些初始化之后内核就具有了一个基本的可以工作的条件了。

如果把内核比喻成一个复杂机器,那么start_kernel函数就是把这个机器的众多零部件组装在一起形成这个机器,让他具有可以工作的基本条件。

当这些部分都初始化完成以后,运行到了start_kernel的最后一个函数rest_init()