内核重映射实现之二:MemorySet

我们实现了页表,但是好像还不足以应对内核重映射的需求。我们要对多个段分别进行不同的映射,而页表只允许我们每次插入一对从虚拟页到物理页帧的映射。

总体抽象

为此,我们另设计几种数据结构来抽象这个过程:

内核重映射实现之二:MemorySet - 图1

在虚拟内存中,每个 MemoryArea 描述一个段,每个段单独映射到物理内存;MemorySet 中则存储所有的 MemoryArea 段,相比巨大的虚拟内存空间,由于它含有的各个段都已经映射到物理内存,它可表示一个程序独自拥有的实际可用的虚拟内存空间。PageTable 相当于一个底层接口,仅是管理映射,事实上它管理了 MemorySet 中所有 MemoryArea 的所有映射。

MemoryArea

我们刻意将不同的段分为不同的 MemoryArea ,说明它们映射到物理内存的方式可以是不同的:

内核重映射实现之二:MemorySet - 图2

我们则使用 MemoryHandler 来描述映射行为的不同。不同的类型的 MemoryArea,会使用不同的 MemoryHandler ,而他们会用不同的方式调用 PageTable 提供的底层接口进行映射,因此导致了最终映射行为的不同。

下面我们看一下这些类是如何实现的。

MemoryAttr

首先是用来修改 PageEntry (我们的页表映射默认将权限设为 R|W|X ,需要修改) 的类 MemoryAttr

  1. // src/memory/memory_set/attr.rs
  2. ......
  3. pub struct MemoryAttr {
  4. user : bool, // 用户态是否可访问
  5. readonly : bool, // 是否只读
  6. execute : bool, // 是否可执行
  7. }
  8. impl MemoryAttr {
  9. // 默认 用户态不可访问;可写;不可执行;
  10. pub fn new() -> Self{
  11. MemoryAttr {
  12. user : false,
  13. readonly : false,
  14. execute : false,
  15. }
  16. }
  17. // 根据要求修改所需权限
  18. pub fn set_user(mut self) -> Self {
  19. self.user = true; self
  20. }
  21. pub fn set_readonly(mut self) -> Self {
  22. self.readonly = true; self
  23. }
  24. pub fn set_execute(mut self) -> Self {
  25. self.execute = true; self
  26. }
  27. // 根据设置的权限要求修改页表项
  28. pub fn apply(&self, entry : &mut PageEntry) {
  29. entry.set_present(true); // 设置页表项存在
  30. entry.set_user(self.user); // 设置用户态访问权限
  31. entry.set_writable(!self.readonly); //设置写权限
  32. entry.set_execute(self.execute); //设置可执行权限
  33. }
  34. }

MemoryHandler

然后是会以不同方式调用 PageTable 接口的 MemoryHandler

  1. // src/memory/memory_set/handler.rs
  2. ......
  3. // 定义 MemoryHandler trait
  4. pub trait MemoryHandler: Debug + 'static {
  5. fn box_clone(&self) -> Box<dyn MemoryHandler>;
  6. // 需要实现 map, unmap 两函数,不同的接口实现者会有不同的行为
  7. // 注意 map 并没有 pa 作为参数,因此接口实现者要给出该虚拟页要映射到哪个物理页
  8. fn map(&self, pt: &mut PageTableImpl, va: usize, attr: &MemoryAttr);
  9. fn unmap(&self, pt: &mut PageTableImpl, va: usize);
  10. }
  11. ......
  12. // 下面给出两种实现 Linear, ByFrame
  13. // 线性映射 Linear: 也就是我们一直在用的带一个偏移量的形式
  14. // 有了偏移量,我们就知道虚拟页要映射到哪个物理页了
  15. pub struct Linear { offset: usize }
  16. impl Linear {
  17. pub fn new(off: usize) -> Self { Linear { offset: off, } }
  18. }
  19. impl MemoryHandler for Linear {
  20. fn box_clone(&self) -> Box<dyn MemoryHandler> { Box::new(self.clone()) }
  21. fn map(&self, pt: &mut PageTableImpl, va: usize, attr: &MemoryAttr) {
  22. // 映射到 pa = va - self.offset
  23. // 同时还使用 attr.apply 修改了原先默认为 R|W|X 的权限
  24. attr.apply(pt.map(va, va - self.offset));
  25. }
  26. fn unmap(&self, pt: &mut PageTableImpl, va: usize) { pt.unmap(va); }
  27. }
  28. // ByFrame: 不知道映射到哪个物理页帧
  29. // 那我们就分配一个新的物理页帧,可以保证不会产生冲突
  30. pub struct ByFrame;
  31. impl ByFrame {
  32. pub fn new() -> Self { ByFrame {} }
  33. }
  34. impl MemoryHandler for ByFrame {
  35. fn box_clone(&self) -> Box<dyn MemoryHandler> {
  36. Box::new(self.clone())
  37. }
  38. fn map(&self, pt: &mut PageTableImpl, va: usize, attr: &MemoryAttr) {
  39. // 分配一个物理页帧作为映射目标
  40. let frame = alloc_frame().expect("alloc_frame failed!");
  41. let pa = frame.start_address().as_usize();
  42. attr.apply(pt.map(va, pa));
  43. }
  44. fn unmap(&self, pt: &mut PageTableImpl, va: usize) {
  45. pt.unmap(va);
  46. }
  47. }

