在分析__create_page_tables函数之前,需要知道以下的知识。
1、head.S首先确定了processor type和 machine type,之后就是创建页表。通过前面的两步,我们已经确定了processor type 和 machine type。此时,一些特定寄存器的值如下所示:
r8 = machine info (struct machine_desc的基地址)
r9 = cpu id (通过cp15协处理器获得的cpu id)
r10 = procinfo (struct proc_info_list的基地址)
2、由于CPU要开启MMU进入虚地址执行模式,因此必须先通过__create_page_tables建立一个临时的page table(将来这个table会被抛弃,重新建立)。
3、函数中出现的宏及其解释
宏 |
默认值 |
定义 |
KERNEL_RAM_VADDR |
0xC0008000 |
内核在内存中的虚拟地址 |
PAGE_OFFSET |
0xC0000000 |
内核虚拟地址空间的起始地址 |
TEXT_OFFSET |
0x00008000 |
内核起始位置相对于内存起始位置的偏移 |
PHYS_OFFSET |
构架相关 |
物理内存的起始地址 |
4、因为在ARM数据处理指令中,当参与操作的第二操作数为立即数时,每个立即数都是采用一个8位的常数循环右移偶数位而间接得到,所以
add r0, r4, #(KERNEL_START & 0xff000000) >> 18
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
这样分开写是由于arm的立即数只能是8位表示。
.type __create_page_tables, %function
__create_page_tables:
pgtbl r4 @通过宏pgtbl将r4设置成页表的基地址(物理地址)
/*页表将4GB的地址空间分成若干个1MB的段(section),因此页表包含4096个页表项(section entry)。每个页表项是32bits(4 bytes),因而页表占用4096*4=16k的内存空间。下面的代码是将这16k的页表清0。
*/
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
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到r7中。
/*
*下面三行设置了kernel的section的页表项,将起始物理地址为0x30008000
*的1M内存空间映射到虚拟地址为0x30008000。
*/
mov r6, pc, lsr #20 @通过PC值的高12位(右移20位),得到kernel的section。并存储在r6中,因为当前是通过运行时地址得到的kernel的section,因而是物理地址。
orr r3, r7, r6, lsl #20 @得到页表需要设置的值。
str r3, [r4, r6, lsl #2] @设置页表:mem[r4+r6*4]=r3,因为页表的每一项是32bits(4字节),所以要乘以4(<<2)。
/*
* 因为KERNEL_START是内核的起始虚拟地址(0xC0008000),KERNEL_END为内核的结束虚拟地址,所以下面的代码实际上是将物理地址为kernel的起始地址(0x30008000)的一段内存空间(大小为内核映像文件的大小)映射到虚拟地址0xC0008000。
*/
add r0, r4, #(KERNEL_START & 0xff000000) >> 18
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
ldr r6, =(KERNEL_END - 1)
add r0, r0, #4
add r6, r4, r6, lsr #18
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
/*
* 如果是XIP技术的内核,上面的映射只能映射内核代码和只读数据部分
* 这里我们再映射一些RAM来作为.data and .bss空间。
*/
#ifdef CONFIG_XIP_KERNEL
orr r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)
.if (KERNEL_RAM_PADDR & 0x00f00000)
orr r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)
.endif
add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18
str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
ldr r6, =(_end - 1)
add r0, r0, #4
add r6, r4, r6, lsr #18
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
#endif
/*
*下面的代码用来设置RAM中起始地址为0x30000000、大小为1M虚拟地址的页表,之所以要设置这个页表项的原因是该区域起始地址为0x30000100存
*储着boot params。因此需要为它建立map,这样开启MMU后就可以访
*问这些参数了。
*/
add r0, r4, #PAGE_OFFSET >> 18
orr r6, r7, #(PHYS_OFFSET & 0xff000000)
.if (PHYS_OFFSET & 0x00f00000)
orr r6, r6, #(PHYS_OFFSET & 0x00f00000)
.endif
str r6, [r0]
mov pc, lr @建好页表后返回
通过__create_page_tables函数可知,启动时虚拟地址和物理地址映射关系如下图所示:
问题:为什么要把物理地址0x30008000开始的一段内存映射到虚拟地址0x30008000?
由于笔者能力有限,文中难免存在不足,如果发现错误之处,希望能帮助我及时指正。