type
status
slug
date
summary
tags
category
password
icon
重写 sys_get_time 和 sys_task_info
gdb调试的,访问vector的特定元素:
其它的mmap和unmap,map的方式就是根据传入的start和len构造一个新的maparea出来,并且map到页表项里面去(框架里面已经给出了需要的api),unmap这里的实现还是仅仅以maparea作为单位来释放。找到对应的几个maparea进行释放。
问答作业
1. 请列举 SV39 页表页表项的组成,描述其中的标志位有何作用?
上图为 SV39 分页模式下的页表项,其中[53:10]这 44 位是物理页号,最低8位 [7:0] 则是标志位,它们的含义如下(请注意,为方便说明,下文我们用 页表项的对应虚拟页面 来表示索引到一个页表项的虚拟页号对应的虚拟页面)
- V(Valid):仅当位 V 为 1 时,页表项才是合法的;
- R(Read)/W(Write)/X(eXecute):分别控制索引到这个页表项的对应虚拟页面是否允许读/写/执行;
- U(User):控制索引到这个页表项的对应虚拟页面是否在 CPU 处于 U 特权级的情况下是否被允许访问;
- G:暂且不理会;
- A(Accessed):处理器记录自从页表项上的这一位被清零之后,页表项的对应虚拟页面是否被访问过;
- D(Dirty):处理器记录自从页表项上的这一位被清零之后,页表项的对应虚拟页面是否被修改过。
除了
G
外的上述位可以被操作系统设置,只有 A
位和 D
位会被处理器动态地直接设置为 1
,表示对应的页被访问过或修过( 注:A
位和 D
位能否被处理器硬件直接修改,取决于处理器的具体实现)。2.缺页
缺页指的是进程访问页面时页面不在页表中或在页表中无效的现象,此时 MMU 将会返回一个中断, 告知 os 进程内存访问出了问题。os 选择填补页表并重新执行异常指令或者杀死进程。
2.1
1)请问哪些异常可能是缺页导致的?
Exception::LoadPageFault—程序试图读取一个不在物理内存中的内存页(因为缺页,所以没有建立页表的映射)
Exception::InstructionPageFault—程序试图执行一个不在物理内存中的指令
Exception::StorePageFault—程序试图写入一个只读的内存页
2)发生缺页时,描述相关重要寄存器的值,上次实验描述过的可以简略。
先给出寄存器以及它们的含义
CSR 名 | 该 CSR 与 Trap 相关的功能 |
sstatus | SPP 等字段给出 Trap 发生之前 CPU 处在哪个特权级(S/U)等信息 |
sepc | 当 Trap 是一个异常的时候,记录 Trap 发生之前执行的最后一条指令的地址 |
scause | 描述 Trap 的原因 |
stval | 给出 Trap 附加信息 |
stvec | 控制 Trap 处理代码的入口地址 |
所以发生缺页的时候:
stval
(Supervisor Trap Value):存储了引发异常的虚拟地址。
scause
:存储了异常的原因。对于缺页异常,scause
的值会是 Exception::StorePageFault、Exception::InstructionPageFault、Exception::LoadPageFault这三个其一
sepc
(Supervisor Exception Program Counter):存储了引发异常的指令的地址。处理器会在处理完异常后返回到这个地址继续执行。
从代码的简单处理可以验证这三个重要寄存器存储的内容:
而剩下的stvec、sstatus的含义是显然的。
3)
缺页有两个常见的原因,其一是 Lazy 策略,也就是直到内存页面被访问才实际进行页表操作。 比如,一个程序被执行时,进程的代码段理论上需要从磁盘加载到内存。但是 os 并不会马上这样做, 而是会保存 .text 段在磁盘的位置信息,在这些代码第一次被执行时才完成从磁盘的加载操作。
这样做有哪些好处?
有时候有些代码由于分支原因,这个程序执行完成都不会执行,这样做能够节省空间,并且减少不必要的搬运时间,并且除了分支原因,还有可能因为异常被杀死,或者用户主动停止程序,那么不一次性加载完整的代码能大大节省时间和空间,并且程序也能快速启动,减少等待代码段完全加载的时间(对于大型程序)。
4)
其实,我们的 mmap 也可以采取 Lazy 策略,比如:一个用户进程先后申请了 10G 的内存空间, 然后用了其中 1M 就直接退出了。按照现在的做法,我们显然亏大了,进行了很多没有意义的页表操作。
- 处理 10G 连续的内存页面,对应的 SV39 页表大致占用多少内存 (估算数量级即可)?
10G对应2.5M个页,需要2.5M个64位的页表项,1个页可以存放512个页表项,所以5K个页存放页表项,而由于三级页表(SV39),那么一个二级页表,可以指向三级(叶子)512个,也就是512个页表(存的是页表项对应到物理地址),5k就需要约10个二级页表,而一个一级页表,可以指向512个二级页表,所以大约需要5K+10+1个页表,一个页表4k,所以大约20M
• 请简单思考如何才能实现 Lazy 策略,缺页时又如何处理?描述合理即可,不需要考虑实现。
在maparea处做一个标记,用来表示是否已经分配,修改原来
insert_framed_area
的实现,不是选择直接push进页表注册,而是修改maparea标记,标记为未分配,当出现缺页中断的时候,先查maparea是否有一个对应的VPNRange包含,并且检测是否是处于未分配状态,如果是,那么就在这个时候进行真正的页表分配push操作。5)
缺页的另一个常见原因是 swap 策略,也就是内存页面可能被换到磁盘上了,导致对应页面失效。
- 此时页面失效如何表现在页表项(PTE)上?
页表项有个标志位V:
将这一位置零就能标记页表项失效。
判定页表项有效的函数是:
这样MMU进行页表转换的时候会触发page fault异常(我们这里具体有三种page fault)
3.双页表与单页表
为了防范侧信道攻击,我们的 os 使用了双页表。但是传统的设计一直是单页表的,也就是说, 用户线程和对应的内核线程共用同一张页表,只不过内核对应的地址只允许在内核态访问。 (备注:这里的单/双的说法仅为自创的通俗说法,并无这个名词概念,详情见 KPTI )
上图是 RV64 架构下
satp
的字段分布。当 MODE
设置为 0 的时候,所有访存都被视为物理地址;而设置为 8 时,SV39 分页机制被启用,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,MMU 会将其转换成 56 位的物理地址;如果转换失败,则会触发异常。MODE
控制 CPU 使用哪种页表实现;
ASID
表示地址空间标识符,这里还没有涉及到进程的概念,我们不需要管这个地方;
PPN
存的是根页表所在的物理页号。这样,给定一个虚拟页号,CPU 就可以从三级页表的根页表开始一步步的将其映射到一个物理页号。
1.
在单页表情况下,如何更换页表?
单页表的情况下,页表切换(切换地址空间)的时机不是在trap的时候,而是在发生任务切换的时候,也就是__switch里面存储当前页表token,并且恢复下一个任务的token(这是发送在__restore里面)因为每次的任务切换,保存的原任务都是处于trap未返回的状态(然后保存了这个信息,就切换到另一个任务执行了),复用__restore完成从trap返回。
2.
单页表情况下,如何控制用户态无法访问内核页面?(tips:看看上一题最后一问)
只需要设置标志位U:
置零即可
3.
tutorial里面讲了,
一个是:TLB利用率高,特别是遇到了大量syscall(未切换任务),这个时候地址空间不变,TLB不会清空
还有实现起来更容易。
4.
双页表实现下,何时需要更换页表?假设你写一个单页表操作系统,你会选择何时更换页表(回答合理即可)?
双页表切换发生在trap的时候,就会进入内核的地址空间,这里需要切换,但是我看到restore恢复的时候也发生了地址空间切换(换了页表),这里就是trap返回(包含任务切换时的复用情况),
不过宽泛来讲,那就是都发生在tarp里面。
我来写,如前面所说的,单页表的情况,切换页表就发生在任务切换的时候,而trap不需要切换,因为内核代码的映射已经在当前每个任务的地址空间了。
- 作者:liamY
- 链接:https://liamy.clovy.top/article/OS_Tutorial/lab4
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。