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: 在xv6中,copy-on-write fork是一种优化技术,它可以在子进程创建时避免不必要的内存复制。具体来说,当父进程调用fork()创建子进程时,子进程会共享父进程的内存页表,而不是复制一份父进程的内存。只有当子进程尝试修改共享的内存时,才会发生实际的复制操作。这种技术可以减少内存使用和复制时间,提高系统性能。 ### 回答2: xv6是一个操作系统教学项目,这是一个现代化风格的UNIX第六版。copy-on-write fork是xv6中实现的一种机制,它与fork系统调用有关。这种机制可减少在进行进程复制时所涉及的空间和时间开销,从而增加操作系统的效率。 在fork系统调用中,操作系统会复制原始进程,创建一个独立的进程。传统方法是,操作系统会将原有进程的内存空间全部复制一份给新进程,并在新进程中对地址进行修正。这样做会消耗大量的空间和时间,尤其是当进程较大时,复制整个内存空间会非常耗时。 copy-on-write fork的实现与传统方法不同。当原始进程需要创建新进程时,操作系统会将进程的内存空间标记为只读状态,并保留原内存页的映射关系。这样,当进程尝试写入内存时,操作系统将会产生一个缺页异常。在此时,操作系统会创建一个新页,将原内存页的内容复制到新页中,并在新页上进行写入操作。这样可以减少空间和时间开销,因为新页仅在需要写入时被复制,而不是在进程创建时。 copy-on-write fork有许多优点。首先,这种机制使系统更高效。使用copy-on-write fork可以显著降低进程复制的时间和空间开销。其次,这种机制还可以提高系统的可扩展性。当进程需要更多内存时,操作系统会重新映射新的内存页,而不是将整个进程复制一次。因此,系统可以更轻松地扩展。 总之,copy-on-write fork是xv6中非常有用的一个机制。它可以减少进程复制所需的时间和空间开销,从而提高操作系统的效率和可扩展性。 ### 回答3: 在操作系统课程xv6中,实现了一种名为“copy-on-write fork”的操作,这种操作可以让父进程和子进程在初始时共享相同的物理内存。当父进程或子进程试图修改内存时,内存页会被复制并分配新的物理内存,以避免父进程和子进程之间的竞争条件。 这种“copy-on-write”技术可以减少系统中的内存浪费,并且在分配内存时减少了复制操作,从而提高了系统的性能。在实现中,当父进程调用fork()创建一个新的子进程时,子进程将直接引用父进程的地址空间。父进程和子进程都共享相同的物理内存,但是它们各自有自己的页目录和页表来管理地址空间和虚拟内存。 当父进程或子进程尝试读取数据时,它们可以访问共享的物理内存。然而,当父进程或子进程试图修改数据时,操作系统会将所涉及的内存页复制到另一个物理内存地址,并使涉及的进程引用新的物理内存地址。这样,父进程和子进程将各自拥有自己的数据副本,一个进程修改数据不会影响另一个进程。 这种技术在许多操作系统中都有广泛应用,因为它可以提供更高效的内存管理和更好的性能。实现“copy-on-write”fork操作在操作系统课程中具有教育意义,因为它可以让学生更深入了解xv6的内部机制和操作系统的基本理论。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值