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.rhkb.cn/news/374659.html

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

相关文章

macOS系统下载navicat安装包

链接: https://pan.baidu.com/s/1SqTIXNL-B8ZMJxIBu1DfIw?pwdc1z8 提取码: c1z8 安装后效果

虚幻引擎 快速的色度抠图 Chroma Key 算法

快就完了 ColorTolerance_PxRange为容差&#xff0c;这里是0-255的输入&#xff0c;也就是px单位&#xff0c;直接用0-1可以更快 Key为目标颜色

Kafka第四篇——生产数据总体概括,源码解析分区策略,数据收集器,Sender发送线程,key值

目录 流程图以及总体概述 拦截器 分区器以及分区计算策略 为啥进行分区计算&#xff1f; producer生产者怎么知道有哪些分区&#xff1f; 分区计算 如何自定义实现分区器&#xff1f; 想说的在图里啦&#xff01;宝宝&#xff01;&#x1f4a1; ​编辑 如果key值忘记传递了呢&a…

python+selenium-UI自动框架之[优化]元素查找和BasePage页面

痛点&#xff1a;在页面查找元素的时候会遇到找不到或者其他无法处理某个字段的情况&#xff0c;又或者想要在输出的log或者report里面显示这个字段名称&#xff0c;这时候加上字段名称就很重要&#xff01; [3]pythonselenium - UI自动框架之封装查找元素https://mp.csdn.net…

电脑的D盘E盘F盘突然消失了 电脑只剩下C盘了其他盘怎么恢复

现如今随着时代的发展&#xff0c;无纸化办公成为主流&#xff0c;这主要归功于电脑&#xff0c;能够通过电脑完成的工作绝不使用纸质文件&#xff0c;这不仅提高了工作效率&#xff0c;也让一些繁杂的工作变的更加简单。不过电脑毕竟是电子产品&#xff0c;不可避免的会出现一…

通信协议_Modbus协议简介

概念介绍 Modbus协议&#xff1a;一种串行通信协议&#xff0c;是Modicon公司&#xff08;现在的施耐德电气Schneider Electric&#xff09;于1979年为使用可编程逻辑控制器&#xff08;PLC&#xff09;通信而发表。Modbus已经成为工业领域通信协议的业界标准&#xff08;De f…

Navicat导入sql文件

文章目录 Navicat导入SQL文件&#xff0c;使用默认导入&#xff0c;不做任何修改报错尝试一修改运行时的选择 尝试二修改my.ini的配置文件 Navicat导入SQL文件&#xff0c;使用默认导入&#xff0c;不做任何修改报错 尝试一 修改运行时的选择 取消勾选 ‘每个运行中运行多重查…

一键掌握天气动态 - 基于Vue和高德API的实时天气查询

前言 本文将学习如何使用Vue.js快速搭建天气预报界面,了解如何调用高德地图API获取所需的天气数据,并掌握如何将两者有机结合,实现一个功能丰富、体验出色的天气预报应用 无论您是前端新手还是有一定经验,相信这篇教程都能为您带来收获。让我们一起开始这段精彩的Vue.js 高德…

Mac的系统数据怎么删除 cleanmymac会乱删东西吗 cleanmymac有用吗

作为一款专业级的苹果电脑清理软件&#xff0c;CleanMyMac可以精准识别系统垃圾&#xff0c;有效防止Mac系统数据被误删。软件可以深入系统底层&#xff0c;清理无用的系统数据&#xff0c;优化苹果电脑设置&#xff0c;提升Mac系统性能。有关Mac的系统数据可以删吗&#xff0c…

拥抱 AGI:PieDataCS 引领云原生数据计算系统新范式

自2023年后&#xff0c;人工智能技术进入了一个更为成熟和广泛应用的阶段&#xff0c;人工通用智能&#xff08;AGI&#xff09;这一概念也成为了科技界和产业界热议的焦点。本文将结合 AGI 时代背景&#xff0c;从架构设计到落地实践&#xff0c;详细介绍拓数派云原生数据计算…

