示例:用大约 100 行安全的 Rust 代码编写内核
为了让你了解 Asterinas OSTD 如何支持用安全 Rust 编写内核,我们将展示一个仅需约 100 行安全 Rust 代码的新内核。
我们的新内核将能够运行以下 Hello World 程序。
# SPDX-License-Identifier: MPL-2.0
.global _start # entry point
.section .text # code section
_start:
mov $1, %rax # syscall number of write
mov $1, %rdi # stdout
mov $message, %rsi # address of message
mov $message_end, %rdx
sub %rsi, %rdx # calculate message len
syscall
mov $60, %rax # syscall number of exit, move it to rax
mov $0, %rdi # exit code, move it to rdi
syscall
.section .rodata # read only data section
message:
.ascii "Hello, world\n"
message_end:
上述汇编程序可通过以下命令编译。
gcc -static -nostdlib hello.S -o hello
上述汇编程序可通过以下命令编译:
- 在用户空间加载程序作为进程镜像;
- 处理写系统调用;
- 处理退出系统调用。
以下给出了一个用安全 Rust 编写的内核示例实现。添加了注释以突出说明 Asterinas OSTD 的 API 如何支持安全的内核开发。
// SPDX-License-Identifier: MPL-2.0
#![no_std]
#![deny(unsafe_code)]
extern crate alloc;
use align_ext::AlignExt;
use core::str;
use alloc::sync::Arc;
use alloc::vec;
use ostd::arch::cpu::context::UserContext;
use ostd::mm::{
CachePolicy, FallibleVmRead, FrameAllocOptions, PageFlags, PageProperty, Vaddr, VmIo, VmSpace,
VmWriter, PAGE_SIZE,
};
use ostd::power::{poweroff, ExitCode};
use ostd::prelude::*;
use ostd::task::{disable_preempt, Task, TaskOptions};
use ostd::user::{ReturnReason, UserMode};
/// 内核的引导和初始化过程由OSTD管理。
/// 在该过程完成后,内核的执行环境
///(例如,栈、堆、任务)将准备就绪
/// 标记为`#[ostd::main]`的入口函数将被调用。
#[ostd::main]
pub fn main() {
let program_binary = include_bytes!("../hello");
let vm_space = Arc::new(create_vm_space(program_binary));
vm_space.activate();
let user_task = create_user_task(vm_space);
user_task.run();
}
fn create_vm_space(program: &[u8]) -> VmSpace {
let nbytes = program.len().align_up(PAGE_SIZE);
let user_pages = {
let segment = FrameAllocOptions::new()
.alloc_segment(nbytes / PAGE_SIZE)
.unwrap();
// 物理内存页只能通过 `UFrame` 或 `USegment` 抽象来访问。
segment.write_bytes(0, program).unwrap();
segment
};
// 用户空间的页表可以通过 `VmSpace` 抽象安全地创建和操作。
let vm_space = VmSpace::new();
const MAP_ADDR: Vaddr = 0x0040_0000; // 静态链接可执行文件的映射地址
let preempt_guard = disable_preempt();
let mut cursor = vm_space
.cursor_mut(&preempt_guard, &(MAP_ADDR..MAP_ADDR + nbytes))
.unwrap();
let map_prop = PageProperty::new_user(PageFlags::RWX, CachePolicy::Writeback);
for frame in user_pages {
cursor.map(frame.into(), map_prop);
}
drop(cursor);
vm_space
}
fn create_user_task(vm_space: Arc<VmSpace>) -> Arc<Task> {
fn user_task() {
let current = Task::current().unwrap();
// 用户空间和内核空间之间的切换是
// 通过 UserMode 抽象来执行的。
let mut user_mode = {
let user_ctx = create_user_context();
UserMode::new(user_ctx)
};
loop {
// 当系统调用、CPU异常发生,
// 或者内核指定的某些事件发生时,
// execute方法返回。
let return_reason = user_mode.execute(|| false);
// 用户空间的 CPU 寄存器
// 可以通过 `UserContext` 抽象进行访问和操作。
let user_context = user_mode.context_mut();
if ReturnReason::UserSyscall == return_reason {
let vm_space = current.data().downcast_ref::<Arc<VmSpace>>().unwrap();
handle_syscall(user_context, &vm_space);
}
}
}
// 内核任务由框架管理,
// 而这些任务的调度算法可由框架用户确定。
Arc::new(TaskOptions::new(user_task).data(vm_space).build().unwrap())
}
fn create_user_context() -> UserContext {
// 用户空间的 CPU 状态可以通过 `UserContext` 抽象初始化为任意值。
let mut user_ctx = UserContext::default();
const ENTRY_POINT: Vaddr = 0x0040_1000; // 静态链接可执行文件的入口点
user_ctx.set_rip(ENTRY_POINT);
user_ctx
}
fn handle_syscall(user_context: &mut UserContext, vm_space: &VmSpace) {
const SYS_WRITE: usize = 1;
const SYS_EXIT: usize = 60;
match user_context.rax() {
SYS_WRITE => {
// 安全访问用户空间 CPU 寄存器。
let (_, buf_addr, buf_len) =
(user_context.rdi(), user_context.rsi(), user_context.rdx());
let buf = {
let mut buf = vec![0u8; buf_len];
// 从用户空间复制数据,避免
// 不安全的指针解引用。
let mut reader = vm_space.reader(buf_addr, buf_len).unwrap();
reader
.read_fallible(&mut VmWriter::from(&mut buf as &mut [u8]))
.unwrap();
buf
};
// 安全地使用控制台进行输出。
println!("{}", str::from_utf8(&buf).unwrap());
// 安全地操作用户空间的 CPU 寄存器。
user_context.set_rax(buf_len);
}
SYS_EXIT => poweroff(ExitCode::Success),
_ => unimplemented!(),
}
}