ARM Linux启动代码分析
别看这个定义这么长,其实需要关注的代码并不多。
220行,pgtbl是一个宏,定义如下:
47 .macro pgtbl, rd48 ldr \rd, =(KERNEL_RAM_PADDR - 0x4)49 .endm
就是将KERNEL_RAM_PADDR - 0x4的值赋给r4,现在关键是KERNEL_RAM_PADDR的定义:
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
其中PHYS_OFFSET就是SDRAM的起始地址,对于s3c6410,它的值为0x50,TEXT_OFFSET在arch/arm/Makefile中定义:
00 TEXT_OFFSET := $(textofs-y)00240 export TEXT_OFFSET GZFLAGS MMUEXT
而textofs-y的定义为:
00118 textofs-y := 0x08
因此KERNEL_RAM_PADDR的值就为0x58,而r4的值就为0x54。
225行,r0 = r4。
226行,r3 = 0。
227行,r6 = r0 + 0x4,即0x58。
228到233行,将0x54开始到0x58这段内存清零。
235行,别忘了r10存的是struct proc_info_list变量的起始地址。这里将其__cpu_mm_mmu_flags成员的值赋给r7。
在分析下面的代码之前,先了解点预备知识。我们知道MMU的主要作用是将虚拟地址转换为物理地址,但是虚拟地址与物理地址的转换关系需要我们预先设置好(就是设置页表项),而转换的过程需要通过页表来完成。对于ARM来说,映射大体分为段映射和二级映射,段映射只需要一级页表,段映射的大小为1MB,二级映射需要两级页表。下面分析的代码都只用到段映射,因此只介绍段映射。
如图1所示(以ARM9为例),根据上面的分析可知,寄存器r4里存放的是一级页表的基地址,当启动MMU后,CPU发出的是虚拟地址(正确来说是修正后的虚拟地址,即MVA),然后MMU利用该地址的最高12位(MVA[31:20])做为索引值,以一级页表基地址作为起始地址索引对应的页表项,当索引到相应的页表项后,根据页表项的内容找到对应的大小为1MB的起始物理地址,然后利用MVA的低20位(MVA[19:0])索引确切的物理地址(精确到1个字节)。
图1 段映射
具体过程如图2所示,关键看图中的虚线部分,由于页表项的大小为4字节,因此最低两位为0,也即4字节对齐,根据虚线里的值就可以找到相应页表项的起始地址。从图中也可以知道页表基地址是16KB对齐的(最低14位为0)。
图2 获取一级描述符
有了上面的基础知识后就可以继续分析代码了。
243行,r6 = pc,保存当前PC的值。
244行,r6 = r6 >> 20。
245行,r3 = r7 (r6 < 20)。此时,r3的值就是一个页表项的内容,也即段描述符。从这就可以知道244行的作用是清零r6的低20位。
246行,mem[r4 + r6 < 2] = r3,刚好与图2中的虚线部分对应。将r3的值存到页表相应的位置里,这样就完成了一个页表项的构建,也即完成了内核前1MB的映射。因为这里直接使用物理地址作为索引,所以虚拟地址与物理地址是直接映射关系,比如说虚拟地址0x58对应的物理地址也是0x58。后面会看到,这样做是为了开启MMU之后不用考虑太多的事情。
252行,r0 = r4 + (KERNEL_START & 0xff) >> 18,KERNEL_START的定义如下:
55 #define KERNEL_START KERNEL_RAM_VADDR
而KERNEL_RAM_VADDR的定义为:
29 #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
PAGE_OFFSET的值板子对应的config文件里定义,这里为0xC0,因此KERNEL_START = 0xC0 + 0x08。
253行,mem[r0 + (KERNEL_START & 0x00f00) >> 18] = r3和r0 = r0 + (KERNEL_START & 0x00f00) >> 18。其实252行253行的意思就是mem[r4 + (0xC8 & 0xfff00) >> 18] = r3,即将内核的前1MB映射到以0xC8为起始的虚拟内存处。
254行,r6 = KERNEL_END – 1,KERNEL_END的定义为:
56 #define KERNEL_END _end
而_end在arch/arm/kernel/vmlinux.lds.S中定义,表示的是内核Image的结束链接地址。
255行,r0 = r0 + 4,即下一个页表项的起始地址。
256行,r6 = r4 + r6 >> 18。
257行,比较r0,r6的值,并根据结果影响标志位。
258行,r3 = r3 + 1 < 20,即将r3的值加1MB。
259行,如果257行r0 <= r6的值就执行次句,mem[r0] = r3,r0 = r0 + 4。
260行,如果257行r0 <= r6的值就执行此句,跳转到257行。
257到260行的作用就是将整个内核Image映射到以0xC8为起始地址的虚拟地址处,如图3所示。
图3 内核Image映射到虚拟地址
162行,XIP大概就是说在Flash里执行内核,而不必把内核拷贝到内存里再执行,具体没了解过,在此略过,直接到284行。
284行,r0 = r4 + PAGE_OFFSET >> 18。
285行,r6 = r7 ( PHYS_OFFSET & 0xff)。
289行,mem[r0] = r6,即将物理内存的前1MB映射到0xC0,因为这1MB里存放有bootloader传过来的启动参数,从
ARMLinux启动代 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)