HTAP 数据库在国有大行反洗钱场景的应用

导读 在金融领域&#xff0c;随着数字化服务的深入和监管要求的提高&#xff0c;反洗钱工作变得尤为关键。洗钱活动不仅威胁金融安全&#xff0c;也对社会秩序构成挑战。本文深入探讨了国产 HTAP 分布式数据库 TiDB 在某国有大行反洗钱系统中的应用实践。 依托 TiDB 构建的新…

springboot大学校园二手书交易APP-计算机毕业设计源码25753

摘 要 在数字化与移动互联网迅猛发展的今天&#xff0c;人们对于图书的需求与消费方式也在悄然改变。为了满足广大读者对图书的热爱与追求&#xff0c;我们倾力打造了一款基于Android平台的图书交易APP。这款APP不仅汇聚了海量的图书资源&#xff0c;提供了便捷的交易平台&…

usbserver工程师手记(三)手工开通 OTP功能

1、设定密钥&#xff0c;用户自行选择一个密钥&#xff0c;以下以密钥为 EAZAYOKNGETBOPC5 为例说明 2、usb server 配置otp 密钥&#xff0c;目前还没有UI 界面开通&#xff0c;后续版本会支持从管理界面开通 curl -X POST -H Content-Type: application/json -H Accept: app…

【深度学习入门篇 ②】Pytorch完成线性回归!

&#x1f34a;嗨&#xff0c;大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; )&#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 易编橙&#xff1a;一个帮助编程小…

数据结构复习计划之复杂度分析(时间、空间)

第二节&#xff1a;算法 时间复杂度和空间复杂度 算法(Algorithm)&#xff1a;是对特定问题求解方法(步骤)的一种描述&#xff0c;是指令的有限序列&#xff0c;其中每一条指令表示一个或多个操作。 算法可以有三种表示形式&#xff1a; 伪代码 自然语言 流程图 算法的五…

FFmpeg 实现从麦克风获取流并通过RTMP推流

使用FFmpeg库&#xff08;版本号为&#xff1a;4.4.2-0ubuntu0.22.04.1&#xff09;实现从麦克风获取流并通过RTMP推流。 RTMP服务器使用的是SRS&#xff0c;我这边是跑在Ubuntu上的&#xff0c;最好是关闭掉系统防火墙&#xff0c;不然连接服务器好像会出问题&#xff0c;拉流…

SpringBoot开发实用篇(三)

一&#xff1a;任务 1&#xff1a;SpringBoot整合Quartz 导入SpringBoot整合quartz的坐标定义具体要执行的任务&#xff0c;继承QuartzJobBean定义工作明细和触发器&#xff0c;并绑定对应关系 2&#xff1a;SpringBoot整合task 开启定时任务功能设置定时执行的任务&#x…

怎么样的主食冻干算好冻干?品质卓越、安全可靠的主食冻干分享

当前主食冻干市场产品质量参差不齐。一些品牌过于追求营养数据的堆砌和利润的增长&#xff0c;却忽视了猫咪健康饮食的基本原则&#xff0c;导致市场上出现了以肉粉冒充鲜肉、修改产品日期等不诚信行为。更令人担忧的是&#xff0c;部分产品未经过严格的第三方质量检测便上市销…

记录文字视差背景学习

效果图 文字背景会随鼠标上下移动变成红色或透明 html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><titl…

【linux】服务器卸载cuda

【linux】服务器卸载cuda 文章目录 【linux】服务器卸载cuda1、查找已安装的 CUDA 包&#xff1a;2、卸载 CUDA&#xff1a;3、删除残留文件4、更新系统的包索引&#xff1a;5、检查是否卸载干净&#xff1a; 1、查找已安装的 CUDA 包&#xff1a; dpkg -l | grep cuda2、卸载…