Linux内核启动分析(一)
序:关于Linux的启动已经是一个老生常谈的话题了,但这里还是对Linux内核的启动进行一下总结。现在的Linux内核代码已经复杂到让人无法剖根究底,但是分析其主干还是很有必要的。就此,本人按自己的观点对Linux启动代码进行剖析。如果有不对之处,还望各位看官予以纠正。
对于Linux内核的启动,我们无须花太多时间在解压缩部分,可直接分析arch/arm/kernel/head.S(分析的内核版本为Linux2.6.30,硬件平台为GT2440,其他软硬件平台原理一致)。
观链接脚本arch/arm/kernel/vmlinux.lds,可知链接的入口点为stext。该该入口定义在arch/arm/kernel/head.S中。
首先对Linux启动的第一阶段的功能进行总结再来分析代码,该阶段的主要功能:
·设置处理器为SVC模式
·检查内核是否支持该处理器
·检查内核是否支持该处理器平台
·检查U-BOOT传递给内核参数的地址是否合法
·为启动阶段运行创建初始化页表
·进行CPU级的初始化(CACHE及协处理器设置)
·使能MMU
·清除BSS
以上代码实现了多半功能,但涉及一些子函数分支。上面的功能代码已经注释的非常详细,下面对一些子函数及疑问进行分析。
注:Linux内核的链接地址在链接脚本中指定为:PAGE_OFFSET + TEXT_OFFSET = 0xC0008000而内核解压后开始运行时会位于物理内存0x30008000(S3C2440)处,因此必定会有一段代码的运行地址与该段代码的链接地址不一致。所以这段代码必须是位置无关的,否则无法运行。
因此在上面这段代码,我们需关注与地址相关的指令。下图为上述汇编代码的反汇编代码:
其中:
sl = r10
b/bl为相对跳转指令,跳转的目的地址为PC值加个偏移值
而我们所不解的指令: ldr r13, __switch_data竟然翻译成了:
ldr sp, [pc,#240] ;即使sp = [30008120] 内存地址为30008120处的值0xC0008148
主要的话说明CPU并不是直接取出0xC0008120处的值赋值给SP,而是通过计算处0xC0008120处的偏移地址后再取值,这也就说的过去了。因此,当我们看到这种无法解释的指令的时候,通过反汇编即可迎刃而解。
而原来的指令: adr lr, __enable_mmu的反汇编为:add lr, pc, #0,而pc此时的值为3000802c+8。因此lr = 300080034,该地址处的代码如下:
因此执行完CPU初始化代码后的返回地址为0x300080034。
现在开始分析这几个子函数:
__lookup_processor_type:查看内核是否支持该处理器
对于处理器,Linux内核使用struct proc_info_list结构来描述。而在arch/arm/mm/proc-xxx.S中针对不同的处理器有该结构的定义(如ARM920T,有proc-arm920.S),以下为该结构的定义:
对于ARM920T,在arch/arm/mm/proc-arm920.S中有如下定义:
以下为__lookup_processor_type的核心代码:
标号3处定义的变量:
__lookup_machine_type:查看内核是否支持该处理器平台
对于处理器的硬件平台,Linux内核使用struct machine_desc结构来描述,并构造宏MACHINE_START(_type,_name)来定义该结构。对于SMDK2440开发板,在arch/arm/mach-s3c2440/mach-smdk2440.c中有如下定义:
硬件平台描述结构如下:
具体的代码分析如下:
__vet_atags:检查参数地址
__create_page_tables:创建初始化页表
前面所有的操作都是运行在MMU关闭的状态,为了是内核运动地址与链接地址一致应尽早的开启MMU,而开启MMU之前需要创建页表。因此,在此处先创建初始化页表,这阶段创建的页表会在后期销毁,只是起临时作用。
第一步:清空页表项所占的内存空间
第二步:创建页表映射
如果支持调试,还得映射好串口:
创建页表后设置好sp和lr后便去初始化CPU了:
因此将调用proc-arm920.S中__arm920_proc_info结构中的跳转指令:
对应的代码:
__enable_mmu:使能MMU
开启MMU后,将R13赋值给PC。因此,内核跳转到__switch_data处执行。
因此,CPU将跳转到__mmap_switched处执行:
总结:
1.Linux启动阶段的映射图
2.为何需要将虚拟地址范围0x30000000 ~ 0x30100000映射到物理地址范围0x30000000 ~ 0x30100000?
当CPU执行mcr p15, 0, r0, c1, c0, 0这条指令时,PC应该指向了第一条mov r3, r3指令,且此时的PC值必然为0x3xxxxxxx。由于已经开启了MMU,所以如果没有虚拟-物理地址相同的映射才mov r3, r3开始的三条指令都无法运行。证明方法:将__create_page_tables子过程的以下几条指令注释:
内核将无法启动。这里涉及几个知识点:ARM支持流水线操作,当执行某条指令时预取指令的地址PC不等于当前正在执行的指令的地址(PC+X)。取指的过程为从PC指向的内存地址取出指令到CPU内部。因此,取指的过程可以看作是先读出PC值然后从PC值对应的内存地址中取出4字节的指令码。