Skip to content

Commit ab34e1d

Browse files
committed
Add basic example
- showcase an OS that boots a minimal kernel using bootloader - update usage guides Signed-off-by: Pepper Gray <hello@peppergray.xyz>
1 parent def819c commit ab34e1d

File tree

16 files changed

+1428
-107
lines changed

16 files changed

+1428
-107
lines changed

.github/workflows/ci.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ jobs:
8585
if: runner.os == 'Linux'
8686
run: cargo test --no-default-features --features bios
8787

88+
examples:
89+
name: Build examples
90+
runs-on: ubuntu-latest
91+
timeout-minutes: 5
92+
steps:
93+
- uses: actions/checkout@v3
94+
- uses: Swatinem/rust-cache@v2
95+
- uses: r7kamura/rust-problem-matchers@v1.1.0
96+
- name: Install QEMU (Linux)
97+
run: sudo apt update && sudo apt install qemu-system-x86
98+
- name: "Build basic example"
99+
run: cargo build
100+
working-directory: examples/basic
101+
- name: "Run basic example (bios)"
102+
run: cargo run -- bios || [ $? -eq 33 ]
103+
working-directory: examples/basic
104+
- name: "Run basic example (uefi)"
105+
run: cargo run -- uefi || [ $? -eq 33 ]
106+
working-directory: examples/basic
107+
88108
fmt:
89109
name: Check Formatting
90110
runs-on: ubuntu-latest

README.md

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too
1414

1515
To use this crate, you need to adjust your kernel to be bootable first. Then you can create a bootable disk image from your compiled kernel. These steps are explained in detail below.
1616

17+
### Migrating from older bootloader version
18+
1719
If you're already using an older version of the `bootloader` crate, follow our [migration guides](docs/migration).
1820

19-
### Kernel
21+
### Starting from scratch
22+
23+
Our [basic example](examples/basic/basic-os.md) showcases an OS that boots a minimal kernel using `bootloader`.
24+
25+
### Using an existing kernel
2026

21-
To make your kernel compatible with `bootloader`:
27+
To combine your kernel with `bootloader` and create a bootable disk image, follow these steps:
28+
29+
#### Make your kernel compatible with `bootloader`
2230

