【MIT 6.S081】Lab3: page tables

Pgtbl

  • Print a page table
  • A kernel page table per process
  • Simplify copyin/copyinstr

本Lab简单优化了系统的页表功能,使得程序在内核态时可以直接解析用户态的指针。
笔者用时约8h

Print a page table

第一部分是为系统添加一个打印给定页表的函数vmprint,该函数接收一个参数pagetable(根页表的物理地址),递归遍历整张页表,打印有效的表项。
参考freewalk函数(定义在kernel/vm.c:331),每次遍历512个表项,若表项有效,则打印相关信息(第几级、第几项、pte内容和pte内容对应的物理地址),且若为一二级页表则继续递归,直到第三级页表返回。参考代码如下:

void 
vmprint_helper(pagetable_t pgtbl, int level) 
{
  // there are 2^9 = 512 PTEs in a page table.
  for(int i = 0; i < 512; i++){
    pte_t pte = pgtbl[i];
    if(pte & PTE_V){
      for (int j = 0; j < level; j ++ ) {
        if (j) printf(" ");
        printf("..");
      }
      printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
      if ((pte & (PTE_R|PTE_W|PTE_X)) == 0) {
        // this PTE points to a lower-level page table.
        uint64 child = PTE2PA(pte);
        vmprint_helper((pagetable_t)child, level + 1);
      }
    }
  }
}

void
vmprint(pagetable_t pgtbl) 
{
  printf("page table %p\n", pgtbl);
  vmprint_helper(pgtbl, 1);
}

A kernel page table per process

如标题,第二部分的内容是为每一个进程添加一个单独的内核页表副本,为下一节直接解引用用户态指针做铺垫。

首先需要在进程的结构体(定义在kernel/proc.h中)struct proc中添加一个字段维护内核页表副本,如下图所示
在这里插入图片描述

然后,由于我们需要在分配进程时需要为每一个进程初始化一个内核页表的副本,于是需要参考kvminit函数(定义在kernel/vm.c:66),编写一个初始化进程中内核页表副本的函数proc_kvminit,代码如下所示。该函数内容与kvminit函数基本一致。其中的uvmmapkvmmap函数(定义在kernel/vm.c:171)类似,映射给定的虚拟地址和物理地址范围,唯一不同点是前者修改的是传入的指定页表而不仅仅是全局的内核页表。

void 
uvmmap(pagetable_t pagetable, uint64 va, uint64 pa, uint64 sz, int perm) 
{
  if(mappages(pagetable, va, sz, pa, perm) != 0)
    panic("uvmmap");
}

/*
 * create a direct-map page table for the given process.
 */
