Skip to content

Commit 7bc54d5

Browse files
finish rust-os chapter 2
Signed-off-by: Henry Gressmann <[email protected]>
1 parent d667556 commit 7bc54d5

File tree

2 files changed

+129
-9
lines changed

2 files changed

+129
-9
lines changed

content/rust-os/1-hello-riscv/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ series = ["rust-os"]
1313

1414
{% quote (class="info")%}
1515

16-
This is a series of posts about my journey creating a kernel in rust. You can find the code for this project [here](https://github.com/explodingcamera/pogos/tree/part-1) and all of the posts in this series [here](/series/os-dev/).
16+
This is a series of posts about my journey creating a kernel in rust. You can find the code for this project [here](https://github.com/explodingcamera/pogos/tree/part-1) and all of the posts in this series [here](/series/rust-os/).
1717

1818
{% end %}
1919

content/rust-os/2-shell/index.md

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
+++
22
transparent = true
3-
title = "Creating a Kernel in Rust #2: Shell [Work in Progress]"
3+
title = "Creating a Kernel in Rust #2: Shell"
44
description = "Creating a simple shell for our kernel to run commands and help us debug"
5-
date = 2023-05-14
5+
date = 2023-07-08
66

77
[taxonomies]
88
tags = ["rust", "riscv", "kernel"]
@@ -11,9 +11,7 @@ series = ["rust-os"]
1111

1212
{% quote (class="info")%}
1313

14-
This is a series of posts about my journey creating a kernel in rust. You can find the code for this project [here](https://github.com/explodingcamera/pogos/tree/part-1) and all of the posts in this series [here](/series/os-dev/).
15-
16-
This post isn't finished yet, but I wanted to get it out so I stop procrastinating on it. More content will follow next week.
14+
This is a series of posts about my journey creating a kernel in rust. You can find the code for this project [here](https://github.com/explodingcamera/pogos/tree/part-2) and all of the posts in this series [here](/series/rust-os/).
1715

1816
{% end %}
1917

@@ -24,7 +22,7 @@ Like I mentioned in the previous post, we can't yet use heap allocated data stru
2422

2523
{{toc}}
2624

27-
# Global Allocator
25+
# Memory Allocators
2826

2927
To better understand what a global allocator is, we'll create a simple linear allocator that will allocate memory from a fixed size buffer. This allocator will only be able to allocate memory, not free it, but it will be enough to get us started.
3028

@@ -67,7 +65,9 @@ impl LinearAllocator {
6765
}
6866
```
6967

70-
We'll also need to implement the `GlobalAlloc` trait, so Rust's `#[global_allocator]` compile built-in knows how to use our allocator. This trait has two methods: `alloc` and `dealloc`. We'll only implement `alloc` for now, since we won't be able to free memory until we implement a more complex allocator.
68+
## Global Allocator
69+
70+
We'll also need to implement the `GlobalAlloc` trait, so Rust's `#[global_allocator]` compile time built-in knows how to use our allocator. This trait has two methods: `alloc` and `dealloc`. We'll only implement `alloc` for now, since we won't be able to free memory with our implementation.
7171

7272
The trait also requires that we mark our implementation as `unsafe` since we are dealing with raw pointers and memory addresses.
7373

@@ -114,7 +114,7 @@ unsafe impl GlobalAlloc for LinearAllocator {
114114
}
115115
```
116116

117-
Now that we have our allocator, we can use it to allocate memory in our kernel. We'll start by allocating a buffer for it to use:
117+
Before we can start using our allocator, we need to initialize it and allocate some memory for it to use. We'll do this in our `src/heap.rs` file:
118118

119119
{{ file(name = "src/heap.rs") }}
120120

@@ -166,3 +166,123 @@ fn main(a0: usize) -> ! {
166166
utils::shutdown();
167167
}
168168
```
169+
170+
Now, we can use our allocator to allocate some memory! Let's create a `Vec` and push some values to it to make sure everything works as expected:
171+
172+
{{ file(name = "src/main.rs") }}
173+
174+
```rust
175+
176+
let mut v = Vec::new();
177+
178+
v.push(1);
179+
v.push(2);
180+
v.push(3);
181+
182+
println!("{:?}", v);
183+
184+
// [1, 2, 3]
185+
```
186+
187+
This is great, but we can't really do much with this allocator since we can't free memory. Alternative allocators are, for example, [linked list allocators](https://os.phil-opp.com/allocator-designs/#linked-list-allocator), [binary buddy allocators](https://www.kernel.org/doc/gorman/html/understand/understand009.html), and [slab allocators](https://www.kernel.org/doc/gorman/html/understand/understand011.html). We won't be implementing any of these in here, but I encourage you to read about them and try to implement them yourself! Some of these are also available as crates on crates.io to use them as drop-in replacements for our naive implementation here ([linked_list_allocator](https://crates.io/crates/linked-list-allocator), [buddy_system_allocator](https://crates.io/crates/buddy_system_allocator) and [slabmalloc](https://crates.io/crates/slabmalloc)).
188+
189+
# Shell
190+
191+
With most essential rust features available, we can now start working on our shell. This shell will allow us to interact with our kernel and inspect its state.
192+
193+
The shell will be a simple loop for now, that reads characters from SBIs `console_getchar` function, and executes some basic commands.
194+
195+
{{ file(name = "src/main.rs") }}
196+
197+
```rust
198+
199+
pub const ENTER: u8 = 13;
200+
pub const BACKSPACE: u8 = 127;
201+
202+
pub fn shell() {
203+
print!("> ");
204+
205+
let mut command = String::new(); // finnaly, we can use heap allocated strings!
206+
207+
loop {
208+
match sbi::legacy::console_getchar() {
209+
Some(ENTER) => {
210+
println!();
211+
process_command(&command);
212+
command.clear();
213+
print!("> ");
214+
}
215+
Some(BACKSPACE) => {
216+
if command.len() > 0 {
217+
command.pop();
218+
print!("{}", BACKSPACE as char)
219+
}
220+
}
221+
}
222+
}
223+
}
224+
225+
226+
fn process_command(command: &str) {
227+
match command {
228+
"help" | "?" | "h" => {
229+
println!("available commands:");
230+
println!(" help print this help message (alias: h, ?)");
231+
println!(" shutdown shutdown the machine (alias: sd, exit)");
232+
}
233+
"shutdown" | "sd" | "exit" => util::shutdown(),
234+
"" => {}
235+
_ => {
236+
println!("unknown command: {command}");
237+
}
238+
};
239+
}
240+
241+
```
242+
243+
```
244+
> help
245+
available commands:
246+
help print this help message (alias: h, ?)
247+
shutdown shutdown the machine (alias: sd, exit)
248+
> exit
249+
```
250+
251+
## Debugging
252+
253+
Being able to shutdown the machine is great and all, but let's add some more functionality. We'll start by adding some commands to trigger different exceptions, so we can test our exception handler from the previous chapter.
254+
255+
{{ file(name = "src/main.rs") }}
256+
257+
```rust
258+
259+
match command {
260+
// ...
261+
"pagefault" => {
262+
// read from an invalid address to trigger a page fault
263+
unsafe { core::ptr::read_volatile(0xdeadbeef as *mut u64); }
264+
}
265+
"breakpoint" => {
266+
// ebreak triggers a breakpoint exception, a trap that can be used for debugging with gdb or similar tools
267+
unsafe { asm!("ebreak") };
268+
}
269+
// ...
270+
}
271+
```
272+
273+
When we now run the `pagefault` command, we'll see that - well - nothing happens. This is because we haven't set up a page table yet, and the kernel is still running in physical memory, something we want to change in the next chapter.
274+
275+
Our breakpoint command, however, will trigger a breakpoint exception, and we'll see the following output:
276+
277+
```
278+
Exception handler called
279+
Trap frame: TrapFrame { ra: 2149605116, t0: 9, t1: 2149632682, t2: 22, t3: 5760, t4: 48, t5: 12288, t6: 1, a0: 8, a1: 0, a2: 2149663232, a3: 2149593222, a4: 110, a5: 2149663744, a6: 4, a7: 1 }
280+
panicked at 'Exception cause: Exception(Breakpoint)', kernel/src/trap.rs:33:5
281+
```
282+
283+
This is great, we can now see the trap frame and the exception cause, so we can start debugging our kernel! To make use of this, you can also use an external debugger like [gdb](https://www.gnu.org/software/gdb/), which allows you to set breakpoints, inspect memory, step through code, and much more.
284+
Setting up gdb is a bit more involved, so if you run into issues check out this [guide](https://os.phil-opp.com/set-up-gdb/). I'm more of a print-debugging and hit my head against the wall until it works kind of guy, so I won't be covering this in detail here.
285+
286+
There's a lot of improvements we can make to this shell, for some ideas, check out my `simple_shell` crate on [crates.io](https://crates.io/crates/simple_shell). I recommend you try to implement some of these yourself, as it's a fun exercise. Something that might be helpful is this ANSI escape code [cheat sheet](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797) for processing user input like arrow keys, backspace, etc.
287+
288+
After this fairly short chapter, we'll add multi-tasking to our kernel using async rust, so we can run multiple programs at the same time and explore paging and virtual memory.

0 commit comments

Comments
 (0)