1 minute read

写一个最小的可执行文件

首先去掉 std

#![no_std]
fn main() {
    println!("Hello, world!");
}

出现错误

error: cannot find macro `println` in this scope
 --> src/main.rs:3:5
  |
3 |     println!("Hello, world!");
  |     ^^^^^^^

error: `#[panic_handler]` function required, but not found

error: unwinding panics are not supported without std
  |
  = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding
  = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem

error: could not compile `candy` (bin "candy") due to 3 previous errors

按照rust 编译器的提示

  1. 没有 println! 这个宏,因为我们没有引入 std,所以我们直接把这个去掉,用一个 loop 代替。
  2. 需要一个 #[panic_handler] 函数,所有的程序都必须有一个 panic 处理的函数,否则我们出错了就不知道怎么办,从这里就可以看出来错误处理多么重要,我们永远都要为自己写的程序留后路,os 更是如此。
  3. 根据提示我们需要添加编译选项,然后设置 panic="abort" 于是修改程序成下面的样子 ```rust #![no_std] use core::panic::PanicInfo; fn main() { loop{

    } }

#[panic_handler] fn panic(_info: &PanicInfo) -> ! { loop {} }

```toml
# Cargo.toml
[package]
name = "candy"
version = "0.1.0"
edition = "2021"

[dependencies]

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

在命令行启动程序

cargo -Zbuild-std run --target x86_64-unknown-none

出现了下面的错误

error: using `fn main` requires the standard library
  |
  = help: use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`

error: could not compile `candy` (bin "candy") due to 1 previous error

编译器提示我们 main 函数是需要标准库的,所以我们需要使用 #![no_main] 的方式来跳过 rust 的入口点,而是自己定义一个入口点,因为我们现在没有语言运行时, main 函数是语言运行时来调用的,语言运行时有自己的入口点 _start,我们需要重写覆盖 _start 这个入口点。并且我们需要 [no_mangle] 来禁止函数被改名 于是我们的代码就是下面这样了。

#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop{

    }
}

我们需要选择一个没有操作系统的编译目标,否则我们将会遇到编译和链接的错误,因为这两个过程都是假定我们有操作系统的存在 此时我们已经禁用 std,所以我们不再需要 -Zbuild-std 的编译标志

cargo build --target thumbv7em-none-eabihf

现在我们什么都没做。

现在我们需要为 x86_64-unknown-none 这个编译目标编译我们的程序 rustc -Z unstable-options --print target-spec-json --target x86_64-unknown-none 这个命令能看到指定编译目标的 json 配置文件

{
  "arch": "x86_64",
  "code-model": "kernel",
  "cpu": "x86-64",
  "crt-objects-fallback": "false",
  "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
  "disable-redzone": true,
  "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float",
  "is-builtin": true,
  "linker": "rust-lld",
  "linker-flavor": "gnu-lld", # 这个冲突
  "llvm-target": "x86_64-unknown-none-elf",
  "max-atomic-width": 64,
  "metadata": {
    "description": null,
    "host_tools": null,
    "std": null,
    "tier": null
  },
  "panic-strategy": "abort",
  "plt-by-default": false,
  "position-independent-executables": true,  # 这个冲突
  "relro-level": "full",
  "stack-probes": {
    "kind": "inline"
  },
  "static-position-independent-executables": true,
  "supported-sanitizers": [
    "kcfi",
    "kernel-address"
  ],
  "target-pointer-width": "64"
}

如果你需要特定的编译目标,也可以配置自己的 json 文件,然后在编译的时候指定 cargo build --target xx-xx-xx.json

输出 hello world

前提:我们知道 VGA 缓冲区。25 行,每行 80 个字符单元。缓冲区地址位于 0xb8000,每个字符单元由一个 ASCII字节和一个彩色字节组成

volatile 防止编译器做优化,确保我们每次读区都是从内存中读,而不是从缓存中。

static 变量是在编译时初始化的,而不是在运行时初始化的,所以一个静态变量必须具有常量值,如果其中有编译时不确定的值,那么我们需要对其进行包装。我们的目的是让静态变量在第一次使用时初始化,而不是在编译时初始化。我们有几种选择

  1. 使用 refcell
  2. 使用 lazy_static
  3. 使用 lazy_static!

写一下这里的逻辑

首先,批处理操作系统的三个用户程序写好了,通过链接脚本链接在一起,然后呢,在 lib.rs 中有一个 _start 函数,就是链接脚本中的 .entry.text。 如果我们现在现在开始启动应用程序,我们就会进入到 _start 函数,然后这个 main() 函数是谁呢?我们现在好像并不知道,现在要有一个 link_app.S 这些 APP 就以一种我们知道的方式链接在一起,并且我们可以找到。 那么现在,我们需要完成下面的事情:

  1. 管理这些 APP,让他们能够顺序执行现在的 APP。也就是说知道现在的APP个数,然后一个循环,清空 APP 执行环境,把APP里的起始地址到终止地址的内容读到指定区域,然后执行,然后退出这个APP,进入下一个。
  2. 在 _start 中跳入到现在的管理 APP 函数。
  3. APP 中调用的函数应该都是自己手写的,跟任何东西无关的。

Reference

1

.cargo/config.toml 配置

Updated:

Comments