8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
(32 bit case)
ARM的两级映射为12 + 8 + 12 = 32
一级页表(也可以称为页目录)地址,该页表总共有4096个索引(4096 × 1MB = 4GB)
CPU首先获取页目录基地址(TTB),加上待转换虚拟地址的高12位,就获得了该虚拟地址的页目录项所在位置。arm的一级页表会包含几种类型,如下图的arm-v7arm translation table的结构:
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
MMU从CP15的C0中的TTB得到基址,加上虚拟地址的高12位,得到了页目录项,MMU发现低2位为10,确定是section-mapping,就会取该页目录项的高12位与虚拟地址的低20位拼接,便获取到了物理地址。
page-mapping
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);
}