215.Mit6.S081-实验三-page tables

在本实验室中,您将探索页表并对其进行修改,以简化将数据从用户空间复制到内核空间的函数。

一、实验准备

开始编码之前,请阅读xv6手册的第3章和相关文件:

  • kernel/memlayout.h,它捕获了内存的布局。
  • kernel/vm.c,其中包含大多数虚拟内存(VM)代码。
  • kernel/kalloc.c,它包含分配和释放物理内存的代码。

可看这一篇博客来增加理解。

211.xv6——3(page tables)-CSDN博客

要启动实验,请切换到pgtbl分支:

$ git fetch
$ git checkout pgtbl
$ make clean

二、Print a page table (easy)

1.实验要求

        为了帮助您了解RISC-V页表,也许为了帮助将来的调试,您的第一个任务是编写一个打印页表内容的函数。

  1. 定义一个名为vmprint()的函数。
  2. 它应当接收一个pagetable_t作为参数,并以下面描述的格式打印该页表。
  3. exec.c中的return argc之前插入if(p->pid==1) vmprint(p->pagetable),以打印第一个进程的页表。
  4. 如果你通过了pte printout测试的make grade,你将获得此作业的满分。

现在,当您启动xv6时,它应该像这样打印输出来描述第一个进程刚刚完成exec()inginit时的页表:

page table 0x0000000087f6e000
..0: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000
.. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000
.. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000
..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000
.. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000
.. .. ..510: pte 0x0000000021fdd807 pa 0x0000000087f76000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
  1. 第一行显示vmprint的参数。之后的每行对应一个PTE,包含树中指向页表页的PTE。
  2. 每个PTE行都有一些“..”的缩进表明它在树中的深度。
  3. 每个PTE行显示其在页表页中的PTE索引、PTE比特位以及从PTE提取的物理地址。
  4. 不要打印无效的PTE。在上面的示例中,顶级页表页具有条目0和255的映射。
  5. 条目0的下一级只映射了索引0,该索引0的下一级映射了条目0、1和2。

您的代码可能会发出与上面显示的不同的物理地址。条目数和虚拟地址应相同。

2.提示

  • 你可以将vmprint()放在kernel/vm.c
  • 使用定义在kernel/riscv.h末尾处的宏
  • 函数freewalk可能会对你有所启发
  • vmprint的原型定义在kernel/defs.h中,这样你就可以在exec.c中调用它了
  • 在你的printf调用中使用%p来打印像上面示例中的完成的64比特的十六进制PTE和地址

3.实现

(1)首先在kernel/vm.c中添加vmprint()函数

// 递归打印页表的函数。
// pagetable是页表,level表示当前递归的深度。
void 
_vmprint(pagetable_t pagetable, int level)
{// 遍历页表中的每一个PTE(页表项)。for (int i = 0; i < 512; i++){pte_t pte = pagetable[i]; // 获取当前的页表项。if(pte & PTE_V) // 如果页表项有效(存在)。{// 打印缩进,根据当前递归的深度level来决定。for (int j = 0; j < level; j++){if(j)printf(" ");printf("..");}uint64 child = PTE2PA(pte); // 获取页表项指向的物理地址。printf("%d: pte %p pa %p\n", i, pte, child); // 打印页表项信息。// 如果不是叶子节点(没有R/W/X权限),继续递归打印下一级页表。if((pte & (PTE_W | PTE_R | PTE_X)) == 0){_vmprint((pagetable_t)child, level + 1);}}}
}// 打印页表的入口函数。
// pagetable是页表的根。
void vmprint(pagetable_t pagetable)
{printf("page table %p\n", pagetable); // 打印页表的根地址。_vmprint(pagetable, 1); // 从根页表开始递归打印,初始深度为1。
}

(2)在kernel/defs.h中添加定义

(3)在kernel/exec.c中添加

4.测试结果

三、A kernel page table per process (hard)

        Xv6有一个单独的用于在内核中执行程序时的内核页表内核页表直接映射(恒等映射)到物理地址,也就是说内核虚拟地址x映射到物理地址仍然是xXv6还为每个进程的用户地址空间提供了一个单独的页表,只包含该进程用户内存的映射,从虚拟地址0开始。因为内核页表不包含这些映射,所以用户地址在内核中无效。因此,当内核需要使用在系统调用中传递的用户指针(例如,传递给write()的缓冲区指针)时,内核必须首先将指针转换为物理地址。本节和下一节的目标是允许内核直接解引用用户指针。

