创建虚拟内存空间

ELF 文件解析与内存空间创建

为了能让用户程序运行起来,内核首先要给它分配用户内存空间,即创建一个虚拟内存空间供它使用。由于用户程序要通过中断访问内核的代码,因此它所在的虚拟内存空间必须也包含内核的各代码段和数据段。

ELF 文件与只含有代码和数据的纯二进制文件不同,需要我们手动去解析它的文件结构来获得各段的信息。所幸的是, rust 已经有 crate xmas-elf帮我们实现了这一点。

ELF 执行文件格式

ELF(Executable and Linking Format)文件格式是 Linux 系统下的一种常用目标文件(object file)格式,有三种主要类型,我们主要关注的是用于执行的可执行文件(Executable File)类型,它提供了程序的可执行代码/数据内容,加载的内存空间布局描述等。 这也是本实验的 OS 和应用的执行文件类型。可参考ELF 描述进一步了解相关信息。

对 ELF 文件解析与内存空间创建的处理,需要解析出 ELF 文件中的关键的段(如 code 段、data 段、BSS 段等),并把段的内容拷贝到段设定的地址中,设置好相关属性。这需要对虚拟内存相关的MemorySetMemoryArea 的相关实现进行扩展。具体修改如下:

解析 ELF 文件

  1. // src/process/structs.rs
  2. trait ElfExt {
  3. fn make_memory_set(&self) -> MemorySet;
  4. }
  5. // 给一个用户程序的ELF可执行文件创建虚拟内存空间
  6. impl ElfExt for ElfFile<'_> {
  7. fn make_memory_set(&self) -> MemorySet {
  8. // MemorySet::new()的实现中已经映射了内核各数据、代码段,以及物理内存段
  9. // 于是我们只需接下来映射用户程序各段即可
  10. let mut memory_set = MemorySet::new();
  11. for ph in self.program_iter() {
  12. // 遍历各段并依次尝试插入 memory_set
  13. if ph.get_type() != Ok(Type::Load) {
  14. continue;
  15. }
  16. let vaddr = ph.virtual_addr() as usize;
  17. let mem_size = ph.mem_size() as usize;
  18. let data = match ph.get_data(self).unwrap() {
  19. SegmentData::Undefined(data) => data,
  20. _ => unreachable!(),
  21. };
  22. // 这里在插入一个 MemoryArea 时还需要复制数据
  23. // 所以我们将 MemorySet 的接口略作修改,最后一个参数为数据源
  24. memory_set.push(
  25. vaddr, vaddr + mem_size,
  26. ph.flags().to_attr(), //将elf段的标志转化为我们熟悉的 MemoryAttr
  27. ByFrame::new(),
  28. Some((data.as_ptr() as usize, data.len())),
  29. );
  30. }
  31. memory_set
  32. }
  33. }
  34. ......

 建立对应的虚拟内存空间

我们对 MemorySetMemoryArea 的接口略作修改:

  1. // src/memory/memory_set/mod.rs
  2. impl MemorySet {
  3. ...
  4. pub fn push(&mut self, start: usize, end: usize, attr: MemoryAttr, handler: impl MemoryHandler, data: Option<(usize, usize)>) {
  5. ...
  6. let area = MemoryArea::new(start, end, Box::new(handler), attr);
  7. // 首先进行映射
  8. area.map(&mut self.page_table);
  9. if let Some((src, length)) = data {
  10. // 如果传入了数据源
  11. // 交给 area 进行复制
  12. area.page_copy(&mut self.page_table, src, length);
  13. }
  14. self.areas.push(area);
  15. }
  16. ......
  17. // src/memory/memory_set/area.rs
  18. impl MemoryArea {
  19. ...
  20. pub fn page_copy(&self, pt: &mut PageTableImpl, src: usize, length: usize) {
  21. let mut l = length;
  22. let mut s = src;
  23. for page in PageRange::new(self.start, self.end) {
  24. // 交给 MemoryHandler 逐页进行复制
  25. self.handler.page_copy(pt, page, s, l...);
  26. s += PAGE_SIZE;
  27. if l >= PAGE_SIZE { l -= PAGE_SIZE; }
  28. }
  29. ......
  30. // src/memory/memory_set/handler.rs
  31. pub trait MemoryHandler: Debug + 'static {
  32. ...
  33. fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize);
  34. }
  35. impl MemoryHandler for Linear {
  36. ...
  37. fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize) {
  38. let pa = pt.get_entry(va)...;
  39. unsafe {
  40. let dst = core::slice::from_raw_parts_mut(va...);
  41. if length > 0 {
  42. let src = core::slice::from_raw_parts(src...);
  43. for i in 0..length { dst[i] = src[i]; }
  44. }
  45. for i in length..PAGE_SIZE { dst[i] = 0; }
  46. }
  47. }
  48. }
  49. impl MemoryHandler for ByFrame {
  50. ...
  51. fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize) {
  52. //类似fn page_copy() in mpl MemoryHandler for Linear
  53. ......
  54. }
  55. // src/memory/paging.rs
  56. // 这里有两处要改成 pub ,其他不必做改动
  57. pub struct PageEntry(pub &'static mut PageTableEntry, Page);
  58. impl PageTableImpl {
  59. ...
  60. pub fn get_entry(&mut self, va: usize) -> Option<&mut PageEntry> {
  61. let page = Page::of_addr(VirtAddr::new(va));
  62. if let Ok(e) = self.page_table.ref_entry(page.clone()) {
  63. let e = unsafe { &mut *(e as *mut PageTableEntry) };
  64. self.entry = Some(PageEntry(e, page));
  65. Some(self.entry.as_mut().unwrap())
  66. }
  67. else {
  68. None
  69. }
  70. }
  71. ...
  72. }

由于 MemorySet::push 的接口发生的变化,我们要将 ElfExt::make_memory_set 之外的所有 push 调用最后均加上一个 None 参数。

现在我们就可以从 ElfFile 创建用户程序的虚拟内存空间了。