[rCore学习笔记 06]运行Lib-OS
QEMU运行第一章代码
切换分支
git checkout ch1
detail
git checkout ch1
命令是用来切换到名为 ch1
的分支或者恢复工作目录中的文件到 ch1
提交的状态
运行代码
cd os
LOG=TRACE make run
detail
LOG=TRACE
是指定 LOG 的级别为 TRACE
,可以查看重要程度不低于 TRACE 的输出日志。目前 TRACE 的重要程度最低,因此这样能够看到全部日志
学习Lib-OS的宗旨
引言 - rCore-Tutorial-Book-v3 3.6.0-alpha.1 文档 (rcore-os.cn)
要仔细看最后一段.
创建一个Rust工程
使用Cargo创建Rust工程
cargo new os --bin
detail
os/Cargo.toml
里的内容,记录着这个项目的信息.
[package]
name = "os"
version = "0.1.0"
edition = "2021"
[dependencies]
运行Rust工程
cd os
cargo run
用strace观察应用程序层是怎么调用标准库的
strace target/debug/os
log
所有的log
execve("target/debug/os", ["target/debug/os"], 0x7ffc9e2f62e0 /* 46 vars */) = 0
brk(NULL) = 0x653c719dd000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffed7691920) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7913b1fc9000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=63807, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 63807, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7913b1fb9000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=125488, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 127720, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7913b1f99000
mmap(0x7913b1f9c000, 94208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7913b1f9c000
mmap(0x7913b1fb3000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a000) = 0x7913b1fb3000
mmap(0x7913b1fb7000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d000) = 0x7913b1fb7000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7913b1c00000
mprotect(0x7913b1c28000, 2023424, PROT_NONE) = 0
mmap(0x7913b1c28000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7913b1c28000
mmap(0x7913b1dbd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7913b1dbd000
mmap(0x7913b1e16000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x7913b1e16000
mmap(0x7913b1e1c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7913b1e1c000
close(3) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7913b1f96000
arch_prctl(ARCH_SET_FS, 0x7913b1f96780) = 0
set_tid_address(0x7913b1f96a50) = 4628
set_robust_list(0x7913b1f96a60, 24) = 0
rseq(0x7913b1f97120, 0x20, 0, 0x53053053) = 0
mprotect(0x7913b1e16000, 16384, PROT_READ) = 0
mprotect(0x7913b1fb7000, 4096, PROT_READ) = 0
mprotect(0x653c70293000, 12288, PROT_READ) = 0
mprotect(0x7913b2003000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7913b1fb9000, 63807) = 0
poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7913b1c42520}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
getrandom("\xbd\x19\x85\x71\xcb\xc5\x6b\x55", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x653c719dd000
brk(0x653c719fe000) = 0x653c719fe000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
newfstatat(3, "", {st_mode=S_IFREG|0444, st_size=0, ...}, AT_EMPTY_PATH) = 0
read(3, "653c70241000-653c70247000 r--p 0"..., 1024) = 1024
read(3, "--p 00215000 08:03 133853 "..., 1024) = 1024
read(3, "-7913b1fcd000 r--p 00000000 08:0"..., 1024) = 913
close(3) = 0
sched_getaffinity(4628, 32, [0, 1]) = 16
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGSEGV, {sa_handler=0x653c70265f30, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7913b1c42520}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x653c70265f30, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7913b1c42520}, NULL, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7913b1fc6000
mprotect(0x7913b1fc6000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0x7913b1fc7000, ss_flags=0, ss_size=8192}, NULL) = 0
write(1, "Hello, world!\n", 14Hello, world!
) = 14
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap(0x7913b1fc6000, 12288) = 0
exit_group(0) = ?
+++ exited with 0 +++
这段输出是Linux系统中通过strace
命令跟踪一个程序执行时的系统调用序列。strace
是一个强大的诊断、调试和故障排查工具,能够展示一个程序及其子进程所执行的系统调用,以及这些调用的返回值。下面是对输出中一些关键步骤的简要解析:
execve
: 程序启动,执行target/debug/os
可执行文件,并传递["target/debug/os"]
作为命令行参数,以及环境变量。- 内存管理: 包括
brk
,mmap
,mprotect
等调用,用于分配、映射和调整进程的内存布局。例如,mmap
用于映射匿名内存、加载共享库如libgcc_s.so.1
和libc.so.6
到进程地址空间。 - 动态链接: 通过打开和读取
/etc/ld.so.cache
和相关共享库文件(如libgcc_s.so.1
和libc.so.6
),进行动态链接库的加载准备。 - 权限与安全: 如
arch_prctl
调用用来设置进程的架构特定寄存器,可能涉及到栈保护或其他安全措施。 - 文件访问: 例如尝试访问
/etc/ld.so.preload
来检查是否有额外的动态链接库需要预先加载,以及读取映射共享库内容。 - 信号处理: 如
rt_sigaction
调用,用于设置对特定信号(如SIGPIPE
,SIGSEGV
,SIGBUS
)的处理方式,确保程序能响应这些信号并采取适当行动。 - 资源限制与管理: 通过
prlimit64
调用来获取或设置进程资源限制,比如堆栈大小。 - I/O操作: 如
openat
,read
,write
等,涉及到文件或标准输入输出的读写操作。此处包括读取自身内存映射信息和最终向标准输出写入"Hello, world!\n"。 - 进程终止: 最后,通过
exit_group(0)
调用,程序正常结束,退出状态码为0,表示成功执行。
这段跟踪记录详细展示了程序从启动到执行完毕的底层系统活动,对于理解程序运行时的行为、性能分析或问题排查非常有用。
detail
与 Hello, world!
应用实际执行相关的只有两个系统调用
# 输出字符串
write(1, "Hello, world!\n", 14) = 14
# 程序退出执行
exit_group(0)
观察"被隐藏"的操作系统
strace一个空的程序
mkdir ./empty_c_project
cd ./empty_c_project
gcc -o empty empty.c
strace ./empty
程序内容
int main()
{
return 0;
}
log
execve("./empty", ["./empty"], 0x7ffc5012dd30 /* 46 vars */) = 0
brk(NULL) = 0x57b5ec70b000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe40adc1c0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7009833a7000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=63807, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 63807, PROT_READ, MAP_PRIVATE, 3, 0) = 0x700983397000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0I\17\357\204\3$\f\221\2039x\324\224\323\236S"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x700983000000
mprotect(0x700983028000, 2023424, PROT_NONE) = 0
mmap(0x700983028000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x700983028000
mmap(0x7009831bd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7009831bd000
mmap(0x700983216000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x700983216000
mmap(0x70098321c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x70098321c000
close(3) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x700983394000
arch_prctl(ARCH_SET_FS, 0x700983394740) = 0
set_tid_address(0x700983394a10) = 5271
set_robust_list(0x700983394a20, 24) = 0
rseq(0x7009833950e0, 0x20, 0, 0x53053053) = 0
mprotect(0x700983216000, 16384, PROT_READ) = 0
mprotect(0x57b5ec153000, 4096, PROT_READ) = 0
mprotect(0x7009833e1000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x700983397000, 63807) = 0
exit_group(0) = ?
+++ exited with 0 +++
查看rustc的运默认配置信息
rustc --version --verbose
从其中的 host 一项可以看出默认的目标平台是 x86_64-unknown-linux-gnu
,其中 CPU 架构是 x86_64,CPU 厂商是 unknown,操作系统是 linux,运行时库是 GNU libc(封装了 Linux 系统调用,并提供 POSIX 接口为主的函数库)。
rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-unknown-linux-gnu
release: 1.79.0
LLVM version: 18.1.7
在另一个平台上运行rust
查看rustc支持的平台
rustc --print target-list | grep riscv
可以看到程序列出来的平台:
riscv32gc-unknown-linux-gnu
riscv32gc-unknown-linux-musl
riscv32i-unknown-none-elf
riscv32im-risc0-zkvm-elf
riscv32im-unknown-none-elf
riscv32ima-unknown-none-elf
riscv32imac-esp-espidf
riscv32imac-unknown-none-elf
riscv32imac-unknown-xous-elf
riscv32imafc-esp-espidf
riscv32imafc-unknown-none-elf
riscv32imc-esp-espidf
riscv32imc-unknown-none-elf
riscv64-linux-android
riscv64gc-unknown-freebsd
riscv64gc-unknown-fuchsia
riscv64gc-unknown-hermit
riscv64gc-unknown-linux-gnu
riscv64gc-unknown-linux-musl
riscv64gc-unknown-netbsd
riscv64gc-unknown-none-elf
riscv64gc-unknown-openbsd
riscv64imac-unknown-none-elf
这里我们选择 riscv64gc-unknown-none-elf
目标平台。这其中的 CPU 架构是 riscv64gc ,CPU厂商是 unknown ,操作系统是 none , elf 表示没有标准的运行时库(表明没有任何系统调用的封装支持),但可以生成 ELF 格式的执行程序。
detail
rustc
: 这是Rust编程语言的编译器命令行工具。它用于编译Rust源代码到各种不同的目标平台上,包括但不限于各种CPU架构和操作系统。--print target-list
: 这是一个rustc
的命令行选项,用来打印出rustc支持的所有编译目标(target triples)。Target triple是一种描述编译目标平台的字符串格式,通常包含架构、操作系统和环境信息,比如x86_64-unknown-linux-gnu
表示64位x86架构、未知操作系统、GNU库环境。|
(管道符号): 这是一个Unix/Linux shell命令,用于将前一个命令的输出作为后一个命令的输入。在这里,它把rustc --print target-list
的输出作为下一个命令(grep riscv
)的输入。grep riscv
:grep
是一个文本搜索工具,用于在输入数据中查找包含指定模式的行。这里使用grep riscv
来过滤出那些包含“riscv”字符串的行。由于前面的命令列出了所有支持的编译目标,这一步就是从这些目标中筛选出与RISC-V架构相关的所有目标。
去掉操作系统支持,编译rust
cargo run --target riscv64gc-unknown-none-elf
log
这里重点关注关于 error[E0463]
的表述,是编译器找不到rust的std
库,
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
warning: `/home/winddevil/.cargo/config` is deprecated in favor of `config.toml`
note: if you need to support cargo 1.38 or earlier, you can symlink `config` to `config.toml`
Compiling os v0.1.0 (/home/winddevil/workspace/os)
error[E0463]: can't find crate for `std`
|
= note: the `riscv64gc-unknown-none-elf` target may not support the standard library
= note: `std` is required by `os` because it does not declare `#![no_std]`
error: cannot find macro `println` in this scope
--> src/main.rs:2:5
|
2 | println!("Hello, world!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
error: requires `sized` lang_item
For more information about this error, try `rustc --explain E0463`.
error: could not compile `os` (bin "os") due to 4 previous errors
detail
当你运行 cargo run --target riscv64gc-unknown-none-elf
命令时,你是在使用Rust的包管理器Cargo来构建并运行一个Rust项目,但目标平台设定为了RISC-V 64位架构、具有GC(General Purpose,通用)扩展、面向一个未知且没有操作系统的环境(通常指嵌入式系统或微控制器)。下面是这个命令的几个关键点解析:
cargo run
: 这个命令告诉Cargo构建当前包(默认是项目的根crate)并随后运行生成的可执行文件。它相当于先执行cargo build
,然后执行生成的二进制文件。--target
: 这是一个选项,用于指定构建的目标架构和平台。这对于交叉编译特别重要,即在一种架构上编译代码,使其能在另一种架构上运行。
riscv64gc-unknown-none-elf
: 这个目标三元组定义了编译的目标平台特征:- riscv64gc: 指定目标架构为RISC-V 64位版本,带GC(General Purpose)扩展,这通常意味着启用了像乘法和除法这样的基本指令集扩展。
- unknown: 表示供应商或制造商未知,这在嵌入式开发中很常见,因为目标硬件可能不是由知名的商业公司生产。
- none: 表明目标系统没有操作系统。这对于裸机编程(bare metal programming)、微控制器编程或自定义OS开发非常重要。
- elf: 表明输出格式为ELF(Executable and Linkable Format),这是一种常用的可执行文件、目标文件和核心转储格式,适用于多种操作系统和体系结构。
使用rust core来代替std
Rust 有一个对 Rust 语言标准库–std 裁剪过后的 Rust 语言核心库 core。core库是不需要任何操作系统支持的,它的功能也比较受限,但是也包含了 Rust 语言相当一部分的核心机制,可以满足我们的大部分功能需求.