MiaOS user app
写一个最小的可执行文件
首先去掉 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 编译器的提示
- 没有
println!
这个宏,因为我们没有引入 std,所以我们直接把这个去掉,用一个 loop 代替。 - 需要一个
#[panic_handler]
函数,所有的程序都必须有一个 panic 处理的函数,否则我们出错了就不知道怎么办,从这里就可以看出来错误处理多么重要,我们永远都要为自己写的程序留后路,os 更是如此。 -
根据提示我们需要添加编译选项,然后设置
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 变量是在编译时初始化的,而不是在运行时初始化的,所以一个静态变量必须具有常量值,如果其中有编译时不确定的值,那么我们需要对其进行包装。我们的目的是让静态变量在第一次使用时初始化,而不是在编译时初始化。我们有几种选择
- 使用 refcell
- 使用 lazy_static
- 使用 lazy_static!
写一下这里的逻辑
首先,批处理操作系统的三个用户程序写好了,通过链接脚本链接在一起,然后呢,在 lib.rs 中有一个 _start 函数,就是链接脚本中的 .entry.text。 如果我们现在现在开始启动应用程序,我们就会进入到 _start 函数,然后这个 main() 函数是谁呢?我们现在好像并不知道,现在要有一个 link_app.S 这些 APP 就以一种我们知道的方式链接在一起,并且我们可以找到。 那么现在,我们需要完成下面的事情:
- 管理这些 APP,让他们能够顺序执行现在的 APP。也就是说知道现在的APP个数,然后一个循环,清空 APP 执行环境,把APP里的起始地址到终止地址的内容读到指定区域,然后执行,然后退出这个APP,进入下一个。
- 在 _start 中跳入到现在的管理 APP 函数。
- APP 中调用的函数应该都是自己手写的,跟任何东西无关的。