mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-17 23:07:50 +00:00
Fix dead links
This commit is contained in:
@@ -245,7 +245,7 @@ It needs to be a function with a defined [calling convention], as it called dire
|
||||
|
||||
It is important that the function is [diverging], i.e. it must never return. The reason is that the hardware doesn't _call_ the handler functions, it just _jumps_ to them after pushing some values to the stack. So our stack might look different:
|
||||
|
||||
[diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions
|
||||
[diverging]: https://doc.rust-lang.org/rust-by-example/fn/diverging.html
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -126,9 +126,9 @@ Instead of pushing a return address, the CPU pushes the stack and instruction po
|
||||
So we can't use a normal `ret` instruction, since it expects a different stack frame layout. Instead, there is a special instruction for returning from exceptions: `iretq`.
|
||||
|
||||
### The `iretq` Instruction
|
||||
The `iretq` instruction is the one and only way to return from exceptions and is specifically designed for this purpose. The AMD64 manual ([PDF][amd-manual]) even demands that `iretq` “_must_ be used to terminate the exception or interrupt handler associated with the exception”.
|
||||
The `iretq` instruction is the one and only way to return from exceptions and is specifically designed for this purpose. The AMD64 instruction manual ([PDF][amd-manual]) even demands that `iretq` “_must_ be used to terminate the exception or interrupt handler associated with the exception”.
|
||||
|
||||
[amd-manual]: https://support.amd.com/TechDocs/24594.pdf
|
||||
[amd-manual]: https://www.amd.com/system/files/TechDocs/24594.pdf
|
||||
|
||||
IRETQ restores `rip`, `cs`, `rflags`, `rsp`, and `ss` from the values saved on the stack and thus continues the interrupted program. The instruction does not handle the optional error code, so it must be popped from the stack before.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Remote 'g' packet reply is too long: [a very long number]
|
||||
```
|
||||
This issue is known [since 2012][gdb issue patch] but it is still not fixed. Maybe we find the reason in the [issue thread][gdb issue thread]:
|
||||
|
||||
[gdb issue patch]: http://www.cygwin.com/ml/gdb-patches/2012-03/msg00116.html
|
||||
[gdb issue patch]: https://web.archive.org/web/20190114181420/http://www.cygwin.com/ml/gdb-patches/2012-03/msg00116.html
|
||||
[gdb issue thread]: https://sourceware.org/bugzilla/show_bug.cgi?id=13984#c11
|
||||
|
||||
> from my (limited) experience, unless you ping the gdb-patches list weekly, this patch is more likely to remain forgotten :-)
|
||||
|
||||
@@ -49,7 +49,7 @@ crate-type = ["staticlib"]
|
||||
```
|
||||
The `package` section contains required project metadata such as the [semantic crate version]. The `lib` section specifies that we want to build a static library, i.e. a library that contains all of its dependencies. This is required to link the Rust project with our kernel.
|
||||
|
||||
[semantic crate version]: http://doc.crates.io/manifest.html#the-package-section
|
||||
[semantic crate version]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-package-section
|
||||
|
||||
Now we place our root source file in `src/lib.rs`:
|
||||
|
||||
@@ -408,7 +408,7 @@ So the linker can't find a function named `_Unwind_Resume` that is referenced e.
|
||||
|
||||
By default, the destructors of all stack variables are run when a `panic` occurs. This is called _unwinding_ and allows parent threads to [recover from panics]. However, it requires a platform specific gcc library, which isn't available in our kernel.
|
||||
|
||||
[recover from panics]: https://doc.rust-lang.org/book/concurrency.html#panics
|
||||
[recover from panics]: https://www.howtobuildsoftware.com/index.php/how-do/fFH/rust-recovering-from-panic-in-another-thread
|
||||
|
||||
Fortunately, Rust allows us to disable unwinding for our target. For that we add the following line to our `x86_64-blog_os.json` file:
|
||||
|
||||
@@ -479,7 +479,7 @@ Some notes:
|
||||
- `buffer_ptr` is a [raw pointer] that points to the center of the VGA text buffer
|
||||
- Rust doesn't know the VGA buffer and thus can't guarantee that writing to the `buffer_ptr` is safe (it could point to important data). So we need to tell Rust that we know what we are doing by using an [unsafe block].
|
||||
|
||||
[byte string]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
||||
[byte string]: https://doc.rust-lang.org/reference/tokens.html#characters-and-strings
|
||||
[enumerate]: https://doc.rust-lang.org/nightly/core/iter/trait.Iterator.html#method.enumerate
|
||||
[unsafe block]: https://doc.rust-lang.org/book/unsafe.html
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ When printing a byte, the writer checks if the current line is full. In that cas
|
||||
|
||||
The `buffer()` auxiliary method converts the raw pointer in the `buffer` field into a safe mutable buffer reference. The unsafe block is needed because the [as_mut()] method of `Unique` is unsafe. But our `buffer()` method itself isn't marked as unsafe, so it must not introduce any unsafety (e.g. cause segfaults). To guarantee that, it's very important that the `buffer` field always points to a valid `Buffer`. It's like a contract that we must stand to every time we create a `Writer`. To ensure that it's not possible to create an invalid `Writer` from outside of the module, the struct must have at least one private field and public creation functions are not allowed either.
|
||||
|
||||
[as_mut()]: https://doc.rust-lang.org/1.10.0/core/ptr/struct.Unique.html#method.as_mut
|
||||
[as_mut()]: https://doc.rust-lang.org/1.26.0/core/ptr/struct.Unique.html#method.as_mut
|
||||
|
||||
### Cannot Move out of Borrowed Content
|
||||
When we try to compile it, we get the following error:
|
||||
@@ -257,7 +257,7 @@ It just creates a new Writer that points to the VGA buffer at `0xb8000`. To use
|
||||
|
||||
Then it writes the byte `b'H'` to it. The `b` prefix creates a [byte character], which represents an ASCII code point. When we call `vga_buffer::print_something` in main, a `H` should be printed in the _lower_ left corner of the screen in light green:
|
||||
|
||||
[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
||||
[byte character]: https://doc.rust-lang.org/reference/tokens.html#characters-and-strings
|
||||
|
||||

|
||||
|
||||
@@ -441,14 +441,14 @@ But we can't use it to print anything! You can try it yourself in the `print_som
|
||||
|
||||
To resolve it, we could use a [mutable static]. But then every read and write to it would be unsafe since it could easily introduce data races and other bad things. Using `static mut` is highly discouraged, there are even proposals to [remove it][remove static mut].
|
||||
|
||||
[mutable static]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[mutable static]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[remove static mut]: https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437
|
||||
|
||||
But what are the alternatives? We could try to use a cell type like [RefCell] or even [UnsafeCell] to provide [interior mutability]. But these types aren't [Sync] \(with good reason), so we can't use them in statics.
|
||||
|
||||
[RefCell]: https://doc.rust-lang.org/nightly/core/cell/struct.RefCell.html
|
||||
[UnsafeCell]: https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html
|
||||
[interior mutability]: https://doc.rust-lang.org/book/mutability.html#interior-vs.-exterior-mutability
|
||||
[interior mutability]: https://doc.rust-lang.org/1.30.0/book/first-edition/mutability.html#interior-vs-exterior-mutability
|
||||
[Sync]: https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html
|
||||
|
||||
To get synchronized interior mutability, users of the standard library can use [Mutex]. It provides mutual exclusion by blocking threads when the resource is already locked. But our basic kernel does not have any blocking support or even a concept of threads, so we can't use it either. However there is a really basic kind of mutex in computer science that requires no operating system features: the [spinlock]. Instead of blocking, the threads simply try to lock it again and again in a tight loop and thus burn CPU time until the mutex is free again.
|
||||
@@ -529,7 +529,7 @@ The macro expands to a call of the [`_print` function] in the `io` module. The [
|
||||
The [`format_args` macro] builds a [fmt::Arguments] type from the passed arguments, which is passed to `_print`. The [`_print` function] of libstd is rather complicated, as it supports different `Stdout` devices. We don't need that complexity since we just want to print to the VGA buffer.
|
||||
|
||||
[`_print` function]: https://github.com/rust-lang/rust/blob/46d39f3329487115e7d7dcd37bc64eea6ef9ba4e/src/libstd/io/stdio.rs#L631
|
||||
[`$crate` variable]: https://doc.rust-lang.org/book/macros.html#the-variable-crate
|
||||
[`$crate` variable]: https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate
|
||||
[`format_args` macro]: https://doc.rust-lang.org/nightly/std/macro.format_args.html
|
||||
[fmt::Arguments]: https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ Note that we call `choose_next_area` manually here because `allocate_frame` retu
|
||||
### Testing it
|
||||
In order to test it in main, we need to [re-export] the `AreaFrameAllocator` in the `memory` module. Then we can create a new allocator:
|
||||
|
||||
[re-export]: https://doc.rust-lang.org/book/crates-and-modules.html#re-exporting-with-pub-use
|
||||
[re-export]: https://doc.rust-lang.org/1.30.0/book/first-edition/crates-and-modules.html#re-exporting-with-pub-use
|
||||
|
||||
```rust
|
||||
let mut frame_allocator = memory::AreaFrameAllocator::new(
|
||||
|
||||
@@ -323,7 +323,7 @@ We convert the address into raw pointers through `as` casts and then convert the
|
||||
|
||||
Note that `self` stays borrowed as long as the returned reference is valid. This is because of Rust's [lifetime elision] rules. Basically, these rules say that the lifetime of an output reference is the same as the lifetime of the input reference by default. So the above function signatures are expanded to:
|
||||
|
||||
[lifetime elision]: https://doc.rust-lang.org/book/lifetimes.html#lifetime-elision
|
||||
[lifetime elision]: https://doc.rust-lang.org/1.30.0/book/first-edition/lifetimes.html#lifetime-elision
|
||||
|
||||
```rust
|
||||
pub fn next_table<'a>(&'a self, index: usize) -> Option<&'a Table> {...}
|
||||
|
||||
@@ -641,7 +641,7 @@ SECTIONS {
|
||||
```
|
||||
The `.` is the “current location counter” and represents the current virtual address. At the beginning of the `SECTIONS` tag we set it to `1M`, so our kernel starts at 1MiB. We use the [ALIGN][linker align] function to align the current location counter to the next `4K` boundary (`4K` is the page size). Thus the end of the `.text` section – and the beginning of the next section – are page aligned.
|
||||
|
||||
[linker align]: http://www.math.utah.edu/docs/info/ld_3.html#SEC12
|
||||
[linker align]: https://www.math.utah.edu/docs/info/ld_3.html#SEC12
|
||||
|
||||
To put all sections on their own page, we add the `ALIGN` statement to all of them:
|
||||
|
||||
@@ -882,7 +882,7 @@ Now we should see the `NEW TABLE!!!` message (and also the `It did not crash!` l
|
||||
### Fixing the Frame Allocator
|
||||
The same problem as above occurs when we try to use our [AreaFrameAllocator] again. Try to add the following to `rust_main` after switching to the new table:
|
||||
|
||||
[AreaFrameAllocator]: http://os.phil-opp.com/allocating-frames.html#the-allocator
|
||||
[AreaFrameAllocator]: @/first-edition/posts/05-allocating-frames/index.md#the-allocator
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
@@ -332,7 +332,7 @@ let heap_test = Box::new(42);
|
||||
|
||||
When we run it, a triple fault occurs and causes permanent rebooting. Let's try debug it using QEMU and objdump as described [in the previous post][qemu debugging]:
|
||||
|
||||
[qemu debugging]: http://os.phil-opp.com/remap-the-kernel.html#debugging
|
||||
[qemu debugging]: @/first-edition/posts/07-remap-the-kernel/index.md#debugging
|
||||
|
||||
```
|
||||
> qemu-system-x86_64 -d int -no-reboot -cdrom build/os-x86_64.iso
|
||||
|
||||
@@ -118,7 +118,7 @@ type HandlerFunc = extern "x86-interrupt" fn(_: &mut ExceptionStackFrame);
|
||||
It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention?
|
||||
|
||||
[type alias]: https://doc.rust-lang.org/book/type-aliases.html
|
||||
[foreign calling convention]: https://doc.rust-lang.org/book/ffi.html#foreign-calling-conventions
|
||||
[foreign calling convention]: https://doc.rust-lang.org/1.30.0/book/first-edition/ffi.html#foreign-calling-conventions
|
||||
|
||||
## The Interrupt Calling Convention
|
||||
Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the called function and executes it. Afterwards, if the function is not diverging, the CPU jumps to the return address and continues the execution of the parent function.
|
||||
@@ -317,7 +317,7 @@ pub fn init() {
|
||||
There are two problems with this. First, statics are immutable, so we can't modify the breakpoint entry from our `init` function. Second, the `Idt::new` function is not a [`const` function], so it can't be used to initialize a `static`. We could solve this problem by using a [`static mut`] of type `Option<Idt>`:
|
||||
|
||||
[`const` function]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md
|
||||
[`static mut`]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
[`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
|
||||
|
||||
```rust
|
||||
static mut IDT: Option<Idt> = None;
|
||||
@@ -334,7 +334,7 @@ pub fn init() {
|
||||
|
||||
This variant compiles without errors but it's far from idiomatic. `static mut`s are very prone to data races, so we need an [`unsafe` block] on each access. Also, we need to explicitly `unwrap` the `IDT` on each use, since might be `None`.
|
||||
|
||||
[`unsafe` block]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||
[`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||
|
||||
#### Lazy Statics to the Rescue
|
||||
The one-time initialization of statics with non-const functions is a common problem in Rust. Fortunately, there already exists a good solution in a crate named [lazy_static]. This crate provides a `lazy_static!` macro that defines a lazily initialized `static`. Instead of computing its value at compile time, the `static` laziliy initializes itself when it's accessed the first time. Thus, the initialization happens at runtime so that arbitrarily complex initialization code is possible.
|
||||
|
||||
@@ -155,7 +155,7 @@ Let's look at the fourth question:
|
||||
|
||||
When our kernel overflows its stack and hits the guard page, a _page fault_ occurs. The CPU looks up the page fault handler in the IDT and tries to push the [exception stack frame] onto the stack. However, our current stack pointer still points to the non-present guard page. Thus, a second page fault occurs, which causes a double fault (according to the above table).
|
||||
|
||||
[exception stack frame]: http://os.phil-opp.com/better-exception-messages.html#exceptions-in-detail
|
||||
[exception stack frame]: @/first-edition/posts/09-handling-exceptions/index.md#the-exception-stack-frame
|
||||
|
||||
So the CPU tries to call our _double fault handler_ now. However, on a double fault the CPU tries to push the exception stack frame, too. Our stack pointer still points to the guard page, so a _third_ page fault occurs, which causes a _triple fault_ and a system reboot. So our current double fault handler can't avoid a triple fault in this case.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user