返回列表 发帖

[站长原创] 移植内核必备知识-ARM的一级页表映射方式和Linux的实现代码

[站长原创] 移植内核必备知识-ARM的一级页表映射方式和Linux的实现代码

移植内核必备知识-ARM的一级页表映射方式和Linux的实现代码

嵌入式开发联盟www.mcuos.com

Osboy站长原创

QQ:82475491

Mcuos.com@gmail.com

(一)LinuxARM的一级页表映射机制的使用

对于一级页表映射的过程参考:http://mcuos.com/thread-8198-1-1.html

Linux在什么情况下使用了一级页表映射呢?

(二)Linux对一级页表的实现和使用

arch/arm/head.S中,程序执行完查找processor ID的时候,接下来要做的重要的工作就是创建内核的初始化页表,为开启mmu做准备,摘录下面的代码:

       bl    __create_page_tables

       /*

        * The following calls CPU specific code in a position independent

        * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of

        * xxx_proc_info structure selected by __lookup_processor_type

        * above.  On return, the CPU will be ready for the MMU to be

        * turned on, and r0 will hold the CPU control register value.

        */


ldr   r13, =__mmap_switched           @ address to jump to after

                                          @ mmu has been enabled

       adr   lr, BSYM(1f)                  @ return (PIC) address

       mov r8, r4                            @ set TTBR1 to swapper_pg_dir

