arm linux 上内核开启mmu的分析

我使用的内核的版本是4.4。处理器是arm v7a内核。

内核中开启虚拟地址的地方

首先找到内核中开启虚拟地址的地方,代码在 arch/arm/kernel/head.S里。

/*
 * Enable the MMU.  This completely changes the structure of the visible
 * memory space.  You will not be able to trace execution through this.
 * If you have an enquiry about this, *please* check the linux-arm-kernel
 * mailing list archives BEFORE sending another post to the list.
 *      
 *  r0  = cp#15 control register
 *  r1  = machine ID           
 *  r2  = atags or dtb pointer 
 *  r9  = processor ID         
 *  r13 = *virtual* address to jump to upon completion                                                                    
 *      
 * other registers depend on the function called upon completion                                                          
 */     
    .align  5
    .pushsection    .idmap.text, "ax"
ENTRY(__turn_mmu_on)           
    mov r0, r0
    instr_sync
    mcr p15, 0, r0, c1, c0, 0       @ write control reg 
    mrc p15, 0, r3, c0, c0, 0       @ read id reg                                                                         
    instr_sync
    mov r3, r3
    mov r3, r13
    ret r3
__turn_mmu_on_end:             
ENDPROC(__turn_mmu_on)
    .popsection

观察一下这段代码,发现其中有两句 cp15的操作。”mcr p15, 0, r0, c1, c0, 0” 是把r0写入cp15的寄存器1。这里放个cp15操作有关的链接以供备忘。http://blog.sina.com.cn/s/blog_858820890102v1gc.html
从这里开始,kernel就进入了虚拟地址时代。这个时候,PC的值是mcr p15, 0, r0, c1, c0, 0这条指令的地址再+4。这个时候PC是一个虚拟地址,它会被转换成哪个物理地址?答案是就是下一条指令的物理地址!如果不是这个结果,那么内核就会取到一个无法预期的指令,从而导致程序崩溃。

虚拟地址=物理地址

内核是如何达到这个目的的呢?
观察一下 __create_page_tables

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 *
 * Returns:
 *  r0, r3, r5-r7 corrupted
 *  r4 = physical page table address
 */
__create_page_tables:
    pgtbl   r4, r8              @ page table address
    ...
        /*
     * Create identity mapping to cater for __enable_mmu.
     * This identity mapping will be removed by paging_init().
     */
    adr r0, __turn_mmu_on_loc
    ldmia   r0, {r3, r5, r6}
    sub r0, r0, r3          @ virt->phys offset
    add r5, r5, r0          @ phys __turn_mmu_on
    add r6, r6, r0          @ phys __turn_mmu_on_end
    mov r5, r5, lsr #SECTION_SHIFT
    mov r6, r6, lsr #SECTION_SHIFT
    ...
ENDPROC(__create_page_tables)    

adr r0, __turn_mmu_on_loc 这条命令把__turn_mmu_on_loc这个标号的物理地址装入r0,然后从这块内存中读3个word到r3,r5,r6 中。那么这3个word里存放的是什么呢?
从System.map 文件中可以知道,__turn_mmu_on_loc标号的虚拟地址是c0008138
c0008138 t __turn_mmu_on_loc

从 vmlinux 的objdump 出来的结果可以知道,在这个地方存放的是3个地址,分别是__turn_mmu_on_loc,__turn_mmu_on,__turn_mmu_on_end的虚拟地址,
c0008138 <__turn_mmu_on_loc>:
c0008138: c0008138 andgt r8, r0, r8, lsr r1
c000813c: c0008280 andgt r8, r0, r0, lsl #5
c0008140: c00082a0 andgt r8, r0, r0, lsr #5

那么 sub r0, r0, r3 @ virt->phys offset 的意义就很明确了,就是求出__turn_mmu_on_loc这个标号的物理地址和虚拟地址之间的偏移量。然后根据这个偏移量求出__turn_mmu_on的物理地址,再把这个物理地址放到地址映射表里去。这样,当MMU开始工作以后,物理地址和虚拟地址是相同的,不会导致PC+4以后取到非法地址的问题。

转入0xc0000000虚拟地址

在执行__turn_mmu_on之前,r13,也就是sp的值已经被设置为 __mmap_switched 的虚拟地址。在__turn_mmu_on的最后阶段,通过ret r3 这条命令把__mmap_switched的虚拟地址装入PC,ret指令返回时就直接返回到__mmap_switched 标号,从而走上了虚拟地址的金光大道。在这之后,内核都运行在 0xc0000000 以上的虚拟地址空间。那么r13又是什么时候装载虚拟地址的呢?在stext段中,在调用 __mmu_enable之前,__mmap_switched 就被装入 r13了。

     *
     * The processor init function will be called with:
     *  r1 - machine type
     *  r2 - boot data (atags/dt) pointer
     *  r4 - translation table base (low word)
     *  r5 - translation table base (high word, if LPAE)
     *  r8 - translation table base 1 (pfn if LPAE)
     *  r9 - cpuid
     *  r13 - virtual address for __enable_mmu -> __turn_mmu_on
     *
     * On return, the CPU will be ready for the MMU to be turned on,
     * r0 will hold the CPU control register value, r1, r2, r4, and
     * r9 will be preserved.  r5 will also be preserved if LPAE.
     */
    ldr r13, =__mmap_switched       @ address to jump to after
                        @ mmu has been enabled
    badr    lr, 1f              @ return (PIC) address
#ifdef CONFIG_ARM_LPAE
    mov r5, #0              @ high TTBR0
    mov r8, r4, lsr #12         @ TTBR1 is swapper_pg_dir pfn
#else
    mov r8, r4              @ set TTBR1 to swapper_pg_dir
#endif
    ldr r12, [r10, #PROCINFO_INITFUNC]
    add r12, r12, r10
    ret r12
1:  b   __enable_mmu
ENDPROC(stext)

到这里为止,linux启动过程中的开启MMU,并跳转到虚拟地址空间的任务就完成了。

猜你喜欢

转载自blog.csdn.net/abeldeng/article/details/80001771
今日推荐