|
4 | 4 |
|
5 | 5 | 我们只需将最终得到的可执行文件直接链接到内核中即可。
|
6 | 6 |
|
| 7 | +这里的实现有一些技巧,我们先写一个[编译脚本](https://doc.rust-lang.org/cargo/reference/build-scripts.html) `build.rs`。注意是直接放在项目文件夹 `os` 中,而不是源码文件夹 `src`: |
| 8 | + |
7 | 9 | ```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 | +} |
9 | 22 |
|
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 |
15 | 33 | _user_img_start:
|
16 |
| - .incbin ""#, |
17 |
| - env!("USER_IMG"), |
18 |
| - r#"" |
| 34 | + .incbin "{}" |
19 | 35 | _user_img_end:
|
20 |
| -"# |
21 |
| -)); |
| 36 | +"#, user_img)?; |
| 37 | + Ok(()) |
| 38 | +} |
22 | 39 | ```
|
23 | 40 |
|
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` 清理干净中间产物后重新编译。 |
25 | 63 |
|
26 |
| -所以我们只需在编译之前利用 ``export `` 修改环境变量 ``USER_IMG`` 为我们最终得到的可执行文件的路径即可。 |
| 64 | +现在,我们每次更新完用户程序镜像后,都可以放心地直接 `make run` 了! |
27 | 65 |
|
28 | 66 | ### elf 文件解析与内存空间创建
|
29 | 67 |
|
|
0 commit comments