I. Page Table
1. Page table的作用
【简洁版】
- 隔离user process,给每个process分配4GB内存【假象,至于到底怎么分配,交给kernel】
- 提供 CPU 可识别的 physical address
【详细版】
- 在没有 page table 之前,processes 如果想在memory中执行,必须做好process isolation。一个方案是为每个process 分配一段“大小合适的”内存,这样的缺点是:
- 首先,分配的内存必须连续;否则还需要 page table
- 其次,多大才算“大小合适”,因为process在加载之前无法预知它会使用多少内存
- 另一种方案是:将内存划分成很小的“页”,按需使用;这样解决了“大小合适”的问题,但仍然存在内存必须连续的问题(否则会引起内存侵扰)
- 最后的方案是:page table。建立虚拟内存,对于32bit机器而言:
- 给每个process的内存都是4GB[0x00000000~0xffffffff]
- 同时kernel在每个processz的PCB中都维护一个 page table
提供给 CPU 可操作的 physical address - 一般情况下不知直接采用一级 page table,即将所有 4GB 的vaddr映射到 4GB的physical addr,占空间太大了!!【page table在memory中, kernel 1GB的address space中】
2. 代码分析 - Weenix
Weenix 中 page table 实际上由 pagedir struct (pagetable.c)负责维护,结构如下:
1 | struct pagedir { |
- 每一个 vaddr 都由唯一对应两个表的index: pagedir table index、page table index
- pagedir index = (vaddr>>12) / PAGE_ENTRY_CNT
- pagetable index = (vaddr>>12) % PAGE_ENTRY_CNT
- 注意:pagedir table中存的是 [index1: pagetable physical addre]的entry
- 注意:page table中存的才是[index2:physical addr]的entry
- 这样就建立起 vaddr 的 Two-Level translation system;
先通过 pagedir table 找到 page table,在查到对应 physical addr
- pagedir:
- 由 PCB 维护一个指针指向该 page directory table
- 从pagedir开始,逐级查找到该 process的address space对应的physical address
- pd_physical[i]:
- 由 (vaddr>>12) / PAGE_ENTRY_CNT 计算出vaddr所对应的 pagedir table中的entry
- 该 entry 中维护一个指针指向下级 page table的 physical address;即vaddr所在的page table
- *pd_virtual[i]:
- 由(vaddr>>12) % PAGE_ENTRY_CNT计算出vaddr对应的 page table 中的entry
- 该 entry 维护中vaddr 对应的 physical addr
II. Page fault
1. page fault handler 具体步骤
page fault 是由MMU检测到的硬件中断,由kernel处理;当一个进程发生缺页中断的时候,进程会陷入(trap)内核态,执行以下操作:
- 检查要访问的虚拟地址是否合法
- 查找/分配一个物理页
- 填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
- 建立映射关系(虚拟地址到物理地址)
- 重新执行发生缺页中断的那条指令
2. 代码分析 - page fault handler
1 | void handle_pagefault(uintptr_t vaddr, uint32_t cause) |
- 检查该 vaddr 真的是cur_proc 地址空间中的使用的 addr 么?
vmarea = vmmap_lookup(curproc->p_vmmap, ADDR_TO_PN(vaddr))
- if == NULL,表明地址空间中就没有 任何一个 region用到该地址,返回Error
- 至此,获得了该 vaddr 所属的 vmarea;
- 获得vmarea的目的,是为了获得该vaddr实际对应的mmobj
- 只有通过
vmmap_lookup()
找出“幕后主使”,才能够进行后续往物理内存中加载内容 - 比如只有找出该vaddr对应的vmarea,继而找到vnode(即对应的file),才能知道该往内存中读入什么!!
- user process 程序操作的全是vaddr,不知道该vaddr实际对应的是什么;但是查询到该vaddr page 所属的 mmobj就知道该往物理内存中读入什么了
- 注意特殊情况:forwrite,这会引起有意思的 Copy_On_Write
- 重要的一步:往physical page加载mmobj,并更新page table with (vaddr:paddr)
- 通过调用
pframe_lookup(vmarea, pageNum, fw, *pframe)
,找该vmarea下的pframe - 实际上vmarea不直接管理pframe,而是vmarea对应的mmobj管理;继而内部调用 vmarea—>mmobj 的
lookuppage()
- mmobj 调用
pframe_get()
寻找是否有相应的 pframe 对应于 mmonj&pageNum - 如果有:直接返回;
- 如果没有:进入“无中生有”这一步:
- 创建 pframe 结构体,获得physical address。 通过:
pframe_alloc(mmobj,pagenum)
- 这一步会调用
page_alloc()
申请到物理内存physical address - 至此vaddr有了与之对应的物理内存,但还未载入vaddr对应的内容
- 这一步会调用
- 调用
pframe_fill()
往刚申请到的physical addr中载入vaddr对应的内容- 这一步调用
mmobj->ops->fillpage(obj,pf)
由vaddr对应的mmobj向物理内存载入内容 - 实际上如果是disk,会调用
read_block()
进入硬件过程;后续还会通过DMA(direct memory access)直接将disk中的内容读入到内存
- 这一步调用
- 至此,mmobj的幕后人物:vnode / file 的内容成功读入到 page frame 中
- 创建 pframe 结构体,获得physical address。 通过:
- 通过调用
- 最后调用
pt_map(curproc->pdir, vaddr, pt_virt_to_phys(pf_addr),...)
将 (vadd:paddr)写到 page table 中
1 | 【实质】 |