xv6实验记录(三)

2021-xv6实验记录(三)

本次实验,将进行三个子任务:

  1. 系统调用性能优化
  2. 打印进程 1 的页表
  3. 跟踪进程的内存访问位。

官方实验指南:在这里!!!

官方参考书:点击此处!!
友情链接: 友情链接

开始实验之前首先切换到官方的pgtbl分支

$ git fetch
$ git checkout pgtbl
$ make clean

实验【Speed up system calls】

一、实验前提示

  • kernel/proc.c文件里的 alloc_proc()函数里,加入对 USYSCALL页面的创建过
    程(可以仿照 TRAPFRAME 页面的构建方法来写相关的代码),并在之后对 usyscall
    结构体的 pid 成员变量进行赋值
  • kernel/proc.c文件里的 proc_pagetable()里,加入对 USYSCALL 页面的映射过
    程并赋予适当的权限(可以仿照 trapframe 和 TRAMPOLINE 的映射方法);
  • 映射的方法用到的核心函数为 mappages();
  • 在进程退出或者分配内存失败的情况下,记得释放 USYSCALL 和相关的页面,防止
    内存泄漏(例如在 freeproc()函数里添加相关的内存撤销/回收语句);
  • 根据上述描述,ugetpid()的核心逻辑其实就是在用户层对进程的 USYSCALL 页面进
    行访问,可以查看 xv6 源码目录下的 user/pgtbltest.c 文件是如何对其进行
    访问的

**简述题意:**给系统调用函数ugetpid()提速,方法是给每个进程的单独内存空间里添加一个USYSCALL页面,而里头存放一个系统函数会经常使用的数据,这里专指pid,而我们把pid存放在struct usyscall中。

  • 通过上述方法,ugetpid()需要用到pid时,会在用户态使用USYSCALL页面直接调用,而不用切换到内核态

二、实验过程

首先打开kernel/memlayout.h,可以看到下述代码

/* kernel/memlayout.h */
#ifdef LAB_PGTBL
#define USYSCALL (TRAPFRAME - PGSIZE)  // USYSCALl = 3FFFFFD000

struct usyscall {
    int pid;  // Process ID
};
#endif

这个USYSCALL页一定是独立于进程页表的一个页,把定义加到kernel/proc.h中:

struct proc{
    struct spinlock lock;
    struct usyscall *usyscall;  		//新添加的代码
    ...
}

然后去kernel/proc.c中初始化这个页面,allocproc()中会分配一些页,在这个函数里面分配出需要的usyscall

static struct proc * allocproc(void){
    ...
    //原有代码
    ...
        
    // 新添加的代码!!
    if ((p->usyscall = (struct usyscall *)kalloc()) == 0) {
        freeproc(p);
        release(&p->lock);
        return 0;
    }
    
    // An empty user page table.
    p->pagetable = proc_pagetable(p);
    ...
}

用户态寻址的时候都要经过页表硬件的翻译,所以usyscall也要映射在进程的pagetable上,刚刚加入的位置的下面就有proc_pagetable(p)用于map page,在kernel/proc.c中加入映射逻辑

pagetable_t proc_pagetable(struct proc *p){
    ...
        
    //新添加的代码
    if (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usyscall),
                 PTE_R | PTE_U) < 0) {
        uvmunmap(pagetable, TRAMPOLINE, 1, 0);
        uvmunmap(pagetable, TRAPFRAME, 1, 0);
        uvmfree(pagetable, 0);
        return 0;
    }
    
    
    return pagetable;
}

映射完成了之后就要对其进行初始化,回到kernel/proc.c,加入代码

static struct proc * allocproc(void){
    ...
    //新添加的代码
    p->usyscall->pid = p->pid;	
    
    return p;
}

根据提示:进程回收的时候,该页面也要释放,在freeproc()中加入释放即可

static void freeproc(struct proc*p){
    ...
    
    //新添加的代码
    if (p->usyscall) kfree((void *)p->usyscall);
    p->usyscall = 0;
}

并且在函数proc_freepagetable()中解除映射,不然启动的时候会报freewalk panic

void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  ...
  //新添加的代码
  uvmunmap(pagetable, USYSCALL, 1, 0);
    
  uvmfree(pagetable, sz);
}

三、实验结果的验证

打开终端,make qemu启动xv6系统,检查是否报错,如果没有遇见错误,在 xv6 的终端里敲:pgtbltest或者./pgtbltest,看看是否能够通过对 ugetpid()的调用测试

image-20231011161305492


实验【Print a page table】

一、实验前提示

  • 输出页表功能由 vmprint()函数实现,它的参数只有一个,类型为
    pagetable_t,其定义在kernel/riscv.h的末尾;
  • kernel/exec.c文件里,在return argc;语句之前,也即是 exec()函数的
    接近末尾处,加入if(p->pid==1) vmprint(p->pagetable);,以便只打印 pid 为 1 进程页表;
  • kernel/vm.c 里,找个地方实现vmprint(),它的逻辑就是输出当前进程的
    页表;
  • 在文件kernel/vm.c里,有个现有的函数叫做 freewalk()——你的 vmprint()
    代码可以参考和借鉴
  • 在文件kernel/riscv.h的末尾,有几个宏定义,在解题的时候会用的上;
  • 输出内存地址的时候,可以在 printf 里用%p 来格式化指针(地址),它的好
    看程度和图 3.2 的地址输出一样美丽;

二、实验过程

实验解决思路:递归遍历页表,碰到有效的就遍历进下一层页表

根据提示在kernel/vm.c文件中添加如下代码

