# 构建用户态执行环境 - Open-Source-OS-Tutorial-Summer-of-Code-2022 文档

Hide navigation sidebar
Hide table of contents sidebar
Toggle table of contents sidebar

# 构建用户态执行环境

注解

前三小节的用户态程序案例代码在 此处 获取。

## 用户态最小化执行环境

### 执行环境初始化

首先我们要给 Rust 编译器编译器提供入口函数 `_start()` ， 在 `main.rs` 中添加如下内容：

    // os/src/main.rs
    #[no_mangle]
    extern "C" fn _start() {
        loop{};
    }

对上述代码重新编译，再用分析工具分析：

    $ cargo build
       Compiling os v0.1.0 (/home/shinbokuow/workspace/v3/rCore-Tutorial-v3/os)
        Finished dev [unoptimized + debuginfo] target(s) in 0.06s

    [反汇编导出汇编程序]
    $ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os
       target/riscv64gc-unknown-none-elf/debug/os:       file format elf64-littleriscv

       Disassembly of section .text:

       0000000000011120 <_start>:
       ;     loop {}
         11120: 09 a0            j       2 <_start+0x2>
         11122: 01 a0            j       0 <_start+0x2>

反汇编出的两条指令就是一个死循环， 这说明编译器生成的已经是一个合理的程序了。 用 `qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os` 命令可以执行这个程序。

### 程序正常退出

我们把 `_start()` 函数中的循环语句注释掉，重新编译并分析，看到其汇编代码是：

    $ rust-objdump -S target/riscv64gc-unknown-none-elf/debug/os

     target/riscv64gc-unknown-none-elf/debug/os: file format elf64-littleriscv

     Disassembly of section .text:

     0000000000011120 <_start>:
     ; }
       11120: 82 80              ret

看起来是合法的执行程序。但如果我们执行它，会引发问题：

    $ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os
      段错误 (核心已转储)

这个简单的程序导致 `qemu-riscv64` 崩溃了！为什么会这样？

注解

QEMU有两种运行模式：

`User mode` 模式，即用户态模拟，如 `qemu-riscv64` 程序， 能够模拟不同处理器的用户态指令的执行，并可以直接解析ELF可执行文件， 加载运行那些为不同处理器编译的用户级Linux应用程序。

`System mode` 模式，即系统态模式，如 `qemu-system-riscv64` 程序， 能够模拟一个完整的基于不同CPU的硬件系统，包括处理器、内存及其他外部设备，支持运行完整的操作系统。

目前的执行环境还缺了一个退出机制，我们需要操作系统提供的 `exit` 系统调用来退出程序。这里先给出代码：

    // os/src/main.rs

    const SYSCALL_EXIT: usize = 93;

    fn syscall(id: usize, args: [usize; 3]) -> isize {
        let mut ret;
        unsafe {
            core::arch::asm!(
                "ecall",
                inlateout("x10") args[0] => ret,
                in("x11") args[1],
                in("x12") args[2],
                in("x17") id,
            );
        }
        ret
    }

    pub fn sys_exit(xstate: i32) -> isize {
        syscall(SYSCALL_EXIT, [xstate as usize, 0, 0])
    }

    #[no_mangle]
    extern "C" fn _start() {
        sys_exit(9);
    }

`main.rs` 增加的内容不多，但还是有点与一般的应用程序有所不同，因为它引入了汇编和系统调用。 第二章的第二节 实现应用程序 会详细介绍上述代码的含义。 这里读者只需要知道 `_start` 函数调用了一个 `sys_exit` 函数， 向操作系统发出了退出的系统调用请求，退出码为 `9` 。

我们编译执行以下修改后的程序：

    $ cargo build --target riscv64gc-unknown-none-elf
      Compiling os v0.1.0 (/media/chyyuu/ca8c7ba6-51b7-41fc-8430-e29e31e5328f/thecode/rust/os_kernel_lab/os)
        Finished dev [unoptimized + debuginfo] target(s) in 0.26s

    [打印程序的返回值]
    $ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os; echo $?
    9

可以看到，返回的结果确实是 `9` 。这样，我们勉强完成了一个简陋的用户态最小化执行环境。

## 有显示支持的用户态执行环境

没有 `println` 输出信息，终究觉得缺了点啥。

Rust 的 core 库内建了以一系列帮助实现显示字符的基本 Trait 和数据结构，函数等，我们可以对其中的关键部分进行扩展，就可以实现定制的 `println!` 功能。

### 实现输出字符串的相关函数

注意

如果你觉得理解 Rust 宏有困难，把它当成黑盒就好！

首先封装一下对 `SYSCALL_WRITE` 系统调用。

    const SYSCALL_WRITE: usize = 64;

    pub fn sys_write(fd: usize, buffer: &[u8]) -> isize {
      syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()])
    }

然后实现基于 `Write` Trait 的数据结构，并完成 `Write` Trait 所需要的 `write_str` 函数，并用 `print` 函数进行包装。

    struct Stdout;

    impl Write for Stdout {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            sys_write(1, s.as_bytes());
            Ok(())
        }
    }

    pub fn print(args: fmt::Arguments) {
        Stdout.write_fmt(args).unwrap();
    }

最后，实现基于 `print` 函数，实现Rust语言 格式化宏 ( formatting macros )。

    #[macro_export]
    macro_rules! print {
        ($fmt: literal $(, $($arg: tt)+)?) => {
            $crate::console::print(format_args!($fmt $(, $($arg)+)?));
        }
    }

    #[macro_export]
    macro_rules! println {
        ($fmt: literal $(, $($arg: tt)+)?) => {
            print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
        }
    }

接下来，我们调整一下应用程序，让它发出显示字符串和退出的请求：

    #[no_mangle]
    extern "C" fn _start() {
        println!("Hello, world!");
        sys_exit(9);
    }

现在，我们编译并执行一下，可以看到正确的字符串输出，且程序也能正确退出！

    $ cargo build --target riscv64gc-unknown-none-elf
       Compiling os v0.1.0 (/media/chyyuu/ca8c7ba6-51b7-41fc-8430-e29e31e5328f/thecode/rust/os_kernel_lab/os)
      Finished dev [unoptimized + debuginfo] target(s) in 0.61s

    $ qemu-riscv64 target/riscv64gc-unknown-none-elf/debug/os; echo $?
      Hello, world!
      9