1.实验要求

        你的第一项工作是修改内核来让每一个进程在内核中执行时使用它自己的内核页表的副本。修改struct proc来为每一个进程维护一个内核页表,修改调度程序使得切换进程时也切换内核页表。对于这个步骤,每个进程的内核页表都应当与现有的的全局内核页表完全一致。如果你的usertests程序正确运行了,那么你就通过了这个实验。

        阅读本作业开头提到的章节和代码;了解虚拟内存代码的工作原理后,正确修改虚拟内存代码将更容易。页表设置中的错误可能会由于缺少映射而导致陷阱,可能会导致加载和存储影响到意料之外的物理页存页面,并且可能会导致执行来自错误内存页的指令。

2.提示

  • struct proc中为进程的内核页表增加一个字段
  • 为一个新进程生成一个内核页表的合理方案是实现一个修改版的kvminit,这个版本中应当创造一个新的页表而不是修改kernel_pagetable。你将会考虑在allocproc中调用这个函数。
  • 确保每一个进程的内核页表都关于该进程的内核栈有一个映射。在未修改的XV6中,所有的内核栈都在procinit中设置。你将要把这个功能部分或全部的迁移到allocproc
  • 修改scheduler()来加载进程的内核页表到核心的satp寄存器(参阅kvminithart来获取启发)。不要忘记在调用完w_satp()后调用sfence_vma()
  • 没有进程运行时scheduler()应当使用kernel_pagetable
  • freeproc中释放一个进程的内核页表
  • 你需要一种方法来释放页表,而不必释放叶子物理内存页面。
  • 调式页表时,也许vmprint能派上用场
  • 修改XV6本来的函数或新增函数都是允许的;你或许至少需要在kernel/vm.ckernel/proc.c中这样做(但不要修改kernel/vmcopyin.ckernel/stats.cuser/usertests.c, 和user/stats.c
  • 页表映射丢失很可能导致内核遭遇页面错误。这将导致打印一段包含sepc=0x00000000XXXXXXXX的错误提示。你可以在kernel/kernel.asm通过查询XXXXXXXX来定位错误。

3.具体实现

本实验主要是让每个进程都有自己的内核页表,这样在内核中执行时使用它自己的内核页表的副本。

(1)首先在kernel/proc.h里面的struct proc增加内核页表的字段,表示内核态页表。

(2)在vm.c中添加新的方法proc_kpt_init,该方法用于在allocproc 中初始化进程的内核页表。这个函数还需要一个辅助函数uvmmap,该函数和kvmmap方法几乎一致,不同的是kvmmap是对Xv6的内核页表进行映射,而uvmmap将用于进程的内核页表进行映射。

//用于映射虚拟地址到物理地址
void uvmmap(pagetable_t pagetable,uint64 va,uint64 pa,uint64 sz,int perm)
{if(mappages(pagetable,va,sz,pa,perm)!=0){panic("uvmmap");}
}//用于初始化内核页表
pagetable_t ukvminit()
{pagetable_t kernelpt = uvmcreate();if(kernelpt==0)return 0;uvmmap(kernelpt, UART0, UART0, PGSIZE, PTE_R | PTE_W);uvmmap(kernelpt, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);uvmmap(kernelpt, CLINT, CLINT, 0x10000, PTE_R | PTE_W);uvmmap(kernelpt, PLIC, PLIC, 0x400000, PTE_R | PTE_W);uvmmap(kernelpt, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);uvmmap(kernelpt, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);uvmmap(kernelpt, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);return kernelpt;
}

(3)在 kernel/proc.c 中的 allocproc 函数里添加调用函数的代码:

记得在 kernel/defs.h 添加函数声明:pagetable_t ukvminit(void);

(4)在内核栈的初始化原来是在 kernel/proc.c 中的 procinit 函数内,这部分要求将函数内的代码转移到 allocproc 函数内,因此在上一步初始化内核态页表的代码下面接着添加初始化内核栈的代码:

kvminithart是用于原先的内核页表,我们将进程的内核页表传进去就可以。在vm.c里面添加一个新方法proc_inithart

然后在scheduler()内调用即可,但在结束的时候,需要切换回原先的kernel_pagetable。直接调用调用上面的kvminithart()就能把Xv6的内核页表加载回去。

(6) 在freeproc中释放一个进程的内核页表。首先释放页表内的内核栈,调用uvmunmap可以解除映射,最后的一个参数(do_free)为一的时候,会释放实际内存。

// free the kernel stack in the RAM
uvmunmap(p->kernelpt, p->kstack, 1, 1);
p->kstack = 0;

然后释放进程的内核页表,先在kernel/proc.c里面添加一个方法proc_freekernelpt。如下,历遍整个内核页表,然后将所有有效的页表项清空为零。如果这个页表项不在最后一层的页表上,需要继续进行递归。

void proc_freekernelpt(pagetable_t kernelpt)
{// similar to the freewalk method// there are 2^9 = 512 PTEs in a page table.for (int i = 0; i < 512;i++){pte_t pte = kernelpt[i];if(pte&PTE_V){kernelpt[i] = 0;if((pte&(PTE_R|PTE_W|PTE_X))==0){uint64 child = PTE2PA(pte);proc_freekernelpt((pagetable_t)child);}}}kfree((void *)kernelpt);
}

(6). 将需要的函数定义添加到 kernel/defs.h 中

(7). 修改vm.c中的kvmpa,将原先的kernel_pagetable改成myproc()->kernelpt,使用进程的内核页表。

最后,在 vm.c 中添加头文件:

#include "spinlock.h"
#include "proc.h"

最后修改kvmpa函数

uint64
kvmpa(uint64 va)
{uint64 off = va % PGSIZE;pte_t *pte;uint64 pa;pte = walk(myproc()->kernelpt, va, 0); // 修改这里if(pte == 0)panic("kvmpa");if((*pte & PTE_V) == 0)panic("kvmpa");pa = PTE2PA(*pte);return pa+off;
}

4.测试结果

四、Simplify copyin/copyinstr

        内核的copyin函数读取用户指针指向的内存。它通过将用户指针转换为内核可以直接解引用的物理地址来实现这一点。这个转换是通过在软件中遍历进程页表来执行的。在本部分的实验中,您的工作是将用户空间的映射添加到每个进程的内核页表(上一节中创建),以允许copyin(和相关的字符串函数copyinstr)直接解引用用户指针。

 1.实验要求

  • 将定义在kernel/vm.c中的copyin的主题内容替换为对copyin_new的调用(在kernel/vmcopyin.c中定义);
  • copyinstrcopyinstr_new执行相同的操作。
  • 为每个进程的内核页表添加用户地址映射,以便copyin_newcopyinstr_new工作。
  • 如果usertests正确运行并且所有make grade测试都通过,那么你就完成了此项作业。

        此方案依赖于用户的虚拟地址范围不与内核用于自身指令和数据的虚拟地址范围重叠。Xv6使用从零开始的虚拟地址作为用户地址空间,幸运的是内核的内存从更高的地址开始。然而,这个方案将用户进程的最大大小限制为小于内核的最低虚拟地址。内核启动后,在XV6中该地址是0xC000000,即PLIC寄存器的地址;请参见kernel/vm.c中的kvminit()kernel/memlayout.h和文中的图3-4。您需要修改xv6,以防止用户进程增长到超过PLIC的地址。

2.提示

  • 先用对copyin_new的调用替换copyin(),确保正常工作后再去修改copyinstr
  • 在内核更改进程的用户映射的每一处,都以相同的方式更改进程的内核页表。包括fork()exec(), 和sbrk().
  • 不要忘记在userinit的内核页表中包含第一个进程的用户页表
  • 用户地址的PTE在进程的内核页表中需要什么权限?(在内核模式下,无法访问设置了PTE_U的页面)
  • 别忘了上面提到的PLIC限制

        Linux使用的技术与您已经实现的技术类似。直到几年前,许多内核在用户和内核空间中都为当前进程使用相同的自身进程页表,并为用户和内核地址进行映射以避免在用户和内核空间之间切换时必须切换页表。然而,这种设置允许边信道攻击,如Meltdown和Spectre。

3.实现

        本实验是实现将用户空间的映射添加到每个进程的内核页表,将进程的页表复制一份到进程的内核页表就好。

        首先添加复制函数。需要注意的是,在内核模式下,无法访问设置了PTE_U的页面,所以我们要将其移除。

(1)复制页表内容

void u2kvmcopy(pagetable_t pagetable, pagetable_t kernelpt, uint64 oldsz, uint64 newsz) {pte_t *pte_from, *pte_to;// 将 oldsz 向上取整到最近的页边界oldsz = PGROUNDUP(oldsz);// 遍历 [oldsz, newsz) 范围内的每一页for (uint64 i = oldsz; i < newsz; i += PGSIZE) {// 从用户页表中获取地址 i 对应的 PTEif ((pte_from = walk(pagetable, i, 0)) == 0)panic("u2kvmcopy: src pte does not exist");// 确保用户页表中的页表项是有效的(即包含 PTE_V 标志)if (!(*pte_from & PTE_V))panic("u2kvmcopy: src pte not valid");// 获取或创建内核页表中地址 i 对应的 PTEif ((pte_to = walk(kernelpt, i, 1)) == 0)panic("u2kvmcopy: pte walk failed");// 从用户页表项中获取物理地址uint64 pa = PTE2PA(*pte_from);// 从用户页表项中获取标志,并移除用户权限标志 PTE_Uuint flags = (PTE_FLAGS(*pte_from)) & (~PTE_U);// 设置内核页表项*pte_to = PA2PTE(pa) | flags;}
}

第二步,fork(),sbrk(),exec()

然后在内核更改进程的用户映射的每一处 (fork()exec(), 和sbrk()),都复制一份到进程的内核页表。

fork()

exec()

sbrk(), 在kernel/sysproc.c里面找到sys_sbrk(void),可以知道只有growproc是负责将用户内存增加或缩小 n 个字节。以防止用户进程增长到超过PLIC的地址,我们需要给它加个限制。

然后替换掉原有的copyin()copyinstr()

// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{return copyin_new(pagetable, dst, srcva, len);
}// Copy a null-terminated string from user to kernel.
// Copy bytes to dst from virtual address srcva in a given page table,
// until a '\0', or max.
// Return 0 on success, -1 on error.
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{return copyinstr_new(pagetable, dst, srcva, max);
}

