Skip to content

Commit 117a549

Browse files
committed
update chapter8 part3: link user image
use build script to ensure linking the latest user image
1 parent c8e0f95 commit 117a549

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

chapter8/part3.md

+51-13
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,64 @@
44

55
我们只需将最终得到的可执行文件直接链接到内核中即可。
66

7+
这里的实现有一些技巧,我们先写一个[编译脚本](https://doc.rust-lang.org/cargo/reference/build-scripts.html) `build.rs`。注意是直接放在项目文件夹 `os` 中,而不是源码文件夹 `src`
8+
79
```rust
8-
// src/init.rs
10+
// build.rs
11+
12+
use std::fs::File;
13+
use std::io::{Result, Write};
14+
15+
fn main() {
16+
println!("cargo:rerun-if-env-changed=USER_IMG");
17+
if let Ok(user_img) = std::env::var("USER_IMG") {
18+
println!("cargo:rerun-if-changed={}", user_img);
19+
}
20+
gen_link_user_asm().unwrap();
21+
}
922

10-
global_asm!(concat!(
11-
r#"
12-
.section .data
13-
.global _user_img_start
14-
.global _user_img_end
23+
/// Generate assembly file for linking user image
24+
fn gen_link_user_asm() -> Result<()> {
25+
let mut f = File::create("src/link_user.S").unwrap();
26+
let user_img = std::env::var("USER_IMG").unwrap();
27+
28+
writeln!(f, "# generated by build.rs - do not edit")?;
29+
writeln!(f, r#"
30+
.section .data
31+
.global _user_img_start
32+
.global _user_img_end
1533
_user_img_start:
16-
.incbin ""#,
17-
env!("USER_IMG"),
18-
r#""
34+
.incbin "{}"
1935
_user_img_end:
20-
"#
21-
));
36+
"#, user_img)?;
37+
Ok(())
38+
}
2239
```
2340

24-
在编译时,编译器会将当前终端环境变量 ``USER_IMG`` 指向的路径对应的文件链接到 $$\text{.data}$$ 段中,而且我们可以通过两个符号得知它所在的虚拟地址。
41+
然后在 `init.rs` 中加入:
42+
43+
```rust
44+
// init.rs
45+
46+
global_asm!(include_str!("link_user.S"));
47+
```
48+
49+
这段编译脚本会在每次编译的**最开始**运行。它的作用是生成一段汇编代码,将用户程序镜像文件原封不动地链接到内核的 $$\text{.data}$$ 段中。这段汇编被生成到 `src/link_user.S` 文件中,然后我们在 `init.rs` 里把它导入进来。此后可以在其它地方通过 `_user_img_start``_user_img_end` 这两个符号得知它所在的虚拟地址。
50+
51+
我们用一个环境变量 ``USER_IMG`` 记录用户程序镜像的路径,编译脚本在执行时,会将这个字符串填入生成的汇编中。所以我们只需在编译之前利用 ``export `` 修改环境变量 ``USER_IMG`` 为我们最终得到的可执行文件的路径即可。
52+
53+
最后让我们关注一开始的两条奇怪语句:
54+
55+
```rust
56+
println!("cargo:rerun-if-env-changed=USER_IMG");
57+
println!("cargo:rerun-if-changed={}", user_img);
58+
```
59+
60+
这是编译脚本发送给构建工具 cargo 的特殊指令,含义是:当检测到环境变量 `USER_IMG` 或者它所指向的文件发生变化时,就强制重新编译。并且每次编译时,都会生成一个新的 `link_user.S` 文件。
61+
62+
这波操作要解决的问题是:由于编译器具有**自动增量构建**的特性,会导致当用户镜像发生变化时,编译器无法自动感知到,最后链接的还是以前的版本,使得我们不得不手动 `cargo clean` 清理干净中间产物后重新编译。
2563

26-
所以我们只需在编译之前利用 ``export `` 修改环境变量 ``USER_IMG`` 为我们最终得到的可执行文件的路径即可。
64+
现在,我们每次更新完用户程序镜像后,都可以放心地直接 `make run` 了!
2765

2866
### elf 文件解析与内存空间创建
2967

chapter9/part1.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,5 @@ pub extern "C" fn rust_main() -> ! {
214214
}
215215
```
216216

217-
需要注意的是,就是我们内核的 ``make run`` ,不能检查到磁盘文件的变化,即使磁盘文件发生了变化它也不会将新的磁盘文件链接到内核中。因此一旦你修改了磁盘文件,构建内核时则需要强制重新构建:即 ``make clean && make run``
217+
程序的运行结果仍与上一节一致,但我们已经用上了文件系统!但是现在问题在于我们运行什么程序是硬编码到内核中的。我们能不能实现一个交互式的终端,告诉内核我们想要运行哪个程序呢?接下来我们就来做这件事情!
218218

219-
程序的运行结果仍与上一节一致,但我们已经用上了文件系统!但是现在问题在于我们运行什么程序是硬编码到内核中的。我们能不能实现一个交互式的终端,告诉内核我们想要运行哪个程序呢?接下来我们就来做这件事情!

0 commit comments

Comments
 (0)