pagetable_t
proc_kvminit() 
{
  pagetable_t pgtbl = (pagetable_t) kalloc();
  if (pgtbl == 0) return 0;
  memset(pgtbl, 0, PGSIZE);
  
  // uart registers
  uvmmap(pgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  uvmmap(pgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // CLINT
  uvmmap(pgtbl, CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  uvmmap(pgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W);

  // map kernel text executable and read-only.
  uvmmap(pgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

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

  // map the trampoline for trap entry/exit to
  // the highest virtual address in the kernel.
  uvmmap(pgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);

  return pgtbl;
}

接着在allocproc函数(定义在kernel/proc.c:95)中调用上述定义的proc_kvminit函数,实现在进程分配时初始化进程中的内核页表副本。同时,还需要将procinit中对进程内核栈对应的页表项初始化代码段移动到allocproc函数中,如下所示。这里需要注意的是,原始代码中对进程context字段的修改一定要放在最下面。(暂时不知道为啥,等知道了再补一下原因)

  ...

  // initialize the process kernel page table
  p->kernel_pagetable = proc_kvminit();
  if(p->kernel_pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // initialize the process kernel stack in kernel process kernel page table
  char *pa = kalloc();
  if(pa == 0)
    panic("kalloc");
  uint64 va = KSTACK((int) (p - proc));
  uvmmap(p->kernel_pagetable, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  p->kstack = va;

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;


  return p;

接下来在scheduler函数(定义在kernel/proc.c:489)中,当调度进程执行时,将进程对应的内核页表加载到satp寄存器中,且调用sfence_vma进行刷新。在进程执行完,调用将页表切换回全局的内核页表,代码段如下所示。

    // load process's kernel page table and flush the TLB
    w_satp(MAKE_SATP(p->kernel_pagetable));
    sfence_vma();

    swtch(&c->context, &p->context);

    // load kernel page table when process done
    kvminithart();

最后,还需要在freeproc函数(定义在kernel/proc.c:155)中释放进程所维护的内核页表副本。需要将进程中内核页表维护的内核栈物理空间释放掉,调用uvmunmap函数(定义在kernel/vm.c:230)即可。同时还需要将维护的内核页表副本销毁掉,由于freewalk函数只销毁第一级和第二级页表表项,需要自己写一个类似的函数来销毁第三级页表的表项,如下所示。

// Recursively free process's kernel page-table pages.
void 
proc_freewalk(pagetable_t pagetable)
{
  // there are 2^9 = 512 PTEs in a page table.
  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i];
    if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
      // this PTE points to a lower-level page table.
      uint64 child = PTE2PA(pte);
      proc_freewalk((pagetable_t)child);
      pagetable[i] = 0;
    } else if(pte & PTE_V){
      pagetable[i] = 0;
    }
  }
  kfree((void*)pagetable);
}

freeproc函数中的相关代码段如下

  // free process's kernel page table
  uvmunmap(p->kernel_pagetable, p->kstack, 1, 1);
  p->kstack = 0;
  proc_freewalk(p->kernel_pagetable);
  p->kernel_pagetable = 0;

Simplify copyin/copyinstr

这一部分需要实现的是将每一个进程的用户空间映射添加到进程维护的内核页表副本中(上一节创建的),由于用户空间的虚拟地址从0开始,且内核的虚拟地址从较高的地址开始(文档里说是PLIC,但是xv6book里面的图3.3是从CLINT开始的,暂时不知道为啥),所以给用户空间的映射留下了一些虚拟空间进行映射(0~PLIC-1)。
我们需要在fork函数、exec函数、growproc函数与userinit函数中,为进程维护的内核页表添加上用户空间的映射,因为这些函数都更改了用户映射。

首先,我仿照uvmcopy函数(定义在kernel/vm.c:384),定义了一个函数uvm2ukvm,它接收两个页表,一个是用户进程页表,一个是用户进程中维护的内核页表,并接收需要映射的起始虚拟地址和末尾虚拟地址,将这个范围内的用户空间虚拟地址复制到进程维护的内核页表中。注意需要将PTE_U标志位置为0,否则内核无法访问。

void uvm2ukvm(pagetable_t upgtbl, pagetable_t ukpgtbl, uint64 st, uint64 ed)
{
  pte_t *pte_u, *pte_uk;
  uint64 pa, i;
  uint flags;

  for (i = st; i < ed; i += PGSIZE) {
    if((pte_u = walk(upgtbl, i, 0)) == 0)
      panic("uvm2ukvm: pte_u should exist");
    if((*pte_u & PTE_V) == 0)
      panic("uvm2ukvm: page not present");
    pa = PTE2PA(*pte_u);
    flags = PTE_FLAGS(*pte_u);
    flags &= (~PTE_U);

    if((pte_uk = walk(ukpgtbl, i, 1)) == 0)
      panic("uvm2ukvm: pte_uk should exist");
    *pte_uk = PA2PTE(pa) | flags;
  }
}

fork函数中(定义在kernel/proc.c:289),调用以上函数,添加一行代码即可。

  ...
  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  uvm2ukvm(np->pagetable, np->kernel_pagetable, 0, np->sz);
  ...

exec函数中(定义在kernel/exec.c:13),也是一样的添加上一行代码即可。

  ...
  // Commit to the user image.
  oldpagetable = p->pagetable;
  p->pagetable = pagetable;
  p->sz = sz;
  uvm2ukvm(p->pagetable, p->kernel_pagetable, 0, sz);
  p->trapframe->epc = elf.entry;  // initial program counter = main
  p->trapframe->sp = sp; // initial stack pointer
  proc_freepagetable(oldpagetable, oldsz);
  ...

growproc函数中,当申请增长内存时,需要判断增长后的虚拟地址上界是否超过PLIC的起始地址,如果超过则返回-1,否则也是调用上述函数将增长的地址范围复制一份到进程维护的内核页表中即可。

  ...
    if (PGROUNDUP(sz + n) > PLIC) {
      return -1;
    }
    if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
    uvm2ukvm(p->pagetable, p->kernel_pagetable, sz - n, sz);
  ...

userinit函数中第一次初始化进程页表时,也要进行复制。

  ...
  // allocate one user page and copy init's instructions
  // and data into it.
  uvminit(p->pagetable, initcode, sizeof(initcode));
  uvm2ukvm(p->pagetable, p->kernel_pagetable, 0, PGSIZE);
  ...

最后,把copyin函数和copyinstr函数体中的内容改成调用copyin_newcopyinstr_new函数即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2756.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言-程序环境和预处理(2)

文章目录预处理详解1.预定义符号2.#define2.1#define定义的标识符2.2#define定义宏2.3#define替换规则注意事项&#xff1a;2.4#和###的作用##的作用2.5带副作用的宏参数2.6宏和函数的对比宏的优势&#xff1a;宏的劣势&#xff1a;宏和函数的一个对比命名约定3.undef4.条件编译…

centos系统/dev/mapper/centos-root目录被占满的解决方式

最近在做虚拟机部署docker微服务时&#xff0c;发现磁盘内存占满&#xff0c;无法进行操作。open /var/lib/dpkg/info/libc6:amd64.templates: no space left on device接下来就写下我在备份虚拟机上如何解决根目录被占满的问题&#xff1a;1、查看虚拟机磁盘使用情况df -h可以…

D - 统计子矩阵 (双指针+前缀和+降维处理)

D - 统计子矩阵 &#xff08;双指针前缀和降维处理&#xff09; 1、问题 D - 统计子矩阵 2、分析 代码 &#xff08;1&#xff09;纯暴力做法&#xff1a; 这个做法就很简单了&#xff0c;我们直接枚举所有的子矩阵&#xff0c;然后在对每一个子矩阵内部的元素逐一累加起…

【计算机二级】综合题目

计算机二级python真题 文章目录计算机二级python真题一、《大学慕课 两问 》二、综合应用题——价值链三、基本操作题 ——信息输出一、《大学慕课 两问 》 附件中的文件data.txt 是教育部爱课程网中国大学MOOC平台的某个 HTML页面源文件,里面包含了我国参与MOOC建设的一批大学…

STM32之点亮一个LED小灯(轮询法)

目录 一、初始化GPIO口 二、按键点亮LED灯&#xff08;轮询法&#xff09; 一、初始化GPIO口 1、点亮LED小灯前&#xff0c;需要先初始化GPIO口 HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) GPIO_TypeDef *GPIOx&#xff1a; //指初始化GPIO…

libvirt零知识学习5 —— libvirt源码编译安装(3)

接前一篇文章libvirt零知识学习4 —— libvirt源码编译安装&#xff08;2&#xff09; 在上篇文章及上上篇文章中构建libvirt的时候遇到了一个问题“ERROR: Problem encountered: YAJL 2 is required to build QEMU driver”。上篇文章讲到即使安装了相应的YAJL库仍然不能解决问…

HC小区管理系统window系统安装教程

实操视频 HC小区管理系统局域网window物理机部署教程_哔哩哔哩_bilibili 一、下载安装包 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1XAjxtpeBjHIQUZs4M7TsRg 提取码&#xff1a;hchc 或者 123盘 hc-window.zip官方版下载丨最新版下载丨绿色版下…

12个 Python 装饰器让代码cleaner

0 — 引言装饰器能在在不影响质量的情况下用更少的代码做更多的事情。Python 装饰器是强大的工具&#xff0c;可帮助你生成干净、可重用和可维护的代码。我等了很久才想了解这些抽象&#xff0c;现在我已经有了扎实的理解&#xff0c;本篇是为了帮助你也掌握这些对象背后的概念…

uni-app+uView如何轮播图滑动时改变背景颜色和导航栏颜色

今儿的创作欲很高涨哈 &#x1f604; 这也是在群里看到的&#xff0c;群友问如何在滑动&#xff08;或者自动滑动&#xff09;的时候背景颜色能跟着变 正好之前做过这个需求&#xff0c;也分享一下 首先&#xff0c;页面的组成分为三部分&#xff1a; 自定义navbar 页面背景轮…

JavaSE进阶之(十六)枚举

十六、枚举16.1 背景16.2 枚举类型16.3 EnumSet 和 EnumMap01、EnumSet02、EnumMap16.1 背景 在 Java 语言中还没有引入枚举类型之前&#xff0c;表示枚举类型的常用模式是声明一组 int 类型的常量&#xff0c;常常用的就是&#xff1a; public static final int SPRING 1; …

ElementUI学习笔记

目录 一、简单介绍 二、安装 1、下载 2、引入 三、布局 1、简介 2、使用 3、好处 四、布局容器 1、常见排布 2、调整样式 五、按钮 1、简单引用 2、改变样式 3、加载中效果 六、表格 1、简单使用 2、样式修改 七、对话框 1、简单使用 2、添加自定义内容 3、…

7个最受瞩目的 Python 库,提升你的开发效率

当今时代&#xff0c;数据分析和处理已经成为了各行各业中不可或缺的一环。Python作为一种非常流行的编程语言&#xff0c;为我们提供了许多强大的工具和库来处理不同类型的数据。 在这篇文章中&#xff0c;我将向您介绍七个非常有用的Python库&#xff0c;这些库各自有着独特…

js调用gpt3.5

参考链接&#xff1a;直接在前端调用 GPT-3 API 效果图&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>ChatGPT Web Example</title><style>body {font-family: "Helvetica Neue"…

TimeQuest时序路径详解

&#x1f4a1; 基于TimeQuest软件来查看时序报告和分析时序路径 分析最坏传输路径 根据[FPGA典型时序路径的分析可知&#xff0c;最坏传输路径对应的建立时间&#xff08;setup time&#xff09;余量最小。所以&#xff0c;查看最坏传输路径也就是查看建立时间余量最小的路径。…

【Linux】安装DHCP服务器

1、先检测网络是否通 get dhcp.txt rpm -qa //查看软件包 rpm -qa |grep dhcp //确定是否安装 yum install dhcp //进行安装 安装完成后 查询 rpm -ql dhcp 进行配置 cd /etc/dhcp 查看是否有遗留dhcpd.conf.rpmsave 删除该文件 cp /usr/share/doc/dhcp-4.1.1/dhcpd.conf.sampl…

ChatGPT能代替Oracle DBA吗?用Oracle OCP(1z0-083)的真题测试一下。

让我们来看看ChatGPT不能通过Oracle OCP的考试&#xff1f; 文章目录引言测试过程总结和分析关于博主&#xff0c;姚远&#xff1a;Oracle ACE&#xff08;Oracle和MySQL数据库方向&#xff09;。Oracle MAA 大师。华为云MVP。《MySQL 8.0运维与优化》的作者。拥有 Oracle 10g和…

mysql和mysql2模块的区别!!(nodejs中的模块)

mysql 和 mysql2 都是 Node.js 中常用的操作 MySQL 数据库的模块&#xff0c;它们的主要区别是在实现方式上略有不同。 mysql&#xff1a;是 Node.js 中比较早期的 MySQL 操作模块&#xff0c;该模块底层使用的是回调函数&#xff08;callback&#xff09;来实现异步操作。在处…

ESP32设备驱动-DHT12温湿度传感器驱动

DHT12温湿度传感器驱动 文章目录DHT12温湿度传感器驱动1、DHT12介绍2、硬件准备3、软件准备4、驱动实现1、DHT12介绍 DHt12是经典DHT11温湿度传感器的升级版&#xff0c;完全向下兼容&#xff0c;精度更高&#xff0c;增加了I2C接口。 DHT12 具有单总线和标准 I 2C 两种通讯&…

一文7个步骤从0到1教你搭建Selenium 自动化测试环境

【导语】Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。本文详细介绍了搭建自动化测试环境所需的工具&#xff0c;让你学习自动化测试不…

不用科学上网,免费的GPT-4 IDE工具Cursor保姆级使用教程

大家好&#xff0c;我是可乐。 过去的一周&#xff0c;真是疯狂的一周。 GPT-4 震撼发布&#xff0c;拥有了多模态能力&#xff0c;不仅能和GPT3一样进行文字对话&#xff0c;还能读懂图片&#xff1b; 然后斯坦福大学发布 Alpaca 7 B&#xff0c;性能匹敌 GPT-3.5&#xff…
最新文章