xv6---Lab3: page tables

目录
参考资料
RISC-V页表的简化图如下所示
?编辑?
多级页表
xv6内核页表
3.6Space
3.7 Code: Sbrk
3.8 Code: Exec
Print a page table
Apage table per
hints
/
参考资料
程序在系统上执行,操作的是虚拟内存地址,而虚拟内存到物理内存的映射需要一个函数f(var1, var2); 其中var1是页表,而var2是偏移量,于是当需要操作一个变量的时候,操作系统可通过函数f(var1, var2); 得到虚拟内存对应的物理内存 。
??
RISC-V页表的简化图如下所示
。。
? 虚拟地址通过页表找到实际的物理内存,一般一个页表的大小设计为4Kxv6运行在Sv39 RISC-V处理器上,64位虚拟地址中,只有低39位在被使用,剩下的25位都暂时保留,供日后的设计者利用 。将每个虚拟地址映射到一个物理地址,页表会以某种形式的表项来保存这种映射关系,这种表项我们称之为页表条目PTE(Page Table Entry)PTE == 44位的物理页帧号PPN ( page )+10位的标志位Flags 。有效位为54位的PTE可以用8B的大小来存储,这刚好是一个类型 。虚拟地址 == 25位的EXT:未使用 + 27位的Index:索引对应的PTE+ 12位:页内偏移量单级页表查询方式:传入一个64位的虚拟地址,通过高27位的index找到在Page Table的位置,即PPN+Flags ==》再通过Flags检查权限 ==》PPN +得到有效的物理地址==>访问物理地址 多级页表
?
一共有三级,整体上是一个树形的结构 。页表被设计为刚好一页的大小(4KB),如果整页的PTE都不存在/无效,就完全不分配该页来装载页表 。为了跟踪装载页表的页是否有效,引入页目录PD(Page )一个PTE占8B的空间,而一个页表大小为4KB,所以一个页表最多存4KB/8B = 512个PTE 。故需要9位来找到页表的每一个PTE,从0~511,即2^9查找过程:通过L1查找1级页表的PTE==> 得到二级页表的位置==>通过L2找到二级页表的PTE===》找到三级页表位置===>通过L3找到3级页表的PTE==>得到 ==> 配合得到实际的物理地址 。xv6内核页表由上图可见:到才对应真正的DRAM芯片,代码也是从到的内存
// 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)
的下方访问相应的物理地址,实际上是直接访问相关I/O设备的控制寄存器在内核启动未使用页表的时候,内核的虚拟地址到实际的物理地址采用直接映射的方法,内核开始使用页表,会把这种直接映射的关系存到页表 。3.6Space
使用页表的好处:
用户进程现在都有自己的页表,在进程之间提供了隔离性 。用户的虚拟地址空间是连续的,而对应的物理帧分布可以是不连续的 。通过页表,内核可以将页映射到用户虚拟地址空间的顶端,所有进程都可以看到这一页 。
用户栈的初始内容是由系统调用exec产生的
3.7 Code: Sbrk
Sbrk是一个系统调用,用户进程调用它以增加或减少自己拥有的物理内存(proc->sz)
3.8 Code: Exec
现在我们来看最后一段代码,系统调用exec的实现(/exec.c),系统调用exec将存储在文件系统上的,新的用户程序装载进内存里,然后执行它 。
int exec(char *path, char **argv)
exec通过路径名打开文件,然后读取该文件的ELF (/elf.h)
Print a page tableApage table perhints内核也是一个main程序,所有的程序都是从第一个程序inint.c作为第0个程序开始的,t通过去执行init程序,然后init程序回去fork(),再去启动程序sh, 然后就有了人机交互的shell终端main.c程序会进入()去调度进程:大概是选一个状态为的程序A,然后切换为状态,在通过函数swtch()来执行程序A 。的调用在fork()【父进程生成子进程】和()函数【初始化的时候,创建第0个进程】