并且添加到 kernel/defs.h 中

// vmcopyin.c
int             copyin_new(pagetable_t, char *, uint64, uint64);
int             copyinstr_new(pagetable_t, char *, uint64, uint64);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python:Python基础知识(注释、命名、数据类型、运算符)

.注释 Python有两种注释方法&#xff1a;单行注释和多行注释。单行注释以#开头&#xff0c;多行注释以三个单引号 或三个双引号 """ 开头和结尾。 2.命名规则 命名规则: 大小写字母、数字、下划线和汉字等字符及组合&#xff1b; 注意事项: 大小写敏感、首…

Linux环境下Oracle 11g的离线安装与配置历程

在成功体验了 Windows 版本的Oracle 11g 后&#xff0c;这几天心血来潮&#xff0c;决定再挑战一下Linux 环境下的安装&#xff0c;特别是在考虑到部门内部虚拟机无法联网的情况下&#xff0c;我选择了在CentOS 7上进行离线安装。这次安装之旅&#xff0c;主要参考了下面大佬的…

【计算机科学】CCF-C特刊征稿合集,见刊快,期刊质量高,速投!

期刊推荐 期刊名称&#xff1a;ACTA INFORMATICA 主题包括以下项目的理论方面。 算法及其分析 自动机和形式语言 可计算性和复杂性 数据处理 离散数学 逻辑学&#xff08;计算机科学&#xff09; 人工智能的数学基础 编程语言理论 安全 系统理论 验证 中科院四区 …

