什么是页表
页表就是用于将虚拟地址转换为物理地址的转换关系表。访问虚拟地址时,计算机通过页表找到对应的实际物理地址访问。
为何需要多级页表
目前在linux中采用4级页表,ARM32采用2级页表,ARM64采用4级页表。但linux是一个通用性的系统,当ARM32时2级页表也是使用linux的4级页表机制,只是将其它两级页表转换不做任何处理。
那么为什么需要多级页表呢?节省内存空间。二级页表可以在需要的时候才建立。
ARM32页表映射
页表查询
内核内存布局
Linux内核在启动的的时候会打印出内核内存空间的布局图。
ARM32内核的内存布局图如下所示。
页表建立分析
linux中使用map_desc数据结构完整地描述一个内存空间:
struct map_desc {
unsigned long virtual; //虚拟地址
unsigned long pfn; //页框号
unsigned long length;
unsigned int type;
};
建立内核页表的过程
static void __init create_mapping(struct map_desc *md, bool force_pages)
{
unsigned long addr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
" at 0x%08lx in user region\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
return;
}
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET &&
(md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
printk(KERN_WARNING "BUG: mapping for 0x%08llx"
" at 0x%08lx out of vmalloc space\n",
(long long)__pfn_to_phys((u64)md->pfn), md->virtual);
}
type = &mem_types[md->type];//根据mem_types建立页表
#ifndef CONFIG_ARM_LPAE
/*
* Catch 36-bit addresses
*/
if (md->pfn >= 0x100000) {
create_36bit_mapping(md, type);
return;
}
#endif
addr = md->virtual & PAGE_MASK;
phys = __pfn_to_phys(md->pfn);
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
(long long)__pfn_to_phys(md->pfn), addr);
return;
}
pgd = pgd_offset_k(addr);//计算出pgd
end = addr + length;
do {
unsigned long next = pgd_addr_end(addr, end);
alloc_init_pud(pgd, addr, next, phys, type, force_pages); //建立页表
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
ARM64页表映射
ARM64一般配置4KB大小页面,48位地址宽度,4级页表映射。
页表查询
内核内存布局
64位Linux已经没有了高端内存这个概念了。因为48位的寻址空间已经足够 大。用户空间和内核空间大小均可达256TB。
页表建立分析
类似于ARM32.不再描述。
内核页表与进程页表存放
内核页表
内核页表存放于init_mm结构体中的pgd中。其值由swapper_pg_dir决定。
swapper_pg_dir对于ARM32与ARM64值的计算方法不一样。
如ARM32时:
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
即kernel运行地址的前16k( PG_DIR_SIZE)
进程页表
每个进程都有自己的页表,存放于task_struct结构体中的pgd中。