页表
intel的5级页表为:
简称 | 全称 |
---|---|
PML5 | Page Map Level 5 |
PML4 | Page Map Level 4 |
PDPT | Page Directory Point Table |
PD | Page Directory |
PT | Page Table |
对应kernel的5级页表为:
简称 | 全称 |
---|---|
PGD | Page Global Directory |
P4D | Page Level 4 Directory |
PUD | Page Upper Directory |
PMD | Page Middle Directory |
PTE | Page Table Entry |
大部分情况下4级页表已经够了,这里以4级页表为例说明
4KB页
offset为低12位

2MB页
offset为低12位 + 9位PT

1GB页
offset为低12位 + 9位PT + 9位PD

PGD初始化
pgd在mm_init中初始化,有3种场景会调用mm_init:
start_kernel -> mm_init
SYSCALL_DEFINE0(fork) -> ... -> dup_mm -> mm_init
SYSCALL_DEFINE3(execve, ...) -> ... -> mm_alloc -> mm_init
mm_init
mm_init
mm_alloc_pgd
pgd_alloc
pgd = _pgd_alloc();
__get_free_pages
mm->pgd = pgd;
pgd_ctor(mm, pgd);
clone_pgd_range(pgd + KERNEL_PGD_BOUNDARY,
swapper_pg_dir + KERNEL_PGD_BOUNDARY,
KERNEL_PGD_PTRS); // 复制内核页表
__get_free_pages
/*
* Common helper functions. Never use with __GFP_HIGHMEM because the returned
* address cannot represent highmem pages. Use alloc_pages and then kmap if
* you need to access high mem.
*/
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
struct page *page;
page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order); // 分配物理页
if (!page)
return 0;
return (unsigned long) page_address(page); // 获取物理页的虚拟地址
}
#define page_address(page) lowmem_page_address(page)
static __always_inline void *lowmem_page_address(const struct page *page)
{
return page_to_virt(page);
}
#define page_to_virt(page) pfn_to_virt(page_to_pfn(page))
切换CR3
切换进程上下文时切换CR3
context_switch
context_switch
switch_mm_irqs_off
load_new_mm_cr3(next->pgd, new_asid, true);
new_mm_cr3 = build_cr3(pgdir, new_asid);
__sme_pa
__pa
__phys_addr
__phys_addr_nodebug // 虚拟地址转物理地址
write_cr3(new_mm_cr3);
__phys_addr_nodebug
当x >= __START_KERNEL_map(kernel映射区),return x - __START_KERNEL_map + phys_base
当x < __START_KERNEL_map(direct mapping映射区),return x - PAGE_OFFSET
static inline unsigned long __phys_addr_nodebug(unsigned long x)
{
unsigned long y = x - __START_KERNEL_map;
/* use the carry flag to determine if x was < __START_KERNEL_map */
x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));
return x;
}
运行模式
IA-32架构包含3个运行模式和1个准运行模式:
- 实模式
- 保护模式
- 系统管理模式
- 虚拟8086模式
Intel 64架构支持IA-32架构的所有模式,同时引入了一个新的运行模式:
- IA-32e模式,包含2个子模式:
- 兼容模式(CS.L为0)
- 64位模式(CS.L为1)



- 实模式、保护模式、虚拟8086模式、IA-32e模式都可以通过SMI#进入系统管理模式,通过RSM回到原模式
- 保护模式、系统管理模式、虚拟8086模式、IA-32e模式都可以通过Reset直接回到实模式
- 实模式通过PE=1进入保护模式,反之亦然
- 保护模式通过VM=1进入虚拟8086模式,反之亦然
- 保护模式通过LME=1 && PG=1进入IA-32e模式,反之亦然
分页模式
4种分页模式:
- 32-bit
- PAE
- 4-level
- 5-level



PG=0 && PAE=0 && LME=0是起点
- 先设置PG=1,进入32-bit,不能设置LME,再设置PAE=1,进入PAE,不能设置LME
- 先设置PAE=1,再设置PG=1,进入PAE,不能设置LME
- 先设置PAE=1,再设置LME=1,PG=0 && PAE=1 && LME=1,最后设置PG=1,进入4-level
- 先设置LME=1,不能设置PG=1,再设置PAE=1,PG=0 && PAE=1 && LME=1,最后设置PG=1,进入4-level
通过虚拟地址访问页表本身
对于CPU来说,MMU需要经过4次转换找到物理页(4-level分页模式),PML4/PDPT/PD/PT(占用一个物理页)和普通物理页没有区别,因此都可以通过页表寻址
设置PML4的最后一个PML4E指向PML4本身
当PLM4 index = 511时,PLM4产生1次递归转换,在CPU完成4次转换时,找到的物理页其实是PT
当PLM4/PDPT index = 511时,PLM4产生2次递归转换,在CPU完成4次转换时,找到的物理页其实是PD
当PLM4/PDPT/PD index = 511时,PLM4产生3次递归转换,在CPU完成4次转换时,找到的物理页其实是PDPT
当PLM4/PDPT/PD/PT index = 511时,PLM4产生4次递归转换,在CPU完成4次转换时,找到的物理页其实是PLM4
通过这种"欺骗"CPU的方式,可以实现通过虚拟地址访问页表本身,代价是高9位为511的虚拟地址被页表占用
访问普通页

访问PT

访问PD

访问PDPT

访问PLM4