2331
- Add a dependency on the `bootloader_api` crate in your kernel's `Cargo.toml`.
2432
- Your kernel binary should be `#![no_std]` and `#![no_main]`.
@@ -35,37 +43,57 @@ To make your kernel compatible with `bootloader`:
3543
};
3644
bootloader_api::entry_point!(kernel_main, config = &CONFIG);
3745
```
38-
- Compile your kernel to an ELF executable by running **`cargo build --target x86_64-unknown-none`**. You might need to run `rustup target add x86_64-unknown-none` before to download precompiled versions of the `core` and `alloc` crates.
46+
- Compile your kernel to an ELF executable by running **`cargo build --target x86_64-unknown-none`**. You might need to run `rustup target add x86_64-unknown-none` before to download precompiled versions of the `core` and `alloc` crates. You can add `x86_64-unknown-none` as default target and add it to your toolchain so that `cargo build` takes care of this.
47+
```toml
48+
# .cargo/config.toml
49+
[build]
50+
target = "x86_64-unknown-none"
51+
```
52+
```sh
53+
$ cargo build
54+
```
3955
- Thanks to the `entry_point` macro, the compiled executable contains a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it.
4056

41-
### Booting
42-
43-
To combine your kernel with a bootloader and create a bootable disk image, follow these steps:
57+
#### Setup the workspace
4458

4559
- Move your full kernel code into a `kernel` subdirectory.
4660
- Create a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html).
47-
- Add a `build-dependencies` on the `bootloader` crate.
48-
- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script.
49-
- Set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) to add your `kernel` crate as a `build-dependency`:
50-
```toml
51-
# in Cargo.toml
52-
[build-dependencies]
53-
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
54-
```
55-
```toml
56-
# .cargo/config.toml
57-
58-
[unstable]
59-
# enable the unstable artifact-dependencies feature, see
60-
# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
61-
bindeps = true
62-
```
63-
Alternatively, you can use [`std::process::Command`](https://doc.rust-lang.org/stable/std/process/struct.Command.html) to invoke the build command of your kernel in the `build.rs` script.
64-
- Obtain the path to the kernel executable. When using an artifact dependency, you can retrieve this path using `std::env::var_os("CARGO_BIN_FILE_MY_KERNEL_my-kernel")`
65-
- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel.
61+
```sh
62+
$ cargo new os --bin
63+
```
64+
- Add your kernel as a workspace member
65+
```toml
66+
# in Cargo.toml
67+
[workspace]
68+
resolver = "3"
69+
members = ["kernel"]
70+
```
71+
- Enable the workspace to build your kernel:
72+
- Set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) to add your `kernel` crate as a `build-dependency`:
73+
```toml
74+
# in Cargo.toml
75+
[build-dependencies]
76+
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
77+
```
78+
Enable the unstable artifact-dependencies feature:
79+
```toml
80+
# .cargo/config.toml
81+
[unstable]
82+
bindeps = true
83+
```
84+
Experimental features are only available on the nightly channel:
85+
```toml
86+
# rust-toolchain.toml
87+
[toolchain]
88+
channel = "nightly"
89+
targets = ["x86_64-unknown-none"]
90+
```
91+
- Alternatively, you can use [`std::process::Command`](https://doc.rust-lang.org/stable/std/process/struct.Command.html) to invoke the build command of your kernel in the `build.rs` script.
92+
- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script in the `os` crate. See our [disk image creation template](docs/create-disk-image.md) for a more detailed example.
93+
- Obtain the path to the kernel executable. When using an artifact dependency, you can retrieve this path using `std::env::var_os("CARGO_BIN_FILE_MY_KERNEL_my-kernel")`
94+
- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel.
6695
- Do something with the bootable disk images in your `main.rs` function. For example, run them with QEMU.
67-
68-
See our [disk image creation template](docs/create-disk-image.md) for a more detailed example.
96+
6997

7098
## Architecture
7199

docs/create-disk-image.md

Lines changed: 15 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,83 +5,18 @@ The [`bootloader`](https://docs.rs/bootloader/0.11) crate provides simple functi
55
A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create
66
a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function.
77

8-
The files could look like this:
9-
10-
```toml
11-
# .cargo/config.toml
12-
13-
[unstable]
14-
# enable the unstable artifact-dependencies feature, see
15-
# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
16-
bindeps = true
17-
```
18-
19-
```toml
20-
# Cargo.toml
21-
22-
[package]
23-
name = "os" # or any other name
24-
version = "0.1.0"
25-
26-
[build-dependencies]
27-
bootloader = "0.11"
28-
test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
29-
30-
[dependencies]
31-
# used for UEFI booting in QEMU
32-
ovmf-prebuilt = "0.1.0-alpha.1"
33-
34-
[workspace]
35-
members = ["kernel"]
36-
```
37-
38-
```rust
39-
// build.rs
40-
41-
use std::path::PathBuf;
42-
43-
fn main() {
44-
// set by cargo, build scripts should use this directory for output files
45-
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
46-
// set by cargo's artifact dependency feature, see
47-
// https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
48-
let kernel = PathBuf::from(std::env::var_os("CARGO_BIN_FILE_KERNEL_kernel").unwrap());
49-
50-
// create an UEFI disk image (optional)
51-
let uefi_path = out_dir.join("uefi.img");
52-
bootloader::UefiBoot::new(&kernel).create_disk_image(&uefi_path).unwrap();
53-
54-
// create a BIOS disk image
55-
let bios_path = out_dir.join("bios.img");
56-
bootloader::BiosBoot::new(&kernel).create_disk_image(&bios_path).unwrap();
57-
58-
// pass the disk image paths as env variables to the `main.rs`
59-
println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display());
60-
println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display());
61-
}
62-
```
63-
64-
```rust
65-
// src/main.rs
66-
67-
fn main() {
68-
// read env variables that were set in build script
69-
let uefi_path = env!("UEFI_PATH");
70-
let bios_path = env!("BIOS_PATH");
71-
72-
// choose whether to start the UEFI or BIOS image
73-
let uefi = true;
74-
75-
let mut cmd = std::process::Command::new("qemu-system-x86_64");
76-
if uefi {
77-
cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
78-
cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}"));
79-
} else {
80-
cmd.arg("-drive").arg(format!("format=raw,file={bios_path}"));
81-
}
82-
let mut child = cmd.spawn().unwrap();
83-
child.wait().unwrap();
84-
}
85-
```
86-
87-
Now you should be able to use `cargo build` to create a bootable disk image and `cargo run` to run in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your `main.rs` to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive).
8+
Our [basic example](examples/basic/basic-os.md) showcases this setup:
9+
- [Cargo.toml](/examples/basic/Cargo.toml)
10+
- create a workspace & add kernel as member
11+
- add kernel as build-dependency
12+
- add ovmf-prebuilt for UEFI booting in QEMU
13+
- [.cargo/config.toml](/examples/basic/Cargo.toml)
14+
- enable the unstable artifact-dependencies feature
15+
- [rust-toolchain.toml](/examples/basic/Cargo.toml)
16+
- use experimental features (nightly)
17+
- [build.rs](/examples/basic/build.rs)
18+
- create bios and uefi disk image
19+
- [src/main.rs](/examples/basic/src/main.rs])
20+
- launch the image using QEMU
21+
22+
Now you should be able to use `cargo build` to create a bootable disk image and `cargo run bios` and `cargo run uefi` to run it in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your `main.rs` to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive).

examples/basic/.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[unstable]
2+
bindeps = true

examples/basic/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target/
2+
**/*.rs.bk

0 commit comments

Comments
 (0)