页表管理

来源:互联网 发布:golang占位符 编辑:程序博客网 时间:2024/06/10 04:58

在不支持PAE的x86机器上,两层页表已足够。中间页目录(PMD)被定义成大小为1,它在系统初始化时直接映射为全局页目录(PGD),并在编译时间以外的时段进行优化。

描述页目录

每个进程都有一个指向其自己PGD的指针(mm_struct->pgd),它其实就是一个物理页面帧。该帧包括了一个pgd_t类型的数组。在x86结构中,进程页表的载入是通过把mm_struct->pgd复制到cr3寄存器完成,这种方式对TLB的刷新有副作用。事实上,这种方式体现了在不同结构中__flush_tlb()的实现情况。
这里写图片描述

描述页表项

三层页表中的每一个项PTEPMDPGD分别由pte_tpmd_tpgd_t描述。他们实际上都是无符号的整型数据,之所以定义成结构,一方面是为了起到数据类型保护的作用,以使他们不会被滥用;另一方面是为了满足某些特性,如在支持PAE的x86中,将有额外的4位用于大于4GB内存的寻址。
未启用PAE的x86结构下,定义如下

typedef struct { unsigned long pte_low; } pte_t;typedef struct { unsigned long pmd; } pmd_t;typedef struct { unsigned long pgd; } pgd_t;typedef struct { unsigned long pgprot; } pgprot_t;

在不支持PAE的x86中,pte_t是一个32位的整形指针。因为每个pte_t指向一个页面帧的地址,而所有的被指向的地址都确定是页对齐的。因此在32为值里面有12位空闲出来用于描述这个页表项的状态位。
这里写图片描述
Linux不会将PSE位(_PAGE_PROTNONE)用于用户页面,当某个页面常驻内存,且内核不希望用户访问的时候,也就是当一个区域通过使用mprotect()函数设置PROT_NONE标志保护起来的时候,实际上是将_PAGE_PRESENT置为0,将_PAGE_PROTNONE置为1。当使用宏pte_present()检查这两位的情况时,只有内核自己知道这个页是在内存中的,但对于用户是不可访问的。因为_PAGE_PRESENT位被清零了,当用户试图访问这个页面时,会触发一次缺页异常,内核能够强制安全检查,以确定是要将页面换出或终止进程。

使用页表项

页表遍历操作:

pgd_t *pgd;pmd_t *pmd;pte_t *ptep, pte;pgd=pgd_offset(mm, address);if(pgd_none(*pgd)||pgd_bad(*pgd))    goto out;pmd=pmd_offset(pgd, address);if(pmd_none(*pmd)||pmd_bad(*pmd))    goto out;ptep=pte_offset(pmd, address);if(!ptep)    goto out;pte=*ptep;

内核页表

对于x86结构,页表初始化分为两个阶段。bootstrap阶段设置页表使之映射8MB的空间,使得分页单元能被启动;第二个阶段初始化了剩余的页表。

  1. bootstrap
    arch/i386/kernel/head.S中的汇编函数startup_32()负责启动分页单元。vmlinuz中的所有内核代码都是以PAGE_OFFSET+1MiB为基址编译的,内核实际上装载在内存的0x00100000处(1MiB)。第一个MiB被用于BIOS与某些设备通信。在启动分页单元之前,bootstrap中的代码通过减去__PAGE_OFFSET将1MiB视为基址。因此在分页单元启动之前,需要建立一个页表映射转换8MiB的物理内存到虚拟地址的PAGE_OFFSET
    页表将虚拟地址的0x00000000~0x00800000和0xc00100000~0xc00900000映射到了0x00100000~0x00900000的物理地址处
0 0