diff --git a/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md
index 5564e899..c704e57c 100644
--- a/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md
+++ b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md
@@ -4,6 +4,8 @@ weight = 1
path = "freestanding-rust-binary"
date = 2018-02-10
+[extra]
+chapter = "Bare Bones"
+++
The first step in creating our own operating system kernel is to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system.
@@ -145,10 +147,11 @@ Language items are special functions and types that are required internally by t
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
-Providing own implementations of language items would be possible, but this should only be done as a last resort. The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types). Fortunately, there is a more stable way to fix the above language item error.
+While providing custom implementations of language items is possible, it should only be done as a last resort. The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types). Fortunately, there is a more stable way to fix the above language item error.
-The `eh_personality` language item marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
+The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
+[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php
[libunwind]: http://www.nongnu.org/libunwind/
[structured exception handling]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx
@@ -507,7 +510,7 @@ cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
```
-Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So it probably for any real use of such a binary, more steps are required**.
+Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
## What's next?
diff --git a/blog/content/second-edition/extra/disable-red-zone/index.md b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/index.md
similarity index 96%
rename from blog/content/second-edition/extra/disable-red-zone/index.md
rename to blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/index.md
index f8740dde..212ccb97 100644
--- a/blog/content/second-edition/extra/disable-red-zone/index.md
+++ b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/index.md
@@ -2,7 +2,7 @@
title = "Disable the Red Zone"
weight = 1
path = "red-zone"
-
+template = "second-edition/extra.html"
+++
The [red zone] is an optimization of the [System V ABI] that allows functions to temporarily use the 128 bytes below its stack frame without adjusting the stack pointer:
@@ -10,6 +10,8 @@ The [red zone] is an optimization of the [System V ABI] that allows functions to
[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
[System V ABI]: http://wiki.osdev.org/System_V_ABI
+
+

The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the return address and the local variables.
diff --git a/blog/content/second-edition/extra/disable-red-zone/red-zone-overwrite.svg b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/red-zone-overwrite.svg
similarity index 100%
rename from blog/content/second-edition/extra/disable-red-zone/red-zone-overwrite.svg
rename to blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/red-zone-overwrite.svg
diff --git a/blog/content/second-edition/extra/disable-red-zone/red-zone.svg b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/red-zone.svg
similarity index 100%
rename from blog/content/second-edition/extra/disable-red-zone/red-zone.svg
rename to blog/content/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/red-zone.svg
diff --git a/blog/content/second-edition/extra/disable-simd/index.md b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-simd/index.md
similarity index 98%
rename from blog/content/second-edition/extra/disable-simd/index.md
rename to blog/content/second-edition/posts/02-minimal-rust-kernel/disable-simd/index.md
index 6810ddf6..6fe2f8d8 100644
--- a/blog/content/second-edition/extra/disable-simd/index.md
+++ b/blog/content/second-edition/posts/02-minimal-rust-kernel/disable-simd/index.md
@@ -2,13 +2,15 @@
title = "Disable SIMD"
weight = 2
path = "disable-simd"
-
+template = "second-edition/extra.html"
+++
[Single Instruction Multiple Data (SIMD)] instructions are able to perform an operation (e.g. addition) simultaneously on multiple data words, which can speed up programs significantly. The `x86_64` architecture supports various SIMD standards:
[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
+
+
- [MMX]: The _Multi Media Extension_ instruction set was introduced in 1997 and defines eight 64 bit registers called `mm0` through `mm7`. These registers are just aliases for the registers of the [x87 floating point unit].
- [SSE]: The _Streaming SIMD Extensions_ instruction set was introduced in 1999. Instead of re-using the floating point registers, it adds a completely new register set. The sixteen new registers are called `xmm0` through `xmm15` and are 128 bits each.
- [AVX]: The _Advanced Vector Extensions_ are extensions that further increase the size of the multimedia registers. The new registers are called `ymm0` through `ymm15` and are 256 bits each. They extend the `xmm` registers, so e.g. `xmm0` is the lower half of `ymm0`.
diff --git a/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md
index 1f77204c..26ce8b18 100644
--- a/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md
+++ b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md
@@ -4,6 +4,8 @@ weight = 2
path = "minimal-rust-kernel"
date = 2018-02-10
+[extra]
+chapter = "Bare Bones"
+++
In this post we create a minimal 64-bit Rust kernel for the x86 architecture. We build upon the [freestanding Rust binary] from the previous post to create a bootable disk image, that prints something to the screen.
@@ -169,7 +171,7 @@ This setting specifies that the target doesn't support [stack unwinding] on pani
We're writing a kernel, so we'll need to handle interrupts at some point. To do that safely, we have to disable a certain stack pointer optimization called the _“red zone”_, because it would cause stack corruptions otherwise. For more information, see our separate post about [disabling the red zone].
-[disabling the red zone]: @/second-edition/extra/disable-red-zone/index.md
+[disabling the red zone]: @/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/index.md
```json
"features": "-mmx,-sse,+soft-float",
@@ -183,7 +185,7 @@ The `mmx` and `sse` features determine support for [Single Instruction Multiple
A problem with disabling SIMD is that floating point operations on `x86_64` require SIMD registers by default. To solve this problem, we add the `soft-float` feature, which emulates all floating point operations through software functions based on normal integers.
-For more information, see our post on [disabling SIMD](@/second-edition/extra/disable-simd/index.md).
+For more information, see our post on [disabling SIMD](@/second-edition/posts/02-minimal-rust-kernel/disable-simd/index.md).
#### Putting it Together
Our target specification file now looks like this:
diff --git a/blog/content/second-edition/posts/03-vga-text-buffer/index.md b/blog/content/second-edition/posts/03-vga-text-buffer/index.md
index 09b46610..3db1bb6a 100644
--- a/blog/content/second-edition/posts/03-vga-text-buffer/index.md
+++ b/blog/content/second-edition/posts/03-vga-text-buffer/index.md
@@ -4,6 +4,8 @@ weight = 3
path = "vga-text-mode"
date = 2018-02-26
+[extra]
+chapter = "Bare Bones"
+++
The [VGA text mode] is a simple way to print text to the screen. In this post, we create an interface that makes its usage safe and simple, by encapsulating all unsafety in a separate module. We also implement support for Rust's [formatting macros].
@@ -590,7 +592,7 @@ macro_rules! println {
Macros are defined through one or more rules, which are similar to `match` arms. The `println` macro has two rules: The first rule for is invocations without arguments (e.g `println!()`), which is expanded to `print!("\n")` and thus just prints a newline. the second rule is for invocations with parameters such as `println!("Hello")` or `println!("Number: {}", 4)`. It is also expanded to an invocation of the `print!` macro, passing all arguments and an additional newline `\n` at the end.
-The `#[macro_export]` attribute makes the available to the whole crate (not just the module it is defined) and external crates. It also places the macro at the crate root, which means that we have to import the macro through `use std::println` instead of `std::macros::println`.
+The `#[macro_export]` attribute makes the macro available to the whole crate (not just the module it is defined) and external crates. It also places the macro at the crate root, which means that we have to import the macro through `use std::println` instead of `std::macros::println`.
The [`print!` macro] is defined as:
diff --git a/blog/content/second-edition/posts/04-testing/index.md b/blog/content/second-edition/posts/04-testing/index.md
index 28c855dc..c63e4ce7 100644
--- a/blog/content/second-edition/posts/04-testing/index.md
+++ b/blog/content/second-edition/posts/04-testing/index.md
@@ -4,6 +4,8 @@ weight = 4
path = "testing"
date = 2019-04-27
+[extra]
+chapter = "Bare Bones"
+++
This post explores unit and integration testing in `no_std` executables. We will use Rust's support for custom test frameworks to execute test functions inside our kernel. To report the results out of QEMU, we will use different features of QEMU and the `bootimage` tool.
diff --git a/blog/content/second-edition/posts/05-cpu-exceptions/index.md b/blog/content/second-edition/posts/05-cpu-exceptions/index.md
index 840117a6..f318cfdb 100644
--- a/blog/content/second-edition/posts/05-cpu-exceptions/index.md
+++ b/blog/content/second-edition/posts/05-cpu-exceptions/index.md
@@ -4,6 +4,8 @@ weight = 5
path = "cpu-exceptions"
date = 2018-06-17
+[extra]
+chapter = "Interrupts"
+++
CPU exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To react to them we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards.
@@ -168,6 +170,8 @@ In contrast to function calls, exceptions can occur on _any_ instruction. In mos
Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for exception handlers. Instead, we need a calling convention means that preserves _all registers_. The `x86-interrupt` calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return.
+Note that this does not mean that all registers are saved to the stack at function entry. Instead, the compiler only backs up the registers that are overwritten by the function. This way, very efficient code can be generated for short functions that only use a few registers.
+
### The Interrupt Stack Frame
On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this:
@@ -193,12 +197,6 @@ In the `x86_64` crate, the interrupt stack frame is represented by the [`Interru
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.8.1/x86_64/structures/idt/struct.InterruptStackFrame.html
-Note that there is currently [a bug in LLVM] that leads to wrong error code arguments. The cause of the issue is already known and a solution is [being worked on].
-
-[a bug in LLVM]: https://github.com/rust-lang/rust/issues/57270
-[being worked on]: https://reviews.llvm.org/D56275
-
-
### Behind the Scenes
The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of:
diff --git a/blog/content/second-edition/posts/06-double-faults/index.md b/blog/content/second-edition/posts/06-double-faults/index.md
index 42c0acf4..52adaf19 100644
--- a/blog/content/second-edition/posts/06-double-faults/index.md
+++ b/blog/content/second-edition/posts/06-double-faults/index.md
@@ -4,6 +4,8 @@ weight = 6
path = "double-fault-exceptions"
date = 2018-06-18
+[extra]
+chapter = "Interrupts"
+++
This post explores the double fault exception in detail, which occurs when the CPU fails to invoke an exception handler. By handling this exception we avoid fatal _triple faults_ that cause a system reset. To prevent triple faults in all cases we also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack.
diff --git a/blog/content/second-edition/posts/07-hardware-interrupts/index.md b/blog/content/second-edition/posts/07-hardware-interrupts/index.md
index 9f1cd8f1..d472b95c 100644
--- a/blog/content/second-edition/posts/07-hardware-interrupts/index.md
+++ b/blog/content/second-edition/posts/07-hardware-interrupts/index.md
@@ -4,6 +4,8 @@ weight = 7
path = "hardware-interrupts"
date = 2018-10-22
+[extra]
+chapter = "Interrupts"
+++
In this post we set up the programmable interrupt controller to correctly forward hardware interrupts to the CPU. To handle these interrupts we add new entries to our interrupt descriptor table, just like we did for our exception handlers. We will learn how to get periodic timer interrupts and how to get input from the keyboard.
diff --git a/blog/content/second-edition/posts/08-paging-introduction/index.md b/blog/content/second-edition/posts/08-paging-introduction/index.md
index 29f8784c..befcfb23 100644
--- a/blog/content/second-edition/posts/08-paging-introduction/index.md
+++ b/blog/content/second-edition/posts/08-paging-introduction/index.md
@@ -4,6 +4,8 @@ weight = 8
path = "paging-introduction"
date = 2019-01-14
+[extra]
+chapter = "Memory Management"
+++
This post introduces _paging_, a very common memory management scheme that we will also use for our operating system. It explains why memory isolation is needed, how _segmentation_ works, what _virtual memory_ is, and how paging solves memory fragmentation issues. It also explores the layout of multilevel page tables on the x86_64 architecture.
diff --git a/blog/content/second-edition/posts/09-paging-implementation/index.md b/blog/content/second-edition/posts/09-paging-implementation/index.md
index ce74724f..ab0c5d9c 100644
--- a/blog/content/second-edition/posts/09-paging-implementation/index.md
+++ b/blog/content/second-edition/posts/09-paging-implementation/index.md
@@ -3,6 +3,9 @@ title = "Paging Implementation"
weight = 9
path = "paging-implementation"
date = 2019-03-14
+
+[extra]
+chapter = "Memory Management"
+++
This post shows how to implement paging support in our kernel. It first explores different techniques to make the physical page table frames accessible to the kernel and discusses their respective advantages and drawbacks. It then implements an address translation function and a function to create a new mapping.
diff --git a/blog/content/second-edition/posts/10-heap-allocation/index.md b/blog/content/second-edition/posts/10-heap-allocation/index.md
index 51fa3932..6e907402 100644
--- a/blog/content/second-edition/posts/10-heap-allocation/index.md
+++ b/blog/content/second-edition/posts/10-heap-allocation/index.md
@@ -3,6 +3,9 @@ title = "Heap Allocation"
weight = 10
path = "heap-allocation"
date = 2019-06-26
+
+[extra]
+chapter = "Memory Management"
+++
This post adds support for heap allocation to our kernel. First, it gives an introduction to dynamic memory and shows how the borrow checker prevents common allocation errors. It then implements the basic allocation interface of Rust, creates a heap memory region, and sets up an allocator crate. At the end of this post all the allocation and collection types of the built-in `alloc` crate will be available to our kernel.
@@ -251,7 +254,7 @@ It defines the two required methods [`alloc`] and [`dealloc`], which correspond
The trait additionally defines the two methods [`alloc_zeroed`] and [`realloc`] with default implementations:
-- The [`alloc_zeroed`] method is equivalent to calling `alloc` and then setting the allocated memory block to zero, which is exactly what the provided default implementation does. An allocator implementations can override the default implementations with a more efficient custom implementation if possible.
+- The [`alloc_zeroed`] method is equivalent to calling `alloc` and then setting the allocated memory block to zero, which is exactly what the provided default implementation does. An allocator implementation can override the default implementations with a more efficient custom implementation if possible.
- The [`realloc`] method allows to grow or shrink an allocation. The default implementation allocates a new memory block with the desired size and copies over all the content from the previous allocation. Again, an allocator implementation can probably provide a more efficient implementation of this method, for example by growing/shrinking the allocation in-place if possible.
[`alloc_zeroed`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#method.alloc_zeroed
@@ -306,15 +309,13 @@ We now have a simple allocator, but we still have to tell the Rust compiler that
The `#[global_allocator]` attribute tells the Rust compiler which allocator instance it should use as the global heap allocator. The attribute is only applicable to a `static` that implements the `GlobalAlloc` trait. Let's register an instance of our `Dummy` allocator as the global allocator:
```rust
-// in src/lib.rs
+// in src/allocator.rs
#[global_allocator]
-static ALLOCATOR: allocator::Dummy = allocator::Dummy;
+static ALLOCATOR: Dummy = Dummy;
```
-Since the `Dummy` allocator is a [zero sized type], we don't need to specify any fields in the initialization expression. Note that the `#[global_allocator]` module [cannot be used in submodules][pr51335], so we need to put it into the `lib.rs`.
-
-[pr51335]: https://github.com/rust-lang/rust/pull/51335
+Since the `Dummy` allocator is a [zero sized type], we don't need to specify any fields in the initialization expression.
When we now try to compile it, the first error should be gone. Let's fix the remaining second error:
@@ -520,7 +521,7 @@ linked_list_allocator = "0.6.4"
Then we can replace our dummy allocator with the allocator provided by the crate:
```rust
-// in src/lib.rs
+// in src/allocator.rs
use linked_list_allocator::LockedHeap;
@@ -547,7 +548,7 @@ pub fn init_heap(
// new
unsafe {
- super::ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
+ ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
}
Ok(())
@@ -745,10 +746,12 @@ As a third test, we create ten thousand allocations after each other:
```rust
// in tests/heap_allocation.rs
+use blog_os::allocator::HEAP_SIZE;
+
#[test_case]
fn many_boxes() {
serial_print!("many_boxes... ");
- for i in 0..10_000 {
+ for i in 0..HEAP_SIZE {
let x = Box::new(i);
assert_eq!(*x, i);
}
diff --git a/blog/content/second-edition/posts/11-allocator-designs/allocation-fragmentation.svg b/blog/content/second-edition/posts/11-allocator-designs/allocation-fragmentation.svg
new file mode 100644
index 00000000..b9c74784
--- /dev/null
+++ b/blog/content/second-edition/posts/11-allocator-designs/allocation-fragmentation.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/blog/content/second-edition/posts/11-allocator-designs/bump-allocation.svg b/blog/content/second-edition/posts/11-allocator-designs/bump-allocation.svg
new file mode 100644
index 00000000..ed47bbb5
--- /dev/null
+++ b/blog/content/second-edition/posts/11-allocator-designs/bump-allocation.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/blog/content/second-edition/posts/11-allocator-designs/fixed-size-block-example.svg b/blog/content/second-edition/posts/11-allocator-designs/fixed-size-block-example.svg
new file mode 100644
index 00000000..2a2de226
--- /dev/null
+++ b/blog/content/second-edition/posts/11-allocator-designs/fixed-size-block-example.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/blog/content/second-edition/posts/11-allocator-designs/index.md b/blog/content/second-edition/posts/11-allocator-designs/index.md
new file mode 100644
index 00000000..e2378592
--- /dev/null
+++ b/blog/content/second-edition/posts/11-allocator-designs/index.md
@@ -0,0 +1,1245 @@
++++
+title = "Allocator Designs"
+weight = 11
+path = "allocator-designs"
+date = 2020-01-20
+
+[extra]
+chapter = "Memory Management"
++++
+
+This post explains how to implement heap allocators from scratch. It presents and discusses different allocator designs, including bump allocation, linked list allocation, and fixed-size block allocation. For each of the three designs, we will create a basic implementation that can be used for our kernel.
+
+
+
+This blog is openly developed on [GitHub]. If you have any problems or questions, please open an issue there. You can also leave comments [at the bottom]. The complete source code for this post can be found in the [`post-11`][post branch] branch.
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[at the bottom]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-11
+
+
+
+## Introduction
+
+In the [previous post] we added basic support for heap allocations to our kernel. For that, we [created a new memory region][map-heap] in the page tables and [used the `linked_list_allocator` crate][use-alloc-crate] to manage that memory. While we have a working heap now, we left most of the work to the allocator crate without trying to understand how it works.
+
+[previous post]: @/second-edition/posts/10-heap-allocation/index.md
+[map-heap]: @/second-edition/posts/10-heap-allocation/index.md#creating-a-kernel-heap
+[use-alloc-crate]: @/second-edition/posts/10-heap-allocation/index.md#using-an-allocator-crate
+
+In this post, we will show how to create our own heap allocator from scratch instead of relying on an existing allocator crate. We will discuss different allocator designs, including a simplistic _bump allocator_ and a basic _fixed-size block allocator_, and use this knowledge to implement an allocator with improved performance (compared to the `linked_list_allocator` crate).
+
+### Design Goals
+
+The responsibility of an allocator is to manage the available heap memory. It needs to return unused memory on `alloc` calls and keep track of memory freed by `dealloc` so that it can be reused again. Most importantly, it must never hand out memory that is already in use somewhere else because this would cause undefined behavior.
+
+Apart from correctness, there are many secondary design goals. For example, the allocator should effectively utilize the available memory and keep [_fragmentation_] low. Furthermore, it should work well for concurrent applications and scale to any number of processors. For maximal performance, it could even optimize the memory layout with respect to the CPU caches to improve [cache locality] and avoid [false sharing].
+
+[cache locality]: http://docs.cray.com/books/S-2315-50/html-S-2315-50/qmeblljm.html
+[_fragmentation_]: https://en.wikipedia.org/wiki/Fragmentation_(computing)
+[false sharing]: http://mechanical-sympathy.blogspot.de/2011/07/false-sharing.html
+
+These requirements can make good allocators very complex. For example, [jemalloc] has over 30.000 lines of code. This complexity is often undesired in kernel code where a single bug can lead to severe security vulnerabilities. Fortunately, the allocation patterns of kernel code are often much simpler compared to userspace code, so that relatively simple allocator designs often suffice.
+
+[jemalloc]: http://jemalloc.net/
+
+In the following we present three possible kernel allocator designs and explain their advantages and drawbacks.
+
+## Bump Allocator
+
+The most simple allocator design is a _bump allocator_. It allocates memory linearly and only keeps track of the number of allocated bytes and the number of allocations. It is only useful in very specific use cases because it has a severe limitation: it can only free all memory at once.
+
+### Idea
+
+The idea behind a bump allocator is to linearly allocate memory by increasing (_"bumping"_) a `next` variable, which points at the beginning of the unused memory. At the beginning, `next` is equal to the start address of the heap. On each allocation, `next` is increased by the allocation so that it always points to the boundary between used and unused memory:
+
+
+
+The `next` pointer only moves in a single direction and thus never hands out the same memory region twice. When it reaches the end of the heap, no more memory can be allocated, resulting in an out-of-memory error on the next allocation.
+
+A bump allocator is often implemented with an allocation counter, which is increased by 1 on each `alloc` call and decreased by 1 on each `dealloc` call. When the allocation counter reaches zero it means that all allocations on the heap were deallocated. In this case, the `next` pointer can be reset to the start address of the heap, so that the complete heap memory is available to allocations again.
+
+### Implementation
+
+We start our implementation by declaring a new `allocator::bump` submodule:
+
+```rust
+// in src/allocator.rs
+
+pub mod bump;
+```
+
+The content of the submodule lives in a new `src/allocator/bump.rs` file, which we create with the following content:
+
+```rust
+// in src/allocator/bump.rs
+
+pub struct BumpAllocator {
+ heap_start: usize,
+ heap_end: usize,
+ next: usize,
+ allocations: usize,
+}
+
+impl BumpAllocator {
+ /// Creates a new empty bump allocator.
+ pub const fn new() -> Self {
+ BumpAllocator {
+ heap_start: 0,
+ heap_end: 0,
+ next: 0,
+ allocations: 0,
+ }
+ }
+
+ /// Initializes the bump allocator with the given heap bounds.
+ ///
+ /// This method is unsafe because the caller must ensure that the given
+ /// memory range is unused. Also, this method must be called only once.
+ pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
+ self.heap_start = heap_start;
+ self.heap_end = heap_start + heap_size;
+ self.next = heap_start;
+ }
+}
+```
+
+The `heap_start` and `heap_end` fields keep track of the lower and upper bound of the heap memory region. The caller needs to ensure that these addresses are valid, otherwise the allocator would return invalid memory. For this reason, the `init` function needs to be `unsafe` to call.
+
+The purpose of the `next` field is to always point to the first unused byte of the heap, i.e. the start address of the next allocation. It is set to `heap_start` in the `init` function because at the beginning the complete heap is unused. On each allocation, this field will be increased by the allocation size (_"bumped"_) to ensure that we don't return the same memory region twice.
+
+The `allocations` field is a simple counter for the active allocations with the goal of resetting the allocator after the last allocation was freed. It is initialized with 0.
+
+We chose to create a separate `init` function instead of performing the initialization directly in `new` in order to keep the interface identical to the allocator provided by the `linked_list_allocator` crate. This way, the allocators can be switched without additional code changes.
+
+### Implementing `GlobalAlloc`
+
+As [explained in the previous post][global-alloc], all heap allocators need to implement the [`GlobalAlloc`] trait, which is defined like this:
+
+[global-alloc]: @/second-edition/posts/10-heap-allocation/index.md#the-allocator-interface
+[`GlobalAlloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html
+
+```rust
+pub unsafe trait GlobalAlloc {
+ unsafe fn alloc(&self, layout: Layout) -> *mut u8;
+ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
+
+ unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { ... }
+ unsafe fn realloc(
+ &self,
+ ptr: *mut u8,
+ layout: Layout,
+ new_size: usize
+ ) -> *mut u8 { ... }
+}
+```
+
+Only the `alloc` and `dealloc` methods are required, the other two methods have default implementations and can be omitted.
+
+#### First Implementation Attempt
+
+Let's try to implement the `alloc` method for our `BumpAllocator`:
+
+```rust
+// in src/allocator/bump.rs
+
+use alloc::alloc::{GlobalAlloc, Layout};
+
+unsafe impl GlobalAlloc for BumpAllocator {
+ unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+ // TODO alignment and bounds check
+ let alloc_start = self.next;
+ self.next = alloc_start + layout.size();
+ self.allocations += 1;
+ alloc_start as *mut u8
+ }
+
+ unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
+ todo!();
+ }
+}
+```
+
+First, we use the `next` field as the start address for our allocation. Then we update the `next` field to point at the end address of the allocation, which is the next unused address on the heap. Before returning the start address of the allocation as a `*mut u8` pointer, we increase the `allocations` counter by 1.
+
+Note that we don't perform any bounds checks or alignment adjustments, so this implementation is not safe yet. This does not matter much because it fails to compile anyway with the following error:
+
+```
+error[E0594]: cannot assign to `self.next` which is behind a `&` reference
+ --> src/allocator/bump.rs:29:9
+ |
+26 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+ | ----- help: consider changing this to be a mutable reference: `&mut self`
+...
+29 | self.next = alloc_start + layout.size();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be written
+```
+
+(The same error also occurs for the `self.allocations += 1` line. We omitted it here for brevity.)
+
+The error occurs because the [`alloc`] and [`dealloc`] methods of the `GlobalAlloc` trait only operate on an immutable `&self` reference, so updating the `next` and `allocations` fields is not possible. This is problematic because updating `next` on every allocation is the essential principle of a bump allocator.
+
+[`alloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.alloc
+[`dealloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.dealloc
+
+Note that the compiler suggestion to change `&self` to `&mut self` in the method declaration does not work here. The reason is that the method signature is defined by the `GlobalAlloc` trait and can't be changed on the implementation side. (I opened an [issue](https://github.com/rust-lang/rust/issues/68049) in the Rust repository about the invalid suggestion.)
+
+#### `GlobalAlloc` and Mutability
+
+Before we look at a possible solution to this mutability problem, let's try to understand why the `GlobalAlloc` trait methods are defined with `&self` arguments: As we saw [in the previous post][global-allocator], the global heap allocator is defined by adding the `#[global_allocator]` attribute to a `static` that implements the `GlobalAlloc` trait. Static variables are immutable in Rust, so there is no way to call a method that takes `&mut self` on the static allocator. For this reason, all the methods of `GlobalAlloc` only take an immutable `&self` reference.
+
+[global-allocator]: @/second-edition/posts/10-heap-allocation/index.md#the-global-allocator-attribute
+
+Fortunately there is a way how to get a `&mut self` reference from a `&self` reference: We can use synchronized [interior mutability] by wrapping the allocator in a [`spin::Mutex`] spinlock. This type provides a `lock` method that performs [mutual exclusion] and thus safely turns a `&self` reference to a `&mut self` reference. We already used the wrapper type multiple times in our kernel, for example for the [VGA text buffer][vga-mutex].
+
+[interior mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
+[vga-mutex]: @/second-edition/posts/03-vga-text-buffer/index.md#spinlocks
+[`spin::Mutex`]: https://docs.rs/spin/0.5.0/spin/struct.Mutex.html
+[mutual exclusion]: https://en.wikipedia.org/wiki/Mutual_exclusion
+
+#### A `Locked` Wrapper Type
+
+With the help of the `spin::Mutex` wrapper type we can implement the `GlobalAlloc` trait for our bump allocator. The trick is to implement the trait not for the `BumpAllocator` directly, but for the wrapped `spin::Mutex