内核重映射实现之二:MemorySet
我们实现了页表,但是好像还不足以应对内核重映射的需求。我们要对多个段分别进行不同的映射,而页表只允许我们每次插入一对从虚拟页到物理页帧的映射。
总体抽象
为此,我们另设计几种数据结构来抽象这个过程:

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

我们则使用 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. }
9. impl MemoryAttr {
10. // 默认 用户态不可访问;可写;不可执行;
11. pub fn new() -> Self{
12. MemoryAttr {
13. user : false,
14. readonly : false,
15. execute : false,
16. }
17. }
18. // 根据要求修改所需权限
19. pub fn set_user(mut self) -> Self {
20. self.user = true; self
21. }
22. pub fn set_readonly(mut self) -> Self {
23. self.readonly = true; self
24. }
25. pub fn set_execute(mut self) -> Self {
26. self.execute = true; self
27. }
28. // 根据设置的权限要求修改页表项
29. pub fn apply(&self, entry : &mut PageEntry) {
30. entry.set_present(true); // 设置页表项存在
31. entry.set_user(self.user); // 设置用户态访问权限
32. entry.set_writable(!self.readonly); //设置写权限
33. entry.set_execute(self.execute); //设置可执行权限
34. }
35. }
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
3. // 声明中给出所在的虚拟地址区间: [start, end)
4. // 使用的 MemoryHandler: handler
5. // 页表项的权限: attr
6. pub struct MemoryArea {
7. start : usize,
8. end : usize,
9. handler : Box<dyn MemoryHandler>,
10. attr : MemoryAttr,
11. }
13. impl MemoryArea {
14. // 同样是插入、删除映射
15. // 遍历虚拟地址区间包含的所有虚拟页,依次利用 handler 完成映射插入/删除
16. pub fn map(&self, pt : &mut PageTableImpl) {
17. // 使用自己定义的迭代器进行遍历,实现在 src/memory/paging.rs 中
18. // 放在下面
19. for page in PageRange::new(self.start, self.end) {
20. self.handler.map(pt, page, &self.attr);
21. }
22. }
23. fn unmap(&self, pt : &mut PageTableImpl) {
24. for page in PageRange::new(self.start, self.end) {
25. self.handler.unmap(pt, page);
26. }
27. }
28. // 是否与另一虚拟地址区间相交
29. pub fn is_overlap_with(&self, start_addr : usize, end_addr : usize) -> bool {
30. let p1 = self.start / PAGE_SIZE;
31. let p2 = (self.end - 1) / PAGE_SIZE + 1;
32. let p3 = start_addr / PAGE_SIZE;
33. let p4 = (end_addr - 1) / PAGE_SIZE + 1;
34. !((p1 >= p4) || (p2 <= p3))
35. }
36. // 初始化
37. pub fn new(start_addr : usize, end_addr : usize, handler : Box<dyn MemoryHandler>, attr : MemoryAttr) -> Self {
38. MemoryArea{
39. start : start_addr,
40. end : end_addr,
41. handler : handler,
42. attr : attr,
43. }
44. }
45. }
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. }
9. impl MemorySet {
10. pub fn push(&mut self, start: usize, end: usize, attr: MemoryAttr, handler: impl MemoryHandler) {
11. // 加入一个新的给定了 handler 以及 attr 的 MemoryArea
13. // 合法性测试
14. assert!(start <= end, "invalid memory area!");
15. // 整段虚拟地址空间均未被占据
16. assert!(self.test_free_area(start, end), "memory area overlap!");
17. // 构造 MemoryArea
18. let area = MemoryArea::new(start, end, Box::new(handler), attr);
19. // 更新本 MemorySet 的映射
20. area.map(&mut self.page_table);
21. // 更新本 MemorySet 的 MemoryArea 集合
22. self.areas.push(area);
23. }
24. fn test_free_area(&self, start: usize, end: usize) -> bool {
25. // 迭代器的基本应用
26. self.areas
27. .iter()
28. .find(|area| area.is_overlap_with(start, end))
29. .is_none()
30. }
31. // 将 CPU 所在的虚拟地址空间切换为本 MemorySet
32. pub unsafe fn activate(&self) {
33. // 这和切换到存储其全部映射的页表是一码事
34. self.page_table.activate();
35. }
36. }
事实上,在内核中运行的所有程序都离不开内核的支持,所以必须要能够访问内核的代码和数据;同时,为了保证任何时候我们都可以修改页表,我们需要物理内存的映射一直存在。因此,在一个 MemorySet 初始化时,我们就要将上述这些段加入进去。
1. // src/memory/memory_set/mod.rs
3. impl MemorySet {
4. ...
5. pub fn new() -> Self {
6. let mut memory_set = MemorySet {
7. areas: Vec::new(),
8. page_table: PageTableImpl::new_bare(),
9. };
10. // 插入内核各段以及物理内存段
11. memory_set.map_kernel_and_physical_memory();
12. memory_set
13. }
14. pub fn map_kernel_and_physical_memory(&mut self) {
15. extern "C" {
16. fn stext();
17. fn etext();
18. fn srodata();
19. fn erodata();
20. fn sdata();
21. fn edata();
22. fn sbss();
23. fn ebss();
24. fn end();
25. }
26. let offset = PHYSICAL_MEMORY_OFFSET;
27. // 各段全部采用偏移量固定的线性映射
28. // .text R|X
29. self.push(
30. stext as usize,
31. etext as usize,
32. MemoryAttr::new().set_readonly().set_execute(),
33. Linear::new(offset),
34. );
35. // .rodata R
36. self.push(
37. srodata as usize,
38. erodata as usize,
39. MemoryAttr::new().set_readonly(),
40. Linear::new(offset),
41. );
42. // .data R|W
43. self.push(
44. sdata as usize,
45. edata as usize,
46. MemoryAttr::new(),
47. Linear::new(offset)
48. );
49. // .bss R|W
50. self.push(
51. sbss as usize,
52. ebss as usize,
53. MemoryAttr::new(),
54. Linear::new(offset)
55. );
56. // 物理内存 R|W
57. self.push(
58. (end as usize / PAGE_SIZE + 1) * PAGE_SIZE,
59. access_pa_via_va(PHYSICAL_MEMORY_END),
60. MemoryAttr::new(),
61. Linear::new(offset),
62. );
63. }
64. }
这样,有了上面的抽象和对应实现,我们就可以根据 OS kernel 这个程序中不同段的属性建立不同属性的页表项,更加精确地体系了 OS kernel 各个部分的被访问特征。具体如何建立,请看下一节。
