Linux内核启动分析(一)

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内核启动分析(一)_第1张图片 

以上代码实现了多半功能,但涉及一些子函数分支。上面的功能代码已经注释的非常详细,下面对一些子函数及疑问进行分析。

注:Linux内核的链接地址在链接脚本中指定为:PAGE_OFFSET + TEXT_OFFSET = 0xC0008000而内核解压后开始运行时会位于物理内存0x30008000(S3C2440)处,因此必定会有一段代码的运行地址与该段代码的链接地址不一致。所以这段代码必须是位置无关的,否则无法运行。

因此在上面这段代码,我们需关注与地址相关的指令。下图为上述汇编代码的反汇编代码:

Linux内核启动分析(一)_第2张图片 

其中:

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,该地址处的代码如下:

Linux内核启动分析(一)_第3张图片 

因此执行完CPU初始化代码后的返回地址为0x300080034。

现在开始分析这几个子函数:

 

__lookup_processor_type:查看内核是否支持该处理器

对于处理器,Linux内核使用struct proc_info_list结构来描述。而在arch/arm/mm/proc-xxx.S中针对不同的处理器有该结构的定义(如ARM920T,有proc-arm920.S),以下为该结构的定义:

Linux内核启动分析(一)_第4张图片 

对于ARM920T,在arch/arm/mm/proc-arm920.S中有如下定义:

Linux内核启动分析(一)_第5张图片 

以下为__lookup_processor_type的核心代码:

Linux内核启动分析(一)_第6张图片 

标号3处定义的变量:

Linux内核启动分析(一)_第7张图片 

 

__lookup_machine_type:查看内核是否支持该处理器平台

对于处理器的硬件平台,Linux内核使用struct machine_desc结构来描述,并构造宏MACHINE_START(_type,_name)来定义该结构。对于SMDK2440开发板,在arch/arm/mach-s3c2440/mach-smdk2440.c中有如下定义:

Linux内核启动分析(一)_第8张图片 

硬件平台描述结构如下:

Linux内核启动分析(一)_第9张图片 

具体的代码分析如下:

Linux内核启动分析(一)_第10张图片 

 

__vet_atags:检查参数地址

Linux内核启动分析(一)_第11张图片 

 

__create_page_tables:创建初始化页表

前面所有的操作都是运行在MMU关闭的状态,为了是内核运动地址与链接地址一致应尽早的开启MMU,而开启MMU之前需要创建页表。因此,在此处先创建初始化页表,这阶段创建的页表会在后期销毁,只是起临时作用。

第一步:清空页表项所占的内存空间

Linux内核启动分析(一)_第12张图片 

第二步:创建页表映射

Linux内核启动分析(一)_第13张图片 

 Linux内核启动分析(一)_第14张图片

如果支持调试,还得映射好串口:

Linux内核启动分析(一)_第15张图片 

 

创建页表后设置好sp和lr后便去初始化CPU了:

 

因此将调用proc-arm920.S中__arm920_proc_info结构中的跳转指令:

 

对应的代码:

Linux内核启动分析(一)_第16张图片 

 

 

__enable_mmu:使能MMU

Linux内核启动分析(一)_第17张图片 

开启MMU后,将R13赋值给PC。因此,内核跳转到__switch_data处执行。

Linux内核启动分析(一)_第18张图片 

因此,CPU将跳转到__mmap_switched处执行:

 Linux内核启动分析(一)_第19张图片

 

总结:

1.Linux启动阶段的映射图

Linux内核启动分析(一)_第20张图片

2.为何需要将虚拟地址范围0x30000000 ~ 0x30100000映射到物理地址范围0x30000000 ~ 0x30100000?

 Linux内核启动分析(一)_第21张图片

当CPU执行mcr p15, 0, r0, c1, c0, 0这条指令时,PC应该指向了第一条mov r3, r3指令,且此时的PC值必然为0x3xxxxxxx。由于已经开启了MMU,所以如果没有虚拟-物理地址相同的映射才mov r3, r3开始的三条指令都无法运行。证明方法:将__create_page_tables子过程的以下几条指令注释:

Linux内核启动分析(一)_第22张图片 

内核将无法启动。这里涉及几个知识点:ARM支持流水线操作,当执行某条指令时预取指令的地址PC不等于当前正在执行的指令的地址(PC+X)。取指的过程为从PC指向的内存地址取出指令到CPU内部。因此,取指的过程可以看作是先读出PC值然后从PC值对应的内存地址中取出4字节的指令码。

你可能感兴趣的:(linux启动分析,ARM9)