创建虚拟内存空间

内核链接

我们只需将最终得到的可执行文件直接链接到内核中即可。

这里的实现有一些技巧,我们先写一个编译脚本 build.rs。注意是直接放在项目文件夹 os 中,而不是源码文件夹 src

// build.rs

use std::fs::File;
use std::io::{Result, Write};

fn main() {
    println!("cargo:rerun-if-env-changed=USER_IMG");
    if let Ok(user_img) = std::env::var("USER_IMG") {
        println!("cargo:rerun-if-changed={}", user_img);
    }
    gen_link_user_asm().unwrap();
}

/// Generate assembly file for linking user image
fn gen_link_user_asm() -> Result<()> {
    let mut f = File::create("src/link_user.S").unwrap();
    let user_img = std::env::var("USER_IMG").unwrap();

    writeln!(f, "# generated by build.rs - do not edit")?;
    writeln!(f, r#"
    .section .data
    .global _user_img_start
    .global _user_img_end
_user_img_start:
    .incbin "{}"
_user_img_end:
"#, user_img)?;
    Ok(())
}

然后在 init.rs 中加入:

// init.rs

global_asm!(include_str!("link_user.S"));

这段编译脚本会在每次编译的最开始运行。它的作用是生成一段汇编代码,将用户程序镜像文件原封不动地链接到内核的 .data\text{.data} 段中。这段汇编被生成到 src/link_user.S 文件中,然后我们在 init.rs 里把它导入进来。此后可以在其它地方通过 _user_img_start_user_img_end 这两个符号得知它所在的虚拟地址。

我们用一个环境变量 USER_IMG 记录用户程序镜像的路径,编译脚本在执行时,会将这个字符串填入生成的汇编中。所以我们只需在编译之前利用 export 修改环境变量 USER_IMG 为我们最终得到的可执行文件的路径即可。

最后让我们关注一开始的两条奇怪语句:

println!("cargo:rerun-if-env-changed=USER_IMG");
println!("cargo:rerun-if-changed={}", user_img);

这是编译脚本发送给构建工具 cargo 的特殊指令,含义是:当检测到环境变量 USER_IMG 或者它所指向的文件发生变化时,就强制重新编译。并且每次编译时,都会生成一个新的 link_user.S 文件。

这波操作要解决的问题是:由于编译器具有自动增量构建的特性,会导致当用户镜像发生变化时,编译器无法自动感知到,最后链接的还是以前的版本,使得我们不得不手动 cargo clean 清理干净中间产物后重新编译。

现在,我们每次更新完用户程序镜像后,都可以放心地直接 make run 了!

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

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

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

// Cargo.toml

[dependencies]
xmas-elf = "0.6"

// src/process/structs.rs

use xmas_elf::{
    header,
    program::{ Flags, SegmentData, Type },
    ElfFile,
};
use crate::memory::memory_set::{
    MemorySet,
    handler::ByFrame,
    attr::MemoryAttr,
};
use core::str;

trait ElfExt {
    fn make_memory_set(&self) -> MemorySet;
}

// 给一个用户 elf 可执行程序创建虚拟内存空间
impl ElfExt for ElfFile<'_> {
    fn make_memory_set(&self) -> MemorySet {
        // MemorySet::new() 已经映射了内核各数据、代码段,以及物理内存段
        // 于是我们只需接下来映射用户程序各段即可
        let mut memory_set = MemorySet::new();
        for ph in self.program_iter() {
            // 遍历各段并依次尝试插入 memory_set
            if ph.get_type() != Ok(Type::Load) {
                continue;
            }
            let vaddr = ph.virtual_addr() as usize;
            let mem_size = ph.mem_size() as usize;
            let data = match ph.get_data(self).unwrap() {
                SegmentData::Undefined(data) => data,
                _ => unreachable!(),
            };

            // 这里在插入一个 MemoryArea 时还需要复制数据
            // 所以我们将 MemorySet 的接口略作修改,最后一个参数为数据源
            memory_set.push(
                vaddr,
                vaddr + mem_size,
                ph.flags().to_attr(),
                ByFrame::new(),
                Some((data.as_ptr() as usize, data.len())),
            );
        }
        memory_set
    }
}

