riscv-xv6的页表机制

本篇文章将简要介绍内核页表和用户进程页表的机制,页表实现了内存的虚拟地址到物理地址的转换,虚拟地址机制实现了进程之间物理存储空间的隔离。在os中,物理内存一般以页(一页,一般为4096字节)进行分配和管理。

在riscv-xv6中实现了三级页表机制:level2,level1,level0。sv39的寻址方式中,virtual address由9+9+9(三级页表项,每一级页表的directory有2^9=512项PTE条目,PTE的组成为(reserved(10bit)+PPN(44bit)+flags(10bit))。其中标记位为PTE_R,PTE_W,PTE_X等,如果这三项都没有标记,则表示该PTE不是叶子PTE,可以通过该PTE进一步检索下一级页表,否则该PPN(对齐到4096,即4K字节)+12位的offset即为具体的物理地址。其中虚拟地址翻译的细节图解如下图所示。

图1:xv6的地址翻译细节图示

内核页表和用户进程的页表的虚拟地址到物理地址的转换机制相同,都是通过上述的地址转换细节实现。两者细节分别如下:

内核页表机制:低地址一般为外围硬件的寄存器和低地址物理内存对应,其地址映射图示为,其中PLIC( platform-level interrupt controller (PLIC) ),UART0定义为UART的寄存器对应的物理内存地址。VIRTIO为 virtio mmio interface,其中KERNBASE(0x80000000)到PHYSTOP(0x88000000=PHYSTOP (KERNBASE + 128*1024*1024))为xv6所支持的实际物理内存空间

图2:内核页表映射机制

其中kvmmake函数内核映射的代码可以参考引文[1]。其中代码段中下面的两行实现内核的代码和剩余空间的映射,确保内核可以管理所有物理内存。

//kvmmap函数的signature为void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
  kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

//map kernel data and the physical RAM we'll make use of.
kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);

其中通过kinit函数将内核代码数据段之上的物理内存空间按照物理页的粒度进行初始化并形成链表,kfree实现以页为单位的内存空间的释放,kalloc实现以页为单位的内存空间的申请(提供了锁机制实现了多进程的互斥访问),释放和申请相应的也需要对链表进行操作,具体实现可以参考引文[2]。从上图可以看出,kstack0,kstack1等为用户进程所对应的内核栈,也在kvmmake函数里实现

用户进程的地址空间通过fork、exec和sbrk等系统调用来生成,其中fork创建子进程,复制父进程地址空间的内容(会申请新的页表和PTE对应的物理内存空间,并通过函数memmove将父进程的内容进行复制,包括增加文件描述符的引用计数等),exec通过加载可行性的elf格式的文件,加载代码段数据段到用户页表里(生成了新的页表并释放就的页表空间,具体见引文[3]),然后生成stack空间,堆空间的生成通过sbrk系统调用来生成,其中栈空间的基地址为低地址,栈顶指针为高地址,栈空间从上到下维护程序代码执行的生命周期,包括参数,临时变量等等。

图3: 用户进程的用户地址空间

几个问题:

1、用户进程和其对应的内核栈如何实现的关联?回答:初始化进程表的时候实现的关联,具体可以参考引文[4]的代码实现。

2、在用户进程的用户态申请内存时如何实现的?是基于srbk的系统调用吗?回答:扩展堆区的大小可以用srbk系统调用来生成,进一步调用growproc函数和uvmalloc来实现进程的用户空间的存储空间的增长或缩减,可以参考引文[5]的具体实现。

References


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *