这是 os summer of code 2020 项目每日记录的一部分:
每日记录github地址(包含根据实验指导实现的每个阶段的代码):https://github.com/yunwei37/os-summer-of-code-daily
这里参考的是rCore tutorial的第三版:https://github.com/rcore-os/rCore-Tutorial
本机环境(新安装的虚拟机系统,上一个系统是搞xv6的qemu,感觉可能实验环境会有冲突不如再开一个):
Linux ubuntu 5.4.0-26-generic #30-Ubuntu SMP Mon Apr 20 16:58:30 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
已换源至阿里云,但版本过低;
sudo apt-get install build-essential
安装编译工具链;wget https://download.qemu.org/qemu-4.2.0.tar.xz
tar xvJf qemu-4.2.0.tar.xz
cd qemu-4.2.0
./configure --target-list=riscv32-softmmu,riscv64-softmmu
ERROR: glib-2.48 gthread-2.0 is required to compile QEMU
:通过 sudo apt-get install libglib2.0-dev
安装;ERROR: pixman >= 0.21.8 not present
,通过 sudo apt-get install libpixman-1-dev
安装yunwei@ubuntu:~/rcore$ qemu-system-riscv64 --version
QEMU emulator version 4.2.0
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
curl https://sh.rustup.rs -sSf | sh
;git clone https://github.com/rcore-os/rCore-Tutorial
make
Failed to execute tool: objcopy
No such file or directory (os error 2)
make[1]: *** [Makefile:28: target/riscv64imac-unknown-none-elf/debug/kernel.bin] Error 101
可能是我先做了下面导致的问题,之后再说;
创建文件夹,并创建项目:
mkdir Project
cd Project
echo "nightly-2020-06-27" > rust-toolchain
cargo new os
cd os
cargo run
移除标准库依赖:
main.rs
#![no_std]
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
Cargo.toml
[package]
name = "os"
version = "0.1.0"
authors = ["yunwei"]
edition = "2018"
# panic 时直接终止,因为我们没有实现堆栈展开的功能
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
main.rs
#![no_std]
use core::panic::PanicInfo;
/// 当 panic 发生时会调用该函数
/// 我们暂时将它的实现为一个死循环
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
rustup target add riscv64imac-unknown-none-elf
cargo build --target riscv64imac-unknown-none-elf
结果:
yunwei@ubuntu:~/Project/os$ cargo build --target riscv64imac-unknown-none-elf
Compiling os v0.1.0 (/home/yunwei/Project/os)
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
在 os/.cargo/config 里面添加了这个:
# 编译的目标平台
[build]
target = "riscv64imac-unknown-none-elf"
就可以 cargo build 啦
运行:
cargo install cargo-binutils
rustup component add llvm-tools-preview
rust-objdump --version
@ubuntu:~/Project/os$ rust-objdump --version
LLVM (http://llvm.org/):
LLVM version 10.0.1-rust-1.46.0-nightly
Optimized build.
Default target: x86_64-unknown-linux-gnu
Host CPU: skylake
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_32 - AArch64 (little endian ILP32)
aarch64_be - AArch64 (big endian)
arm - ARM
arm64 - ARM64 (little endian)
arm64_32 - ARM64 (little endian ILP32)
armeb - ARM (big endian)
avr - Atmel AVR Microcontroller
hexagon - Hexagon
mips - MIPS (32-bit big endian)
mips64 - MIPS (64-bit big endian)
mips64el - MIPS (64-bit little endian)
mipsel - MIPS (32-bit little endian)
msp430 - MSP430 [experimental]
nvptx - NVIDIA PTX 32-bit
nvptx64 - NVIDIA PTX 64-bit
ppc32 - PowerPC 32
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
sparc - Sparc
sparcel - Sparc LE
sparcv9 - Sparc V9
systemz - SystemZ
thumb - Thumb
thumbeb - Thumb (big endian)
wasm32 - WebAssembly 32-bit
wasm64 - WebAssembly 64-bit
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
运行:
target/riscv64imac-unknown-none-elf/debug/os: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped
运行:(和教程里面并不完全一样)
yunwei@ubuntu:~/Project/os$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -x --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format ELF64-riscv
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_str 000003fd 0000000000000000
3 .debug_abbrev 00000113 0000000000000000
4 .debug_info 0000053c 0000000000000000
5 .debug_aranges 00000040 0000000000000000
6 .debug_ranges 00000030 0000000000000000
7 .debug_pubnames 000000a4 0000000000000000
8 .debug_pubtypes 00000308 0000000000000000
9 .debug_frame 00000050 0000000000000000
10 .debug_line 0000005b 0000000000000000
11 .comment 00000013 0000000000000000
12 .symtab 00000108 0000000000000000
13 .shstrtab 000000a5 0000000000000000
14 .strtab 0000002d 0000000000000000
SYMBOL TABLE:
0000000000000000 l df *ABS* 00000000 3gqd1qcioyc9uzqc
0000000000011120 .text 00000000
0000000000011120 .text 00000000
0000000000011120 .text 00000000
0000000000011124 .text 00000000
0000000000000000 .debug_info 00000000
0000000000000000 .debug_ranges 00000000
0000000000000000 .debug_frame 00000000
0000000000000000 .debug_line 00000000 .Lline_table_start0
0000000000011120 g F .text 00000004 _start
反汇编:
yunwei@ubuntu:~/Project/os$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format ELF64-riscv
Disassembly of section .text:
0000000000011120 _start:
11120: 09 a0 j 2
11122: 01 a0 j 0
生成镜像:
rust-objcopy target/riscv64imac-unknown-none-elf/debug/os --strip-all -O binary target/riscv64imac-unknown-none-elf/debug/kernel.bin
可以参考:https://sourceware.org/binutils/docs/ld/Scripts.html 但还没去看
编写链接脚本:
/* 有关 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 = .;
}
在os/.cargo/config
里面添加:
# 使用我们的 linker script 来进行链接
[target.riscv64imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tsrc/linker.ld",
]
cargo build
rust-objdump target/riscv64imac-unknown-none-elf/debug/os -h --arch-name=riscv64
输出:
Idx Name Size VMA Type
0 00000000 0000000000000000
1 .text 00000004 0000000080200000 TEXT
2 .debug_str 000003fd 0000000000000000
3 .debug_abbrev 00000113 0000000000000000
4 .debug_info 0000053c 0000000000000000
5 .debug_aranges 00000040 0000000000000000
6 .debug_ranges 00000030 0000000000000000
7 .debug_pubnames 000000a4 0000000000000000
8 .debug_pubtypes 00000308 0000000000000000
9 .debug_frame 00000050 0000000000000000
10 .debug_line 0000005b 0000000000000000
11 .comment 00000013 0000000000000000
12 .symtab 000001b0 0000000000000000
13 .shstrtab 000000a5 0000000000000000
14 .strtab 0000007f 0000000000000000
rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
yunwei@ubuntu:~/Project/os$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -d --arch-name=riscv64
target/riscv64imac-unknown-none-elf/debug/os: file format ELF64-riscv
Disassembly of section .text:
0000000080200000 text_start:
80200000: 09 a0 j 2
80200002: 01 a0 j 0
似乎并没有把错误处理函数编译出来;
OpenSBI
固件(Firmware)把 main.rs
换成:
//! # 全局属性
//! - `#![no_std]`
//! 禁用标准库
#![no_std]
//!
//! - `#![no_main]`
//! 不使用 `main` 函数等全部 Rust-level 入口点来作为程序入口
#![no_main]
//! # 一些 unstable 的功能需要在 crate 层级声明后才可以使用
//! - `#![feature(asm)]`
//! 内嵌汇编
#![feature(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 {}
}
编译的时候会出现:
yunwei@ubuntu:~/Project/os$ cargo build
Compiling os v0.1.0 (/home/yunwei/Project/os)
error[E0658]: use of unstable library feature 'llvm_asm': prefer using the new asm! syntax instead
--> src/main.rs:38:9
|
38 | llvm_asm!("ecall"
| ^^^^^^^^
|
= note: see issue #70173 <https://github.com/rust-lang/rust/issues/70173> for more information
= help: add `#![feature(llvm_asm)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.
error: could not compile `os`.
To learn more, run the command again with --verbose.
根据编译器的提示,在头部添加:
#![feature(llvm_asm)]
编译通过;
尝试建立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 env
# 默认 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
make run
,报错: Makefile:16: *** missing separator. Stop.
问题解决:
如果是直接复制的话,可能会把 Makefile
的 tab
键替换为空格键,而 makefile
的命令行,开头必须用 tab
键;比如把
doc:
@cargo doc --document-private-items
@前面的空格替换为 tab 即可解决问题。
输出:
OpenSBI v0.5 (Oct 9 2019 12:03:04)
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : QEMU Virt Machine
Platform HART Features : RV64ACDFIMSU
Platform Max HARTs : 8
Current Hart : 0
Firmware Base : 0x80000000
Firmware Size : 116 KB
Runtime SBI Version : 0.2
PMP0: 0x0000000080000000-0x000000008001ffff (A)
PMP1: 0x0000000000000000-0xffffffffffffffff (A,R,W,X)
OK
看起来是OK的;
执行 kill
, 退出 qemu
可以考虑去看看 openSBI的文档:https://github.com/riscv/riscv-sbi-doc/blob/master/riscv-sbi.adoc#legacy-sbi-extension-extension-ids-0x00-through-0x0f
新建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!()
}
新建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`]
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
console_putchar(c 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)+)?));
}
}
新建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` 是控制终端字符输出格式的指令,在支持的平台上可以改变文字颜色等等
// 这里使用错误红
// 需要全局开启 feature(panic_info_message) 才可以调用 .message() 函数
// 参考:https://misc.flogisoft.com/bash/tip_colors_and_formatting
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]
//!
//! - `#![deny(missing_docs)]`
//! 任何没有注释的地方都会产生警告:这个属性用来压榨写实验指导的学长,同学可以删掉了
#![warn(missing_docs)]
//! # 一些 unstable 的功能需要在 crate 层级声明后才可以使用
//! - `#![feature(asm)]`
//! 内嵌汇编
#![feature(asm)]
//!
//! - `#![feature(global_asm)]`
//! 内嵌整个汇编文件
#![feature(global_asm)]
//!
//! - `#![feature(llvm_asm)]`
//! 声明需要使用 llvm_asm 宏特性
#![feature(llvm_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")
}
运行:make run
:
OpenSBI v0.5 (Oct 9 2019 12:03:04)
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : QEMU Virt Machine
Platform HART Features : RV64ACDFIMSU
Platform Max HARTs : 8
Current Hart : 0
Firmware Base : 0x80000000
Firmware Size : 116 KB
Runtime SBI Version : 0.2
PMP0: 0x0000000080000000-0x000000008001ffff (A)
PMP1: 0x0000000000000000-0xffffffffffffffff (A,R,W,X)
Hello rCore-Tutorial!
panic: 'end of rust_main'
看起来是好啦!