linux内存管理之boot阶段identity mapping及MMU开启过程

复习下kernel内存管理相关内容,顺便写个内存管理的系列文章做记录。
U-Boot跳转到内核时候MMU是关闭的,所以在内核boot阶段需要开启MMU,开MMU之前就需要初始化页表。
boot阶段的页表创建是通过函数__create_page_table来完成的,代码位于arch/arm/kernel/head.S中
##基本宏变量

  • PAGE_OFFSET
    这个变量一般被定义为0xC000 0000, 但在tiny4412平台上被定义为0x8000 0000。这个地址表示内核在虚拟地址空间中的起始地址。
  • TEXT_OFFSET
    定义在arch/arm/Makefile中,具体数值为:0x00008000。内核相对起始地址的偏移,也就是真实内核并
    不是从起始地址0x8000 0000开始,而是从0x8000 0000 + 0x0000 8000开始.
  • PG_DIR_SIZE
    size = 0x4000 = 16KB
  • 内核链接地址
    -ld在链接过程还指定了一个链接地址,具体摘vmlinux.lds中。
518  . = 0x80000000 + 0x00008000;                                                                                                                                                                           
519  .head.text : {
520   _text = .;
521   *(.head.text)
522  }  

可以发现链接的时候,就是虚拟起始地址(PAGE_OFFSET) + text偏移(TEXT_OFFSET),和代码中设置的一样了。


##页表初始化
在正式执行页表初始化之前,内核会首先初始化两个寄存器r4 & r8

108 #ifndef CONFIG_XIP_KERNEL
109     adr r3, 2f
110     ldmia   r3, {r4, r8}
111     sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)
112     add r8, r8, r4          @ PHYS_OFFSET
113 #else    
114     ldr r8, =PLAT_PHYS_OFFSET       @ always constant in this case
115 #endif   
... ...
147 #ifndef CONFIG_XIP_KERNEL
148 2:  .long   .
149     .long   PAGE_OFFSET
150 #endif

因为adr命令获取的是相对PC的地址,并且此时MMU是关闭的所以PC对应的就是物理地址。
所以r3通过adr命令取到的也是物理地址。
r4通过ldmia命令拿到的是链接地址。
链接地址在vmlinux.lds中已经指定,是从0x8000 0000开始。
所以sub r4, r3, r4 得到的是物理地址和虚拟地址之间的OFFSET,这个虚拟地址也是开启MMU之后的运行地址。
最终,
r4 存放的是kernel当前运行的物理地址和开启MMU之后kernel运行的虚拟地址之间的OFFSET。
r8 存放的是kernel当前运行的物理起始地址不含TEXT_OFFSET。

接着正式分析页表的创建过程。

 54     .macro  pgtbl, rd, phys                                                                                                                                                                             
 55     add \rd, \phys, #TEXT_OFFSET
 56     sub \rd, \rd, #PG_DIR_SIZE
 57     .endm    

163 __create_page_tables:
164     pgtbl   r4, r8              @ page table address                                                                                                                                                    
165      
166     /*
167      * Clear the swapper page table
168      */
169     mov r0, r4
170     mov r3, #0
171     add r6, r0, #PG_DIR_SIZE
172 1:  str r3, [r0], #4
173     str r3, [r0], #4
174     str r3, [r0], #4
175     str r3, [r0], #4
176     teq r0, r6
177     bne 1b   

可以看到在,pgtbl就是设置页表的起始地址,并将其存放在r4寄存器中,页表位于TEXT_OFFSET之前。最后通过循环初始化这块RAM。


