应用地址空间

Overview

这里对应用地址空间的布局进行简单介绍:

  • .text:这个区域包含应用程序的代码。它是只读的,以防止程序意外更改其代码,且它是可执行的。
  • .rodata:即 Read Only Data。顾名思义,这部分包含只读的数据,如代码中使用的常数。这部分显然是不可执行的。
  • .data:这个段通常包含初始化的全局和静态变量。可读且可写,但不可执行。
  • .bss:这个区域包含未初始化的全局和静态变量。这些是程序运行时将被填充的内存区域,但在程序开始时并不需要保存有意义的数据。因此,这个段通常被初始化为零。可读且可写,但不可执行。
  • stack:用来保存局部变量和函数调用信息函数栈。它向更低的内存地址增长。可读且可写,但不可执行。
  • trap context:这部分用于在处理中断或者陷阱(如系统调用)时保存 CPU 的状态。当陷阱发生时,内核保存当前上下文(寄存器,指令指针等),做完其工作后恢复 trap context。
  • trampoline:这是一小段代码,用于在执行系统调用或处理中断时页表的切换以及上下文的切换。
  • guard page:guard page 用于在溢出或越界访问时触发异常。

Layout

以下展示了 rCore 中的应用地址空间布局:

+------------------------+ <- end of address space (2^64 Byte)
|       trampoline       | 
+------------------------+
|      trap context      | 
+------------------------+
|          ....          | <- empty
+------------------------+ 
|         stack          |
+------------------------+
|       guard page       |
+------------------------+
|         .bss           |
+------------------------+
|         .data          |
+------------------------+
|        .rodata         |
+------------------------+
|         .text          |
+------------------------+
|       unallocated      |
+------------------------+ <- start of address space

rCore 中的 user stack 是定长的,并不是很优秀的设计。可以删去 guard page,在进程初始时并不分配栈空间,等到程序有需要,触发 page fault 之后再分配,以节省内存。

Load ELF

我们现在对于应用程序的加载是通过 ELF 文件来进行的。我们需要解析 ELF 文件,将其加载到内存中。我们可以借用 xmas_elf crate 来加载 ELF 文件。利用 xmas_elf 来读取不同的 map areas,并相应的配置 page table,大致如下(节选自 rCore):

let elf = xmas_elf::ElfFile::new(elf_data).unwrap();
let elf_header = elf.header;
let magic = elf_header.pt1.magic;
assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!");
let ph_count = elf_header.pt2.ph_count();
for i in 0..ph_count {
    let ph = elf.program_header(i).unwrap();
    if ph.get_type().unwrap() == xmas_elf::program::Type::Load {
        let start_va: VirtAddr = (ph.virtual_addr() as usize).into();
        let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into();
        let mut map_perm = MapPermission::U;
        let ph_flags = ph.flags();
        if ph_flags.is_read() { map_perm |= MapPermission::R; }
        if ph_flags.is_write() { map_perm |= MapPermission::W; }
        if ph_flags.is_execute() { map_perm |= MapPermission::X; }
        todo!("map the segment to memory in page table");
    }
}