今天早上终于把之前实验环境的问题解决了!搞了有一周了吧,事实证明还是要多问不能闭门造车啊。
原来之前做的是实验指导书上第一章的内容,lab在习题上,所以今天才算是做lab的第一天吧。时间要不够了…
lab0
创建项目,构建并运行
- cargo new os
报错:error: Failed to create packageos
at/lab0/os
Caused by:
could not determine the current user, please set $USER
实际上已经建好了,不知道为什么会报错
实际上原来还没有建好,刚刚仔细看了一下lab0发现里面有个上次工程没删干净的文件,是个配置文件,不知道为什么会影响我新建工程,现在删掉好了
移除标准库依赖
#![no_std]
- 实现标准库panic
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
- 修改配置文件,遇到panic直接退出
os/Cargo.toml
...
# panic 时直接终止,因为我们没有实现堆栈展开的功能
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
移除运行时环境依赖
重写覆盖整个 crt0 入口点:
os/src/main.rs
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
//!
//! - `#![no_main]`
//! 不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
/// 覆盖 crt0 中的 _start 函数
/// 我们暂时将它的实现为一个死循环
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
编译为裸机目标
查看当前运行环境
root@b347e8e9e0d1:/lab0/os# rustc --version --verbose
rustc 1.55.0-nightly (798baebde 2021-07-02)
binary: rustc
commit-hash: 798baebde1fe77e5a660490ec64e727a5d79970d
commit-date: 2021-07-02
host: x86_64-unknown-linux-gnu
release: 1.55.0-nightly
LLVM version: 12.0.1
运行命令
rustup target add riscv64imac-unknown-none-elf
在 os 文件夹中创建一个 .cargo 文件夹,并在其中创建一个名为 config 的文件,在其中填入以下内容:
os/.cargo/config
# 编译的目标平台
[build]
target = "riscv64imac-unknown-none-elf"
生成内核镜像
安装 binutils 工具集
cargo install cargo-binutils
我发现挂代理运行这条命令的时候会失败,大概是之前换过镜像的原因(
rustup component add llvm-tools-preview
用 rust-objdump --version 命令看看是否安装成功。
查看它的文件类型:
$ file target/riscv64imac-unknown-none-elf/debug/os
使用 rust-objdump 工具看看它的具体信息:
$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -x --arch-name=riscv64
root@b347e8e9e0d1:/lab0/os# rust-objdump target/riscv64imac-unknown-none-elf/debug/os -x --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format elf64-littleriscv
architecture: riscv64
start address: 0x0000000000011120
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000010040 paddr 0x0000000000010040 align 2**3
filesz 0x00000000000000e0 memsz 0x00000000000000e0 flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000010000 paddr 0x0000000000010000 align 2**12
filesz 0x0000000000000120 memsz 0x0000000000000120 flags r--
LOAD off 0x0000000000000120 vaddr 0x0000000000011120 paddr 0x0000000000011120 align 2**12
filesz 0x0000000000000004 memsz 0x0000000000000004 flags r-x
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**64
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
Dynamic Section:
Sections:
Idx Name Size VMA Type
0 00000000 0000000000000000
1 .text 00000004 0000000000011120 TEXT
2 .debug_abbrev 00000113 0000000000000000
3 .debug_info 00000536 0000000000000000
4 .debug_aranges 00000040 0000000000000000
5 .debug_ranges 00000030 0000000000000000
6 .debug_str 000003ad 0000000000000000
7 .debug_pubnames 0000009c 0000000000000000
8 .debug_pubtypes 000002cb 0000000000000000
9 .riscv.attributes 0000002b 0000000000000000
10 .debug_frame 00000050 0000000000000000
11 .debug_line 0000005b 0000000000000000
12 .comment 00000013 0000000000000000
13 .symtab 00000108 0000000000000000
14 .shstrtab 000000b7 0000000000000000
15 .strtab 0000002d 0000000000000000
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 1ujncti96fi79wvv
0000000000011120 l .text 0000000000000000
0000000000011120 l .text 0000000000000000
0000000000011120 l .text 0000000000000000
0000000000011124 l .text 0000000000000000
0000000000000000 l .debug_info 0000000000000000
0000000000000000 l .debug_ranges 0000000000000000
0000000000000000 l .debug_frame 0000000000000000
0000000000000000 l .debug_line 0000000000000000 .Lline_table_start0
0000000000011120 g F .text 0000000000000004 _start
root@b347e8e9e0d1:/lab0/os# rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format elf64-littleriscv
Disassembly of section .text:
0000000000011120 <_start>:
11120: 09 a0 j 0x11122 <_start+0x2>
11122: 01 a0 j 0x11122 <_start+0x2>
- start address:程序的入口地址
- Sections:从这里我们可以看到程序各段的各种信息。后面以 debug 开头的段是调试信息
- SYMBOL TABLE:符号表,从中我们可以看到程序中所有符号的地址。例如 _start 函数就位于入口地址上
- Program Header:程序加载时所需的段信息
其中的 off 是它在文件中的位置,vaddr 和 paddr 是要加载到的虚拟地址和物理地址,align 规定了地址的对齐,filesz 和 memsz 分别表示它在文件和内存中的大小,flags 描述了相关权限(r 表示可读,w 表示可写,x 表示可执行)
反汇编
$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
root@b347e8e9e0d1:/lab0/os# rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format elf64-littleriscv
Disassembly of section .text:
0000000000011120 <_start>:
11120: 09 a0 j 0x11122 <_start+0x2>
11122: 01 a0 j 0x11122 <_start+0x2>
生成镜像
rust-objcopy target/riscv64imac-unknown-none-elf/debug/os --strip-all -O binary target/riscv64imac-unknown-none-elf/debug/kernel.bin
调整内存布局
编写链接脚本
os/src/linker.ld
/* 有关 Linker Script 可以参考:https://sourceware.org/binutils/docs/ld/Scripts.html */
/* 目标架构 */
OUTPUT_ARCH(riscv)
/* 执行入口 */
ENTRY(_start)
/* 数据存放起始地址 */
BASE_ADDRESS = 0x80200000;
SECTIONS
{
/* . 表示当前地址(location counter) */
. = BASE_ADDRESS;
/* start 符号表示全部的开始位置 */
kernel_start = .;
text_start = .;
/* .text 字段 */
.text : {
/* 把 entry 函数放在最前面 */
*(.text.entry)
/* 要链接的文件的 .text 字段集中放在这里 */
*(.text .text.*)
}
rodata_start = .;
/* .rodata 字段 */
.rodata : {
/* 要链接的文件的 .rodata 字段集中放在这里 */
*(.rodata .rodata.*)
}
data_start = .;
/* .data 字段 */
.data : {
/* 要链接的文件的 .data 字段集中放在这里 */
*(.data .data.*)
}
bss_start = .;
/* .bss 字段 */
.bss : {
/* 要链接的文件的 .bss 字段集中放在这里 */
*(.sbss .bss .bss.*)
}
/* 结束地址 */
kernel_end = .;
}
使用链接脚本
在 .cargo/config 文件中加入以下配置:
# 使用我们的 linker script 来进行链接
[target.riscv64imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tsrc/linker.ld",
]
重写程序入口 _start
os/src/entry.asm
# 操作系统启动时所需的指令以及字段
#
# 我们在 linker.ld 中将程序入口设置为了 _start,因此在这里我们将填充这个标签
# 它将会执行一些必要操作,然后跳转至我们用 rust 编写的入口函数
#
# 关于 RISC-V 下的汇编语言,可以参考 https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md
.section .text.entry
.globl _start
# 目前 _start 的功能:将预留的栈空间写入 $sp,然后跳转至 rust_main
_start:
la sp, boot_stack_top
call rust_main
# 回忆:bss 段是 ELF 文件中只记录长度,而全部初始化为 0 的一段内存空间
# 这里声明字段 .bss.stack 作为操作系统启动时的栈
.section .bss.stack
.global boot_stack
boot_stack:
# 16K 启动栈大小
.space 4096 * 16
.global boot_stack_top
boot_stack_top:
# 栈结尾
将 os/src/main.rs 里面的 _start 函数删除,并换成 rust_main :
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
//!
//! - `#![no_main]`
//! 不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]
//!
//! - `#![feature(global_asm)]`
//! 内嵌整个汇编文件
#![feature(global_asm)]
// 汇编编写的程序入口,具体见该文件
global_asm!(include_str!("entry.asm"));
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
/// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这是第一个被调用的 Rust 函数
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
loop {}
}
使用 QEMU 运行内核
使用 OpenSBI
qemu-system-riscv64 \
--machine virt \
--nographic \
--bios default
QEMU 可以使用 ctrl+a (macOS 为 control+a) 再按下 x 键退出 。
加载内核镜像
os/src/main.rs
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
//!
//! - `#![no_main]`
//! 不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]
//! # 一些 unstable 的功能需要在 crate 层级声明后才可以使用
//! - `#![feature(llvm_asm)]`
//! 内嵌汇编
#![feature(llvm_asm)]
//!
//! - `#![feature(global_asm)]`
//! 内嵌整个汇编文件
#![feature(global_asm)]
// 汇编编写的程序入口,具体见该文件
global_asm!(include_str!("entry.asm"));
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
/// 在屏幕上输出一个字符,目前我们先不用了解其实现原理
pub fn console_putchar(ch: u8) {
let _ret: usize;
let arg0: usize = ch as usize;
let arg1: usize = 0;
let arg2: usize = 0;
let which: usize = 1;
unsafe {
llvm_asm!("ecall"
: "={x10}" (_ret)
: "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
: "memory"
: "volatile"
);
}
}
/// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这是第一个被调用的 Rust 函数
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
// 在屏幕上输出 "OK\n" ,随后进入死循环
console_putchar(b'O');
console_putchar(b'K');
console_putchar(b'\n');
loop {}
}
通过在 os 目录下建立一个 Makefile 来简化这一过程:
os/Makefile
TARGET := riscv64imac-unknown-none-elf
MODE := debug
KERNEL_FILE := target/$(TARGET)/$(MODE)/os
BIN_FILE := target/$(TARGET)/$(MODE)/kernel.bin
OBJDUMP := rust-objdump --arch-name=riscv64
OBJCOPY := rust-objcopy --binary-architecture=riscv64
.PHONY: doc kernel build clean qemu run
# 默认 build 为输出二进制文件
build: $(BIN_FILE)
# 通过 Rust 文件中的注释生成 os 的文档
doc:
@cargo doc --document-private-items
# 编译 kernel
kernel:
@cargo build
# 生成 kernel 的二进制文件
$(BIN_FILE): kernel
@$(OBJCOPY) $(KERNEL_FILE) --strip-all -O binary $@
# 查看反汇编结果
asm:
@$(OBJDUMP) -d $(KERNEL_FILE) | less
# 清理编译出的文件
clean:
@cargo clean
# 运行 QEMU
qemu: build
@qemu-system-riscv64 \
-machine virt \
-nographic \
-bios default \
-device loader,file=$(BIN_FILE),addr=0x80200000
# 一键运行
run: build qemu
接口封装和代码整理
使用 OpenSBI 提供的服务
OpenSBI 文档
os/src/sbi.rs
//! 调用 Machine 层的操作
// 目前还不会用到全部的 SBI 调用,暂时允许未使用的变量或函数
#![allow(unused)]
/// SBI 调用
#[inline(always)]
fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize {
let ret;
unsafe {
llvm_asm!("ecall"
: "={x10}" (ret)
: "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which)
: "memory" // 如果汇编可能改变内存,则需要加入 memory 选项
: "volatile"); // 防止编译器做激进的优化(如调换指令顺序等破坏 SBI 调用行为的优化)
}
ret
}
关机函数
const SBI_SET_TIMER: usize = 0;
const SBI_CONSOLE_PUTCHAR: usize = 1;
const SBI_CONSOLE_GETCHAR: usize = 2;
const SBI_CLEAR_IPI: usize = 3;
const SBI_SEND_IPI: usize = 4;
const SBI_REMOTE_FENCE_I: usize = 5;
const SBI_REMOTE_SFENCE_VMA: usize = 6;
const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7;
const SBI_SHUTDOWN: usize = 8;
/// 向控制台输出一个字符
///
/// 需要注意我们不能直接使用 Rust 中的 char 类型
pub fn console_putchar(c: usize) {
sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0);
}
/// 从控制台中读取一个字符
///
/// 没有读取到字符则返回 -1
pub fn console_getchar() -> usize {
sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0)
}
/// 调用 SBI_SHUTDOWN 来关闭操作系统(直接退出 QEMU)
pub fn shutdown() -> ! {
sbi_call(SBI_SHUTDOWN, 0, 0, 0);
unreachable!()
}
实现整个 print 和 println 宏
os/src/console.rs
//! 实现控制台的字符输入和输出
//!
//! # 格式化输出
//!
//! [`core::fmt::Write`] trait 包含
//! - 需要实现的 [`write_str`] 方法
//! - 自带实现,但依赖于 [`write_str`] 的 [`write_fmt`] 方法
//!
//! 我们声明一个类型,为其实现 [`write_str`] 方法后,就可以使用 [`write_fmt`] 来进行格式化输出
//!
//! [`write_str`]: core::fmt::Write::write_str
//! [`write_fmt`]: core::fmt::Write::write_fmt
use crate::sbi::*;
use core::fmt::{self, Write};
/// 一个 [Zero-Sized Type],实现 [`core::fmt::Write`] trait 来进行格式化输出
///
/// ZST 只可能有一个值(即为空),因此它本身就是一个单件
struct Stdout;
impl Write for Stdout {
/// 打印一个字符串
///
/// [`console_putchar`] sbi 调用每次接受一个 `usize`,但实际上会把它作为 `u8` 来打印字符。
/// 因此,如果字符串中存在非 ASCII 字符,需要在 utf-8 编码下,对于每一个 `u8` 调用一次 [`console_putchar`]
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut buffer = [0u8; 4];
for c in s.chars() {
for code_point in c.encode_utf8(&mut buffer).as_bytes().iter() {
console_putchar(*code_point as usize);
}
}
Ok(())
}
}
/// 打印由 [`core::format_args!`] 格式化后的数据
///
/// [`print!`] 和 [`println!`] 宏都将展开成此函数
///
/// [`core::format_args!`]: https://doc.rust-lang.org/nightly/core/macro.format_args.html
pub fn print(args: fmt::Arguments) {
Stdout.write_fmt(args).unwrap();
}
/// 实现类似于标准库中的 `print!` 宏
///
/// 使用实现了 [`core::fmt::Write`] trait 的 [`console::Stdout`]
#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?));
}
}
/// 实现类似于标准库中的 `println!` 宏
///
/// 使用实现了 [`core::fmt::Write`] trait 的 [`console::Stdout`]
#[macro_export]
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?));
}
}
整理 panic 处理模块
os/src/panic.rs
//! 代替 std 库,实现 panic 和 abort 的功能
use core::panic::PanicInfo;
use crate::sbi::shutdown;
/// 打印 panic 的信息并 [`shutdown`]
///
/// ### `#[panic_handler]` 属性
/// 声明此函数是 panic 的回调
#[panic_handler]
fn panic_handler(info: &PanicInfo) -> ! {
// `\x1b[??m` 是控制终端字符输出格式的指令,在支持的平台上可以改变文字颜色等等,这里使用红色
// 参考:https://misc.flogisoft.com/bash/tip_colors_and_formatting
//
// 需要全局开启 feature(panic_info_message) 才可以调用 .message() 函数
println!("\x1b[1;31mpanic: '{}'\x1b[0m", info.message().unwrap());
shutdown()
}
/// 终止程序
///
/// 调用 [`panic_handler`]
#[no_mangle]
extern "C" fn abort() -> ! {
panic!("abort()")
}
检验我们的成果
os/src/main.rs
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
//!
//! - `#![no_main]`
//! 不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]
//! # 一些 unstable 的功能需要在 crate 层级声明后才可以使用
//! - `#![feature(llvm_asm)]`
//! 内嵌汇编
#![feature(llvm_asm)]
//!
//! - `#![feature(global_asm)]`
//! 内嵌整个汇编文件
#![feature(global_asm)]
//!
//! - `#![feature(panic_info_message)]`
//! panic! 时,获取其中的信息并打印
#![feature(panic_info_message)]
#[macro_use]
mod console;
mod panic;
mod sbi;
// 汇编编写的程序入口,具体见该文件
global_asm!(include_str!("entry.asm"));
/// Rust 的入口函数
///
/// 在 `_start` 为我们进行了一系列准备之后,这是第一个被调用的 Rust 函数
#[no_mangle]
pub extern "C" fn rust_main() -> ! {
println!("Hello rCore-Tutorial!");
panic!("end of rust_main")
}
运行结果:
root@b347e8e9e0d1:/lab0/os# make run
Compiling os v0.1.0 (/lab0/os)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
OpenSBI v0.6
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : QEMU Virt Machine
Platform HART Features : RV64ACDFIMSU
Platform Max HARTs : 8
Current Hart : 0
Firmware Base : 0x80000000
Firmware Size : 120 KB
Runtime SBI Version : 0.2
MIDELEG : 0x0000000000000222
MEDELEG : 0x000000000000b109
PMP0 : 0x0000000080000000-0x000000008001ffff (A)
PMP1 : 0x0000000000000000-0xffffffffffffffff (A,R,W,X)
Hello rCore-Tutorial!
panic: 'end of rust_main'
虽然只是lab0,但是拖了我一周多了吧,今天终于完整解决了!又有动力了呢!