目录
参考资料
RISC -V页表的简化图如下所示
编辑
多级页表
xv6内核页表
3.6 Process Address Space
3.7 Code: Sbrk
3.8 Code: Exec
Print a page table
A kernel page table per process
hints
copyin/copyout
参考资料
程序在系统上执行,操作的是虚拟内存地址,而虚拟内存到物理内存的映射需要一个函数f(var1, var2); 其中var1是页表,而var2是偏移量,于是当需要操作一个变量的时候,操作系统可通过函数f(var1, var2); 得到虚拟内存对应的物理内存。
- 一级页表==》64位的机器消耗的内存过大==》多级页表来减少开销。
- 参考:Chapter 3: Page Tables - 知乎
-
系统之上的内存分配叫虚拟内存,通过内核的MMU单元去管理并映射到物理内存。这是额外的话题。
-
一种内存空间管理方法是分页,将空间切成固定长度的分片,每个固定长度的单元我们称之为页Page,然后我们将物理内存看成是定长槽块的阵列,这些槽块大小与页相同,每个槽块叫做页帧Page Frame。
-
一般的分页硬件如下图,在MMU中实现这些分页硬件。
- RISC-V指令(用户指令或内核指令)对虚拟地址进行操作,物理地址则是用于寻址实际物理内存RAM的(RISC-V可以处理64位的虚拟地址,而物理地址只被设计成56位)
RISC -V页表的简化图如下所示
。。
- 虚拟地址通过页表找到实际的物理内存,一般一个页表的大小设计为4K
- xv6运行在Sv39 RISC-V处理器上,64位虚拟地址中,只有低39位在被使用,剩下的25位都暂时保留,供日后的设计者利用。
- 将每个虚拟地址映射到一个物理地址,页表会以某种形式的表项来保存这种映射关系,这种表项我们称之为页表条目PTE(Page Table Entry)
- PTE == 44位的物理页帧号PPN (physical page number)+10位的标志位Flags。有效位为54位的PTE可以用8B的大小来存储,这刚好是一个uint64类型。
- 虚拟地址 == 25位的EXT:未使用 + 27位的Index:索引对应的PTE+ 12位offset:页内偏移量
- 单级页表查询方式:传入一个64位的虚拟地址,通过高27位的index找到在Page Table的位置,即PPN+Flags ==》再通过Flags检查权限 ==》PPN + offset 得到有效的物理地址==>访问物理地址
多级页表
- 一共有三级,整体上是一个树形的结构。
- 页表被设计为刚好一页的大小(4KB),如果整页的PTE都不存在/无效,就完全不分配该页来装载页表。为了跟踪装载页表的页是否有效,引入页目录PD(Page Directory)
- 一个PTE占8B的空间,而一个页表大小为4KB,所以一个页表最多存4KB/8B = 512个PTE。故需要9位来找到页表的每一个PTE,从0~511,即2^9
- 查找过程:通过L1查找1级页表的PTE==> 得到二级页表的位置==>通过L2找到二级页表的PTE===》找到三级页表位置===>通过L3找到3级页表的PTE==>得到physical address==> 配合offset得到实际的物理地址。
xv6内核页表
- xv6为内核单独维护了一个页表,下图展示了内核的虚拟地址空间如何映射到实际的物理地址空间中。
- 由上图可见:KERNBASE到PHYSTOP才对应真正的DRAM芯片,代码也是从KERNBASE到PHYSTOP的内存
// the kernel expects there to be RAM
// for use by the kernel and user pages
// from physical address 0x80000000 to PHYSTOP.
#define KERNBASE 0x80000000L
#define PHYSTOP (KERNBASE + 128*1024*1024)
- KERNBASE的下方访问相应的物理地址,实际上是直接访问相关I/O设备的控制寄存器
- 在内核启动未使用页表的时候,内核的虚拟地址到实际的物理地址采用直接映射的方法,内核开始使用页表,会把这种直接映射的关系存到页表。
3.6 Process Address Space
使用页表的好处:
- 用户进程现在都有自己的页表,在进程之间提供了隔离性。
- 用户的虚拟地址空间是连续的,而对应的物理帧分布可以是不连续的。
- 通过页表,内核可以将trampoline页映射到用户虚拟地址空间的顶端,所有进程都可以看到这一页。
用户栈的初始内容是由系统调用exec产生的
- 在初始的用户栈上包括了:各命令行参数的字符串,指向各命令行参数的指针数组argv[ ],用于从调用main(argc, argv[ ])返回的其它参数(argc、argv指针和伪造的返回pc值)。在初始用户栈的内容被设置好之后,用户程序就返回并开始执行main函数。
- 为了防止用户栈溢出,在栈的下面也放置了一页保护页。栈溢出时会访问到该保护页,从而出现缺页错误异常,用户进程因此陷入内核并等待处理,内核可能会终止掉该进程。
3.7 Code: Sbrk
Sbrk是一个系统调用,用户进程调用它以增加或减少自己拥有的物理内存(proc->sz)
3.8 Code: Exec
现在我们来看最后一段代码,系统调用exec的实现(kernel/exec.c),系统调用exec将存储在文件系统上的,新的用户程序装载进内存里,然后执行它。
int exec(char *path, char **argv)
exec通过路径名打开文件,然后读取该文件的ELF Header(kernel/elf.h)
- ELF二进制文件 = ELF Header + Program Section Headers,每个Program Section Header都对应一段需要加载到内存中的程序
Print a page table
- 需要理解xv6是用的三级页表,通过一级页表--》二级页表--》三级页表
- 根据页表级数打印.. 参考freewalk函数得知如何遍历页表。
- 代码实现:https://gitee.com/mm526830lbw/xv6-labs-2020/compare/0f9135b603835dbd7fa5ef362a1ba774caf7970a...3ce192a90ea0bbaa8e9264dac392172a0830e36e
A kernel page table per process
hints
- 为新进程生成内核页表的一种合理方法是实现修改过的kvminit版本,它生成新的页表,而不是修改kernel_pagetable。您需要从allocproc调用这个函数。
- 将进程的内核页表加载到内核的satp寄存器中:模仿kvminithart()函数
- scheduler调度器执行用户进程的时候,启用内核页表:把proc->kernel_pagetable装入SATP寄存器。 用户进程执行结束:用系统的页表。
- kvmpa去读取页表的pte的时候:应该用proc->kernel_pagetable,因为此时算进程在运行,所以该用proc->kernel_pagetable
- 程序从main.c启动,进入userinit(); 并启动程序sh。
- 内核也是一个main程序,所有的程序都是从第一个程序inint.c作为第0个程序开始的,t通过initcode去执行init程序,
- 然后init程序回去fork(),再去启动程序sh, 然后就有了人机交互的shell终端
- main.c程序会进入scheduler()去调度进程:大概是选一个状态为RUNNABLE的程序A,然后切换为RUNNING状态,在通过函数swtch()来执行程序A。
- 要理解freeproc ,就需要理解allocproc是如何调用的,
- allocproc的调用在fork()【父进程生成子进程】和userinit()函数【初始化的时候,创建第0个进程】
-
procinit()规定了kernel最多可以有NPROC, 共64个进程,然后每个进程初始化会通过kalloc取得一片物理内存,将整片物理内存,分配到每个进程。
-
而allocproc则是在这初始化的64个进程中挑一个状态为RUNNABLE的程序,通过kalloc为之分配trapframe和进程页表
-
那么freeproc则是:(1) 释放进程的页表 :即让虚拟内存和物理地址不在有映射关系proc_freepagetable(p->pagetable, p->sz); (2)通过kfree释放trapframe对应的内存
- 以fork出子进程为入口点,参考pagetable = proc_pagetable()操作,可知:
- 通过kalloc()分配一页页表。再执行mappages操作执行TRAMPOLINE和TRAPFRAME映射操作。
- mappages函数:通过walk函数从虚拟地址得到物理地址。前两个页目录设为有效的PTE_V, 最后一页设置为传入的权限参数。
- 如果需要为进程分配一个和内核一样的页表,那么需要参考内核页表的分配过程
-
kvminit()是内核栈的初始化,这里可理解为逻机除运行程序之外,需要的外设,中断,程序运行需要的寄存器等等。
-
那么同理:进程需要额外的页表,来为每个进程执行一样的映射,映射到外设,中断,程序运行需要的寄存器等等。只不过需要放到进程proc结构体里的kernel_pagetable。
-
关于free操作,在页表初始化映射了什么,就free什么,类似malloc和free是一对操作。
- 参考:xv6-lab3-pgtbl_Wound+=s的博客-CSDN博客
- 参考:Mit6.S081-实验3-Page tables_解析Ta的博客-CSDN博客_page tables
copyin/copyout
理解的不是很透彻,大多数的理解都放在代码里了。加了很多注释
https://gitee.com/mm526830lbw/xv6-labs-2020/tree/pgtbl/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)