seL4 VSpace 分析
地址空间结构
详见 seL4 启动流程一文。
地址转换
seL4 内核中的基本地址有 3 类:paddr、pptr、kpptr
| 地址类型 | 说明 |
|---|---|
| paddr | 物理地址 |
| pptr | 内核直接映射区域虚拟地址 |
| kpptr | 内核 ELF 区域虚拟地址 |
seL4 存在三个基本的地址转换:
| 地址转换 | 别名 | 实现 | 说明 |
|---|---|---|---|
| ptrFromPAddr | paddr_to_pptr | paddr + PPTR_BASE_OFFSET | 从一个物理地址到内核可以解引用的虚拟地址 |
| addrFromPPtr | pptr_to_paddr | pptr - PPTR_BASE_OFFSET | 从一个内核虚拟地址到物理地址 |
| addrFromKPPtr | kpptr_to_paddr | pptr - KERNEL_ELF_BASE_OFFSET | 从一个内核 ELF 区域地址到物理地址 |
从逻辑上来说,内核动态构造的数据结构都是通过 pptr 进行访问,而内核 ELF 文件通过 ld 脚本对其中的各种段进行编址是从 KERNEL_ELF_BASE 开始。也就是说,内核静态对象引用地址减去 KERNEL_ELF_BASE 即是物理地址。
因此,可以看到 activate_kernel_vspace 中设置根页表使用了 kpptr_to_paddr(&kernel_root_pageTable) 得到物理地址,其中 kernel_root_pageTable 是一个 ELF 中的静态数组。
关键函数分析
getPPtrFromHWPTE(pte_t *pte)
功能
给定页表项,获取下一级页表的虚拟地址。
实现
将页表项 PPN 左移页帧大小,再翻译为虚拟地址。
return PTE_PTR(ptrFromPAddr(pte_ptr_get_ppn(pte) << seL4_PageTableBits));lookupPTSlot(pte_t *lvl1pt, vptr_t vptr)
功能
给定一级页表指针和虚拟地址,返回对应页表项。
实现
ret.ptSlot 指向 vptr 在给定 lvl1pt 页表的槽位地址。
lookupPTSlot_ret_t ret;
word_t level = CONFIG_PT_LEVELS - 1;
pte_t *pt = lvl1pt;
ret.ptBitsLeft = PT_INDEX_BITS * level + seL4_PageBits;
ret.ptSlot = pt + ((vptr >> ret.ptBitsLeft) & MASK(PT_INDEX_BITS));接下来逐层遍历,通过 getPPtrFromHWPTE(ret.ptSlot) 得到下一级页表。
while (isPTEPageTable(ret.ptSlot) && likely(0 < level)) {
level--;
ret.ptBitsLeft -= PT_INDEX_BITS;
pt = getPPtrFromHWPTE(ret.ptSlot);
ret.ptSlot = pt + ((vptr >> ret.ptBitsLeft) & MASK(PT_INDEX_BITS));
}findVSpaceForASID(asid_t asid)
根据一个 ASID,获取其一级页表的引用。
deleteASIDPool(asid_t asid_base, asid_pool_t *pool)
解除一个 ASID 与 ASID 池的绑定。执行此操作后,会将当前页表重置为当前线程页表。
performASIDControlInvocation(void *frame, cte_t *slot, cte_t *parent, asid_t asid_base)
为一个 ASID 绑定一个 ASID 池(对应一个物理页帧),并在对应槽位插入 asid pool cap。
performASIDPoolInvocation(asid_t asid, asid_pool_t *poolPtr, cte_t *vspaceCapSlot)
功能
为一个地址空间绑定一个 ASID,同时初始化页表。
实现
为地址空间 cap 设置 ASID 等信息。
cap_t cap = vspaceCapSlot->cap;
pte_t *regionBase = PTE_PTR(cap_page_table_cap_get_capPTBasePtr(cap));
cap = cap_page_table_cap_set_capPTMappedASID(cap, asid);
cap = cap_page_table_cap_set_capPTMappedAddress(cap, 0);
cap = cap_page_table_cap_set_capPTIsMapped(cap, 1);
vspaceCapSlot->cap = cap;为其页表拷贝全局映射。
copyGlobalMappings(regionBase as usize);在 ASID Pool 对应位置记录页表基址。
poolPtr->array[asid & MASK(asidLowBits)] = regionBase;unmapPageTable(asid_t asid, vptr_t vptr, pte_t *target_pt)
根据 ASID 和虚拟地址,找到 target_pt 所在页表,删除对应页表项。
unmapPage(page_size: usize, asid: asid_t, vptr: vptr_t, pptr: pptr_t)
根据 ASID 和虚拟地址,删除一个从 vptr 到 pptr 的物理页映射。
与 unmapPageTable 类似,不过是删除叶子页表项。
