深入学习操作系统!详细剖析 MIT 6.S081 课程 Lab 3 : page tables - 3 Print a page table

本文细致的剖析了 2021 FALL MIT 6.S081 课程的一项实验, Lab 链接 Lab: page tables (mit.edu)

新人博主,大家的每一次阅读都会激励我继续创作,大家的点赞将会是我继续更新的巨大动力,对文中内容或实验过程中有任何疑问欢迎留言!

Lab 3 主要探索页表并对其进行修改,以简化将数据从用户空间复制到内核空间的函数。本实验涉及到一些虚拟地址和物理地址结构,以及两者之间的转换,需要对虚拟内存的知识有基本认识。

Lab 3 实验 1 深入学习操作系统!详细剖析 MIT 6.S081 课程 Lab 3 : page tables - 1 Speed up system calls

Lab 3 实验 2 深入学习操作系统!详细剖析 MIT 6.S081 课程 Lab 3 : page tables - 2 Print a page table

Detecting which pages have been accessed (hard)

垃圾收集器(一种自动内存管理形式)可以利用页面是否已被访问(读取或写入)的信息。在本实验的这一部分,您将向 xv6 添加一个新功能,通过检查 RISC-V 页表中的访问位来检测和报告此信息给用户空间。RISC-V hardware page walker 在解析 TLB 未命中时会标记 PTE 中的这些位。

您的任务是实现 pgaccess(),一个报告哪些页面已被访问的系统调用。该系统调用接受三个参数。第一个是要检测的第一个用户页面的起始虚拟地址。第二个是要检测的页面数。最后一个参数是一个用户地址,指向一个用于存储结果的位掩码的缓冲区(每页使用一个位,其中第一页对应最低有效位)。如果在运行pgtbltest时pgaccess测试用例通过,您将获得此实验的完整学分。

提示

  • Start by implementing sys_pgaccess() in kernel/sysproc.c.
  • You'll need to parse arguments using argaddr() and argint().
  • For the output bitmask, it's easier to store a temporary buffer in the kernel and copy it to the user (via copyout()) after filling it with the right bits.
  • It's okay to set an upper limit on the number of pages that can be scanned.
  • walk() in kernel/vm.c is very useful for finding the right PTEs.
  • You'll need to define PTE_A, the access bit, in kernel/riscv.h. Consult the RISC-V manual to determine its value.(Finished)
  • Be sure to clear PTE_A after checking if it is set. Otherwise, it won't be possible to determine if the page was accessed since the last time pgaccess() was called (i.e., the bit will be set forever).
  • vmprint() may come in handy to debug page tables.

过程

依据提示,根据 xv6 手册内容,我们应在 kernel/riscv.h 内定义 PTE_A

C 复制代码
// kernel/riscv.h
// ...
#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // 1 -> user can access
#define PTE_A (1L << 6) // 1 -> accessed
//...

然后,我们在 kernel/sysproc.c 中定义 sys_pgaccess() ,查阅相关代码发现 sys_pgaccess() 相关的入口、函数声明等都已给出,仅定义 sys_pgaccess() 即可:

C 复制代码
// kernel/sysproc.c
// ...
//uint64
sys_pgaccess(void) {}

根据题目要求和提示, sys_pgaccess() 系统调用有三个参数,包括两个地址和一个整型,对于系统调用,应该用 argint()argaddr() 来读取参数:

C 复制代码
// kernel/sysproc.c
// ...
uint64
sys_pgaccess(void)
{
    // the starting virtual address of the first user page to check
    uint64 va;
    // the number of user pages to check
    int page_nums;
    // a user address as a buffer to store the results into a bitmask
    uint64 user_addr;

    if(argaddr(0, &va) < 0)
        return -1;
    if(argint(1, &page_nums) < 0)
        return -1;
    if(argaddr(2, &user_addr) < 0)
        return -1;

    return 0;
}

下面我们开始计算掩码,起始 mask 应初始化为 0 ,然后开始遍历从 va 开始的页面,如果当前页面对应的页表项(利用 walk() 函数得到)的 PTE_A = 1 ,则将掩码对应位置 1 ,置 1 操作可通过或运算完成:

C 复制代码
// kernel/sysproc.c
// ...
uint64
sys_pgaccess(void)
{
    // the starting virtual address of the first user page to check
    uint64 va;
    // the number of user pages to check
    int page_nums;
    // a user address as a buffer to store the results into a bitmask
    uint64 user_addr;

    if(argaddr(0, &va) < 0)
        return -1;
    if(argint(1, &page_nums) < 0)
        return -1;
    if(argaddr(2, &user_addr) < 0)
        return -1;

    uint64 mask = 0;
	
    struct proc *p = myproc();
    for (int i = 0; i < npage; ++i) {
      pte_t *pte = walk(p->pagetable, startaddr+i*PGSIZE, 0);
      if (*pte & PTE_A) {
        mask |= (1 << i);
      }
    }

    return 0;
}

注意, walk() 函数未在 kernel/def.h 中声明,需要在添加这一声明。

根据提示,在检查 PTE_A 是否被设置之后,需要将其清除(置 0 )。否则,将无法确定自上次调用 pgaccess() 以来页面是否被访问过,换句话说,该位将被永久置位。为将 PTE_A 置 0 ,我们将其取反与页表项相与即可,最终的代码如下所示:

C 复制代码
// kernel/sysproc.c
// ...
uint64
sys_pgaccess(void)
{
    // the starting virtual address of the first user page to check
    uint64 va;
    // the number of user pages to check
    int page_nums;
    // a user address as a buffer to store the results into a bitmask
    uint64 user_addr;

    if(argaddr(0, &va) < 0)
        return -1;
    if(argint(1, &page_nums) < 0)
        return -1;
    if(argaddr(2, &user_addr) < 0)
        return -1;

    uint64 mask = 0;
    uint64 complement = ~PTE_A;
	
    struct proc *p = myproc();
    for (int i = 0; i < npage; ++i) {
        pte_t *pte = walk(p->pagetable, startaddr+i*PGSIZE, 0);
        if (*pte & PTE_A) {
            mask |= (1 << i);
            *pte &= complement;
        }
    }

    return 0;
}
相关推荐
双叶83622 分钟前
(C语言)超市管理系统 (正式版)(指针)(数据结构)(清屏操作)(文件读写)
c语言·开发语言·数据结构·c++·windows
序属秋秋秋1 小时前
《数据结构初阶》【二叉树 精选9道OJ练习】
c语言·数据结构·c++·算法·leetcode
欧先生^_^2 小时前
rust语言,与c,go语言一样也是编译成二进制文件吗?
c语言·golang·rust
再睡一夏就好3 小时前
从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区
linux·服务器·c语言·开发语言·学习笔记
C_Liu_7 小时前
C语言:深入理解指针(5)
java·c语言·算法
small_wh1te_coder7 小时前
从经典力扣题发掘DFS与记忆化搜索的本质 -从矩阵最长递增路径入手 一步步探究dfs思维优化与编程深度思考
c语言·数据结构·c++·stm32·算法·leetcode·深度优先
邹诗钰-电子信息工程13 小时前
嵌入式自学第二十二天(5.15)
c语言
范纹杉想快点毕业16 小时前
以项目的方式学QT开发(一)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!
c语言·数据结构·c++·git·qt·链表·github
xueyinan16 小时前
小刚说C语言刷题—1088求两个数M和N的最大公约数
c语言
王RuaRua18 小时前
[数据结构]7. 堆-Heap
c语言·数据结构·算法·链表