linux 2.6.35 arm map_lowmem,Linux kernel linear mapping area

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

(32 bit case)

ARM的两级映射为12 + 8 + 12 = 32

一级页表(也可以称为页目录)地址,该页表总共有4096个索引(4096 × 1MB = 4GB)

CPU首先获取页目录基地址(TTB),加上待转换虚拟地址的高12位,就获得了该虚拟地址的页目录项所在位置。arm的一级页表会包含几种类型,如下图的arm-v7arm translation table的结构:

8cf7d0a45b1cefa35697084eea52d97c.png

linux利用了section和small page来实现内存映射,其中线性映射区是利用section。

利用表项的bits[1:0]区别了下级页表的类型:0b01, Page table

The descriptor gives the address of a second-level translation table, that specifies the mapping of the associated 1MByte VA range.

0b10, Section or Supersection

The descriptor gives the base address of the Section or Supersection. Bit[18] determines whether the entry describes a Section or a Supersection.

section-mapping

66988a5f8e517742a61275b37c25b1ca.png

MMU从CP15的C0中的TTB得到基址,加上虚拟地址的高12位,得到了页目录项,MMU发现低2位为10,确定是section-mapping,就会取该页目录项的高12位与虚拟地址的低20位拼接,便获取到了物理地址。

page-mapping

5a7c0039bd744fb548199e925b6a3ca8.png

4KB page-mapping 是二级页表方式:MMU利用TTB(页目录)基址,与虚拟地址的高12位相加,得到页目录项值

MMU获取页目录项最低2bit是01,说明本次映射的1MB数据为4KB小页的page-mapping

MMU获取页目录项的高22位(页表是256X4=1K,所以页表基址是1K对齐的)是页表基地址,与虚拟地址的中间8位相加,即该虚拟地址的对应页表项地址,从而获取虚拟地址对应的页表项值(page table entry)

MMU获取页表项值的高20位,这就是该4K页对应的物理地址了,与虚拟地址低12位相加(也就是4KB页内的偏移),得到虚拟地址对应的物理地址

Linux kernel 线性映射区

在kernel启动时,mmu从关闭状态到打开状态,需要为mmu准备page table。下面是详细描述建表过程:

建立临时映射表

首先提到一个重要的文件head.S,这个文件包含了Kernel startup entry point:__HEAD

ENTRY(stext)

此时的mmu处于关闭状态,不过因为C函数的地址和变量地址都是虚拟地址,所以在进入C world之前,要建立映射表,然后开机mmu,在stext中有体现:bl __create_page_tables

//...

1: b __enable_mmu

__create_page_tables

这个函数的作用是建立mmu的page table,当然在这段汇编里,只建立能刚好满足kernel运行的page table,剩下的交给强大的C语言完成,所以这个页表被叫做“临时映射表”。

这个函数分成三大部分:Create identity mapping to cater for __enable_mmu

Map our RAM from the start to the end of the kernel

Then map boot params address in r2 if specified

1. Create identity mapping to cater for __enable_mmu/*

* 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

1: orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base //制作pgd表项

str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping //存储pgd表项

cmp r5, r6

addlo r5, r5, #1 @ next section

blo 1b

创建同一映射,也被称为平映射,该映射的特点是:虚拟地址和物理地址相同。

映射区间为 __turn_mmu_on 到 __turn_mmu_on_end,按section方式进行映射1MB空间(如果该函数敲好在1MB边界上,则映射2MB)。__turn_mmu_on函数实现:/*

* 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

mov pc, r3

__turn_mmu_on_end:

ENDPROC(__turn_mmu_on)

.popsection

在write control reg之后,mmu就被打开了,下条指令的PC值,指向的位置仍然是之前的物理地址+4。当cpu执行新的PC值指向的指令时,就要利用MMU访问内存,由于之前制作了虚拟地址和物理地址的同一映射,所以新的物理地址和虚拟地址相同,所以平滑过渡了打开mmu造成的地址映射问题。

2. Map our RAM from the start to the end of the kernel/*

* Map our RAM from the start to the end of the kernel .bss section.

*/

add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)

ldr r6, =(_end - 1)

orr r3, r8, r7 //制作pgd表项内容

add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)

1: str r3, [r0], #1 << PMD_ORDER //存储pgd表项

add r3, r3, #1 << SECTION_SHIFT

cmp r0, r6

bls 1b

利用section方式,完成kernel线性区的映射,线性区的范围是PAGE_OFFSET到kernel .bss段的结束。

3. Then map boot params address in r2 if specified/*

* Then map boot params address in r2 if specified.

* We map 2 sections in case the ATAGs/DTB crosses a section boundary.

*/

mov r0, r2, lsr #SECTION_SHIFT

movs r0, r0, lsl #SECTION_SHIFT

subne r3, r0, r8

addne r3, r3, #PAGE_OFFSET

addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)

orrne r6, r7, r0

strne r6, [r3], #1 << PMD_ORDER

addne r6, r6, #1 << SECTION_SHIFT

strne r6, [r3]

利用section方式,映射了dtb空间,映射大小为两个sections(2MB),原因是避免dtb跨section的问题。

至此,临时映射表已经键完,之后call __enable_mmu 函数,调用 __turn_mmu_on 打开MMU,终于进入C world。

建立最终线性页表| start_kernel

--+ setup_arch

--+ paging_init

--+ map_lowmem

--+ create_mapping

--+ alloc_init_pud

--+ alloc_init_pmd

--+ __map_init_section

针对kernel线性区,重新做了一遍section-mappingstatic void __init __map_init_section(pmd_t *pmd, unsigned long addr,

unsigned long end, phys_addr_t phys,

const struct mem_type *type)

{

pmd_t *p = pmd;

do {

*pmd = __pmd(phys | type->prot_sect);

phys += SECTION_SIZE;

} while (pmd++, addr += SECTION_SIZE, addr != end);

flush_pmd_entry(p);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值