STM32智能物流机器人系统教程

目录 引言环境准备智能物流机器人系统基础代码实现&#xff1a;实现智能物流机器人系统 4.1 数据采集模块 4.2 数据处理与导航算法 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;物流机器人管理与优化问题解决方案与优化收尾与总结 1. 引言 智能物流…

mindspore打卡23天之微调本地MindNLP ChatGLM-6B StreamChat

MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 1 环境配置 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mindspore的版本号 !pip uninstall mindspore -y !p…

基于JavaSpringBoot+Vue+uniapp微信小程序校园宿舍管理系统设计与实现(7000字论文参考+源码+LW+部署讲解)

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

Linux:NFS共享存储

目录 一、NFS基本概述 二、NFS共享文件实验 2.1、安装nfs和rpcbind软件 2.2、修改配置文件设置共享 2.3、创建共享目录 ​编辑 2.4、开启服务 2.5、客户端验证共享目录可访问 三、tcpdump命令 3.1、概述 3.2、简单表达 3.3、过滤规则 ​编辑 3.4、tcpdump常见参数…

强化学习实战2:动手写迷宫环境

迷宫环境介绍与创建 迷宫环境图示如下&#xff1a; 如图所示&#xff0c;其为一个 三乘三 的网格世界&#xff0c;我们要让 agent 从 S0 采取策略出发&#xff0c;然后走到 S8&#xff0c;图中红线部分表示障碍不能逾越&#xff0c;其中 S1 和 S4 之间有一个障碍&#xff0c;S…

C语言有哪些特点?

