OSLab3

实验1: 加速系统调用速度

代码实现 & 思路

其中,代码添加部分在注释// add start from here // add end之间

  • kernel/proc.h proc 结构体中添加一项指针来保存页表地址
// kernel/proc.h
struct proc {
	...
	struct usyscall *usyscallpage; // 添加usyscallpage指针
}
  • kernel/proc.callocproc() 中为其分配空间(kalloc)。并初始化其保存当前进程的pid
// kernel/proc.c
static struct proc*
allocproc(void)
{
  ...
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  
  // add start from here
  // 分配内存空间,返回指针赋值。分配失败时,指针为0。
  if ((p->usyscallpage = (struct usyscall *)kalloc()) == 0) {
    freeproc(p); // 释放进程
    release(&p->lock); // 释放进程锁
    return 0;
  }
  
  p->usyscallpage->pid = p->pid; // 初始化pid,记录当前进程的pid。
  // add end

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  ...
}
  • kernel/proc.cproc_pagetable() 中将这个映射(PTE)写入 pagetable 中,权限是用户态可读
// kernel/proc.c
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  // An empty page table.
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;
  
  // add start from here
  // 映射失败,即返回值小于0
  if(mappages(pagetable, USYSCALL, PGSIZE,
  	      (uint64)(p->usyscallpage), PTE_R | PTE_U) < 0) {
    uvmfree(pagetable, 0); // 释放页表
    return 0;
  }
  // add end

  // map the trampoline code (for system call return)
  // at the highest user virtual address.
  // only the supervisor uses it, on the way
  // to/from user space, so not PTE_U.
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }

  ...
}
  • kernel/proc.cfreeproc() 中确保释放进程的时候,能够释放该共享页,将页表插入空闲页链表中
// kernel/proc.c
static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  
  // add start from here
  // 判断是否为非空指针,即是否分配共享页。是,则释放内存空间,并将其设置为0.
  if(p->usyscallpage)
    kfree((void *)p->usyscallpage);
  p->usyscallpage = 0;
  // add end
  
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  ...
}
  • kernel/proc.cproc_freepagetable() 中解除映射关系
// kernel/proc.c
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, USYSCALL, 1, 0); // 解除映射关系
  ...
}

运行结果

在这里插入图片描述


系统调用流程

  1. 用户程序通过系统调用指令(如syscall指令)触发系统调用。
  2. CPU从用户态切换到内核态。
  3. CPU执行中断处理程序,将控制权转移到内核中的系统调用处理函数。
  4. 系统调用处理函数根据系统调用号,执行相应的操作。
  5. 在执行系统调用之前,系统调用处理函数可以访问用户空间和内核空间之间的只读区域,其中包括了共享的usyscallpage页表。
  6. 系统调用处理函数根据需要,读取或修改usyscallpage中的数据。
  7. 系统调用处理函数执行完毕后,将结果返回给用户程序。
  8. CPU从内核态切换回用户态,用户程序继续执行。

实验2: 打印页表

代码实现

  • defs.h 中定义一个 vmprint()
// kernel/defs.h
...
// vm.c
...
void            vmprint(pagetable_t);
...
  • vm.c 中实现 vmprint() 的函数
// kernel/vm.c
...
static int ptLevel = 0; // ptLevel:表示当前打印页表层级
void
vmprint(pagetable_t pagetable) {
  // 页表层级为0时,打印页表地址
  if (ptLevel == 0)
    printf("page table %p\n", (uint64)pagetable);
  // 遍历页表
  for (int i = 0; i < 512; i++) {
    pte_t pte = pagetable[i];
    // 检查PTE_V标志位是否有效
    if (pte & PTE_V) {
      // 根据页表层级打印对应数量的缩进
      for (int j = 0; j <= ptLevel; j++) {
        printf("..");
      }
      // 打印页表条目的索引、值、地址
      printf("%d: pte %p pa %p\n", i, (uint64)pte, (uint64)PTE2PA(pte));
    }
    // 对于有效的页表条目,检查读、写、执行权限的标志位,若都为0,进行操作
    if((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) {
      ptLevel++;  // 满足条件,层级加1
      uint64 childPT = PTE2PA(pte);  // childPT:将页表条目转换为物理地址
      vmprint((pagetable_t)childPT); // 作为vmprint起始地址的页表,打印更低层级的页表,完成递归。
      ptLevel--; // 恢复原先层级
    }
  }
}
  • exec.creturn argc; 之前插入
// kernel/exec.c
int
exec(char *path, char **argv)
{
  ...
  // 当前进程pid为1时,开始执行
  if(p->pid == 1) {
    vmprint(p->pagetable);
  }
  
  return argc; // this ends up in a0, the first argument to main(argc, argv)
  ...	
}

运行结果

在这里插入图片描述


系统调用流程

  1. 调用exec()函数。
  2. exec()函数中,如果当前进程的pid为1,则调用vmprint(p->pagetable)打印页表内容。
  3. vmprint()函数会递归地遍历页表,并打印每个页表条目的信息。
  4. 打印完成后,回到main()

实验3: 检测哪些页被访问

代码实现

  • kernel/sysproc.c 中实现 sys_pgaccess()
// kernel/sysproc.c
...
#ifdef LAB_PGTBL
int
sys_pgaccess(void)
{
  // lab pgtbl: your code here.
  uint64 virtual_addr; // virtual_addr: 第一个用户页的起始虚拟地址
  argaddr(0, &virtual_addr);
  
  int num_pages;       // num_pages: 被检查的页面数
  argint(1, &num_pages);
  
  uint64 abitsaddr;    // abitsaddr: 缓冲区地址
  argaddr(2, &abitsaddr);
  
  uint64 maskbits = 0; // maskbits: 储存页访问情况的掩码
  
  // 获取当前进程的指针,循环遍历要检查页面
  struct proc *proc = myproc();
  for (int i = 0; i < num_pages; i++) {
    // 获取页面对应的页表条目指针
    pte_t *pte = walk(proc->pagetable, virtual_addr + i * PGSIZE, 0);
    // 条目不存在,报错
    if(pte == 0)
      panic("Page does not exist!");
    // 被访问过,将掩码设置为1
    if(PTE_FLAGS(*pte) & PTE_A) {
      maskbits |= (1L << i);
    }
    // 记录、重置访问位
    *pte = ((*pte & PTE_A) ^ *pte) ^ 0;
  }
  // 通过copyout()将掩码存在缓冲区,然后复制到用户态
  if (copyout(proc->pagetable, abitsaddr, (char *)&maskbits, sizeof(maskbits)) < 0)
    panic("sys_pgacess copyout error!");
  return 0;
}
#endif
...
  • 需要在 kernel/riscv.h 中定义 PTE_A(访问位)
// kernel/riscv.h
...
// 定义访问位
#define PTE_A (1L << 6)
...

运行结果

在这里插入图片描述


系统调用流程

  1. 通过系统调用指令触发系统调用。
  2. 转内核态,内核会根据系统调用号调用相应的处理函数,即sys_pgaccess()
  3. 处理函数会获取用户传递的参数,并进行页访问检测的操作。
  4. 将结果返回给用户程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值