##页表填充
页表初始化就可以准备填充了。kernel首先会将函数__turn_mmu_on和__turn_mmu_on_end之间的内容填入页表。正好也可以通过这个函数管中窥豹,了解下具体转换过程。

  • 段映射
    kernel在boot阶段使用的页表是通过段映射实现的,也就是按照1M为单位,将4GB的地址空间写入到16KB大小的页表中。一项需要4Byte,16KB可以提供4KB项页表条目。4KB条目,每条对应1M,所以16KB的页表正好能放下4GB的地址空间。
  • 页表项举例
    例如,我们打算给第1MB和第2MB填充到页表中
    0x0000 0000 ~ 0x 000F FFFF [0M ~ 1M)
    0x00010 0000 ~ 0x 001F FFFF [1M~2M)
    因为页表起始地址是0x8000 4000,每一个条目占4Byte,所以页表形式应该如下:
    0x8000 4000 --> 0M~1M (0x000X XXXX)
    0x8000 4004 --> 1M~2M (0x001X XXXX)
    0x8000 4008 --> 2M~3M (0x002X XXXX)
    第1 MB右移20bit之后基址是0x000,直接存放到页表基址。
    第 2MB 右移20bit之后变成0x001,怎么计算应该存放在哪个页表条目呢?
    可以直接将基址<< 2然后加到页表基址,最后就变成0x8000 4000 + 1<<2 = 0x8000 4004
    所以第N兆内存应该填入第(0x80004000 + N << 2)项。
    因为是分段,段基址只占了12bit,剩余的20bit被MMU拿来存放一些flag,比如页表描述符访问权限/映射类型(分段or分页)等。
    PS:MMU将虚拟地址转回物理地址的过程参考杜春雷《ARM体系结构与编程》 P180页。
    有了这个基础之后再看代码就容易多了,代码就是按照这个思路写的。
207     ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
208                            
209     /*                     
210      * Create identity mapping to cater for __enable_mmu.
211      * This identity mapping will be removed by paging_init().
212      */                    
213     adr r0, __turn_mmu_on_loc
214     ldmia   r0, {r3, r5, r6}
215     sub r0, r0, r3          @ virt->phys offset
216     add r5, r5, r0          @ phys __turn_mmu_on
217     add r6, r6, r0          @ phys __turn_mmu_on_end
218     mov r5, r5, lsr #SECTION_SHIFT
219     mov r6, r6, lsr #SECTION_SHIFT
220                            
221 1:  orr r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel base
222     str r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
223     cmp r5, r6             
224     addlo   r5, r5, #1          @ next section
225     blo 1b   

这边只是把__turn__mmu_on到__turn_mmu_on_end代码之间的地址填入页表,这么点代码肯定可以填满一个页表项,所以这里只进行一次循环就够了。

接着就是将整个内核image所用的物理地址空间全部初始化到页表项中。

244     /*   
245      * Map our RAM from the start to the end of the kernel .bss section.
246      */  
247     add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
248     ldr r6, =(_end - 1)
249     orr r3, r8, r7
250     add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
251 1:  str r3, [r0], #1 << PMD_ORDER
252     add r3, r3, #1 << SECTION_SHIFT
253     cmp r0, r6
254     bls 1b

这里_end符号可以在vmlinux.lds中找到,存放的是整个image链接结束地址。所以可以知道这段代码映射的是整个kernel image的地址空间。
映射过程和之前分析的是一样的,需要注意的是str 那条指令,这条指令执行完之后会更新r0寄存器的值。根据代码逻辑也能推出来必须更新r0,不然这个循环没法执行 — —!
##开启MMU
页表映射好了,接着就可以准备开启MMU了。r4寄存器中存放的是页表的基址。
在正式开启MMU之前还需要对MMU进行一些设置。源码注释中已经提到MMU的初始化设置操作是在arch/arm/mm/proc-*.S中。如果是exynos4412,对应的就是proc-v7.S文件。MMU寄存器初始化主要包括设置页表基址到MMU c2寄存器,设置MMU控制寄存器的flag,清理cache等。最后跳转到__enbale_mmu。

130     /*      
131      * The following calls CPU specific code in a position independent
132      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
133      * xxx_proc_info structure selected by __lookup_processor_type
134      * above.  On return, the CPU will be ready for the MMU to be
135      * turned on, and r0 will hold the CPU control register value.
136      */     
137     ldr r13, =__mmap_switched       @ address to jump to after
138                         @ mmu has been enabled                                                                                                                                                          
139     adr lr, BSYM(1f)            @ return (PIC) address
140     mov r8, r4              @ set TTBR1 to swapper_pg_dir
141     ldr r12, [r10, #PROCINFO_INITFUNC]
142     add r12, r12, r10
143     ret r12 
144 1:  b   __enable_mmu

__enable_mmu的过程也比较简单,设置MMU一些和domain控制相关的flags,然后是能MMU。使能之后便跳转到了__mmap_switched函数。
mmap_switch回去清下bss以及重定位data段,最后便跳转到了C代码的入口start_kernel函数。
需要注意的是,此页表只是在初始化阶段用到。内核稍后会开启分页机制。。。

猜你喜欢

转载自blog.csdn.net/rockrockwu/article/details/79792511