[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是一个强大的诊断、调试和故障排查工具,能够展示一个程序及其子进程所执行的系统调用,以及这些调用的返回值。下面是对输出中一些关键步骤的简要解析:

  1. execve: 程序启动,执行target/debug/os可执行文件,并传递["target/debug/os"]作为命令行参数,以及环境变量。
  2. 内存管理: 包括brk, mmap, mprotect等调用,用于分配、映射和调整进程的内存布局。例如,mmap用于映射匿名内存、加载共享库如libgcc_s.so.1libc.so.6到进程地址空间。
  3. 动态链接: 通过打开和读取/etc/ld.so.cache和相关共享库文件(如libgcc_s.so.1libc.so.6),进行动态链接库的加载准备。
  4. 权限与安全: 如arch_prctl调用用来设置进程的架构特定寄存器,可能涉及到栈保护或其他安全措施。
  5. 文件访问: 例如尝试访问/etc/ld.so.preload来检查是否有额外的动态链接库需要预先加载,以及读取映射共享库内容。
  6. 信号处理: 如rt_sigaction调用,用于设置对特定信号(如SIGPIPE, SIGSEGV, SIGBUS)的处理方式,确保程序能响应这些信号并采取适当行动。
  7. 资源限制与管理: 通过prlimit64调用来获取或设置进程资源限制,比如堆栈大小。
  8. I/O操作: 如openat, read, write等,涉及到文件或标准输入输出的读写操作。此处包括读取自身内存映射信息和最终向标准输出写入"Hello, world!\n"。
  9. 进程终止: 最后,通过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

  1. rustc: 这是Rust编程语言的编译器命令行工具。它用于编译Rust源代码到各种不同的目标平台上,包括但不限于各种CPU架构和操作系统。
  2. --print target-list: 这是一个rustc的命令行选项,用来打印出rustc支持的所有编译目标(target triples)。Target triple是一种描述编译目标平台的字符串格式,通常包含架构、操作系统和环境信息,比如x86_64-unknown-linux-gnu表示64位x86架构、未知操作系统、GNU库环境。
  3. |(管道符号): 这是一个Unix/Linux shell命令,用于将前一个命令的输出作为后一个命令的输入。在这里,它把rustc --print target-list的输出作为下一个命令(grep riscv)的输入。
  4. 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 语言相当一部分的核心机制,可以满足我们的大部分功能需求.

posted @ 2024-07-09 19:49  winddevil  阅读(120)  评论(0编辑  收藏  举报