void vmprintlevel(pagetable_t pt, int level) {
    char *delim = 0;
    if (level == 2) delim = "..";
    if (level == 1) delim = ".. ..";
    if (level == 0) delim = ".. .. ..";
    for (int i = 0; i < 512; i++) {
        pte_t pte = pt[i];
        if ((pte & PTE_V)) {
            //  this PTE points to a lower level page table.
            printf("%s%d: pte %p pa %p\n", delim, i, pte, PTE2PA(pte));
            uint64 child = PTE2PA(pte);
            if (level != 0) {
                vmprintlevel((pagetable_t)child, level - 1);
            }
        }
    }
}

void vmprint(pagetable_t pt) {
    printf("page table %p\n", pt);
    vmprintlevel(pt, 2);
}

记得在kernel/defs.h文件中添加函数的声明

...
void            vmprintlevel(pagetable_t pt, int level);	//新添加
void            vmprint(pagetable_t pt);					//新添加

写完了函数还要记得在make的时候启动输出因此找到kernel/exec.c 文件里,在return argc;`语句之前,也即是 exec()函数的
接近末尾处,加入if(p->pid==1) vmprint(p->pagetable);

// 文件kernel/exec.c
int
exec(char *path, char **argv)
{
 ... 
  if(p->pid==1) {			//新添加的代码
  	vmprint(p->pagetable);
  }
  
  return argc; // this ends up in a0, the first argument to main(argc, argv)
  ...

三、实验结果的验证

此时我们在终端使用make qemu启动后可以看到如下效果:

image-20231011160530691


实验【Detecting which pages have been accessed】

一、实验前提示

  • kernel/sysproc.c里实现 pgaccess()系统调用,名为sys_pgaccess()
    如果你已经切换到了本 lab 的代码分支,那么该系统调用的声明已经完成,你只
    需实现该系统调用本体即可;
  • 有两个内核函数可以用到,分别是:argaddr()argint(),它们的作用都是把用
    户层的系统调用参数通过一个安全的方法导入至内核使用;这两个内核函数的定
    义在 kernel/syscall.c
  • 因为这里用 bitmask来保存指定页面范围内的访问情况,所以 pgaccess()系统调
    用在内核层收集完信息后,我们需要把这个信息拷贝回用户层,这里用到的一个
    函数叫做 copyout(),它的实现在 kernel/vm.c的末尾**(还记得 lab2 中的**
    Sysinfo 吗?)
  • 可以设定 pgaccess()系统调用所涉及的页面数量最大值,比如 32;
  • kernel/vm.c文件里的 walk()函数可以在本次任务中发挥较大的用处,它的执行
    逻辑是:给定一个 VA(Virtual Address),返回其在指定页表内的 L0 级页目录的
    PTE 地址;你可以用它来检测返回值的对应访问位是否为 1;
  • 需要在 kernel/riscv.h 里定义页表的访问位,也即PTE_A,否则你的默认页表里
    没有 PTE_A位可供系统调用查询
  • 在 pgaccess()执行期间,确保 PTE_A 为 1 的页面在 check 之后置其为 0

二、实验过程

主要思路就是用户调用系统调用的时候,我们去查找页表,获得对应的PTE,然后检查PTE_A(需要自己定义),然后决定是否在答案设置对应有效位。

首先我们自己定义PTE_A,打开kernel/riscv.h文件

...
#define PTE_U (1L << 4) // 1 -> user can access
    
#define PTE_A (1L << 6) // 改行代码为新添加的内容

kernel/sysproc.c中完善sys_pgaccess()

#ifdef LAB_PGTBL
uint64 sys_pgaccess(void) {			//函数体的内容都是新添加的	
    // lab pgtbl: your code here.
    uint64 buf;
    int number;
    uint64 ans;
    if (argaddr(0, &buf) < 0) return -1;
    if (argint(1, &number) < 0) return -1;
    if (argaddr(2, &ans) < 0) return -1;
    return pgaccess((void*)buf, number, (void*)ans);
}
#endif

看到上一步使用了pgaccess函数因此在kernel/defs.h中需要添加声明

uint64 pgaccess(void *pg, int number, void *store);

然后去kernel/proc.c中加入pgaceess()的定义

//新添加的函数
uint64 pgaccess(void *pg, int number, void *store) {
    struct proc *p = myproc();
    if (p == 0) {
        return 1;
    }
    pagetable_t pagetable = p->pagetable;
    int ans = 0;
    for (int i = 0; i < number; i++) {
        pte_t *pte;
        pte = walk(pagetable, ((uint64)pg) + (uint64)PGSIZE * i, 0);
        if (pte != 0 && ((*pte) & PTE_A)) {
            ans |= 1 << i;
            *pte ^= PTE_A;  // clear PTE_A
        }
    }
    // copyout
    return copyout(pagetable, (uint64)store, (char *)&ans, sizeof(int));
}

因为上一步过程使用了一个walk函数因此我们需要在kernel/defs.h中添加walk函数的声明

pte_t*          walk(pagetable_t,uint64,int);	//新添加的函数声明

三、实验结果的验证

使用命令make grade检测前面三个实验是否完全通过

image-20231011165721153

注意:需要创建一个文件answers-pgtbl.txt文件用于保存答案否则make grade的时候会在在test answer - pgtbl.txt报错,同时创建了文件之后在里面随便敲入多个字符保留答案输出空间即可:

即:在xv6 - labs - 2021目录下

  1. touch answers-pgtbl.txt
  2. vim answers-pgtbl.txt
  3. 随便敲入一些内容

~~

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_橙留香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值