xv6---Lab3: page tables

目录

 参考资料

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


 参考资料

  • 什么是pagetable?

程序在系统上执行,操作的是虚拟内存地址,而虚拟内存到物理内存的映射需要一个函数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页表的简化图如下所示

。。

e68bccb5b225fd2fe0202d7b4fb76b9f.png

  1. 虚拟地址通过页表找到实际的物理内存,一般一个页表的大小设计为4K
  2. xv6运行在Sv39 RISC-V处理器上,64位虚拟地址中,只有低39位在被使用,剩下的25位都暂时保留,供日后的设计者利用。
  3. 将每个虚拟地址映射到一个物理地址,页表会以某种形式的表项来保存这种映射关系,这种表项我们称之为页表条目PTE(Page Table Entry)
  4.  PTE == 44位的物理页帧号PPN (physical page number)+10位的标志位Flags。有效位为54位的PTE可以用8B的大小来存储,这刚好是一个uint64类型。
  5. 虚拟地址 == 25位的EXT:未使用 + 27位的Index:索引对应的PTE+ 12位offset:页内偏移量
  6. 单级页表查询方式:传入一个64位的虚拟地址,通过高27位的index找到在Page Table的位置,即PPN+Flags ==》再通过Flags检查权限 ==》PPN + offset 得到有效的物理地址==>访问物理地址 

多级页表

format,png

  1. 一共有三级,整体上是一个树形的结构。
  2. 页表被设计为刚好一页的大小(4KB),如果整页的PTE都不存在/无效,就完全不分配该页来装载页表。为了跟踪装载页表的页是否有效,引入页目录PD(Page Directory)
  3. 一个PTE占8B的空间,而一个页表大小为4KB,所以一个页表最多存4KB/8B = 512个PTE。故需要9位来找到页表的每一个PTE,从0~511,即2^9
  4. 查找过程:通过L1查找1级页表的PTE==> 得到二级页表的位置==>通过L2找到二级页表的PTE===》找到三级页表位置===>通过L3找到3级页表的PTE==>得到physical address==> 配合offset得到实际的物理地址。 

xv6内核页表

  • xv6为内核单独维护了一个页表,下图展示了内核的虚拟地址空间如何映射到实际的物理地址空间中。format,png
  1. 由上图可见: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)
  2. KERNBASE的下方访问相应的物理地址,实际上是直接访问相关I/O设备的控制寄存器
  3. 在内核启动未使用页表的时候,内核的虚拟地址到实际的物理地址采用直接映射的方法,内核开始使用页表,会把这种直接映射的关系存到页表。
  • 用户为每个进程分配页表

3.6 Process Address Space

使用页表的好处:

  1. 用户进程现在都有自己的页表,在进程之间提供了隔离性
  2. 用户的虚拟地址空间是连续的,而对应的物理帧分布可以是不连续的。
  3. 通过页表,内核可以将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

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。
  1. 内核也是一个main程序,所有的程序都是从第一个程序inint.c作为第0个程序开始的,t通过initcode去执行init程序,
  2. 然后init程序回去fork(),再去启动程序sh, 然后就有了人机交互的shell终端
  3. main.c程序会进入scheduler()去调度进程:大概是选一个状态为RUNNABLE的程序A,然后切换为RUNNING状态,在通过函数swtch()来执行程序A。
  • 要理解freeproc ,就需要理解allocproc是如何调用的,
  1. allocproc的调用在fork()【父进程生成子进程】和userinit()函数【初始化的时候,创建第0个进程】
  2. procinit()规定了kernel最多可以有NPROC, 共64个进程,然后每个进程初始化会通过kalloc取得一片物理内存,将整片物理内存,分配到每个进程。

  3. 而allocproc则是在这初始化的64个进程中挑一个状态为RUNNABLE的程序,通过kalloc为之分配trapframe和进程页表

  4. 那么freeproc则是:(1) 释放进程的页表 :即让虚拟内存和物理地址不在有映射关系proc_freepagetable(p->pagetable, p->sz);    (2)通过kfree释放trapframe对应的内存

  • 以fork出子进程为入口点,参考pagetable = proc_pagetable()操作,可知:
  1. 通过kalloc()分配一页页表。再执行mappages操作执行TRAMPOLINE和TRAPFRAME映射操作。
  2. mappages函数:通过walk函数从虚拟地址得到物理地址。前两个页目录设为有效的PTE_V, 最后一页设置为传入的权限参数。
  • 如果需要为进程分配一个和内核一样的页表,那么需要参考内核页表的分配过程
  1. kvminit()是内核栈的初始化,这里可理解为逻机除运行程序之外,需要的外设,中断,程序运行需要的寄存器等等。

  2. 那么同理:进程需要额外的页表,来为每个进程执行一样的映射,映射到外设,中断,程序运行需要的寄存器等等。只不过需要放到进程proc结构体里的kernel_pagetable。

  3. 关于free操作,在页表初始化映射了什么,就free什么,类似malloc和free是一对操作。

copyin/copyout

理解的不是很透彻,大多数的理解都放在代码里了。加了很多注释

https://gitee.com/mm526830lbw/xv6-labs-2020/tree/pgtbl/

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值