// 将 elf 段的标志转化为我们熟悉的 MemoryAttr
trait ToMemoryAttr {
    fn to_attr(&self) -> MemoryAttr;
}
impl ToMemoryAttr for Flags {
    fn to_attr(&self) -> MemoryAttr {
        // 由于是用户程序,各段均首先设置为用户态
        let mut flags = MemoryAttr::new().set_user();
        if self.is_execute() {
            flags = flags.set_execute();
        }
        flags
    }
}

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

// src/memory/memory_set/mod.rs

impl MemorySet {
    ...
    pub fn push(&mut self, start: usize, end: usize, attr: MemoryAttr, handler: impl MemoryHandler, data: Option<(usize, usize)>) {
        assert!(start <= end, "invalid memory area!");
        assert!(self.test_free_area(start, end), "memory area overlap!");
        let area = MemoryArea::new(start, end, Box::new(handler), attr);
        // 首先进行映射
        area.map(&mut self.page_table);
        if let Some((src, length)) = data {
            // 如果传入了数据源
            // 交给 area 进行复制
            area.page_copy(&mut self.page_table, src, length);
        }
        self.areas.push(area);

    }
    pub fn token(&self) -> usize {
        self.page_table.token()
    }
}

// src/memory/memory_set/area.rs

impl MemoryArea {
    ...
    pub fn page_copy(&self, pt: &mut PageTableImpl, src: usize, length: usize) { 
        let mut l = length;
        let mut s = src;
        for page in PageRange::new(self.start, self.end) {
            // 交给 MemoryHandler 逐页进行复制
            self.handler.page_copy(
                pt,
                page,
                s,
                if l < PAGE_SIZE { l } else { PAGE_SIZE },
            );
            s += PAGE_SIZE;
            if l >= PAGE_SIZE { l -= PAGE_SIZE; }
        }
    }
}

// src/memory/memory_set/handler.rs

use crate::memory::access_pa_via_va;
use crate::consts::PAGE_SIZE;

pub trait MemoryHandler: Debug + 'static {
    ...
    fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize);
}

impl MemoryHandler for Linear {
    ...
    fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize) {
        let pa = pt.get_entry(va)
            .expect("get pa error!")
            .0
            .addr()
            .as_usize();
        assert!(va == access_pa_via_va(pa));
        assert!(va == pa + self.offset);
        unsafe {
            let dst = core::slice::from_raw_parts_mut(
                va as *mut u8,
                PAGE_SIZE,
            );
            if length > 0 {
                let src = core::slice::from_raw_parts(
                    src as *const u8,
                    PAGE_SIZE,
                );
                for i in 0..length { dst[i] = src[i]; }
            }
            for i in length..PAGE_SIZE { dst[i] = 0; }
        }
    }
}

impl MemoryHandler for ByFrame {
    ...
    fn page_copy(&self, pt: &mut PageTableImpl, va: usize, src: usize, length: usize) {
        let pa = pt.get_entry(va)
            .expect("get pa error!")
            .0
            .addr()
            .as_usize();
        unsafe {
            let dst = core::slice::from_raw_parts_mut(
                access_pa_via_va(pa) as *mut u8,
                PAGE_SIZE,
            );
            if length > 0 {
                let src = core::slice::from_raw_parts(
                    src as *const u8,
                    PAGE_SIZE,
                );
                for i in 0..length { dst[i] = src[i]; }
            }
            for i in length..PAGE_SIZE { dst[i] = 0; }
        }
    }
}

// src/memory/paging.rs
// 这里有两处要改成 pub ,其他不必做改动

pub struct PageEntry(pub &'static mut PageTableEntry, Page);

impl PageTableImpl {
    ...
    pub fn get_entry(&mut self, va: usize) -> Option<&mut PageEntry> {
        let page = Page::of_addr(VirtAddr::new(va));
        if let Ok(e) = self.page_table.ref_entry(page.clone()) {
            let e = unsafe { &mut *(e as *mut PageTableEntry) };
            self.entry = Some(PageEntry(e, page));
            Some(self.entry.as_mut().unwrap())
        }
        else {
            None
        }
    }
    ...
}

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

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

results matching ""

    No results matching ""