Compare commits
167 Commits
b2e12433b9
...
edition-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd3550ea87 | ||
|
|
ecb60ec326 | ||
|
|
8a1267477a | ||
|
|
ce01059620 | ||
|
|
4d0c3ac188 | ||
|
|
d565cd125b | ||
|
|
ca86085360 | ||
|
|
5f3d38884c | ||
|
|
f557d1c698 | ||
|
|
0c248d027e | ||
|
|
2cf0675a2d | ||
|
|
916ad36e78 | ||
|
|
3c2e91fa4e | ||
|
|
c9683a2cd9 | ||
|
|
514736b1d8 | ||
|
|
647b509971 | ||
|
|
1118350b16 | ||
|
|
fb096a7484 | ||
|
|
8a41fd65bf | ||
|
|
50802c8332 | ||
|
|
ba410f40ba | ||
|
|
a119d36cc9 | ||
|
|
9080e69a09 | ||
|
|
2676d69c31 | ||
|
|
c31f3c2728 | ||
|
|
5799263124 | ||
|
|
ded60de8d0 | ||
|
|
171956adc8 | ||
|
|
5681d3f0f7 | ||
|
|
68d0c946f4 | ||
|
|
9baab55788 | ||
|
|
2bc74ce8f7 | ||
|
|
7ce9ae1caf | ||
|
|
646e5ba502 | ||
|
|
bb754eadba | ||
|
|
f377050605 | ||
|
|
dd228c4bb1 | ||
|
|
020f2d7e8e | ||
|
|
c337650fb3 | ||
|
|
7262e552ae | ||
|
|
2d6fc6bd76 | ||
|
|
33b6b6ebe8 | ||
|
|
94d87308f4 | ||
|
|
c81a2e11db | ||
|
|
3ff7f48257 | ||
|
|
dba81f6d1c | ||
|
|
ae6f001eae | ||
|
|
5c00dbcb60 | ||
|
|
79be18bfb5 | ||
|
|
50cbf4f01c | ||
|
|
96c8aaf89d | ||
|
|
9f0bd81a79 | ||
|
|
55508e6800 | ||
|
|
ffc6d54009 | ||
|
|
dfd0e6c5d8 | ||
|
|
6fe8365b93 | ||
|
|
6d8075fe87 | ||
|
|
db2d2bca19 | ||
|
|
ce0c6c133b | ||
|
|
c40ac1b1d7 | ||
|
|
cc6d5eefb5 | ||
|
|
2642fa80e8 | ||
|
|
264fd90abe | ||
|
|
87b440c79a | ||
|
|
2c96cfe972 | ||
|
|
9adc094f43 | ||
|
|
1da47d3177 | ||
|
|
bc9d1a545a | ||
|
|
c73804789b | ||
|
|
233dec4caf | ||
|
|
6f1b982f3a | ||
|
|
f6d06bcf67 | ||
|
|
fb5eca717b | ||
|
|
0400ce692c | ||
|
|
d475912811 | ||
|
|
729a8241ea | ||
|
|
d7b93ab855 | ||
|
|
5af0499af5 | ||
|
|
ebf482379c | ||
|
|
91005553b3 | ||
|
|
06b7d345e4 | ||
|
|
b3eace1260 | ||
|
|
c61c37643a | ||
|
|
1aa7d21d8b | ||
|
|
d6885843e6 | ||
|
|
0b9231f0ba | ||
|
|
db47b27024 | ||
|
|
9c1babd027 | ||
|
|
ff3f055383 | ||
|
|
8740b619a5 | ||
|
|
a63c51c156 | ||
|
|
1ff447b378 | ||
|
|
83be6c7868 | ||
|
|
033be9ac25 | ||
|
|
91d65504db | ||
|
|
aeb72889ae | ||
|
|
c2fe9960a7 | ||
|
|
d6f424e338 | ||
|
|
843bd4ca87 | ||
|
|
b1f6a85a02 | ||
|
|
c48335747b | ||
|
|
81a1c0bded | ||
|
|
dfdbc6ec6d | ||
|
|
09f96c9221 | ||
|
|
aaae70974f | ||
|
|
268a5ccb7d | ||
|
|
f9f9b969bb | ||
|
|
943f950c54 | ||
|
|
01327746d1 | ||
|
|
acb478c0b5 | ||
|
|
08c84d2f59 | ||
|
|
9c11cebef7 | ||
|
|
b3b263db2a | ||
|
|
a8ef154d03 | ||
|
|
957855a18e | ||
|
|
ec1f80416b | ||
|
|
2e52b681ec | ||
|
|
8fbdf53598 | ||
|
|
72e4851bc7 | ||
|
|
76090f6656 | ||
|
|
cd7ac05395 | ||
|
|
d56f51a2d3 | ||
|
|
ae4c53fa75 | ||
|
|
de71899d19 | ||
|
|
6b1c01477c | ||
|
|
8e0f531334 | ||
|
|
a6b8623468 | ||
|
|
57bbb13e41 | ||
|
|
3dfc7ee84f | ||
|
|
c97c27f4e6 | ||
|
|
4aa5981252 | ||
|
|
b5bd0296bd | ||
|
|
7e0911b42e | ||
|
|
4027e61dab | ||
|
|
5e3062407f | ||
|
|
fd623fd033 | ||
|
|
d56977598e | ||
|
|
73b42d4747 | ||
|
|
4a86515a8d | ||
|
|
300a6f452a | ||
|
|
eb767523a5 | ||
|
|
ece6a9bb9d | ||
|
|
5c3015acc3 | ||
|
|
7cc3d5e3b9 | ||
|
|
376ce0ad95 | ||
|
|
b353866952 | ||
|
|
50683507da | ||
|
|
60a0b3bc28 | ||
|
|
798d5c58c5 | ||
|
|
e6c099ee5b | ||
|
|
874f9dbaed | ||
|
|
d45572f9fb | ||
|
|
7e86caf786 | ||
|
|
e8bfca0adb | ||
|
|
3a23f0555f | ||
|
|
352ba47971 | ||
|
|
c175d25048 | ||
|
|
d7f8dd78de | ||
|
|
f4eeda64d8 | ||
|
|
2147e67e3a | ||
|
|
8c87368eee | ||
|
|
645057fe0b | ||
|
|
4f108cc36e | ||
|
|
75a1d19b93 | ||
|
|
4ec30d4624 | ||
|
|
61aabc688c | ||
|
|
f39923545d |
20
.github/workflows/blog.yml
vendored
@@ -104,3 +104,23 @@ jobs:
|
|||||||
- name: "Push Changes"
|
- name: "Push Changes"
|
||||||
run: "git push"
|
run: "git push"
|
||||||
working-directory: "blog_os_deploy"
|
working-directory: "blog_os_deploy"
|
||||||
|
|
||||||
|
drafts:
|
||||||
|
name: "Build Drafts"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: 'Download Zola'
|
||||||
|
run: curl -sL https://github.com/getzola/zola/releases/download/v0.16.1/zola-v0.16.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||||
|
- name: 'Install Python Libraries'
|
||||||
|
run: python -m pip install --user -r requirements.txt
|
||||||
|
working-directory: "blog"
|
||||||
|
|
||||||
|
- name: "Run before_build.py script"
|
||||||
|
run: python before_build.py
|
||||||
|
working-directory: "blog"
|
||||||
|
- name: "Build Site with drafts"
|
||||||
|
run: ../zola build --drafts
|
||||||
|
working-directory: "blog"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ skip_anchor_prefixes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
subtitle = "Philipp Oppermann's blog"
|
subtitle = "by Philipp Oppermann"
|
||||||
author = { name = "Philipp Oppermann" }
|
author = { name = "Philipp Oppermann" }
|
||||||
default_language = "en"
|
default_language = "en"
|
||||||
languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko"]
|
languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko"]
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ This folder contains the content for the _"Writing an OS in Rust"_ blog.
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This folder is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, available in [LICENSE-CC-BY-NC](LICENSE-CC-BY-NC) or under <https://creativecommons.org/licenses/by-nc/4.0/>.
|
This folder is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, available in [LICENSE-CC-BY-NC](LICENSE-CC-BY-NC) or under <https://creativecommons.org/licenses/by-nc/4.0/>. The following exceptions exist:
|
||||||
|
|
||||||
All _code examples_ between markdown code blocks denoted by three backticks (<code>\`\`\`</code>) are additionally licensed under either of
|
- The post icons are taken from the [Bootstrap Icons](https://icons.getbootstrap.com/) project, which are [licensed under the MIT license by "The Bootstrap Authors"](https://github.com/twbs/icons/blob/main/LICENSE.md).
|
||||||
|
- All files that have a custom license header. These files are licensed as described in this header.
|
||||||
|
|
||||||
|
In addition to the above license terms, all _code examples_ between markdown code blocks denoted by three backticks (<code>\`\`\`</code>) are additionally licensed under either of
|
||||||
|
|
||||||
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or
|
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or
|
||||||
https://www.apache.org/licenses/LICENSE-2.0)
|
https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
+++
|
+++
|
||||||
title = "Second Edition"
|
|
||||||
template = "redirect-to-frontpage.html"
|
template = "redirect-to-frontpage.html"
|
||||||
aliases = ["second-edition/index.html"]
|
aliases = ["second-edition/index.html"]
|
||||||
+++
|
+++
|
||||||
|
|||||||
21
blog/content/edition-3/_index.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
+++
|
||||||
|
template = "edition-3/index.html"
|
||||||
|
+++
|
||||||
|
|
||||||
|
<h1>Writing an OS in Rust</h1>
|
||||||
|
|
||||||
|
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Writing an OS in Rust</h1>
|
||||||
|
|
||||||
|
<p>A blog by Philipp Oppermann <em class="gray">— Third Edition (Alpha Release)</em></p>
|
||||||
|
|
||||||
|
<div class="front-page-introduction">
|
||||||
|
|
||||||
|
This blog series creates a small operating system in the [Rust programming language](https://www.rust-lang.org/). Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding [Github repository](https://github.com/phil-opp/blog_os).
|
||||||
|
|
||||||
|
<!-- alpha-warning -->
|
||||||
|
|
||||||
|
We explain how to create an operating system for the **`x86_64`** architecture step by step. Starting from scratch, we create a bootable OS kernel, implement basic input/output support, show how to test and debug our kernel, explain virtual memory management, and add support for multitasking and userspace programs.
|
||||||
|
|
||||||
|
Latest post: <!-- latest-post -->
|
||||||
|
|
||||||
|
</div>
|
||||||
4
blog/content/edition-3/chapters/_index.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
+++
|
||||||
|
title = "Chapters"
|
||||||
|
render = false
|
||||||
|
+++
|
||||||
9
blog/content/edition-3/chapters/bare-bones/_index.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
+++
|
||||||
|
title = "Bare Bones"
|
||||||
|
render = false
|
||||||
|
+++
|
||||||
|
|
||||||
|
In this first chapter, we explain how to create an operating system for the `x86_64` architecture step by step.
|
||||||
|
Starting from scratch, we first create a minimal Rust executable that doesn't depend on the standard library.
|
||||||
|
We then turn it into a bootable OS kernel by combining it with a bootloader.
|
||||||
|
The resulting disk image can then be launched in the [QEMU](https://www.qemu.org/) emulator or booted on a real machine.
|
||||||
6
blog/content/edition-3/chapters/basic-i-o/_index.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
+++
|
||||||
|
title = "Basic I/O"
|
||||||
|
render = false
|
||||||
|
+++
|
||||||
|
|
||||||
|
Soluta cum voluptatem fuga reprehenderit tenetur dicta rerum. Ullam minima eaque saepe voluptatum saepe in illum cumque. Debitis doloribus dolores dolores earum minima accusamus eius. Nostrum qui saepe ducimus laudantium temporibus.
|
||||||
606
blog/content/edition-3/posts/01-minimal-kernel/index.md
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
+++
|
||||||
|
title = "Minimal Kernel"
|
||||||
|
weight = 1
|
||||||
|
path = "minimal-kernel"
|
||||||
|
date = 0000-01-01
|
||||||
|
draft = true
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
chapter = "Bare Bones"
|
||||||
|
icon = '''
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark-binary" viewBox="0 0 16 16">
|
||||||
|
<path d="M7.05 11.885c0 1.415-.548 2.206-1.524 2.206C4.548 14.09 4 13.3 4 11.885c0-1.412.548-2.203 1.526-2.203.976 0 1.524.79 1.524 2.203zm-1.524-1.612c-.542 0-.832.563-.832 1.612 0 .088.003.173.006.252l1.559-1.143c-.126-.474-.375-.72-.733-.72zm-.732 2.508c.126.472.372.718.732.718.54 0 .83-.563.83-1.614 0-.085-.003-.17-.006-.25l-1.556 1.146zm6.061.624V14h-3v-.595h1.181V10.5h-.05l-1.136.747v-.688l1.19-.786h.69v3.633h1.125z"/>
|
||||||
|
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
|
||||||
|
</svg>
|
||||||
|
'''
|
||||||
|
+++
|
||||||
|
|
||||||
|
The first step in creating our own operating system kernel is to create a [bare metal] Rust executable that does not depend on an underlying operating system.
|
||||||
|
For that we need to disable most of Rust's standard library and adjust various compilation settings.
|
||||||
|
The result is a minimal operating system kernel that forms the base for the following posts of this series.
|
||||||
|
|
||||||
|
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
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-3.1`][post branch] branch.
|
||||||
|
|
||||||
|
[GitHub]: https://github.com/phil-opp/blog_os
|
||||||
|
[at the bottom]: #comments
|
||||||
|
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||||
|
[post branch]: https://github.com/phil-opp/blog_os/tree/post-3.1
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Kernels are the heart of an operating system.
|
||||||
|
They provide all the fundamental building blocks that are required for building higher-level programs.
|
||||||
|
Typical building blocks are threads, files, heap memory, timers, or sockets.
|
||||||
|
Other important tasks of a kernel are the isolation of different programs and the multiplexing of resources.
|
||||||
|
|
||||||
|
When writing an operating system kernel, we need to provide all of these building blocks ourselves.
|
||||||
|
This means that we can't use most of the [Rust standard library].
|
||||||
|
However, there are still a lot of Rust features that we _can_ use.
|
||||||
|
For example, we can use [iterators], [closures], [pattern matching], [`Option`] and [`Result`], [string formatting], and of course the [ownership system].
|
||||||
|
These features make it possible to write a kernel in a very expressive, high level way and worry less about [undefined behavior] or [memory safety].
|
||||||
|
|
||||||
|
[`Option`]: https://doc.rust-lang.org/core/option/
|
||||||
|
[`Result`]: https://doc.rust-lang.org/core/result/
|
||||||
|
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||||
|
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
|
||||||
|
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
|
||||||
|
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
|
||||||
|
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||||
|
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
|
||||||
|
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||||
|
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||||
|
|
||||||
|
In this post, we create a minimal OS kernel that can be run without an underlying operating system.
|
||||||
|
Such an executable is often called a “freestanding” or “bare-metal” executable.
|
||||||
|
We then make this executable compatible with the early-boot environment of the `x86_64` architecture so that we can boot it as an operating system kernel.
|
||||||
|
|
||||||
|
## Disabling the Standard Library
|
||||||
|
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking.
|
||||||
|
It also depends on the C standard library `libc`, which closely interacts with OS services.
|
||||||
|
Since our plan is to write an operating system, we cannot use any OS-dependent libraries.
|
||||||
|
So we have to disable the automatic inclusion of the standard library, which we can do through the [`no_std` attribute].
|
||||||
|
|
||||||
|
[standard library]: https://doc.rust-lang.org/std/
|
||||||
|
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||||
|
|
||||||
|
We start by creating a new cargo application project.
|
||||||
|
The easiest way to do this is through the command line:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo new kernel --bin --edition 2021
|
||||||
|
```
|
||||||
|
|
||||||
|
We name the project `kernel` here, but of course you can choose your own name.
|
||||||
|
The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2021` flag specifies that we want to use the [2021 edition] of Rust for our crate.
|
||||||
|
When we run the command, cargo creates the following directory structure for us:
|
||||||
|
|
||||||
|
[2021 edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
|
||||||
|
|
||||||
|
```
|
||||||
|
kernel
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src
|
||||||
|
└── main.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Cargo.toml` contains the crate configuration, for example the crate name, the [semantic version] number, and dependencies.
|
||||||
|
The `src/main.rs` file contains the root module of our crate and our `main` function.
|
||||||
|
You can compile your crate through `cargo build` and then run the compiled `kernel` binary in the `target/debug` subfolder.
|
||||||
|
|
||||||
|
[semantic version]: https://semver.org/
|
||||||
|
|
||||||
|
### The `no_std` Attribute
|
||||||
|
|
||||||
|
Right now our crate implicitly links the standard library.
|
||||||
|
Let's try to disable this by adding the [`no_std` attribute]:
|
||||||
|
|
||||||
|
```rust,hl_lines=3
|
||||||
|
// main.rs
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When we try to build it now (by running `cargo build`), the following errors occur:
|
||||||
|
|
||||||
|
```
|
||||||
|
error: cannot find macro `println!` in this scope
|
||||||
|
--> src/main.rs:4:5
|
||||||
|
|
|
||||||
|
4 | println!("Hello, world!");
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: `#[panic_handler]` function required, but not found
|
||||||
|
|
||||||
|
error: language item required, but not found: `eh_personality`
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
The reason for the first error is that the [`println` macro] is part of the standard library, which we no longer include.
|
||||||
|
So we can no longer print things.
|
||||||
|
This makes sense, since `println` writes to [standard output], which is a special file descriptor provided by the operating system.
|
||||||
|
|
||||||
|
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||||
|
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||||
|
|
||||||
|
So let's remove the printing and try again with an empty main function:
|
||||||
|
|
||||||
|
```rust,hl_lines=5
|
||||||
|
// main.rs
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build
|
||||||
|
error: `#[panic_handler]` function required, but not found
|
||||||
|
|
||||||
|
error: language item required, but not found: `eh_personality`
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
The `println` error is gone, but the compiler is still missing a `#[panic_handler]` function and a _language item_.
|
||||||
|
|
||||||
|
### Panic Implementation
|
||||||
|
|
||||||
|
The `panic_handler` attribute defines the function that the compiler should invoke when a [panic] occurs.
|
||||||
|
The standard library provides its own panic handler function, but in a `no_std` environment we need to define one ourselves:
|
||||||
|
|
||||||
|
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||||
|
|
||||||
|
```rust,hl_lines=3 9-13
|
||||||
|
// in main.rs
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
/// This function is called on panic.
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The [`PanicInfo` parameter][PanicInfo] contains the file and line where the panic happened and the optional panic message.
|
||||||
|
The handler function should never return, so it is marked as a [diverging function] by returning the [“never” type] `!`.
|
||||||
|
There is not much we can do in this function for now, so we just loop indefinitely.
|
||||||
|
|
||||||
|
[PanicInfo]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html
|
||||||
|
[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
|
||||||
|
[“never” type]: https://doc.rust-lang.org/std/primitive.never.html
|
||||||
|
|
||||||
|
After defining a panic handler, only the `eh_personality` language item error remains:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build
|
||||||
|
error: language item required, but not found: `eh_personality`
|
||||||
|
|
|
||||||
|
= note: this can occur when a binary crate with `#![no_std]` is compiled for a
|
||||||
|
target where `eh_personality` is defined in the standard library
|
||||||
|
= help: you may be able to compile for a target that doesn't need `eh_personality`,
|
||||||
|
specify a target with `--target` or in `.cargo/config`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabling Unwinding
|
||||||
|
|
||||||
|
Language items are special functions and types that are required internally by the compiler.
|
||||||
|
They are normally provided by the standard library, which we disabled using the `#![no_std]` attribute.
|
||||||
|
|
||||||
|
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 complex process and requires some OS-specific libraries, such as [libunwind] on Linux or [structured exception handling] on Windows.
|
||||||
|
|
||||||
|
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||||
|
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||||
|
[libunwind]: https://www.nongnu.org/libunwind/
|
||||||
|
[structured exception handling]: https://docs.microsoft.com/de-de/windows/win32/debug/structured-exception-handling
|
||||||
|
|
||||||
|
While unwinding is very useful, it also has some drawbacks.
|
||||||
|
For example, it increases the size of the compiled executable because it requires additional context at runtime.
|
||||||
|
Because of these drawbacks, Rust provides an option to [abort on panic] instead.
|
||||||
|
|
||||||
|
[abort on panic]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html#unwinding-the-stack-or-aborting-in-response-to-a-panic
|
||||||
|
|
||||||
|
We already use a custom panic handler that never returns, so we don't need unwinding for our kernel.
|
||||||
|
By disabling it, the `eh_personality` language item won't be required anymore.
|
||||||
|
|
||||||
|
There are multiple ways to set the panic strategy, the easiest is to use [cargo profiles]:
|
||||||
|
|
||||||
|
[cargo profiles]: https://doc.rust-lang.org/cargo/reference/profiles.html
|
||||||
|
|
||||||
|
```toml,hl_lines=3-7
|
||||||
|
# in Cargo.toml
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
```
|
||||||
|
|
||||||
|
This sets the panic strategy to `abort` for both the `dev` profile (used for `cargo build`) and the `release` profile (used for `cargo build --release`).
|
||||||
|
Now the `eh_personality` language item should no longer be required.
|
||||||
|
|
||||||
|
When we try to compile our kernel now, a new error occurs:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build
|
||||||
|
error: requires `start` lang_item
|
||||||
|
```
|
||||||
|
|
||||||
|
Our kernel is missing the `start` language item, which defines the _entry point_ of the executable.
|
||||||
|
|
||||||
|
|
||||||
|
## Setting the Entry Point
|
||||||
|
|
||||||
|
The [entry point] of a program is the function that is called when the executable is started.
|
||||||
|
One might think that the `main` function is the first function called, however, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go).
|
||||||
|
This runtime needs to be called before `main`, since it needs to initialize itself.
|
||||||
|
|
||||||
|
[entry point]: https://en.wikipedia.org/wiki/Entry_point
|
||||||
|
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||||
|
|
||||||
|
In a typical Rust binary that links the standard library, execution starts in a C runtime library called [`crt0`] (“C runtime zero”), which sets up the environment for a C application.
|
||||||
|
This includes creating a [call stack] and placing the command line arguments in the right CPU registers.
|
||||||
|
The C runtime then invokes the [entry point of the Rust runtime][rt::lang_start], which is marked by the `start` language item.
|
||||||
|
Rust only has a very minimal runtime, which takes care of some small things such as setting up stack overflow guards or printing a backtrace on panic.
|
||||||
|
The runtime then finally calls the `main` function.
|
||||||
|
|
||||||
|
[`crt0`]: https://en.wikipedia.org/wiki/Crt0
|
||||||
|
[call stack]: https://en.wikipedia.org/wiki/Call_stack
|
||||||
|
[rt::lang_start]: hhttps://github.com/rust-lang/rust/blob/0d97f7a96877a96015d70ece41ad08bb7af12377/library/std/src/rt.rs#L59-L70
|
||||||
|
|
||||||
|
Since we're building an operating system kernel that should run without any underlying operating system, we don't want our kernel to depend on any Rust or C runtime.
|
||||||
|
To remove these dependencies, we need to do two things:
|
||||||
|
|
||||||
|
1. Instruct the compiler that we want to build for a bare-metal target environment. This removes the dependency on the C library.
|
||||||
|
2. Disable the Rust main function to remove the Rust runtime.
|
||||||
|
|
||||||
|
### Bare-Metal Target
|
||||||
|
|
||||||
|
By default Rust tries to build an executable that is able to run in your current system environment.
|
||||||
|
For example, if you're using Windows and an `x86_64` CPU, Rust tries to build a `.exe` Windows executable that uses `x86_64` instructions.
|
||||||
|
This environment is called your "host" system.
|
||||||
|
|
||||||
|
To describe different environments, Rust uses a string called [_target triple_].
|
||||||
|
You can see the target triple for your host system by running `rustc --version --verbose`:
|
||||||
|
|
||||||
|
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||||
|
|
||||||
|
```
|
||||||
|
rustc 1.68.1 (8460ca823 2023-03-20)
|
||||||
|
binary: rustc
|
||||||
|
commit-hash: 8460ca823e8367a30dda430efda790588b8c84d3
|
||||||
|
commit-date: 2023-03-20
|
||||||
|
host: x86_64-unknown-linux-gnu
|
||||||
|
release: 1.68.1
|
||||||
|
LLVM version: 15.0.6
|
||||||
|
```
|
||||||
|
|
||||||
|
The above output is from a `x86_64` Linux system.
|
||||||
|
We see that the `host` triple is `x86_64-unknown-linux-gnu`, which includes the CPU architecture (`x86_64`), the vendor (`unknown`), the operating system (`linux`), and the [ABI] (`gnu`).
|
||||||
|
|
||||||
|
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||||
|
|
||||||
|
By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that uses the C runtime by default, which requires the `start` language item.
|
||||||
|
To avoid the runtimes, we can compile for a different environment with no underlying operating system.
|
||||||
|
|
||||||
|
#### The `x86_64-unknown-none` Target
|
||||||
|
|
||||||
|
Rust supports a [variety of target systems][platform-support], including some bare-metal targets.
|
||||||
|
For example, the `thumbv7em-none-eabihf` target triple can be used to compile for an [embedded] [ARM] system with a `Cortex M4F` CPU, as used in the [Rust Embedded Book].
|
||||||
|
|
||||||
|
[platform-support]: https://doc.rust-lang.org/rustc/platform-support.html
|
||||||
|
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
||||||
|
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||||
|
[Rust Embedded Book]: https://docs.rust-embedded.org/book/intro/index.html
|
||||||
|
|
||||||
|
Our kernel should run on a bare-metal `x86_64` system, so the suitable target triple is [`x86_64-unknown-none`].
|
||||||
|
The `-none` suffix indicates that there is no underlying operating system.
|
||||||
|
To be able to compile for this target, we need to add it using [`rustup`].
|
||||||
|
|
||||||
|
[`x86_64-unknown-none`]: https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html
|
||||||
|
|
||||||
|
<div class = "note"><details>
|
||||||
|
<summary><em>What is <code>rustup</code></em>?</summary>
|
||||||
|
|
||||||
|
The [`rustup`] tool is the [officially recommended] way of installing Rust.
|
||||||
|
It supports having multiple versions of Rust installed simultaneously and makes upgrading Rust easy.
|
||||||
|
It also provides access to optional tools and components such as [`rustfmt`] or [`rust-analyzer`].
|
||||||
|
This guide requires `rustup`, so please install it if you haven't already.
|
||||||
|
|
||||||
|
[`rustup`]: https://rustup.rs/
|
||||||
|
[officially recommended]: https://www.rust-lang.org/learn/get-started
|
||||||
|
[`rustfmt`]: https://github.com/rust-lang/rustfmt/
|
||||||
|
[`rust-analyzer`]: https://github.com/rust-lang/rust-analyzer
|
||||||
|
|
||||||
|
</details></div>
|
||||||
|
|
||||||
|
To download and set up the `x86_64-unknown-none` target, we use the following `rustup` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
rustup target add x86_64-unknown-none
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads a pre-compiled copy of the `core` library for the target.
|
||||||
|
Afterwards, we can [cross compile] our executable for a bare metal environment by passing a `--target` argument:
|
||||||
|
|
||||||
|
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build --target x86_64-unknown-none
|
||||||
|
Compiling kernel v0.1.0 (/<...>/kernel)
|
||||||
|
error: requires `start` lang_item
|
||||||
|
```
|
||||||
|
|
||||||
|
We still get the error about a missing `start` language item because the custom target only removed the dependency on the C library.
|
||||||
|
To remove the dependency on the Rust runtime as well, we can use the `#[no_main]` attribute.
|
||||||
|
|
||||||
|
Before that, we can do a small cleanup.
|
||||||
|
The `x86_64-unknown-none` target defaults to `panic = "abort"`, so the we can remove the `profile.dev` and `profile.release` tables from our `Cargo.toml` again.
|
||||||
|
|
||||||
|
|
||||||
|
### The `#[no_main]` Attribute
|
||||||
|
|
||||||
|
To tell the Rust compiler that we don't want to use the normal entry point chain, we add the `#![no_main]` attribute.
|
||||||
|
|
||||||
|
```rust,hl_lines=4
|
||||||
|
// main.rs
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
/// This function is called on panic.
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You might notice that we removed the `main` function.
|
||||||
|
The reason is that a `main` doesn't make sense without an underlying runtime that calls it.
|
||||||
|
Instead, we are now overwriting the operating system entry point with our own `_start` function:
|
||||||
|
|
||||||
|
```rust,hl_lines=3-6
|
||||||
|
// in main.rs
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By using the `#[no_mangle]` attribute we disable the [name mangling] to ensure that the Rust compiler really outputs a function with the name `_start`.
|
||||||
|
Without the attribute, the compiler would generate some cryptic `_ZN3kernel4_start7hb173fedf945531caE` symbol to give every function an unique name.
|
||||||
|
The reason for naming the function `_start` is that this is the default entry point name for most systems.
|
||||||
|
|
||||||
|
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
|
||||||
|
|
||||||
|
We mark the function as `extern "C"` to tell the compiler that it should use the [C calling convention] for this function (instead of the unspecified Rust calling convention).
|
||||||
|
|
||||||
|
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||||
|
|
||||||
|
Like in our panic handler, the `!` return type means that the function is diverging, i.e. not allowed to ever return.
|
||||||
|
This is required because the entry point is not called by any function, but invoked directly by the operating system or bootloader.
|
||||||
|
So instead of returning, the entry point should e.g. invoke the [`exit` system call] of the operating system.
|
||||||
|
In our case, shutting down the machine could be a reasonable action, since there's nothing left to do if a freestanding binary returns.
|
||||||
|
For now, we fulfill the requirement by looping endlessly.
|
||||||
|
|
||||||
|
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||||
|
|
||||||
|
When we run `cargo build --target x86_64-unknown-none` now, it should finally compile without any errors:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build --target x86_64-unknown-none
|
||||||
|
Compiling kernel v0.1.0 (/<...>/kernel)
|
||||||
|
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
|
||||||
|
```
|
||||||
|
|
||||||
|
We successfully created a minimal bare-metal kernel executable! The compiled executable can be found at `target/x86_64-unknown-none/debug/kernel`.
|
||||||
|
There is no `.exe` extension even if you're on Windows because the `x86_64-unknown-none` target uses UNIX standards.
|
||||||
|
|
||||||
|
To build the kernel with optimizations, we can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo build --target x86_64-unknown-none --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled executable is placed at `target/x86_64-unknown-none/release/kernel` in this case.
|
||||||
|
|
||||||
|
In the next post we will cover how to turn this kernel into a bootable disk image that can be run in a virtual machine or on real hardware.
|
||||||
|
In the rest of this post, we will introduce some tools for examining our kernel executable.
|
||||||
|
These tools are very useful for debugging future issues, so it's good to know about them.
|
||||||
|
|
||||||
|
## Useful Tools
|
||||||
|
|
||||||
|
In this section, we will examine our kernel executable using the [`objdump`], [`nm`], and [`size`] tools.
|
||||||
|
|
||||||
|
[`objdump`]: https://www.man7.org/linux/man-pages/man1/objdump.1.html
|
||||||
|
[`nm`]: https://man7.org/linux/man-pages/man1/nm.1.html
|
||||||
|
[`size`]: https://man7.org/linux/man-pages/man1/size.1.html
|
||||||
|
|
||||||
|
If you're on a UNIX system, you might already have the above tools installed.
|
||||||
|
Otherwise (and on Windows), you can use the LLVM binutils shipped by `rustup` through the [`cargo-binutils`] crate.
|
||||||
|
To install it, run **`cargo install cargo-binutils`** and **`rustup component add llvm-tools-preview`**.
|
||||||
|
Afterwards, you can run the tools through `rust-nm`, `rust-objdump`, and `rust-strip`.
|
||||||
|
|
||||||
|
[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
|
||||||
|
|
||||||
|
### List Symbols using `nm`
|
||||||
|
|
||||||
|
We defined a `_start` function as the entry point of our kernel.
|
||||||
|
To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ rust-nm target/x86_64-unknown-none/debug/kernel
|
||||||
|
0000000000201120 T _start
|
||||||
|
```
|
||||||
|
|
||||||
|
If we comment out the `_start` function or if we remove the `#[no_mangle]` attribute, the `_start` symbol is no longer there after recompiling:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ rust-nm target/x86_64-unknown-none/debug/kernel
|
||||||
|
```
|
||||||
|
|
||||||
|
This way we can ensure that we set the `_start` function correctly.
|
||||||
|
|
||||||
|
### Inspect ELF File using `objdump`
|
||||||
|
|
||||||
|
The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable.
|
||||||
|
|
||||||
|
[ELF file format]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||||
|
|
||||||
|
#### File Headers
|
||||||
|
|
||||||
|
Among other things, the ELF [file header] specifies the target architecture and the entry point address of the executable files.
|
||||||
|
To print the file header, we can use `objdump -f`:
|
||||||
|
|
||||||
|
[file header]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ rust-objdump -f target/x86_64-unknown-none/debug/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
|
||||||
|
architecture: x86_64
|
||||||
|
start address: 0x0000000000001210
|
||||||
|
```
|
||||||
|
|
||||||
|
As expected, our kernel targets the `x86_64` CPU architecture.
|
||||||
|
The start address specifies the memory address of our `_start` function.
|
||||||
|
Here the function name `_start` becomes important.
|
||||||
|
If we rename the function to something else (e.g., `_start_here`) and recompile, we see that no start address is set in the ELF file anymore:
|
||||||
|
|
||||||
|
```bash,hl_lines=5
|
||||||
|
❯ rust-objdump -f target/x86_64-unknown-none/debug/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
|
||||||
|
architecture: x86_64
|
||||||
|
start address: 0x0000000000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sections
|
||||||
|
|
||||||
|
Using `objdump -h`, we can print the various sections of our kernel executable:
|
||||||
|
|
||||||
|
```bash,hl_lines=12
|
||||||
|
❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
|
||||||
|
|
||||||
|
Sections:
|
||||||
|
Idx Name Size VMA Type
|
||||||
|
0 00000000 0000000000000000
|
||||||
|
1 .dynsym 00000018 00000000000001c8
|
||||||
|
2 .gnu.hash 0000001c 00000000000001e0
|
||||||
|
3 .hash 00000010 00000000000001fc
|
||||||
|
4 .dynstr 00000001 000000000000020c
|
||||||
|
5 .text 00000004 0000000000001210 TEXT
|
||||||
|
6 .dynamic 000000a0 0000000000002218
|
||||||
|
7 .debug_abbrev 0000010c 0000000000000000 DEBUG
|
||||||
|
8 .debug_info 000005ce 0000000000000000 DEBUG
|
||||||
|
9 .debug_aranges 00000040 0000000000000000 DEBUG
|
||||||
|
10 .debug_ranges 00000030 0000000000000000 DEBUG
|
||||||
|
11 .debug_str 00000492 0000000000000000 DEBUG
|
||||||
|
12 .debug_pubnames 000000bc 0000000000000000 DEBUG
|
||||||
|
13 .debug_pubtypes 0000036c 0000000000000000 DEBUG
|
||||||
|
14 .debug_frame 00000050 0000000000000000 DEBUG
|
||||||
|
15 .debug_line 00000059 0000000000000000 DEBUG
|
||||||
|
16 .comment 00000013 0000000000000000
|
||||||
|
17 .symtab 00000060 0000000000000000
|
||||||
|
18 .shstrtab 000000ce 0000000000000000
|
||||||
|
19 .strtab 00000022 0000000000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
The `.text` section contains the program code, the other sections are not important right now.
|
||||||
|
The section dump is useful for debugging, for example for checking which section a pointer points to.
|
||||||
|
|
||||||
|
Most of the sections only contain debug information and are not needed for execution.
|
||||||
|
We can remove this debug information using `rust-strip`:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ rust-strip target/x86_64-unknown-none/debug/kernel
|
||||||
|
❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
|
||||||
|
|
||||||
|
Sections:
|
||||||
|
Idx Name Size VMA Type
|
||||||
|
0 00000000 0000000000000000
|
||||||
|
1 .dynsym 00000018 00000000000001c8
|
||||||
|
2 .gnu.hash 0000001c 00000000000001e0
|
||||||
|
3 .hash 00000010 00000000000001fc
|
||||||
|
4 .dynstr 00000001 000000000000020c
|
||||||
|
5 .text 00000004 0000000000001210 TEXT
|
||||||
|
6 .dynamic 000000a0 0000000000002218
|
||||||
|
7 .shstrtab 00000034 0000000000000000
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Disassembling
|
||||||
|
|
||||||
|
Sometimes we need to check the [assembly code] that certain functions compile to.
|
||||||
|
We can use the `objdump -d` command to print the `.text` section of an executable in assembly language:
|
||||||
|
|
||||||
|
[assembly code]: https://en.wikipedia.org/wiki/X86_assembly_language
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ rust-objdump -d target/x86_64-unknown-none/debug/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
|
||||||
|
|
||||||
|
Disassembly of section .text:
|
||||||
|
|
||||||
|
0000000000001210 <_start>:
|
||||||
|
1210: eb 00 jmp 0x1212 <_start+0x2>
|
||||||
|
1212: eb fe jmp 0x1212 <_start+0x2>
|
||||||
|
```
|
||||||
|
|
||||||
|
We see that our `_start` function consists of just two [`jmp` instructions], which jump to the given address.
|
||||||
|
The first `jmp` command jumps to the second `jmp` command at address `1212`.
|
||||||
|
The second `jmp` command jumps to itself again, thereby representing the infinite loop that we've written in our `_start` function.
|
||||||
|
|
||||||
|
[`jmp` instructions]: https://www.felixcloutier.com/x86/jmp
|
||||||
|
|
||||||
|
As you probably noticed, the first `jmp` command is not really needed.
|
||||||
|
Such inefficiencies can happen in debug builds because the compiler does not optimize them.
|
||||||
|
If we disassemble the optimized release build, we see that the compiler indeed removed the unneeded `jmp`:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ cargo build --target x86_64-unknown-none --release
|
||||||
|
❯ rust-objdump -d target/x86_64-unknown-none/release/kernel
|
||||||
|
|
||||||
|
target/x86_64-unknown-none/release/kernel: file format elf64-x86-64
|
||||||
|
|
||||||
|
Disassembly of section .text:
|
||||||
|
|
||||||
|
0000000000001210 <_start>:
|
||||||
|
1210: eb fe jmp 0x1210 <_start>
|
||||||
|
```
|
||||||
|
|
||||||
|
We will use continue to use the above tools in future posts, as they're quite useful for debugging issues.
|
||||||
|
|
||||||
|
## What's next?
|
||||||
|
|
||||||
|
In the [next post], we will learn how to turn our minimal kernel in a bootable disk image, which can then be started in the [QEMU] virtual machine and on real hardware.
|
||||||
|
For this, we'll explore the boot process of `x86_64` systems and learn about the differences between UEFI and the legacy BIOS firmware.
|
||||||
|
|
||||||
|
[next post]: @/edition-3/posts/02-booting/index.md
|
||||||
|
[QEMU]: https://www.qemu.org/
|
||||||
1167
blog/content/edition-3/posts/02-booting/index.md
Normal file
BIN
blog/content/edition-3/posts/02-booting/qemu-bios.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
blog/content/edition-3/posts/02-booting/qemu-gray.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
blog/content/edition-3/posts/02-booting/qemu-uefi.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
1107
blog/content/edition-3/posts/02-booting/uefi/index.md
Normal file
|
After Width: | Height: | Size: 19 KiB |
491
blog/content/edition-3/posts/03-screen-output/index.md
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
+++
|
||||||
|
title = "Screen Output"
|
||||||
|
weight = 3
|
||||||
|
path = "screen-output"
|
||||||
|
date = 0000-01-01
|
||||||
|
draft = true
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
chapter = "Basic I/O"
|
||||||
|
icon = '''<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-display" viewBox="0 0 16 16">
|
||||||
|
<path d="M0 4s0-2 2-2h12s2 0 2 2v6s0 2-2 2h-4c0 .667.083 1.167.25 1.5H11a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1h.75c.167-.333.25-.833.25-1.5H2s-2 0-2-2V4zm1.398-.855a.758.758 0 0 0-.254.302A1.46 1.46 0 0 0 1 4.01V10c0 .325.078.502.145.602.07.105.17.188.302.254a1.464 1.464 0 0 0 .538.143L2.01 11H14c.325 0 .502-.078.602-.145a.758.758 0 0 0 .254-.302 1.464 1.464 0 0 0 .143-.538L15 9.99V4c0-.325-.078-.502-.145-.602a.757.757 0 0 0-.302-.254A1.46 1.46 0 0 0 13.99 3H2c-.325 0-.502.078-.602.145z"/>
|
||||||
|
</svg>'''
|
||||||
|
+++
|
||||||
|
|
||||||
|
In this post we focus on the [framebuffer], a special memory region that controls the screen output.
|
||||||
|
Using an [external crate], we will create functions for writing individual pixels, lines, and various shapes.
|
||||||
|
In the the second half of this post, we will explore text rendering and learn how to print the obligatory _["Hello, World!"]_.
|
||||||
|
|
||||||
|
[framebuffer]: https://en.wikipedia.org/wiki/Framebuffer
|
||||||
|
[external crate]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
|
||||||
|
["Hello, World!"]: https://en.wikipedia.org/wiki/Hello_world
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
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-3.3`][post branch] branch.
|
||||||
|
|
||||||
|
[GitHub]: https://github.com/phil-opp/blog_os
|
||||||
|
[at the bottom]: #comments
|
||||||
|
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
|
||||||
|
[post branch]: https://github.com/phil-opp/blog_os/tree/post-3.3
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
## Bitmap Images
|
||||||
|
|
||||||
|
In the [previous post], we learned how to make our minimal kernel bootable.
|
||||||
|
Using the [`BootInfo`] provided by the bootloader, we were able to access a special memory region called the _[framebuffer]_, which controls the screen output.
|
||||||
|
We wrote some example code to display a gray background:
|
||||||
|
|
||||||
|
[previous post]: @/edition-3/posts/02-booting/index.md
|
||||||
|
[`BootInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.BootInfo.html
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
|
for byte in framebuffer.buffer_mut() {
|
||||||
|
*byte = 0x90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The reason that the above code affects the screen output is because the graphics card interprets the framebuffer memory as a [bitmap] image.
|
||||||
|
A bitmap describes an image through a predefined number of bytes per pixel.
|
||||||
|
The pixels are laid out line by line, typically starting at the top.
|
||||||
|
|
||||||
|
[bitmap]: https://en.wikipedia.org/wiki/Bitmap
|
||||||
|
[RGB]: https://en.wikipedia.org/wiki/Rgb
|
||||||
|
|
||||||
|
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
|
||||||
|
|
||||||
|
<table style = "width: fit-content;"><tbody>
|
||||||
|
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
|
||||||
|
<tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
|
||||||
|
<tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
|
||||||
|
So top left pixel is stored at offset 0 in the bitmap array.
|
||||||
|
The pixel on its right is at pixel offset 1.
|
||||||
|
The first pixel of the next line starts at pixel offset `line_length`, which is 10 in this case.
|
||||||
|
The last line starts at pixel offset 20, which is `line_length * 2`.
|
||||||
|
|
||||||
|
### Padding
|
||||||
|
|
||||||
|
Depending on the hardware and GPU firmware, it is often more efficient to make lines start at well-aligned offsets.
|
||||||
|
Because of this, there is often some additional padding at the end of each line.
|
||||||
|
So the actual memory layout of the 10x3 example image might look like this, with the padding marked as yellow:
|
||||||
|
|
||||||
|
<table style = "width: fit-content;"><tbody>
|
||||||
|
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td style="background-color:yellow;">10</td><td style="background-color:yellow;">11</td></tr>
|
||||||
|
<tr><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td><td>20</td><td>21</td><td style="background-color:yellow;">22</td><td style="background-color:yellow;">23</td></tr>
|
||||||
|
<tr><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>31</td><td>32</td><td>33</td><td style="background-color:yellow;">34</td><td style="background-color:yellow;">35</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
|
||||||
|
So now the second line starts at pixel offset 12.
|
||||||
|
The two pixels at the end of each line are considered as padding and ignored.
|
||||||
|
So if we want to set the first pixel of the second line, we need to be aware of the additional padding and set the pixel at offset 12 instead of offset 10.
|
||||||
|
|
||||||
|
The line length plus the padding bytes is typically called the _stride_ or _pitch_ of the buffer.
|
||||||
|
In the example above, the stride is 12 and the line length is 10.
|
||||||
|
|
||||||
|
Since the amount of padding depends on the hardware, the stride is only known at runtime.
|
||||||
|
The `bootloader` crate queries the framebuffer parameters from the UEFI or BIOS firmware and reports them as part of the `BootInfo`.
|
||||||
|
It provides the stride of the framebuffer, among other parameters, in form of a [`FrameBufferInfo`] struct that can be created using the [`FrameBuffer::info`] method.
|
||||||
|
|
||||||
|
[`FrameBufferInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html
|
||||||
|
[`FrameBuffer::info`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBuffer.html#method.info
|
||||||
|
|
||||||
|
### Color formats
|
||||||
|
|
||||||
|
The [`FrameBufferInfo`] also specifies the [`PixelFormat`] of the framebuffer, which also depends on the underlying hardware.
|
||||||
|
Using this information, we can set pixels to different colors.
|
||||||
|
For example, the [`PixelFormat::Rgb`] variant specifies that each pixel is represented in the [RGB color space], which stores the red, green, and blue parts of the pixel as separate bytes.
|
||||||
|
In this model, the color red would be represented as the three bytes `[255, 0, 0]`, or `0xff0000` in [hexadecimal representation].
|
||||||
|
The color yellow is represented the addition of red and green, which results in `[255, 255, 0]` (or `0xffff00` in hexadecimal representation).
|
||||||
|
|
||||||
|
[`PixelFormat`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html
|
||||||
|
[`PixelFormat::Rgb`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html#variant.Rgb
|
||||||
|
[RGB color space]: https://en.wikipedia.org/wiki/RGB_color_spaces
|
||||||
|
[hexadecimal representation]: https://en.wikipedia.org/wiki/RGB_color_model#Numeric_representations
|
||||||
|
|
||||||
|
While the `Rgb` format is most common, there are also framebuffers that use a different color format.
|
||||||
|
For example, the [`PixelFormat::Bgr`] stores the three colors in inverted order, i.e. blue first and red last.
|
||||||
|
There are also buffers that don't support colors at all and can represent only grayscale pixels.
|
||||||
|
The `bootloader_api` crate reports such buffers as [`PixelFormat::U8`].
|
||||||
|
|
||||||
|
[`PixelFormat::Bgr`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.Bgr
|
||||||
|
[`PixelFormat::U8`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.U8
|
||||||
|
|
||||||
|
Note that there might be some additional padding at the pixel-level as well.
|
||||||
|
For example, an `Rgb` pixel might be stored as 4 bytes instead of 3 to ensure 32-bit alignment.
|
||||||
|
The number of bytes per pixel is reported by the bootloader in the [`FrameBufferInfo::bytes_per_pixel`] field.
|
||||||
|
|
||||||
|
[`FrameBufferInfo::bytes_per_pixel`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html#structfield.bytes_per_pixel
|
||||||
|
|
||||||
|
## Setting specific Pixels
|
||||||
|
|
||||||
|
Based on this above details, we can now create a function to set a specific pixel to a certain color.
|
||||||
|
We start by creating a new `framebuffer` [module]:
|
||||||
|
|
||||||
|
[module]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
|
||||||
|
|
||||||
|
```rust ,hl_lines=3-5
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
// declare a submodule -> the compiler will automatically look
|
||||||
|
// for a file named `framebuffer.rs` or `framebuffer/mod.rs`
|
||||||
|
mod framebuffer;
|
||||||
|
```
|
||||||
|
|
||||||
|
In the new module, we create basic structs for representing pixel positions and colors:
|
||||||
|
|
||||||
|
```rust ,hl_lines=3-16
|
||||||
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Position {
|
||||||
|
pub x: usize,
|
||||||
|
pub y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Color {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By marking the structs and their fields as `pub`, we make them accessible from the parent `kernel` module.
|
||||||
|
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
|
||||||
|
These traits allow us to duplicate, compare, and print the structs.
|
||||||
|
|
||||||
|
[`Debug`]: https://doc.rust-lang.org/stable/core/fmt/trait.Debug.html
|
||||||
|
[`Clone`]: https://doc.rust-lang.org/stable/core/clone/trait.Clone.html
|
||||||
|
[`Copy`]: https://doc.rust-lang.org/stable/core/marker/trait.Copy.html
|
||||||
|
[`PartialEq`]: https://doc.rust-lang.org/stable/core/cmp/trait.PartialEq.html
|
||||||
|
[`Eq`]: https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html
|
||||||
|
|
||||||
|
Next, we create a function for setting a specific pixel in the framebuffer to a given color:
|
||||||
|
|
||||||
|
```rust ,hl_lines=3 5-39
|
||||||
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
use bootloader_api::info::{FrameBuffer, PixelFormat};
|
||||||
|
|
||||||
|
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
||||||
|
let info = framebuffer.info();
|
||||||
|
|
||||||
|
// calculate offset to first byte of pixel
|
||||||
|
let byte_offset = {
|
||||||
|
// use stride to calculate pixel offset of target line
|
||||||
|
let line_offset = position.y * info.stride;
|
||||||
|
// add x position to get the absolute pixel offset in buffer
|
||||||
|
let pixel_offset = line_offset + position.x;
|
||||||
|
// convert to byte offset
|
||||||
|
pixel_offset * info.bytes_per_pixel
|
||||||
|
};
|
||||||
|
|
||||||
|
// set pixel based on color format
|
||||||
|
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
|
||||||
|
match info.pixel_format {
|
||||||
|
PixelFormat::Rgb => {
|
||||||
|
pixel_buffer[0] = color.red;
|
||||||
|
pixel_buffer[1] = color.green;
|
||||||
|
pixel_buffer[2] = color.blue;
|
||||||
|
}
|
||||||
|
PixelFormat::Bgr => {
|
||||||
|
pixel_buffer[0] = color.blue;
|
||||||
|
pixel_buffer[1] = color.green;
|
||||||
|
pixel_buffer[2] = color.red;
|
||||||
|
}
|
||||||
|
PixelFormat::U8 => {
|
||||||
|
// use a simple average-based grayscale transform
|
||||||
|
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
||||||
|
pixel_buffer[0] = gray;
|
||||||
|
}
|
||||||
|
other => panic!("unknown pixel format {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first step is to calculate the byte offset within the framebuffer slice at which the pixel starts.
|
||||||
|
For this, we first calculate the pixel offset of the line by multiplying the `y` position with the stride of the framebuffer, i.e. its line width plus the line padding.
|
||||||
|
We then add the `x` position to get the absolute index of the pixel.
|
||||||
|
As the framebuffer slice is a byte slice, we need to transform the pixel index to a byte offset by multiplying it with the number of `bytes_per_pixel`.
|
||||||
|
|
||||||
|
[`FrameBuffer::buffer_mut`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/struct.FrameBuffer.html#method.buffer_mut
|
||||||
|
|
||||||
|
The second step is to set the pixel to the desired color.
|
||||||
|
We first use the [`FrameBuffer::buffer_mut`] method to get access to the actual bytes of the framebuffer in form of a slice.
|
||||||
|
Then, we use the slicing operator `[byte_offset..]` to get a sub-slice starting at the `byte_offset` of the target pixel.
|
||||||
|
As the write operation depends on the pixel format, we use a [`match`] statement:
|
||||||
|
|
||||||
|
[`match`]: https://doc.rust-lang.org/stable/std/keyword.match.html
|
||||||
|
|
||||||
|
- For `Rgb` framebuffers, we write three bytes; first red, then green, then blue.
|
||||||
|
- For `Bgr` framebuffers, we also write three bytes, but blue first and red last.
|
||||||
|
- For `U8` framebuffers, we first convert the color to grayscale by taking the average of the three color channels.
|
||||||
|
Note that there are multiple [different ways to convert colors to grayscale], so you can also use different factors here.
|
||||||
|
- For all other framebuffer formats, we [panic] for now.
|
||||||
|
|
||||||
|
[different ways to convert colors to grayscale]: https://www.baeldung.com/cs/convert-rgb-to-grayscale#bd-convert-rgb-to-grayscale
|
||||||
|
[panic]: https://doc.rust-lang.org/stable/core/macro.panic.html
|
||||||
|
|
||||||
|
Let's try to use our new function to write a blue pixel in our `kernel_main` function:
|
||||||
|
|
||||||
|
```rust ,hl_lines=5-11
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
|
let position = framebuffer::Position { x: 20, y: 100 };
|
||||||
|
let color = framebuffer::Color {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When we run our code in QEMU using `cargo run --bin qemu-bios` (or `--bin qemu-uefi`) and look _very closely_, we can see the blue pixel.
|
||||||
|
It's really difficult to see, so I marked with an arrow below:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
As this single pixel is too difficult to see, let's draw a filled square of 100x100 pixels instead:
|
||||||
|
|
||||||
|
```rust ,hl_lines=10-18
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
|
let color = framebuffer::Color {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255,
|
||||||
|
};
|
||||||
|
for x in 0..100 {
|
||||||
|
for y in 0..100 {
|
||||||
|
let position = framebuffer::Position {
|
||||||
|
x: 20 + x,
|
||||||
|
y: 100 + y,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we clearly see that our code works as intended:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Feel free to experiment with different positions and colors if you like.
|
||||||
|
You can also try to draw a circle instead of a square, or a line with a certain thickness.
|
||||||
|
|
||||||
|
As you can probably imagine, it would be a lot of work to draw more complex shapes this way.
|
||||||
|
One example for such complex shapes is _text_, i.e. the rendering of letters and punctuation.
|
||||||
|
Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate, which provides draw functions for text, various shapes, and image data.
|
||||||
|
|
||||||
|
[`embedded-graphics`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/index.html
|
||||||
|
|
||||||
|
## The `embedded-graphics` crate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Implementing `DrawTarget`
|
||||||
|
|
||||||
|
```rust ,hl_lines=3
|
||||||
|
// in kernel/src/framebuffer.rs
|
||||||
|
use embedded_graphics::{
|
||||||
|
Pixel,
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
geometry::{OriginDimensions, Size},
|
||||||
|
pixelcolor::{Rgb888, RgbColor},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Display<'f> {
|
||||||
|
framebuffer: &'f mut FrameBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f> Display<'f> {
|
||||||
|
pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
|
||||||
|
Display { framebuffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(&mut self, Pixel(coordinates, color): Pixel<Rgb888>) {
|
||||||
|
// ignore any out of bounds pixels
|
||||||
|
let (width, height) = {
|
||||||
|
let info = self.framebuffer.info();
|
||||||
|
|
||||||
|
(info.width, info.height)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (x, y) = {
|
||||||
|
let c: (i32, i32) = coordinates.into();
|
||||||
|
(c.0 as usize, c.1 as usize)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (0..width).contains(&x) && (0..height).contains(&y) {
|
||||||
|
let color = Color { red: color.r(), green: color.g(), blue: color.b() };
|
||||||
|
|
||||||
|
set_pixel_in(self.framebuffer, Position { x, y }, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<'f> DrawTarget for Display<'f> {
|
||||||
|
type Color = Rgb888;
|
||||||
|
|
||||||
|
/// Drawing operations can never fail.
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for pixel in pixels.into_iter() {
|
||||||
|
self.draw_pixel(pixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f> OriginDimensions for Display<'f> {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
let info = self.framebuffer.info();
|
||||||
|
|
||||||
|
Size::new(info.width as u32, info.height as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
So far, we have drawn shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
|
||||||
|
|
||||||
|
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
|
||||||
|
|
||||||
|
In most other programming languages, implementing this behind the scenes can be a daunting task. With Rust, however, we have a toolset at our disposal that can pave the way for setting up proper framebuffer logging using very little code of our own.
|
||||||
|
|
||||||
|
# The `log` crate
|
||||||
|
|
||||||
|
Rust developers used to writing user-mode code will recognize the `log` crate from a mile away:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# in Cargo.toml
|
||||||
|
[dependencies]
|
||||||
|
log = { version = "0.4.17", default-features = false }
|
||||||
|
```
|
||||||
|
|
||||||
|
This crate has both a set of macros for logging either to the console or to a log file for later reading and a trait — also called `Log` with a capital L — that can be implemented to provide a backend, called a `Logger` in Rust parlance. Loggers are provided by a myriad of crates for a wide variety of use cases, and some of them even run on bare metal. We already used one such extant logger in the UEFI booting module when we used the logger provided by the `uefi` crate to print text to the UEFI console. That won't work in the kernel, however, because UEFI boot services need to be active in order for the UEFI logger to be usable.
|
||||||
|
|
||||||
|
If you were paying attention to the post before that one, however, you may have noticed that the bootloader is itself able to log directly to the framebuffer as it did when we booted the barebones kernel for the first time, and unlike the UEFI console logger, this logger is usable long after UEFI boot services are exited. It's this logger, therefore, that provides the easiest means of implementation on our end.
|
||||||
|
|
||||||
|
## `bootloader-x86_64-common`
|
||||||
|
|
||||||
|
In version 0.11.x of the bootloader crate, each component is separate, unlike in 0.10.x where the bootloader was a huge monolith. This is fantastic as it means that a lot of the APIs that the bootloader uses behind the scenes are also free for kernels to use, including, of course, the logger. The set of APIs that the logger belongs to are in a crate called `bootloader-x86_64-common` which also contains some other useful abstractions related to things like memory management that will come in handy later:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# in Cargo.toml
|
||||||
|
[dependencies]
|
||||||
|
bootloader-x86_64-common = "0.11.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
For now, however, only the logger will be used. If you are curious as to how this logger is written behind the scenes, however, don't worry; a sub-module of this chapter will include a tutorial on how to write a custom logger from scratch.
|
||||||
|
|
||||||
|
# Putting it all together
|
||||||
|
|
||||||
|
Before we use the bootloader's logger, we first need to initialize it. This requires creating a static instance, since it needs to live for as long as the kernel lives — which would mean for as long as the computer is powered on. Unfortunately, this is easier said than done, as Rust statics can be rather finicky — understandably so for security reasons. Luckily, there's a crate for this too.
|
||||||
|
|
||||||
|
## The `conquer_once` crate
|
||||||
|
|
||||||
|
Those used to using the standard library know that it provides a `OnceCell` which is exactly what it sounds like: you write to it only once, and then after that it's just there to use whenever. We're in a kernel and don't have access to the standard library, however, so is there a crate on crates.io that provides a replacement? Ah, yes there is:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# in Cargo.toml
|
||||||
|
[dependencies]
|
||||||
|
conquer-once = { version = "0.4.0", default-features = false }
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we need to add `default-features = false` to our `conquer-once` dependency —that's because the [`conquer-once` crate](https://crates.io/crates/conquer-once) tries to pull in the standard library by default, which in the kernel will result in compilation errors.
|
||||||
|
|
||||||
|
Now that we've added our two dependencies, it's time to use them:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/main.rs
|
||||||
|
use conquer_once::spin::OnceCell;
|
||||||
|
use bootloader_x86_64_common::logger::LockedLogger;
|
||||||
|
// ...
|
||||||
|
pub(crate) static LOGGER: OnceCell<LockedLogger> = OnceCell::uninit();
|
||||||
|
```
|
||||||
|
|
||||||
|
By setting the logger up as a static `OnceCell` it becomes much easier to initialize. We use `pub(crate)` to ensure that the kernel can see it but nothing else can.
|
||||||
|
|
||||||
|
After this, it's time to actually initialize it. To do that, we use a function:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/main.rs
|
||||||
|
use bootloader_api::info::FrameBufferInfo;
|
||||||
|
// ...
|
||||||
|
pub(crate) fn init_logger(buffer: &'static mut [u8], info: FrameBufferInfo) {
|
||||||
|
let logger = LOGGER.get_or_init(move || LockedLogger::new(buffer, info, true, false));
|
||||||
|
log::set_logger(logger).expect("Logger already set");
|
||||||
|
log::set_max_level(log::LevelFilter::Trace);
|
||||||
|
log::info!("Hello, Kernel Mode!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This function takes two parameters: a byte slice representing a raw framebuffer and a `FrameBufferInfo` structure containing information about the first parameter. Getting those parameters, however, requires jumping through some hoops to satisfy the borrow checker:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/main.rs
|
||||||
|
fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
|
||||||
|
// ...
|
||||||
|
// free the doubly wrapped framebuffer from the boot info struct
|
||||||
|
let frame_buffer_optional = &mut boot_info.framebuffer;
|
||||||
|
|
||||||
|
// free the wrapped framebuffer from the FFI-safe abstraction provided by bootloader_api
|
||||||
|
let frame_buffer_option = frame_buffer_optional.as_mut();
|
||||||
|
|
||||||
|
// unwrap the framebuffer
|
||||||
|
let frame_buffer_struct = frame_buffer_option.unwrap();
|
||||||
|
|
||||||
|
// extract the framebuffer info and, to satisfy the borrow checker, clone it
|
||||||
|
let frame_buffer_info = frame_buffer_struct.info().clone();
|
||||||
|
|
||||||
|
// get the framebuffer's mutable raw byte slice
|
||||||
|
let raw_frame_buffer = frame_buffer_struct.buffer_mut();
|
||||||
|
|
||||||
|
// finally, initialize the logger using the last two variables
|
||||||
|
init_logger(raw_frame_buffer, frame_buffer_info);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Any one of these steps, if skipped, will cause the borrow checker to throw a hissy fit due to the use of the `move ||` closure by the initializer function. With this, however, you're done, and you'll know the logger has been initialized when you see "Hello, Kernel Mode!" printed on the screen.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
<!-- TODO: update relative link in 02-booting/uefi/index.md when this post is finished -->
|
||||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 100 KiB |
23
blog/content/edition-3/posts/04-keyboard-and-serial/index.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
+++
|
||||||
|
title = "Keyboard & Serial"
|
||||||
|
weight = 4
|
||||||
|
path = "keyboard-and-serial"
|
||||||
|
date = 0000-01-01
|
||||||
|
draft = true
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
chapter = "Basic I/O"
|
||||||
|
icon = '''<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-keyboard" viewBox="0 0 16 16">
|
||||||
|
<path d="M14 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h12zM2 4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2z"/>
|
||||||
|
<path d="M13 10.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm0-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5 0A.25.25 0 0 1 8.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 8 8.75v-.5zm2 0a.25.25 0 0 1 .25-.25h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5a.25.25 0 0 1-.25-.25v-.5zm1 2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-5-2A.25.25 0 0 1 6.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 6 8.75v-.5zm-2 0A.25.25 0 0 1 4.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 4 8.75v-.5zm-2 0A.25.25 0 0 1 2.25 8h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 2 8.75v-.5zm11-2a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm-2 0A.25.25 0 0 1 9.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 9 6.75v-.5zm-2 0A.25.25 0 0 1 7.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 7 6.75v-.5zm-2 0A.25.25 0 0 1 5.25 6h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5A.25.25 0 0 1 5 6.75v-.5zm-3 0A.25.25 0 0 1 2.25 6h1.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-1.5A.25.25 0 0 1 2 6.75v-.5zm0 4a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-.5zm2 0a.25.25 0 0 1 .25-.25h5.5a.25.25 0 0 1 .25.25v.5a.25.25 0 0 1-.25.25h-5.5a.25.25 0 0 1-.25-.25v-.5z"/>
|
||||||
|
</svg>'''
|
||||||
|
+++
|
||||||
|
|
||||||
|
Dolores qui incidunt sit fugiat amet consequatur. Qui ab vel et molestias ex nemo corporis consequatur. Quia consequuntur itaque sequi quia autem. Maxime vel quis maxime at. Tenetur eveniet velit dolor quidem temporibus tenetur.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
23
blog/content/edition-3/posts/05-simple-shell/index.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
+++
|
||||||
|
title = "Simple Shell"
|
||||||
|
weight = 5
|
||||||
|
path = "simple-shell"
|
||||||
|
date = 0000-01-01
|
||||||
|
draft = true
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
chapter = "Basic I/O"
|
||||||
|
icon = '''<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-terminal" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 9a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3A.5.5 0 0 1 6 9zM3.854 4.146a.5.5 0 1 0-.708.708L4.793 6.5 3.146 8.146a.5.5 0 1 0 .708.708l2-2a.5.5 0 0 0 0-.708l-2-2z"/>
|
||||||
|
<path d="M2 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H2zm12 1a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h12z"/>
|
||||||
|
</svg>'''
|
||||||
|
+++
|
||||||
|
|
||||||
|
Dolores qui incidunt sit fugiat amet consequatur. Qui ab vel et molestias ex nemo corporis consequatur. Quia consequuntur itaque sequi quia autem. Maxime vel quis maxime at. Tenetur eveniet velit dolor quidem temporibus tenetur.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
23
blog/content/edition-3/posts/06-basic-games/index.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
+++
|
||||||
|
title = "Basic Games"
|
||||||
|
weight = 6
|
||||||
|
path = "basic-games"
|
||||||
|
date = 0000-01-01
|
||||||
|
draft = true
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
chapter = "Basic I/O"
|
||||||
|
icon = '''<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-controller" viewBox="0 0 16 16">
|
||||||
|
<path d="M11.5 6.027a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-1.5 1.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zm2.5-.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-1.5 1.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zm-6.5-3h1v1h1v1h-1v1h-1v-1h-1v-1h1v-1z"/>
|
||||||
|
<path d="M3.051 3.26a.5.5 0 0 1 .354-.613l1.932-.518a.5.5 0 0 1 .62.39c.655-.079 1.35-.117 2.043-.117.72 0 1.443.041 2.12.126a.5.5 0 0 1 .622-.399l1.932.518a.5.5 0 0 1 .306.729c.14.09.266.19.373.297.408.408.78 1.05 1.095 1.772.32.733.599 1.591.805 2.466.206.875.34 1.78.364 2.606.024.816-.059 1.602-.328 2.21a1.42 1.42 0 0 1-1.445.83c-.636-.067-1.115-.394-1.513-.773-.245-.232-.496-.526-.739-.808-.126-.148-.25-.292-.368-.423-.728-.804-1.597-1.527-3.224-1.527-1.627 0-2.496.723-3.224 1.527-.119.131-.242.275-.368.423-.243.282-.494.575-.739.808-.398.38-.877.706-1.513.773a1.42 1.42 0 0 1-1.445-.83c-.27-.608-.352-1.395-.329-2.21.024-.826.16-1.73.365-2.606.206-.875.486-1.733.805-2.466.315-.722.687-1.364 1.094-1.772a2.34 2.34 0 0 1 .433-.335.504.504 0 0 1-.028-.079zm2.036.412c-.877.185-1.469.443-1.733.708-.276.276-.587.783-.885 1.465a13.748 13.748 0 0 0-.748 2.295 12.351 12.351 0 0 0-.339 2.406c-.022.755.062 1.368.243 1.776a.42.42 0 0 0 .426.24c.327-.034.61-.199.929-.502.212-.202.4-.423.615-.674.133-.156.276-.323.44-.504C4.861 9.969 5.978 9.027 8 9.027s3.139.942 3.965 1.855c.164.181.307.348.44.504.214.251.403.472.615.674.318.303.601.468.929.503a.42.42 0 0 0 .426-.241c.18-.408.265-1.02.243-1.776a12.354 12.354 0 0 0-.339-2.406 13.753 13.753 0 0 0-.748-2.295c-.298-.682-.61-1.19-.885-1.465-.264-.265-.856-.523-1.733-.708-.85-.179-1.877-.27-2.913-.27-1.036 0-2.063.091-2.913.27z"/>
|
||||||
|
</svg>'''
|
||||||
|
+++
|
||||||
|
|
||||||
|
Dolores qui incidunt sit fugiat amet consequatur. Qui ab vel et molestias ex nemo corporis consequatur. Quia consequuntur itaque sequi quia autem. Maxime vel quis maxime at. Tenetur eveniet velit dolor quidem temporibus tenetur.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
Molestiae quidem ipsa nihil laboriosam sapiente laudantium quia. Praesentium et repudiandae minima voluptas et. Repellendus voluptatem distinctio enim et alias distinctio recusandae quos. Dolores ex eum culpa quo sunt sint voluptate voluptates. Facere unde sequi quo ea vel nihil. Rem deleniti repellat rem molestias
|
||||||
7
blog/content/edition-3/posts/_index.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
+++
|
||||||
|
title = "Posts"
|
||||||
|
sort_by = "weight"
|
||||||
|
insert_anchor_links = "left"
|
||||||
|
render = false
|
||||||
|
page_template = "edition-3/page.html"
|
||||||
|
+++
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Iosevka";
|
font-family: 'Iosevka Web';
|
||||||
src: url("/fonts/iosevka-regular.woff2") format("woff2"), url("/fonts/iosevka-regular.woff") format("woff");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: normal;
|
||||||
|
font-style: normal;
|
||||||
|
src: url('/fonts/ttf/iosevka-regular.ttf') format('truetype'), url('/fonts/woff2/iosevka-regular.woff2') format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -221,7 +221,7 @@ abbr[title] {
|
|||||||
/* Code */
|
/* Code */
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
font-family: "Iosevka", monospace;
|
font-family: "Iosevka Web", monospace;
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
|
|||||||
1212
blog/sass/css/edition-3/main.scss
Normal file
BIN
blog/static/fonts/ttf/iosevka-bold.ttf
Normal file
BIN
blog/static/fonts/ttf/iosevka-italic.ttf
Normal file
BIN
blog/static/fonts/ttf/iosevka-regular.ttf
Normal file
BIN
blog/static/fonts/woff2/iosevka-bold.woff2
Normal file
BIN
blog/static/fonts/woff2/iosevka-italic.woff2
Normal file
BIN
blog/static/fonts/woff2/iosevka-regular.woff2
Normal file
99
blog/static/js/edition-3/main.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
window.onload = function () {
|
||||||
|
let container = document.querySelector('#toc-aside');
|
||||||
|
if (container != null) {
|
||||||
|
resize_toc(container);
|
||||||
|
toc_scroll_position(container);
|
||||||
|
window.onscroll = function () { toc_scroll_position(container) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let theme = localStorage.getItem("theme");
|
||||||
|
if (theme != null) {
|
||||||
|
setTimeout(() => {
|
||||||
|
set_giscus_theme(theme)
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize_toc(container) {
|
||||||
|
let containerHeight = container.clientHeight;
|
||||||
|
|
||||||
|
let resize = function () {
|
||||||
|
if (containerHeight > document.documentElement.clientHeight - 100) {
|
||||||
|
container.classList.add('coarse');
|
||||||
|
} else {
|
||||||
|
container.classList.remove('coarse');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
resize();
|
||||||
|
|
||||||
|
let resizeId;
|
||||||
|
window.onresize = function () {
|
||||||
|
clearTimeout(resizeId);
|
||||||
|
resizeId = setTimeout(resize, 300);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toc_scroll_position(container) {
|
||||||
|
if (container.offsetParent === null) {
|
||||||
|
// skip computation if ToC is not visible
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove active class for all items
|
||||||
|
for (item of container.querySelectorAll("li")) {
|
||||||
|
item.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for active item
|
||||||
|
let site_offset = document.documentElement.scrollTop;
|
||||||
|
let current_toc_item = null;
|
||||||
|
for (item of container.querySelectorAll("li")) {
|
||||||
|
if (item.offsetParent === null) {
|
||||||
|
// skip items that are not visible
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let anchor = item.firstElementChild.getAttribute("href");
|
||||||
|
let heading = document.querySelector(anchor);
|
||||||
|
if (heading.offsetTop <= (site_offset + document.documentElement.clientHeight / 3)) {
|
||||||
|
current_toc_item = item;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set active class for current ToC item
|
||||||
|
if (current_toc_item != null) {
|
||||||
|
current_toc_item.classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_lights() {
|
||||||
|
if (document.documentElement.getAttribute("data-theme") === "dark") {
|
||||||
|
set_theme("light")
|
||||||
|
} else if (document.documentElement.getAttribute("data-theme") === "light") {
|
||||||
|
set_theme("dark")
|
||||||
|
} else {
|
||||||
|
set_theme(window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "dark")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_theme(theme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme)
|
||||||
|
set_giscus_theme(theme)
|
||||||
|
localStorage.setItem("theme", theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_theme_override() {
|
||||||
|
document.documentElement.removeAttribute("data-theme");
|
||||||
|
set_giscus_theme("preferred_color_scheme")
|
||||||
|
localStorage.removeItem("theme")
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_giscus_theme(theme) {
|
||||||
|
let comment_form = document.querySelector("iframe.giscus-frame");
|
||||||
|
if (comment_form != null) {
|
||||||
|
comment_form.contentWindow.postMessage({
|
||||||
|
giscus: { setConfig: { theme: theme } }
|
||||||
|
}, "https://giscus.app")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<script async src="/js/edition-2/main.js"></script>
|
<script async src="/js/edition-2/main.js"></script>
|
||||||
|
|
||||||
<title>{% block title %}{% endblock title %}</title>
|
<title>{% block title %}{% endblock title %} (Second Edition)</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<header class="masthead">
|
<header class="masthead">
|
||||||
<div style="position:relative">
|
<div style="position:relative">
|
||||||
<h2 class="masthead-title">
|
<h2 class="masthead-title">
|
||||||
<a href="{{ config.base_url | safe }}" title="Home">{{ config.title | safe }}</a>
|
<a href="{{ config.base_url | safe }}" title="Home">{{ config.title | safe }} (Second Edition)</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p><small>{{ config.extra.subtitle | replace(from=" ", to=" ") | safe }}</small></p>
|
<p><small>{{ config.extra.subtitle | replace(from=" ", to=" ") | safe }}</small></p>
|
||||||
{% block header %}{% endblock header %}
|
{% block header %}{% endblock header %}
|
||||||
|
|||||||
@@ -68,6 +68,8 @@
|
|||||||
|
|
||||||
{% block after_main %}
|
{% block after_main %}
|
||||||
<aside class="page-aside-right">
|
<aside class="page-aside-right">
|
||||||
|
<div class="block" id="language-selector">
|
||||||
|
{% if section.translations -%}
|
||||||
<div class="block" id="language-selector">
|
<div class="block" id="language-selector">
|
||||||
<h2>Other Languages</h2>
|
<h2>Other Languages</h2>
|
||||||
{% set translations = section.translations | group_by(attribute="lang") %}
|
{% set translations = section.translations | group_by(attribute="lang") %}
|
||||||
@@ -80,6 +82,8 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% endfor %}</ul>
|
{% endfor %}</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2>Recent Updates</h2>
|
<h2>Recent Updates</h2>
|
||||||
{% include "auto/recent-updates.html" %}
|
{% include "auto/recent-updates.html" %}
|
||||||
|
|||||||
@@ -42,3 +42,4 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
{% endmacro toc %}
|
{% endmacro toc %}
|
||||||
|
|
||||||
|
|||||||
25
blog/templates/edition-2/news-page.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "edition-2/base.html" %}
|
||||||
|
|
||||||
|
{% import "snippets.html" as snippets %}
|
||||||
|
|
||||||
|
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
<time datetime="{{ page.date | date(format="%Y-%m-%d") }}" class="post-date">
|
||||||
|
{{ page.date | date(format="%b %d, %Y") }}
|
||||||
|
{% if page.extra.updated %} (updated on {{ page.extra.updated | date(format="%b %d, %Y") }}) {% endif %}
|
||||||
|
</time>
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endblock main %}
|
||||||
|
|
||||||
|
{% block after_main %}
|
||||||
|
<hr>
|
||||||
|
<section>
|
||||||
|
<h2 id="comments">Comments</h2>
|
||||||
|
{{ snippets::giscus(search_term=page.title ~ " (News Post)", lang=page.lang) }}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock after_main %}
|
||||||
|
|
||||||
|
|
||||||
14
blog/templates/edition-3/base.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends "edition-3/foundation.html" %}
|
||||||
|
|
||||||
|
{% block masthead %}
|
||||||
|
<header class="masthead">
|
||||||
|
<div style="position:relative">
|
||||||
|
<h2 class="masthead-title">
|
||||||
|
<a href="{{ config.base_url | safe }}/edition-3" title="Home">{{ config.title | safe }}</a>
|
||||||
|
</h2>
|
||||||
|
<p><small>{{ config.extra.subtitle | replace(from=" ", to=" ") | safe }}
|
||||||
|
— Third Edition (Alpha Release)</small></p>
|
||||||
|
{% block header %}{% endblock header %}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{% endblock masthead %}
|
||||||
22
blog/templates/edition-3/extra.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends "edition-3/base.html" %}
|
||||||
|
|
||||||
|
{% import "snippets.html" as snippets %}
|
||||||
|
|
||||||
|
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block description -%}
|
||||||
|
{{ page.summary | safe | striptags | truncate(length=150) }}
|
||||||
|
{%- endblock description %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endblock main %}
|
||||||
|
|
||||||
|
{% block after_main %}
|
||||||
|
<hr>
|
||||||
|
<section>
|
||||||
|
<h2 id="comments">Comments</h2>
|
||||||
|
{{ snippets::giscus(search_term=page.title ~ " (Extra Post)", lang=page.lang) }}
|
||||||
|
</section>
|
||||||
|
{% endblock after_main %}
|
||||||
60
blog/templates/edition-3/foundation.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="{% if lang %}{{ lang }}{% else %}{{ config.default_language }}{% endif %}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}">
|
||||||
|
<meta name="author" content="{{ config.extra.author.name }}">
|
||||||
|
|
||||||
|
{% if current_url %}
|
||||||
|
<link rel="canonical" href="{{ current_url | safe }}" />
|
||||||
|
{% endif %}
|
||||||
|
<link href="/css/edition-3/main.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="RSS feed for os.phil-opp.com"
|
||||||
|
href="{{ config.base_url | safe }}/rss.xml" />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let theme = localStorage.getItem("theme");
|
||||||
|
if (theme != null) {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script async src="/js/edition-3/main.js"></script>
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock title %} (Third Edition - Alpha)</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container content">
|
||||||
|
{% block masthead %}{% endblock masthead %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% block toc_aside %}{% endblock toc_aside %}
|
||||||
|
<main>{% block main %}{% endblock main %}</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{% block after_main %}{% endblock after_main %}</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<hr>
|
||||||
|
<div class="theme-switch">
|
||||||
|
<div class="light-switch" onclick="toggle_lights()" title="Switch between light and dark theme"></div>
|
||||||
|
<div class="light-switch-reset" onclick="clear_theme_override()"
|
||||||
|
title="Clear the theme override and go back to the system theme"></div>
|
||||||
|
</div>
|
||||||
|
<small>
|
||||||
|
© <time datetime="2022">2022</time>. All rights reserved.
|
||||||
|
<a class="spaced" href="https://github.com/phil-opp/blog_os#license">License</a>
|
||||||
|
<a class="spaced" href="{{ get_url(path="@/pages/contact.md") | safe }}">Contact</a>
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
175
blog/templates/edition-3/index.html
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
{% extends "edition-3/foundation.html" %}
|
||||||
|
|
||||||
|
{% import "edition-3/macros.html" as macros %}
|
||||||
|
{% import "snippets.html" as snippets %}
|
||||||
|
|
||||||
|
{% block title %}{{ config.title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
{% set posts_section = get_section(path = "edition-3/posts/_index.md") %}
|
||||||
|
{% set posts = posts_section.pages %}
|
||||||
|
|
||||||
|
{{ section.content
|
||||||
|
| replace(from="<!-- latest-post -->", to=macros::latest_post(posts=posts))
|
||||||
|
| replace(from="<!-- alpha-warning -->", to=macros::alpha_warning())
|
||||||
|
| safe
|
||||||
|
}}
|
||||||
|
|
||||||
|
{%- set chapter = "none" -%}
|
||||||
|
{%- for post in posts -%}
|
||||||
|
{%- if post.extra["chapter"] != chapter -%}
|
||||||
|
{%- if not loop.first -%}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{# Begin new chapter #}
|
||||||
|
{%- set_global chapter = post.extra["chapter"] -%}
|
||||||
|
|
||||||
|
{% set chapter_slug = chapter | slugify %}
|
||||||
|
<div class="posts {{chapter_slug}}">
|
||||||
|
{% set chapter_section = get_section(path = "edition-3/chapters/" ~ chapter_slug ~ "/_index.md" ) %}
|
||||||
|
<h2>{{ chapter_section.title }}</h2>
|
||||||
|
<div class="chapter-introduction">
|
||||||
|
{{ chapter_section.content | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
<li>{{ macros::post_link(page=post) }}</li>
|
||||||
|
|
||||||
|
{% if loop.last %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{%- endfor -%}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="after-posts">
|
||||||
|
<h2>Subscribe</h2>
|
||||||
|
<p>Receive notifications about new posts and other major changes! You can either:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Subscribe to our <a href="/rss.xml">RSS/Atom Feed</a>,</li>
|
||||||
|
<li>Subscribe to <a href="https://github.com/phil-opp/blog_os/issues/479">this GitHub issue</a>, or</li>
|
||||||
|
<li>Subscribe to our <a href="https://tinyletter.com/phil-opp/">email newsletter</a>.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frontpage-section">
|
||||||
|
<h2>Status Updates</h2>
|
||||||
|
{% set status_updates = get_section(path = "status-update/_index.md") %}
|
||||||
|
<p>{{ status_updates.description }}</p>
|
||||||
|
<ul>
|
||||||
|
{% include "auto/status-updates-truncated.html" %}
|
||||||
|
<li><a href="{{ get_url(path="@/status-update/_index.md") | safe }}"><em>view all »</em></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frontpage-section">
|
||||||
|
<h2>Previous Editions</h2>
|
||||||
|
<p>You are currently viewing the third edition of “Writing an OS in Rust”. In case you are interested in the older
|
||||||
|
editions, you can still find them here:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><strong><a href="{{ get_url(path="@/edition-2/_index.md")}}">Second Edition:</a></strong> The second
|
||||||
|
edition is based on older version of the <code>bootloader</code> crate, which uses the hardware-provided
|
||||||
|
VGA text buffer instead of a pixel-based framebuffer for screen output. Instead of the APIC, the legacy
|
||||||
|
PIC is used for implementing hardware interrupts. The second edition only works on BIOS-based systems,
|
||||||
|
not on the newer UEFI standard. <a class="read-more" href="{{ get_url(path = " edition-2") | safe
|
||||||
|
}}"><em>read the second edition »</em></a></p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong><a href="{{ get_url(path="@/edition-1/_index.md")}}">First Edition:</a></strong> The first
|
||||||
|
edition was already started in 2015. It is very different in many aspects, for example it builds upon
|
||||||
|
the GRUB bootloader instead of using the `bootloader` crate. This means that it requires you to manually
|
||||||
|
write some assembly code and do an elaborate remap of the kernel's virtual pages in order to improve
|
||||||
|
safety.<a class="read-more" href="{{ get_url(path = " edition-1") | safe
|
||||||
|
}}"><em>read the first edition »</em></a></p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p><em>Note that the older editions are no longer updated and might no longer work or contain outdated
|
||||||
|
information.</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<h2>Support Me</h2>
|
||||||
|
{{ snippets::support() }}
|
||||||
|
</div>
|
||||||
|
{% endblock main %}
|
||||||
|
|
||||||
|
{% block after_main %}
|
||||||
|
<aside class="page-aside-right">
|
||||||
|
{% if section.translations | length > 1 %}
|
||||||
|
<div class="block" id="language-selector">
|
||||||
|
<h2>Other Languages</h2>
|
||||||
|
{% set translations = section.translations | group_by(attribute="lang") %}
|
||||||
|
<ul>{%- for lang_code in config.extra.languages -%}
|
||||||
|
{%- if translations[lang_code] and lang_code != lang -%}
|
||||||
|
{%- set translation = translations[lang_code].0 -%}
|
||||||
|
<li data-lang-switch-to="{{ translation.lang }}" class=""><a href="{{ translation.permalink | safe }}">
|
||||||
|
{{ trans(key="lang_name", lang = translation.lang) }}
|
||||||
|
</a></li>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="block">
|
||||||
|
<h2>Recent Updates</h2>
|
||||||
|
{% include "auto/recent-updates.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<h2>Repository</h2>
|
||||||
|
<div class="gh-repo-box">
|
||||||
|
<div>
|
||||||
|
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
<a href="https://github.com/phil-opp/blog_os" class="repo-link">
|
||||||
|
<span title="blog_os">phil-opp/blog_os</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="subtitle">
|
||||||
|
Writing an OS in Rust
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="stars-forks">
|
||||||
|
<a href="https://github.com/phil-opp/blog_os/stargazers" class="stars">
|
||||||
|
<svg aria-label="stars" viewBox="0 0 14 16" version="1.1" width="14" height="16" role="img">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74L14 6z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
{% include "auto/stars.html" %}
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/phil-opp/blog_os/network/members" class="forks">
|
||||||
|
<svg aria-label="forks" viewBox="0 0 10 16" version="1.1" width="10" height="16" role="img">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
{% include "auto/forks.html" %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://github.com/sponsors/phil-opp" class="sponsor">
|
||||||
|
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M9 2c-.97 0-1.69.42-2.2 1-.51.58-.78.92-.8 1-.02-.08-.28-.42-.8-1-.52-.58-1.17-1-2.2-1-1.632.086-2.954 1.333-3 3 0 .52.09 1.52.67 2.67C1.25 8.82 3.01 10.61 6 13c2.98-2.39 4.77-4.17 5.34-5.33C11.91 6.51 12 5.5 12 5c-.047-1.69-1.342-2.913-3-3z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Sponsor
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{% endblock after_main %}
|
||||||
89
blog/templates/edition-3/macros.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{% macro latest_post(posts) %}
|
||||||
|
{% set post = posts|last %}
|
||||||
|
{% if post %}
|
||||||
|
<strong><a href="{{ post.path | safe }}">{{ post.title }}</a></strong>
|
||||||
|
{% else %}
|
||||||
|
<em>none yet, stay tuned!</em>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro latest_post %}
|
||||||
|
|
||||||
|
{% macro post_link(page) %}
|
||||||
|
<div>
|
||||||
|
{% set translations = page.translations | filter(attribute="lang", value=lang) -%}
|
||||||
|
{%- if translations -%}
|
||||||
|
{%- set post = get_page(path = translations.0.path) -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- set post = page -%}
|
||||||
|
{%- set not_translated = true -%}
|
||||||
|
{%- endif -%}
|
||||||
|
<h3 class="post-list-title"><a href="{{ post.path | safe }}">{{ post.title }}</a></h3>
|
||||||
|
<span class="post-list-icon">
|
||||||
|
{%- if post.extra.icon -%}{{post.extra.icon | safe}}{%- endif -%}
|
||||||
|
</span>
|
||||||
|
<div class="post-summary">
|
||||||
|
{{ post.summary | safe }}
|
||||||
|
<a class="read-more" href="{{ post.path | safe }}"><em>read more »</em></a>
|
||||||
|
|
||||||
|
{% if page.extra.extra_content %}
|
||||||
|
<aside class="post-extra-content">
|
||||||
|
<h4>Extra Content:</h4>
|
||||||
|
{% for name in page.extra.extra_content %}
|
||||||
|
{% set path = page.relative_path | split(pat="/") | slice(end=-1) | concat(with=name) | join(sep="/") %}
|
||||||
|
|
||||||
|
{% set extra_page = get_page(path = path) %}
|
||||||
|
|
||||||
|
<span class="post-list-extra-post-icon">
|
||||||
|
{%- if extra_page.extra.icon -%}{{extra_page.extra.icon | safe}}{%- endif -%}
|
||||||
|
</span>
|
||||||
|
<a href="{{ extra_page.path | safe }}">{{ extra_page.title }}</a>{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if lang and not_translated and lang != config.default_language -%}
|
||||||
|
<aside class="no-translation">
|
||||||
|
(This post is not translated yet.)
|
||||||
|
</aside>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro post_link %}
|
||||||
|
|
||||||
|
{% macro toc(toc) %}
|
||||||
|
<details id="toc-inline">
|
||||||
|
<summary><b>Table of Contents</b></summary>
|
||||||
|
<ul>
|
||||||
|
{% for h2 in toc %}<li>
|
||||||
|
<a href="#{{h2.id | safe}}">{{ h2.title | safe }}</a>
|
||||||
|
{% if h2.children %}<ul>
|
||||||
|
{% for h3 in h2.children %}<li>
|
||||||
|
<a href="#{{h3.id | safe}}">{{ h3.title | safe }}</a>
|
||||||
|
</li>{% endfor %}
|
||||||
|
</ul>{% endif %}
|
||||||
|
</li>{% endfor %}
|
||||||
|
<li class="toc-comments-link"><a href="#comments">Comments</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<div class="theme-switch-inline">
|
||||||
|
Switch between light and dark mode:
|
||||||
|
<span class="switches">
|
||||||
|
<span class="light-switch" onclick="toggle_lights()" title="Switch between light and dark theme"></span>
|
||||||
|
<span class="light-switch-reset" onclick="clear_theme_override()"
|
||||||
|
title="Clear the theme override and go back to the system theme"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endmacro toc %}
|
||||||
|
|
||||||
|
{% macro alpha_warning() %}
|
||||||
|
<div class="warning">
|
||||||
|
<p>
|
||||||
|
This is an <strong>early preview</strong> of the upcoming <em>third edition</em> of this guide. The edition is
|
||||||
|
still in alpha state, so things might be still in progress, not work, or change without warning!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For a more stable experience, check out the current <a href="{{ get_url(path = "@/edition-2/_index.md") | safe
|
||||||
|
}}"><strong>Second Edition</strong></a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endmacro alpha_warning %}
|
||||||
157
blog/templates/edition-3/page.html
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
{% extends "edition-3/base.html" %}
|
||||||
|
|
||||||
|
{% import "edition-3/macros.html" as macros %}
|
||||||
|
{% import "snippets.html" as snippets %}
|
||||||
|
|
||||||
|
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||||
|
{% block header %}
|
||||||
|
{% if lang != "en" -%}
|
||||||
|
<aside id="all-posts-link"><a href="{{ get_url(path="@/edition-3/_index.md") }}/{{ lang }}" title="All Posts">{{
|
||||||
|
trans(key="all_posts", lang=lang) }}</a></aside>
|
||||||
|
{%- else -%}
|
||||||
|
<aside id="all-posts-link"><a href="{{ get_url(path="@/edition-3/_index.md") }}" title="All Posts">{{
|
||||||
|
trans(key="all_posts", lang=lang) }}</a></aside>
|
||||||
|
{%- endif %}
|
||||||
|
{% endblock header %}
|
||||||
|
|
||||||
|
{% block description -%}
|
||||||
|
{{ page.summary | safe | striptags | truncate(length=150) }}
|
||||||
|
{%- endblock description %}
|
||||||
|
|
||||||
|
{% block toc_aside %}
|
||||||
|
<aside id="toc-aside" class="{% if page.extra.rtl %}right-to-left{% endif %}">
|
||||||
|
<h2>{{ trans(key="toc", lang=lang) }}</h2>
|
||||||
|
<ol>
|
||||||
|
{% for h2 in page.toc %}<li>
|
||||||
|
<a href="#{{h2.id | safe}}">{{ h2.title | safe }}</a>
|
||||||
|
{% if h2.children %}<ol>
|
||||||
|
{% for h3 in h2.children %}<li>
|
||||||
|
<a href="#{{h3.id | safe}}">{{ h3.title | safe }}</a>
|
||||||
|
</li>{% endfor %}
|
||||||
|
</ol>{% endif %}
|
||||||
|
</li>{% endfor %}
|
||||||
|
<li class="toc-comments-link"><a href="#comments">{{ trans(key="comments", lang=lang) }}</a></li>
|
||||||
|
</ol>
|
||||||
|
</aside>
|
||||||
|
{% endblock toc_aside %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div class="{% if page.extra.rtl %}right-to-left{% endif %}">
|
||||||
|
<div class="post-title">
|
||||||
|
<h1>{{ page.title }}</h1><span class="post-icon">
|
||||||
|
{%- if page.extra.icon -%}{{page.extra.icon | safe}}{%- endif -%}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<time datetime="{{ page.date | date(format=" %Y-%m-%d") }}" class="post-date">
|
||||||
|
{{ page.date | date(format="%b %d, %Y") }}
|
||||||
|
{% if page.extra.updated %} (updated on {{ page.extra.updated | date(format="%b %d, %Y") }}) {% endif %}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ macros::alpha_warning() }}
|
||||||
|
|
||||||
|
{% if page.extra.warning %}
|
||||||
|
<div class="warning">
|
||||||
|
{% if page.extra.warning_short %} <b>{{ page.extra.warning_short }}</b> {% endif %}
|
||||||
|
{{ page.extra.warning | markdown(inline=true) | safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if page.lang != "en" %}
|
||||||
|
<div class="warning{% if page.extra.rtl %} right-to-left{% endif %}">
|
||||||
|
{% set translations = page.translations | filter(attribute="lang", value="en") %}
|
||||||
|
{% set original = translations.0 %}
|
||||||
|
<p>
|
||||||
|
<b>{{ trans(key="translated_content", lang=lang) }}</b>
|
||||||
|
{{ trans(key="translated_content_notice", lang=lang) |
|
||||||
|
replace(from="_original.permalink_", to=original.permalink) |
|
||||||
|
replace(from="_original.title_", to=original.title) | safe }}
|
||||||
|
</p>
|
||||||
|
{%- if page.extra.translators %}
|
||||||
|
<p>
|
||||||
|
{{ trans(key="translated_by", lang=lang) }} {% for user in page.extra.translators -%}
|
||||||
|
{%- if not loop.first -%}
|
||||||
|
{%- if loop.last %} {{ trans(key="word_separator", lang=lang) }} {% else %}, {% endif -%}
|
||||||
|
{%- endif -%}
|
||||||
|
<a href="https://github.com/{{user}}">@{{user}}</a>
|
||||||
|
{%- endfor %}.
|
||||||
|
|
||||||
|
{%- if page.extra.translation_contributors %}
|
||||||
|
<span class="translation_contributors">
|
||||||
|
{{ trans(key="translation_contributors", lang=lang) }} {% for user in page.extra.translation_contributors
|
||||||
|
-%}
|
||||||
|
{%- if not loop.first -%}
|
||||||
|
{%- if loop.last %} {{ trans(key="word_separator", lang=lang) }} {% else %}, {% endif -%}
|
||||||
|
{%- endif -%}
|
||||||
|
<a href="https://github.com/{{user}}">@{{user}}</a>
|
||||||
|
{%- endfor %}.
|
||||||
|
</span>
|
||||||
|
{% endif -%}
|
||||||
|
</p>
|
||||||
|
{% endif -%}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="{% if page.extra.rtl %}right-to-left{% endif %}">
|
||||||
|
{{ page.content | replace(from="<!-- toc -->", to=macros::toc(toc=page.toc)) | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post-footer-support{% if page.extra.rtl %} right-to-left{% endif %}">
|
||||||
|
<h2>Support Me</h2>
|
||||||
|
{{ snippets::support() }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not page.extra.hide_next_prev %}
|
||||||
|
<hr>
|
||||||
|
<div class="PageNavigation">
|
||||||
|
{% if page.lower %}
|
||||||
|
<a class="prev" href="{{ page.lower.path | safe }}">« {{ page.lower.title }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if page.higher %}
|
||||||
|
<a class="next" href="{{ page.higher.path | safe }}">{{ page.higher.title }} »</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<section>
|
||||||
|
<h2 id="comments" class="{% if page.extra.rtl %}right-to-left{% endif %}">{{ trans(key="comments", lang=lang) }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{% if page.extra.comments_search_term %}
|
||||||
|
{% set search_term=page.extra.comments_search_term %}
|
||||||
|
{% elif page.lang != "en" %}
|
||||||
|
{% set translations = page.translations | filter(attribute="lang", value="en") %}
|
||||||
|
{% set original = translations.0 %}
|
||||||
|
{% set search_term=original.title ~ " (" ~ page.lang ~ ")" %}
|
||||||
|
{% else %}
|
||||||
|
{% set search_term=page.title %}
|
||||||
|
{% endif %}
|
||||||
|
{{ snippets::giscus(search_term=search_term, lang=page.lang) }}
|
||||||
|
|
||||||
|
{%- if page.lang != "en" %}
|
||||||
|
<p class="{% if page.extra.rtl %}right-to-left{% endif %}">
|
||||||
|
{{ trans(key="comments_notice", lang=lang) }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside class="page-aside-right">
|
||||||
|
{% if page.translations | length > 1-%}
|
||||||
|
<div class="block" id="language-selector">
|
||||||
|
<h2>Other Languages</h2>
|
||||||
|
{% set translations = page.translations | group_by(attribute="lang") %}
|
||||||
|
<ul>{%- for lang_code in config.extra.languages -%}{%- if translations[lang_code] -%}
|
||||||
|
{%- set translation = translations[lang_code] | first -%}
|
||||||
|
{%- if translation and lang_code != lang -%}
|
||||||
|
<li data-lang-switch-to="{{ translation.lang }}" class=""><a href="{{ translation.permalink | safe }}">
|
||||||
|
{{ trans(key="lang_name", lang = translation.lang) }}
|
||||||
|
</a></li>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endif -%}{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{% endblock main %}
|
||||||
1
blog/templates/edition-3/raw.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{ page.content | safe }}
|
||||||
@@ -1,25 +1 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "edition-2/news-page.html" %}
|
||||||
|
|
||||||
{% import "snippets.html" as snippets %}
|
|
||||||
|
|
||||||
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<h1>{{ page.title }}</h1>
|
|
||||||
<time datetime="{{ page.date | date(format="%Y-%m-%d") }}" class="post-date">
|
|
||||||
{{ page.date | date(format="%b %d, %Y") }}
|
|
||||||
{% if page.extra.updated %} (updated on {{ page.extra.updated | date(format="%b %d, %Y") }}) {% endif %}
|
|
||||||
</time>
|
|
||||||
{{ page.content | safe }}
|
|
||||||
{% endblock main %}
|
|
||||||
|
|
||||||
{% block after_main %}
|
|
||||||
<hr>
|
|
||||||
<section>
|
|
||||||
<h2 id="comments">Comments</h2>
|
|
||||||
{{ snippets::giscus(search_term=page.title ~ " (News Post)", lang=page.lang) }}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{% endblock after_main %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="canonical" href="{{ config.base_url | safe }}" />
|
<link rel="canonical" href="{{ config.base_url | safe }}" />
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
<meta http-equiv="refresh" content="0;url={{ config.base_url | safe }}" />
|
<meta http-equiv="refresh" content="0;url={{ config.base_url | safe }}" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -3,10 +3,7 @@
|
|||||||
Creating and <a href="{{ get_url(path="@/status-update/_index.md") }}">maintaining</a> this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance.
|
Creating and <a href="{{ get_url(path="@/status-update/_index.md") }}">maintaining</a> this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>, since they don't charge any fees. If you prefer other platforms, I also have <a href="https://www.patreon.com/phil_opp"><em>Patreon</em></a> and <a href="https://donorbox.org/phil-opp"><em>Donorbox</em></a> accounts. The latter is the most flexible as it supports multiple currencies and one-time contributions.
|
The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Thank you!
|
|
||||||
</p>
|
</p>
|
||||||
{% endmacro support %}
|
{% endmacro support %}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>Thank You!</h2>
|
<h2>Thank You!</h2>
|
||||||
<p>Thanks a lot to all the contributors this month!</p>
|
<p>Thanks a lot to all the contributors this month!</p>
|
||||||
<p>I also want to thank all the people who support me on <a href="https://github.com/sponsors/phil-opp">GitHub</a>, <a href="https://www.patreon.com/phil_opp">Patreon</a>, and <a href="https://donorbox.org/phil-opp">Donorbox</a>. It means a lot to me!</p>
|
<p>I also want to thank all the people who support me on <a href="https://github.com/sponsors/phil-opp">GitHub</a>,
|
||||||
|
<a href="https://www.patreon.com/phil_opp">Patreon</a>, and <a
|
||||||
|
href="https://donorbox.org/phil-opp">Donorbox</a>. It means a lot to me!
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
<p>{{ section.description }}</p>
|
<p>{{ section.description }}</p>
|
||||||
{% endblock introduction %}
|
{% endblock introduction %}
|
||||||
|
|
||||||
<div class="status-update-list"><ul>
|
<div class="status-update-list">
|
||||||
|
<ul>
|
||||||
{% include "auto/status-updates.html" %}
|
{% include "auto/status-updates.html" %}
|
||||||
{% for page in section.pages %}
|
{% for page in section.pages %}
|
||||||
<li><b><a href="{{ page.path | safe }}">{{ page.title }}</a></b></li>
|
<li><b><a href="{{ page.path | safe }}">{{ page.title }}</a></b></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul></div>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
|
|||||||