这是 os summer of code 2020 项目每日记录的一部分:
每日记录github地址(包含根据实验指导实现的每个阶段的代码):https://github.com/yunwei37/os-summer-of-code-daily
这里参考的是rCore tutorial的第三版:https://github.com/rcore-os/rCore-Tutorial
lab6 学习报告
这一章的实验指导包含:
- 单独生成 ELF 格式的用户程序,并打包进文件系统中
- 创建并运行用户进程
- 使用系统调用为用户程序提供服务
构建用户程序框架
接下来,我们需要为用户程序提供一个类似的没有Rust std标准运行时依赖的极简运行时环境。这里我们会快速梳理一遍我们为用户程序进行的流程。
首先,我们在 os 的旁边建立一个 user crate,移除默认的 main.rs,而是在 src 目录下建立 lib 和 bin 子目录, 在 lib 中存放的是极简运行时环境,在 bin 中存放的源文件会被编译成多个单独的执行文件。
基础框架搭建
和操作系统一样,我们需要为用户程序移除 std 依赖,并且补充一些必要的功能:
在 lib.rs 中添加:
- 声明
- 堆栈相关
- panic 处理
- 入口函数
#![no_std]
#![feature(llvm_asm)]
#![feature(lang_items)]
#![feature(panic_info_message)]
#![feature(linkage)]
/// 大小为 [`USER_HEAP_SIZE`] 的堆空间
static mut HEAP_SPACE: [u8; USER_HEAP_SIZE] = [0; USER_HEAP_SIZE];
/// 使用 `buddy_system_allocator` 中的堆
#[global_allocator]
static HEAP: LockedHeap = LockedHeap::empty();
/// 打印 panic 信息并退出用户程序
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
if let Some(location) = info.location() {
println!(
"\x1b[1;31m{}:{}: '{}'\x1b[0m",
location.file(),
location.line(),
info.message().unwrap()
);
} else {
println!("\x1b[1;31mpanic: '{}'\x1b[0m", info.message().unwrap());
}
sys_exit(-1);
}
/// 程序入口
#[no_mangle]
pub extern "C" fn _start(_args: isize, _argv: *const u8) -> ! {
unsafe {
HEAP.lock()
.init(HEAP_SPACE.as_ptr() as usize, USER_HEAP_SIZE);
}
sys_exit(main())
}
/// 默认的 main 函数
///
/// 设置了弱的 linkage,会被 `bin` 中文件的 `main` 函数取代
#[linkage = "weak"]
#[no_mangle]
fn main() -> isize {
panic!("no main() linked");
}
/// 终止程序
#[no_mangle]
pub extern "C" fn abort() {
panic!("abort");
}
/// 内存不足时终止程序
#[lang = "oom"]
fn oom(_: Layout) -> ! {
panic!("out of memory");
}
另外,在 .cargo/config 还需要设置编译目标为 RISC-V 64:
# 编译的目标平台
[build]
target = "riscv64imac-unknown-none-elf"
console.rs:
在 stdout stdin 基础上进行输入输出
//! 在系统调用基础上实现 `print!` `println!`
//!
//! 代码与 `os` crate 中的 `console.rs` 基本相同
use crate::syscall::*;
use alloc::string::String;
use core::fmt::{self, Write};
/// 实现 [`core::fmt::Write`] trait 来进行格式化输出
struct Stdout;
impl Write for Stdout {
/// 打印一个字符串
fn write_str(&mut self, s: &str) -> fmt::Result {
sys_write(STDOUT, s.as_bytes());
Ok(())
}
}
/// 打印由 [`core::format_args!`] 格式化后的数据
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
/// 实现类似于标准库中的 `print!` 宏
#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
}
}
/// 实现类似于标准库中的 `println!` 宏
#[macro_export]
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}
/// 从控制台读取一个字符(阻塞)
pub fn getchar() -> u8 {
let mut c = [0u8; 1];
sys_read(STDIN, &mut c);
c[0]
}
/// 从控制台读取一个或多个字符(阻塞)
pub fn getchars() -> String {
let mut buffer = [0u8; 64];
loop {
let size = sys_read(STDIN, &mut buffer);
if let Ok(string) = String::from_utf8(buffer.iter().copied().take(size as usize).collect())
{
return string;
}
}
}
打包为磁盘镜像
现在,我们只需要利用工具将编译后的用户程序打包为镜像,就可以使用了。
安装工具:
cargo install rcore-fs-fuse --git https://github.com/rcore-os/rcore-fs
打包:
这个工具可以将一个目录打包成 SimpleFileSystem 格式的磁盘镜像。
将elf文件单独放在一个导出目录中,即
user/build/disk
:
user/Makefile
.PHONY: build
TARGET := riscv64imac-unknown-none-elf
MODE := debug
# 用户程序目录
SRC_DIR := src/bin
# 编译后执行文件目录
TARGET_DIR :&