rcore lab4 记录

“本章中内核将实现虚拟内存机制,这注定是一趟艰难的旅程。”

在本章中,出现了虚拟地址和物理地址的转换,整个过程中的关键部分是几个结构体。

MMU

不论是内核还是用户态的地址,都需要经过 MMU 的转换,只不过内核是直接映射了,虚拟地址=物理地址,但是用户态的虚拟地址是需要找到对应的实际物理地址的。

对于每一个进程,用到两个比较关键的结构体来进行虚拟地址和物理地址的转换,一个是 PageTable 一个是 MapArea,从功能上来区分的话,PageTable 是提供单个页面到单个页面的映射,MapArea 记录了相应虚拟地址的映射类型(直接映射还是 framed),而且是记录的粒度比起 PageTable 单页的粒度更大,是记录一整个段的信息。

画了一个图来说明范围,虽然通过 PageTable 和 MapArea 都能完成 vpn 转换 ppn,但是 MapArea 的粒度更大(红色区域),PageTable 记录的粒度更小(黄色):

image-20250406113113007

在研究这两个结构体的时候发现了一个问题,PageTable 和 MapArea 记录的信息有一些重叠:

  • 都存放权限位
  • 都存放了虚拟地址到物理地址的映射

我不理解的是,如果 MapArea 期待记录的是一个数据段的映射方式以及其权限位,那么在 MapArea 中为什么需要存放 vpn 对应的 ppn,这不是多此一举么,明明 PageTable 里面也有。

直到看到了 FrameTracker 这个结构——FrameTracker 用于追踪每一个物理页,如果这个物理页不再使用,被回收了,那就会回到 FRAME_ALLOCATOR,这是通过 FrameTracker 的 Drop Trait 实现的。代码如下:

rust

trait FrameAllocator {
    fn new() -> Self;
    fn alloc(&mut self) -> Option<PhysPageNum>;
    fn dealloc(&mut self, ppn: PhysPageNum);
}

所以说,一个物理页在它的声明周期的开始和结束都有需要做的事情(比如开始的时候要清零,声明周期结束了要回收),那么这个生命周期是如何记录呢?把一个进程使用到的物理页分成两类,一种是 PageTable Frame 专门用于记录页表的信息,一种是 Data Frame,用于存放进程用到的数据。PageTable Frame 的声明周期绑定在 PageTable 的结构中,而 Data Frame 的生命周期绑定在 MapArea 结构中。

如果一段 Area 被释放,那么其对应的物理页的生命周期也结束了。如果 PageTable 被释放,那么 Page Table frame 的生命周期结束。

image-20250406113128293

感觉和上一章节完全不是一个难度…

参考了 rCore-Tutorial-Book-v3/chapter0/5setup-devel-env · Issue #11 · rcore-os/rCore-Tutorial-Book-v3 简单来说就是在 docker 里面启动 gdbserver,然后把 port 转发出来,在 vscode 中使用 lldb 去连接端口。

为了让 dokcer 开启端口映射,需要这么启动容器:

text

docker run -it -v .:/mnt -p 11234:1234 --name rcorelab rcore1 bash

因为 docker 内部和外部的文件路径不同,需要指定 source file map 才能找到文件去下断点。

codelldb/MANUAL.md at master · vadimcn/codelldb · GitHub

对于 syscall 的硬编码记录不是很好,所以参考了 练习 - rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档 的实现方式

涉及读写的操作,打包成一个函数吧,仿照 translated_byte_buffer 函数写了内核向用户态写数据和内核从用户态读取数据,这里需要检查一个用户地址是否合法,能不能读写。要怎么判断一个地址能不能读写?可以在 PageTable 里面增加 pte 可读可写的判断,每次写一个页都检查一下。

在 memoryset 中增加两个处理函数,需要新增或者删除一个 map area,并且把页面的改变在 pagetable 里面修改。这个比上面的 strace 修改好改多了…