ARM(    add  pc, r10, #PROCINFO_INITFUNC   )


THUMB(       add  r12, r10, #PROCINFO_INITFUNC  )


THUMB(       mov pc, r12                         )

1:    b     __enable_mmu

ENDPROC(stext)

       .ltorg

#ifndef CONFIG_XIP_KERNEL

2:    .long       .

       .long       PAGE_OFFSET

#endif

我们将分__create_page_tablesldr       r13, =__mmap_switched__enable_mmu三大块详细讲解:

1__create_page_tables做一个详细的讲解:

__create_page_tables:

       pgtbl       r4, r8                           @ page table address

pgtbl的定义原型为:

       .macro    pgtbl, rd, phys

       ldr   \rd, =(TEXT_OFFSET - 0x4000)

       add  \rd, \phys, \rd

       .endm

我们在前面的系列文章中知道这里的TEXT_OFFSET == 0x8000,这里ldr rd, =addr,这条指令是把一个32bit的地址值addr保存到rd寄存器中,通常这条指令会被替换成ldr rd, [pc+#offset],也就是说这个addr会被保存到地址pc+#offset的内存池中。

在调用__create_page_tables的时候我们已经执行过ldr   r8, =PLAT_PHYS_OFFSET,这段代码,PLAT_PHYS_OFFSET我们前面的文章里面提到过为:0x50000000,实际的物理内存地址基地址。Pgtbl要做的事情就是初始化R4 = 0x50000000 + 0x4000,就是实际内存的偏移量为0x4000的内存基地址。

       /*

        * Clear the 16K level 1 swapper page table


*/

       mov r0, r4//执行完后r0 == 0x50000000 + 0x4000 = 0x50004000

       mov r3, #0//r3 == 0

       add  r6, r0, #0x4000//执行完后,r6==0x50008000

1:     str   r3, [r0], #4//r3==0的值存储到0x50004000作为起始地址的内存中,然后+4,因为32bitarm内存地址为4字节对齐。

       str   r3, [r0], #4

       str   r3, [r0], #4

       str   r3, [r0], #4

       teq   r0, r6 //此段代码是判断r0的值有没有达到上限0x50008000

       bne  1b//如果r0 !=r6,那么往前跳到标号1处,注意这里的1b的用法,b是往前跳,f是向后跳。

上面这段代码就是清空从0x500040000x50008000的地址空间的内存。

       ldr   r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags


.section ".proc.info.init", #alloc, #execinstr

这里我们知道PROCINFO_MM_MMUFLAGSinclude/generated/asm-offsets.h中被定义:#define PROCINFO_MM_MMUFLAGS 8

而此时的r10寄存器就是前面的文章提到的__v6_proc_info的存储地址,下面我摘录下armv6架构的__v6_proc_info结构体实现:

       .type       __v6_proc_info, #object

__v6_proc_info:

       .long       0x0007b000

       .long       0x0007f000//__v6_proc_info+4


ALT_SMP(.long \  //__v6_proc_info+8


PMD_TYPE_SECT | \

              PMD_SECT_AP_WRITE | \

              PMD_SECT_AP_READ | \

              PMD_FLAGS_SMP)

       ALT_UP(.long \

              PMD_TYPE_SECT | \

              PMD_SECT_AP_WRITE | \

              PMD_SECT_AP_READ | \

              PMD_FLAGS_UP)

       .long   PMD_TYPE_SECT | \


PMD_SECT_XN | \

所以此时的R7寄存器存储的是:            

PMD_TYPE_SECT | \

              PMD_SECT_AP_WRITE | \

              PMD_SECT_AP_READ | \

              PMD_FLAGS_SMP)

这些正是我们前文提到的一级页表描述符的属性部分,你可以参考关于MMU那一章节的关于一级页表描述符的结构图来对比。


/*

        * Create identity mapping to cater for __enable_mmu.

        * This identity mapping will be removed by paging_init().


*/

       adr   r0, __enable_mmu_loc //将基于PC的地址值__enable_mmu_loc读取到r0寄存器中。

       ldmia       r0, {r3, r5, r6} //把当前的地址,__enable_mmu__enable_mmu_end装载进r3r5r6寄存器。


sub  r0, r0, r3               @ virt->phys offset //此时的mmu还没开,没什么虚拟实际地址之分。

       add  r5, r5, r0               @ phys __enable_mmu

       add  r6, r6, r0               @ phys __enable_mmu_end

       mov r5, r5, lsr #20 //__enable_mmu的标号地址逻辑右移20

       mov r6, r6, lsr #20 //__enable_mmu_end的标号地址逻辑右移20

这里 __enable_mmu_loc,定义为

__enable_mmu_loc:

       .long       .

       .long       __enable_mmu

       .long       __enable_mmu_end

1:     orr   r3, r7, r5, lsl #20           @ flags + kernel base//此处是在计算一级描述符仍然取r5作为物理地址的基地址是为了做一对一的map

       str   r3, [r4, r5, lsl #2]          @ identity mapping//存储到相应的页表项中,结合本人的MMU基础知识那篇文章的例子,就可以知道为什么是r4+r5<<2了。


teq   r5, r6

       addne      r5, r5, #1               @ next section //这里的做法就是把__enable_mmu__enable_mmu_end之间的物理内存地址空间一对一的map成虚拟地址空间。


bne  1b

       /*

        * Now setup the pagetables for our kernel direct


* mapped region.

        */

       mov r3, pc //把当前的PC值(物理地址)赋值给R3寄存器

       mov r3, r3, lsr #20//右移动20bit是为了取得table index

       orr   r3, r7, r3, lsl #20//r3左移动20bit或上r7,然后赋值给r3,此时r3就是一级描述符

       add  r0, r4,  #(KERNEL_START & 0xff000000) >> 18 //取得KERNEL_START虚拟地址的table index需要KERNEL_START >> 20,但是为了取得页表的页目录我们知道每个页目录是4字节所以知道了r4的页表基地址找到一级描述符地址应该是:

(r4+KERNEL_START >> 20<< 2) == r4+KERNEL_START >> 18

       str   r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!//存储一级描述符到相应的页表目录。

       ldr   r6, =(KERNEL_END - 1)//这边的减1很有讲究如果此时KERNEL_END=0x c1800000,如果不减去1,那么map的虚拟地址将是c1800000 - c1900000的空间。而这个时候我们的内核最大也就到c1800000,所以会出问题;如果我们减去1,那么c1800000-1=c17fffff,那么map的虚拟地址将是c1700000 - c1800000的空间,正好和我们的要求相符合。


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

上面这段code将把整个内核image的占用的内存物理空间mapc0000000空间。


2ldr r13, =__mmap_switched

该段代码其实就是把__mmap_switched的链接地址装载到寄存器r13中。我这里的__mmap_switched 的链接地址为:0xc10081e0。是个虚拟地址。

3__enable_mmu

在(1)中我们已经知道了__enable_mmu_enable_mmu_end之间的代码是一对一map的,即物理地址和虚拟地址相等。因为我们在开启MMU之前和之后的代码是很有讲究的,不能有地址跳变的,所以最新的3.0的代码对这里做了改善,之前的linux2.6的代码是无论哪段代码,都会把内存的前4M做两次map,一次是一对一,一次是map0xc0000000的虚拟地址空间。

在这段code的最后一段:

       .align      5

__turn_mmu_on:

       mov r0, r0

       mcr  p15, 0, r0, c1, c0, 0              @ write control reg

       mrc  p15, 0, r3, c0, c0, 0              @ read id reg


mov r3, r3


mov       r3, r13


mov       pc, r3

__enable_mmu_end:

由于前面我们讲过r13__mmap_switched的虚拟地址0xc10081e0,所以执行到最后一句:mov     pc, r3,其实程序已经开始在Linux内核的虚拟地址空间运行了。从而完成了内核的初始化MMU使能的过程。

分享到: QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友

返回列表
网页右侧QQ悬浮滚动在线客服
网页右侧QQ悬浮滚动在线客服