以下解读针对6.20.1的内核
内核被boot-loader装入内存,然后解压缩,跳到第一条指令处执行.此时mmu是关闭的,就是说此时指令寄存器pc中的地址不经过转换直接对应到物理地址.而内核映像文件的入口地址(stext)在编译时是被链接到0xC0008000处(见内核链接脚本).这个地址也被定义为内存中第一条内核指令的虚拟地址:
stext <==> KERNEL_RAM_VADDR
#define KERNEL_RAM_VADDR (PAGE_OFFSET+TEXT_OFFSET) //0XC0000000+0X8000
相应的物理地址用KERNEL_RAM_PADDR变量指定,定义为
#define KERNEL_RAM_PADDR (PHYS_OFFSET+TXST_OFFSET)
//如对s3c2410,PHYS_OFFSET=0X30000000,物理地址为0X30008000
head.S中的大部分代码的地址都是位置无关的,即代码中的地址是相对于入口地址的偏移值,但打开mmu
后,程序中的地址都要通过页表转换,因此在这之前,首要任务是建立正确的页表,使得程序地址在转换时对应到相应内存物理地址上。这样当打开mmu后就可以正确执行内存地址处的指令了.打开MMU后的程序地址称为虚拟地址。因此进入内核入口后,很快就有一条指令为:bl __create_page_table既是跳到建立页表的地方执行.
在跳转到建立页表的指令之前,先要取处理器号以及根据处理器号找到该处理器信息结构的指针,
__lookup_processor_type标号处的程序完成这个任务,从它返回的时候:
r9--处理器号(cpuid),
r5--处理器信息结构(procinfo)的物理地址
r10--r5的复制.
然后跳到__lookup_machine_type处,取机器类型结构(machinfo)的物理地址,
返回时:
r5--结果
r8--r5的复制.
这两个子任务的细节后面分析.这些安排好以后就可以进入建立页表的过程了.
内核定义临时页表起始地址在内核入口地址以下16K处.一个全局变量swapper_pg_dir被定义为
表示这个一级页表起始(虚拟)地址:
.equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000
首先取页表入口的物理地址到寄存器r4中,这是通过宏pgtbl实现的:pgtbl r4
---------
宏定义
.macro pgtbl, rd
ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm
---------
接着以r4中的页表物理入口地址为参照,开始对16K的页表初始化.
第一步:清空页表
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
第二步:填写页表中对应内核所在页的页目录项(描述符).关于页表结构和描述符的
具体内容可参考arm手册.页表的内容是从r10指向的处理器信息结构中复制过来的.处理器信息结构
中用的是段(section)类型的页表,每个页表项描述1m内存区域,称为1段.16k页表有4k个表项,
覆盖4GB的虚拟内存空间
建立相同映射表项,即identity mapping,通俗的说即自己映射到自己的项.
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
取处理器信息结构中的标志部分放入r7,作为描述符的标志部分.
mov r6, pc, lsr #20 内核所在内存段开始地址(物理地址)在页表中
的索引号(当前地址的前12位)->r6
orr r3, r7, r6, lsl #20 r6和r7内容合并->r3
str r3, [r4, r6, lsl #2] 每个描述符占4字节,所以内核的起始页
目录项地址应该在[r4]+[r6]x4处,将r3的
描述符内容送入其中.
接着为当前内核所在直接可映射区域建立页表项
add r0, r4, #(TEXTADDR & 0xff000000) >> 18
内核入口处的虚拟地址所处的段开始地址对齐到16字节边界处(为内核区域分配了4个表项),右移20位得到对应的地址在页表中的索引号,每个表项占4个字位,左移2位(索引号x4)即共右移18位,得到表项距页表开始地址(r4)的总字节数,和r4相加就是这个页表项的物理地址.这个地址存入r0中.
str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
前面r3的描述符内容存入内核入口地址对应的表项(这里就是4个表项的第一个表项.因为TEXTADDR=0XC00--)
ldr r6, =(_end - PAGE_OFFSET - 1) r6存入内核所占段大小-1
mov r6, r6, lsr #20
1: add r3, r3, #1 << 20 根据内核的段大小相继填写完后面的表项.
str r3, [r0, #4]!
subs r6, r6, #1
bgt 1b
最后映射内存开始1m段,填写对应的表项,因为这里可能存放内核启动参数.
add r0, r4, #PAGE_OFFSET >> 18 表项的物理地址->r0
orr r6, r7, #(PHYS_OFFSET & 0xff000000)
orr r6, r6, #(PHYS_OFFSET & 0x00e00000) 物理起始地址是2m对齐的.
str r6, [r0] 物理起始地址写入0XC0000000对应的表项中.
到此所有需要的映射已经完成.
从协处理器寄存器中(CP#15,CR0)读出cpuid到r9中(如arm720核的cpuid是0x41807200),
再到链接时建立的处理器列表中(在.proc.info.init节中)查找这个cpuid对应的类型和操作信息.
链接时定义了_proc_info_begin和_proc_info_end两个地址符号变量分别指向这个节的开始和
结束的虚拟地址.同样在.arch.info.init节中有一个体系结构相关信息表,它的开始和结束处的符号
地址是_arch_info_star和_arch_info_end,把这些符号地址放在文件中以便引用来计算地址偏移量.
.long __proc_info_begin
.long __proc_info_end
3: .long . 这里放标号处的链接(虚拟)地址
.long __arch_info_begin
.long __arch_info_end
从_lookup_processor_type开始看起:
adr r3, 3f 用伪指令adr取相对地址,以保证位置无关性.
r3中存标号3处物理地址
ldmda r3, {r5 - r7} 在r5中得标号3处的虚拟地址,r7中得符号地址
__proc_info_begin
sub r3, r3, r7 标号3处的物理地址到__proc_info_begin
的差.
用3处的虚拟地址加上这个差得到是
__proc_info_begin的物理地址.
add r5, r5, r3 r5+(r3-r7),有点不容易理解,那就变换一
下r3+(r5-r7),即标号3的物理地址(r3)+
标号3到__proc-info-begin的偏移(位置
无关量)(r5-r7)就是_proc_info_begin
的物理地址.
1: ldmia r5, {r3, r4} 现在找到了表的入口物理地址,传送它的前2项
内容即cpu值和掩码值到r3,r4中.然后比较从cpu中读出的id(r9中)和表中的id值,找到匹配就返回,否则r5中存入0返回.
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
__lookup_machine_type的过程和以上相同.
打开MMU
调用位置无关的cpu特定代码,这些代码在arch/arm/mm/proc-*.S文件中.
通过
add pc, r10, #PROCINFO_INITFUNC
跳到__arm920_setup标号的代码处.r10是由前面__lookup_machine_type选定的xxx_proc_info 结构的基地址,当从__arm920_setup返回时,初始化系统协处理器中有关寄存器的工作已完成,cpu做好了打开MMU的准备.r0中存放了cpu控制寄存器的值.
跳到标号__enable_mmu处执行打开mmu操作.
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @关闭数据和指令cache
mcr p15, 0, r0, c7, c10,4@泄放写缓冲的内容到内存
mcr p15, 0, r0, c8, c7 @禁止数据和指令TLBs
mcr p15, 0, r4, c2, c0 @装入页表起始指针
mov r0, #0x1f @页域值,管理者,不受访问权限约束.
mcr p15, 0, r0, c3, c0 @装入页域访问寄存器
mrc p15, 0, r0, c1, c0 @取控制(配置)寄存器值
/*
* 关闭控制寄存器的某些位*/
@ VI ZFRS BLDP WCAM
bic r0, r0, #0x0e00
bic r0, r0, #0x0002
bic r0, r0, #0x000c
bic r0, r0, #0x1000 @ ...0 000. .... 000.
/*
* 打开需要的位
*/
orr r0, r0, #0x0031
orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1
/*根据内核配置选项决定要打开的位*/
#ifdef CONFIG_CPU_ARM920_D_CACHE_ON
orr r0, r0, #0x0004 @ .... .... .... .1..
#endif
#ifdef CONFIG_CPU_ARM920_I_CACHE_ON
orr r0, r0, #0x1000 @ ...1 .... .... ....
#endif
mov pc, lr @返回.其后,
@mcr p15, 0, r0, c1, c0使以上设置生效。
最后完成的工作:
1。初始化BSS段,全部清零,BSS是全局变量区域。
2。保存与系统相关的信息:如
.long SYMBOL_NAME(compat)
.long SYMBOL_NAME(__bss_start)
.long SYMBOL_NAME(_end)
.long SYMBOL_NAME(processor_id)
.long SYMBOL_NAME(__machine_arch_type)
.long SYMBOL_NAME(cr_alignment)
.long SYMBOL_NAME(init_task_union)+8192
不用讲,大家一看就明白意思
3。重新设置堆栈指针,指向init_task的堆栈。init_task是系统的第一个任务,init_task的堆栈在task structure的后8K,我们后面会看到。
4。最后跳到C代码的start_kernel。
b SYMBOL_NAME(start_kernel)
arm的数据cache必须和mmu一起打开,而指令cache可以单独打开