C语言是一种结构化语言&#xff0c;它有着清晰的层次&#xff0c;可按照模块的方式对程序进行编写&#xff0c;十分有利于程序的调试&#xff0c;且c语言的处理和表现能力都非常的强大&#xff0c;依靠非常全面的运算符和多样的数据类型&#xff0c;可以轻易完成各种数据结构的…

Kotlin MultiPlatform(KMP)

Kotlin MultiPlatform 1.KMP 是什么 Kotlin Multiplatform 是一个工具&#xff0c;它让我们用同一种编程语言&#xff08;Kotlin&#xff09;写代码&#xff0c;这些代码可以同时在不同的设备上运行&#xff0c;比如手机、电脑和网页。这样做可以节省时间&#xff0c;因为你不…

1、项目目录设计

文章目录 前言一、项目目录设计 前言 本项目我们将会完成一个Go项目开发框架&#xff0c;该项目不会包含具体的CRUD业务代码&#xff0c;而是从头搭建一个工作中实用的开发框架。让开发者能够熟悉整个项目的搭建流程&#xff0c;能够独立完成项目从0到1的搭建&#xff0c;而且…

【RHCE】实验(HTTP,DNS,SELinux,firewalld的运用)

一、题目 二、主服务器配置 1.下载HTTP服务&#xff0c;DNS服务 [rootlocalhost ~]# yum install -y httpd bind 2.开启防火墙&#xff0c;放行服务 # 开启防火墙 [rootlocalhost ~]# systemctl start firewalld # 放行服务 [rootlocalhost ~]# firewall-cmd --add-service…

上班摸鱼吗?一文详解代码生成神器-Velocity

引言 “我不是在教你学坏,而是教你如何提高生产效率。” ----------- 牛顿 人类社会能够一直进步发展出现在的文明世界,最大的一个原因就是这个世界上懒人居多,懒人为了偷懒就需要提高生产效率,效率提高节省下来的时间才能创造出艺术、娱乐以及更高效率的科学技术。程序员…

MySQL DDL

数据库 1 创建数据库 CREATE DATABASE 数据库名 CREATE DATABASE IF NOT EXISTS 数据库名;&#xff08;判断是否存在) CREATE DATABASE 数据库名 CHARACTER SET 字符 2 查看数据库 SHOW DATABASES; 查看某个数据库的信息 SHOW CAEATE DATABASE 数据库名 3 修改数据库 …

信息学奥赛初赛天天练-44-CSP-J2020基础题-排列组合、乘法原理、捆绑法、隔板法、排除法示例及应用

PDF文档公众号回复关键字:20240711 2020 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 10.有5 个小朋友并排站成一列&#xff0c;其中有两个小朋友是双胞胎&#xff0c;如果要求这…

dev小熊猫,clion设置模版教程

首先点击工具 然后进入设置 &#xff0c;找到代码模版 然后点击c模版&#xff0c;进入之后直接输入模版之后&#xff0c;&#xff08;还没有结束&#xff01;&#xff01;&#xff01;&#xff09;&#xff0c;先点击应用&#xff0c;然后是确定&#xff01;&#xff01;&#…

【js面试题】深入理解浏览器对象模型(BOM)

面试题&#xff1a;请你说说对bom的理解&#xff0c;常见的bom对象你了解哪些 引言&#xff1a; 浏览器对象模型&#xff08;BOM&#xff09;是JavaScript中用于与浏览器窗口及其内容进行交互的一组对象和方法。 BOM的核心是window对象&#xff0c;它代表了浏览器窗口本身&…

【SQL】DML、DDL、ROLLBACK 、COMMIT详解

DML DML&#xff08;Data Manipulation Language&#xff09;数据操作语言&#xff0c;是用于对数据库中的数据进行基本操作的一种编程语言。DML是数据库管理系统&#xff08;DBMS&#xff09;中的一个重要部分&#xff0c;它允许用户或应用程序对数据库中的数据进行增、删、改…

探索GitHub上的两个革命性开源项目

在数字世界中,总有一些项目能够以其创新性和实用性脱颖而出,吸引全球开发者的目光。今天,我们将深入探索GitHub上的两个令人惊叹的开源项目:Comic Translate和GPTPDF,它们不仅改变了我们处理信息的方式,还极大地丰富了我们的数字生活体验。 01 漫画爱好者的福音:Comi…

PostgreSQl 物化视图

物化视图&#xff08;Materialized View&#xff09;是 PostgreSQL 提供的一个扩展功能&#xff0c;它是介于视图和表之间的一种对象。 物化视图和视图的最大区别是它不仅存储定义中的查询语句&#xff0c;而且可以像表一样存储数据。物化视图和表的最大区别是它不支持 INSERT…