接着,是描述一个段的 MemoryArea

  1. // src/memory/memory_set/area.rs
  2. // 声明中给出所在的虚拟地址区间: [start, end)
  3. // 使用的 MemoryHandler: handler
  4. // 页表项的权限: attr
  5. pub struct MemoryArea {
  6. start : usize,
  7. end : usize,
  8. handler : Box<dyn MemoryHandler>,
  9. attr : MemoryAttr,
  10. }
  11. impl MemoryArea {
  12. // 同样是插入、删除映射
  13. // 遍历虚拟地址区间包含的所有虚拟页,依次利用 handler 完成映射插入/删除
  14. pub fn map(&self, pt : &mut PageTableImpl) {
  15. // 使用自己定义的迭代器进行遍历,实现在 src/memory/paging.rs 中
  16. // 放在下面
  17. for page in PageRange::new(self.start, self.end) {
  18. self.handler.map(pt, page, &self.attr);
  19. }
  20. }
  21. fn unmap(&self, pt : &mut PageTableImpl) {
  22. for page in PageRange::new(self.start, self.end) {
  23. self.handler.unmap(pt, page);
  24. }
  25. }
  26. // 是否与另一虚拟地址区间相交
  27. pub fn is_overlap_with(&self, start_addr : usize, end_addr : usize) -> bool {
  28. let p1 = self.start / PAGE_SIZE;
  29. let p2 = (self.end - 1) / PAGE_SIZE + 1;
  30. let p3 = start_addr / PAGE_SIZE;
  31. let p4 = (end_addr - 1) / PAGE_SIZE + 1;
  32. !((p1 >= p4) || (p2 <= p3))
  33. }
  34. // 初始化
  35. pub fn new(start_addr : usize, end_addr : usize, handler : Box<dyn MemoryHandler>, attr : MemoryAttr) -> Self {
  36. MemoryArea{
  37. start : start_addr,
  38. end : end_addr,
  39. handler : handler,
  40. attr : attr,
  41. }
  42. }
  43. }

MemorySet

最后,则是最高层的 MemorySet ,它描述一个实际可用的虚拟地址空间以供程序使用。

  1. // src/memory/memory_set/mod.rs
  2. pub struct MemorySet {
  3. // 管理有哪些 MemoryArea
  4. areas: Vec<MemoryArea>,
  5. // 使用页表来管理其所有的映射
  6. page_table: PageTableImpl,
  7. }
  8. impl MemorySet {
  9. pub fn push(&mut self, start: usize, end: usize, attr: MemoryAttr, handler: impl MemoryHandler) {
  10. // 加入一个新的给定了 handler 以及 attr 的 MemoryArea
  11. // 合法性测试
  12. assert!(start <= end, "invalid memory area!");
  13. // 整段虚拟地址空间均未被占据
  14. assert!(self.test_free_area(start, end), "memory area overlap!");
  15. // 构造 MemoryArea
  16. let area = MemoryArea::new(start, end, Box::new(handler), attr);
  17. // 更新本 MemorySet 的映射
  18. area.map(&mut self.page_table);
  19. // 更新本 MemorySet 的 MemoryArea 集合
  20. self.areas.push(area);
  21. }
  22. fn test_free_area(&self, start: usize, end: usize) -> bool {
  23. // 迭代器的基本应用
  24. self.areas
  25. .iter()
  26. .find(|area| area.is_overlap_with(start, end))
  27. .is_none()
  28. }
  29. // 将 CPU 所在的虚拟地址空间切换为本 MemorySet
  30. pub unsafe fn activate(&self) {
  31. // 这和切换到存储其全部映射的页表是一码事
  32. self.page_table.activate();
  33. }
  34. }

事实上,在内核中运行的所有程序都离不开内核的支持,所以必须要能够访问内核的代码和数据;同时,为了保证任何时候我们都可以修改页表,我们需要物理内存的映射一直存在。因此,在一个 MemorySet 初始化时,我们就要将上述这些段加入进去。

  1. // src/memory/memory_set/mod.rs
  2. impl MemorySet {
  3. ...
  4. pub fn new() -> Self {
  5. let mut memory_set = MemorySet {
  6. areas: Vec::new(),
  7. page_table: PageTableImpl::new_bare(),
  8. };
  9. // 插入内核各段以及物理内存段
  10. memory_set.map_kernel_and_physical_memory();
  11. memory_set
  12. }
  13. pub fn map_kernel_and_physical_memory(&mut self) {
  14. extern "C" {
  15. fn stext();
  16. fn etext();
  17. fn srodata();
  18. fn erodata();
  19. fn sdata();
  20. fn edata();
  21. fn sbss();
  22. fn ebss();
  23. fn end();
  24. }
  25. let offset = PHYSICAL_MEMORY_OFFSET;
  26. // 各段全部采用偏移量固定的线性映射
  27. // .text R|X
  28. self.push(
  29. stext as usize,
  30. etext as usize,
  31. MemoryAttr::new().set_readonly().set_execute(),
  32. Linear::new(offset),
  33. );
  34. // .rodata R
  35. self.push(
  36. srodata as usize,
  37. erodata as usize,
  38. MemoryAttr::new().set_readonly(),
  39. Linear::new(offset),
  40. );
  41. // .data R|W
  42. self.push(
  43. sdata as usize,
  44. edata as usize,
  45. MemoryAttr::new(),
  46. Linear::new(offset)
  47. );
  48. // .bss R|W
  49. self.push(
  50. sbss as usize,
  51. ebss as usize,
  52. MemoryAttr::new(),
  53. Linear::new(offset)
  54. );
  55. // 物理内存 R|W
  56. self.push(
  57. (end as usize / PAGE_SIZE + 1) * PAGE_SIZE,
  58. access_pa_via_va(PHYSICAL_MEMORY_END),
  59. MemoryAttr::new(),
  60. Linear::new(offset),
  61. );
  62. }
  63. }

这样,有了上面的抽象和对应实现,我们就可以根据 OS kernel 这个程序中不同段的属性建立不同属性的页表项,更加精确地体系了 OS kernel 各个部分的被访问特征。具体如何建立,请看下一节。