Compare commits

...

131 Commits

Author SHA1 Message Date
Philipp Oppermann
dd228c4bb1 Start revising second half of booting post 2023-04-09 21:13:49 +02:00
Philipp Oppermann
020f2d7e8e Highlight start address in rust-objdump output 2023-04-09 20:09:49 +02:00
Philipp Oppermann
c337650fb3 Tweak highlighting of bash snippets 2023-04-09 20:09:22 +02:00
Philipp Oppermann
7262e552ae Update fonts 2023-04-09 19:59:52 +02:00
Philipp Oppermann
2d6fc6bd76 Merge branch 'main' into edition-3 2023-04-09 19:33:05 +02:00
Philipp Oppermann
33b6b6ebe8 Improve line highlighting: left marker instead of different background 2023-04-09 18:49:12 +02:00
Philipp Oppermann
94d87308f4 Fix typo 2023-04-09 13:52:58 +02:00
Philipp Oppermann
c81a2e11db Improve first half of post 2023-03-25 20:24:02 +01:00
Philipp Oppermann
3ff7f48257 Highlight changed lines in code examples + other improvements 2023-03-25 19:10:59 +01:00
Philipp Oppermann
dba81f6d1c Rewrite introduction 2023-03-25 18:30:19 +01:00
Philipp Oppermann
ae6f001eae Use new branch naming scheme for edition-3 posts 2023-03-25 17:36:33 +01:00
Philipp Oppermann
5c00dbcb60 Merge branch 'main' into edition-3 2023-03-25 16:28:01 +01:00
Philipp Oppermann
79be18bfb5 Merge edition-3 changes of PR #1187 2023-03-25 16:07:21 +01:00
emanuele-em
50cbf4f01c Update index.md (#1193) 2023-02-23 17:23:15 +01:00
Philipp Oppermann
96c8aaf89d Fix --edition inconsistency 2023-01-02 09:50:56 +01:00
Philipp Oppermann
9f0bd81a79 Build drafts on CI 2022-12-18 20:19:10 +01:00
Philipp Oppermann
55508e6800 Mark post as draft again 2022-12-18 20:17:13 +01:00
Philipp Oppermann
ffc6d54009 Improve formatting of extra content on main page 2022-12-18 20:14:19 +01:00
Philipp Oppermann
dfd0e6c5d8 Improve chapter description 2022-12-18 20:04:20 +01:00
Philipp Oppermann
6fe8365b93 Fix broken links caused by autoformatter 2022-12-18 20:02:19 +01:00
Philipp Oppermann
6d8075fe87 Rework minimal kernel post 2022-12-18 19:58:48 +01:00
Philipp Oppermann
db2d2bca19 Auto-format edition-3 templates 2022-12-18 12:11:36 +01:00
Philipp Oppermann
ce0c6c133b Format edition-3 markdown as 'one sentence per line'
This makes the diffs nicer when we change something in the future.
2022-12-18 12:09:00 +01:00
Philipp Oppermann
c40ac1b1d7 Tweak icon margins 2022-12-18 12:00:43 +01:00
Philipp Oppermann
cc6d5eefb5 Remove goatcounter from edition-3 2022-12-18 11:55:13 +01:00
Philipp Oppermann
2642fa80e8 Add translation contributors to edition-3 page template too 2022-12-18 11:52:49 +01:00
Philipp Oppermann
264fd90abe Update edition-3 page template to zola 0.16 2022-12-18 11:50:31 +01:00
Philipp Oppermann
87b440c79a Fix uses of giscus macro in edition-3 templates 2022-12-18 11:48:02 +01:00
Philipp Oppermann
2c96cfe972 Merge branch 'main' into edition-3 2022-12-18 11:44:21 +01:00
Kenny Strawn
9adc094f43 Add screenshot of wrapping_add output (#1073) 2022-01-27 12:46:34 +01:00
Kenny Strawn
1da47d3177 Add additional “real hardware” instructions (#1072) 2022-01-26 13:44:00 +01:00
Philipp Oppermann
bc9d1a545a Fix typo 2022-01-25 13:21:35 +01:00
Philipp Oppermann
c73804789b Minor tweaks to subtitle and 'support me' text 2022-01-23 21:33:04 +01:00
Philipp Oppermann
233dec4caf Various 3rd edition template improvements
- merge improvements from second edition (e.g. improved light switch, prefered theme in session storage, translation support)
- giscus instead of utterances
- add an alpha warning
- fix error caused by missing posts

etc
2022-01-23 21:32:43 +01:00
Philipp Oppermann
6f1b982f3a Fix 2nd edition template errors accidentally introduced by merge 2022-01-23 21:29:25 +01:00
Philipp Oppermann
f6d06bcf67 Update js with latest improvements from second edition 2022-01-23 21:27:56 +01:00
Philipp Oppermann
fb5eca717b Merge style improvements of second edition + further tweaks 2022-01-23 21:26:46 +01:00
Philipp Oppermann
0400ce692c Improve UEFI booting post and start preparing it for pre-release 2022-01-23 21:24:53 +01:00
Philipp Oppermann
d475912811 Rust no longer optimizes loops away 2022-01-23 21:23:40 +01:00
Philipp Oppermann
729a8241ea Work around false positives in zola's anchor checker 2022-01-23 21:23:00 +01:00
Philipp Oppermann
d7b93ab855 Mark all 3rd edition posts as drafts 2022-01-23 21:21:59 +01:00
Philipp Oppermann
5af0499af5 Merge branch 'main' into edition-3 2022-01-23 18:02:34 +01:00
Philipp Oppermann
ebf482379c Minor improvements 2022-01-23 17:59:49 +01:00
bjorn3
91005553b3 Fix typos in edition 3 uefi booting post (#981) 2021-05-07 11:49:05 +02:00
Philipp Oppermann
06b7d345e4 Add UEFI hello world QEMU screenshot 2021-04-12 10:46:52 +02:00
Philipp Oppermann
b3eace1260 Explain how to run the disk images in QEMU (both BIOS and UEFI) 2021-04-12 10:46:33 +02:00
Philipp Oppermann
c61c37643a Resolve some TODOs 2021-04-11 16:51:50 +02:00
Philipp Oppermann
1aa7d21d8b Finish first draft of 'UEFI Booting' post 2021-04-07 13:11:18 +02:00
Philipp Oppermann
d6885843e6 Merge branch 'master' into edition-3 2021-04-06 20:21:36 +02:00
Philipp Oppermann
0b9231f0ba Minor improvements to UEFI code examples 2021-04-06 18:17:35 +02:00
Philipp Oppermann
db47b27024 Provide a high-level explanation on how to create bootloader 2021-03-22 12:10:04 +01:00
Philipp Oppermann
9c1babd027 Describe how to use various UEFI protocols with the uefi crate 2021-03-19 19:44:43 +01:00
Philipp Oppermann
ff3f055383 Add QEMU screenshot 2021-03-19 14:35:25 +01:00
Philipp Oppermann
8740b619a5 Describe how to include the uefi crate 2021-03-11 19:01:05 +01:00
Philipp Oppermann
a63c51c156 Improve introduction for uefi booting post 2021-03-11 17:45:34 +01:00
Philipp Oppermann
1ff447b378 Update icon comment 2021-03-11 17:26:34 +01:00
Philipp Oppermann
83be6c7868 Explain how to create FAT filesystem and GPT disk image 2021-02-24 11:24:10 +01:00
Philipp Oppermann
033be9ac25 Explain how to create minimal UEFI app 2021-02-23 11:46:09 +01:00
Philipp Oppermann
91d65504db Link to rustc's Target and TargetOptions docs 2021-02-23 11:45:21 +01:00
Philipp Oppermann
aeb72889ae Create structure for UEFI post 2021-02-20 17:00:13 +01:00
Philipp Oppermann
c2fe9960a7 Add boilerplate for new 'UEFI Booting' post 2021-02-16 12:20:54 +01:00
Philipp Oppermann
d6f424e338 Install llvm-tools-preview rustup component 2021-02-11 21:03:18 +01:00
Philipp Oppermann
843bd4ca87 Minor improvements 2021-02-11 20:49:02 +01:00
Philipp Oppermann
b1f6a85a02 Change bootloader version to alpha 2 2021-02-11 20:15:14 +01:00
Philipp Oppermann
c48335747b Mention that rustfmt/clippy can be installed via rust-toolchain too 2021-02-11 20:13:18 +01:00
Philipp Oppermann
81a1c0bded Fix remaining mentions of 'bootimage' 2021-02-11 19:57:48 +01:00
Philipp Oppermann
dfdbc6ec6d Improve introduction for booting post 2021-02-11 17:20:28 +01:00
Philipp Oppermann
09f96c9221 Remove some old content 2021-02-11 17:08:41 +01:00
Philipp Oppermann
aaae70974f Simplify boot crate 2021-02-11 17:06:17 +01:00
Philipp Oppermann
268a5ccb7d Minor improvements 2021-02-07 19:31:14 +01:00
Philipp Oppermann
f9f9b969bb Specify dependency on rust-src component in rust-toolchain 2021-02-07 19:07:41 +01:00
Philipp Oppermann
943f950c54 Use TOML syntax for rust-toolchain file 2021-02-07 19:07:15 +01:00
Philipp Oppermann
01327746d1 Various improvements to 'Booting' post 2021-02-06 15:15:37 +01:00
Philipp Oppermann
acb478c0b5 Finish first draft of 'Minimal Kernel' post 2021-02-05 17:38:40 +01:00
Philipp Oppermann
08c84d2f59 Restructure: Move minimal kernel content to first post 2021-01-24 16:46:30 +01:00
Philipp Oppermann
9c11cebef7 More improvements to booting section 2021-01-24 14:45:27 +01:00
Philipp Oppermann
b3b263db2a Improve and extend section about BIOS booting 2021-01-24 14:00:04 +01:00
Philipp Oppermann
a8ef154d03 Improve note block style 2021-01-14 16:02:40 +01:00
Philipp Oppermann
957855a18e Improve note block style 2021-01-14 15:52:22 +01:00
Philipp Oppermann
ec1f80416b Make chapters sections to avoid rendering them
Instead of redirecting the chapter pages to a 404 site.
2021-01-07 20:07:43 +01:00
Philipp Oppermann
2e52b681ec Move edition info to subtitle for third edition 2021-01-03 15:22:54 +01:00
Philipp Oppermann
8fbdf53598 Increase margin after chapter introduction 2021-01-03 15:16:07 +01:00
Philipp Oppermann
72e4851bc7 Improve introduction for booting post 2021-01-03 15:13:23 +01:00
Philipp Oppermann
76090f6656 Reduce margin around light switch 2021-01-03 15:08:24 +01:00
Philipp Oppermann
cd7ac05395 Remove comments for old status update pages
New status updates are published on rust-osdev.com and there were only very few comments on the old posts, so that it's not worth keeping them.
2021-01-03 15:04:34 +01:00
Philipp Oppermann
d56f51a2d3 Move macros
Each edition now has their own macros, so create a macros.html for the third edition too. Since the utterances theme differs between editions, we need to move it from snippets.html to the edition-specific macros.html too.
2021-01-03 15:03:49 +01:00
Philipp Oppermann
ae4c53fa75 Merge branch 'master' into edition-3 2021-01-03 14:48:32 +01:00
Philipp Oppermann
de71899d19 Rename second post to 'Booting' 2021-01-03 14:27:13 +01:00
Philipp Oppermann
6b1c01477c Change icon 2021-01-03 14:26:52 +01:00
Philipp Oppermann
8e0f531334 Style gh repo box for dark mode 2021-01-03 10:57:16 +01:00
Philipp Oppermann
a6b8623468 Clarify license for bootstrap icons 2021-01-03 10:30:14 +01:00
Philipp Oppermann
57bbb13e41 Create a top-level _index.md file 2020-12-30 18:53:38 +01:00
Philipp Oppermann
3dfc7ee84f Use translations list instead of constructing links manually on index pages 2020-12-30 18:52:03 +01:00
Philipp Oppermann
c97c27f4e6 Remove unused title in index frontmatter 2020-12-30 18:51:25 +01:00
Philipp Oppermann
4aa5981252 Add placeholders posts for 'Basic I/O' chapter 2020-12-30 18:15:07 +01:00
Philipp Oppermann
b5bd0296bd Add icons to posts 2020-12-30 18:14:17 +01:00
Philipp Oppermann
7e0911b42e Add copy of freestanding binary post to third edition 2020-12-30 16:09:12 +01:00
Philipp Oppermann
4027e61dab Rename post to 'Minimal Kernel' 2020-12-30 16:08:49 +01:00
Philipp Oppermann
5e3062407f Link to second and first edition from index page 2020-12-30 16:08:06 +01:00
Philipp Oppermann
fd623fd033 Add '(Second Edition)' to second edition title 2020-12-30 16:07:48 +01:00
Philipp Oppermann
d56977598e Use Iosevka font for code blocks 2020-12-30 15:44:23 +01:00
Philipp Oppermann
73b42d4747 Add redirect-to-404 template (needed for chapter pages) 2020-12-28 20:48:10 +01:00
Philipp Oppermann
4a86515a8d Autoformat javascript 2020-12-28 20:47:32 +01:00
Philipp Oppermann
300a6f452a Implement a switch for switching between light and dark mode 2020-12-28 20:47:17 +01:00
Philipp Oppermann
eb767523a5 Tweak chapter colors in dark mode 2020-12-28 20:45:55 +01:00
Philipp Oppermann
ece6a9bb9d Increase margin to right aside 2020-12-28 19:49:56 +01:00
Philipp Oppermann
5c3015acc3 Display posts of chapter as list 2020-12-28 19:48:47 +01:00
Philipp Oppermann
7cc3d5e3b9 Adjust border color 2020-12-28 19:48:11 +01:00
Philipp Oppermann
376ce0ad95 Don't render chapter pages
Disabling the rendering completely is not possible, so we instead redirect to a 404 page.
2020-12-28 19:17:13 +01:00
Philipp Oppermann
b353866952 Set chapter title separately
We avoid the anchor link on mouse-over this way.
2020-12-16 20:23:48 +01:00
Philipp Oppermann
50683507da Clarify on index page that 3rd edition is an alpha release 2020-12-16 20:21:54 +01:00
Philipp Oppermann
60a0b3bc28 Create an initial style for 3rd edition with dark mode support 2020-12-16 20:18:22 +01:00
Philipp Oppermann
798d5c58c5 Set up posts properly 2020-12-16 20:16:29 +01:00
Philipp Oppermann
e6c099ee5b Avoid Zola errors in new 3rd edition draft 2020-12-16 16:00:09 +01:00
Philipp Oppermann
874f9dbaed Restore original second edition post 2020-12-16 15:55:41 +01:00
Philipp Oppermann
d45572f9fb Move updated post to new edition-3 subfolder 2020-12-16 15:55:18 +01:00
Philipp Oppermann
7e86caf786 Begin restructuring the post again 2020-12-16 15:49:16 +01:00
Philipp Oppermann
e8bfca0adb Rename disk_image crate to bootimage
It is more clear this way that this crate is related to making the kernel bootable.
2020-12-16 15:48:55 +01:00
Philipp Oppermann
3a23f0555f Fix some typos 2020-12-16 15:48:55 +01:00
Philipp Oppermann
352ba47971 The memcpy optimization PR was merged 2020-12-16 15:47:18 +01:00
Philipp Oppermann
c175d25048 Add section about basic screen output 2020-12-16 15:46:48 +01:00
Philipp Oppermann
d7f8dd78de Cover boot info, disk image creation, and running in QEMU 2020-12-16 15:46:46 +01:00
Philipp Oppermann
f4eeda64d8 Start describing the code of the disk_image crate 2020-12-16 15:45:41 +01:00
Philipp Oppermann
2147e67e3a Start working on new bootloader section 2020-12-16 15:45:39 +01:00
Philipp Oppermann
8c87368eee Use -Z flags instead of .cargo/config files 2020-12-16 15:44:30 +01:00
Philipp Oppermann
645057fe0b Finish section on UEFI 2020-12-16 15:43:08 +01:00
Philipp Oppermann
4f108cc36e Improve UEFI section and summarize issues of UEFI 2020-12-16 15:43:02 +01:00
Philipp Oppermann
75a1d19b93 Improve structure of BIOS section 2020-12-16 15:43:01 +01:00
Philipp Oppermann
4ec30d4624 Begin describing the UEFI standard 2020-12-16 15:42:54 +01:00
Philipp Oppermann
61aabc688c Restructure and update description of the Multiboot standard 2020-12-16 15:42:51 +01:00
Philipp Oppermann
f39923545d Backup second edition minimal rust kernel post 2020-12-16 15:37:35 +01:00
52 changed files with 4567 additions and 95 deletions

View File

@@ -104,3 +104,23 @@ jobs:
- name: "Push Changes"
run: "git push"
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"

View File

@@ -31,7 +31,7 @@ skip_anchor_prefixes = [
]
[extra]
subtitle = "Philipp Oppermann's blog"
subtitle = "by Philipp Oppermann"
author = { name = "Philipp Oppermann" }
default_language = "en"
languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko"]

View File

@@ -4,9 +4,12 @@ This folder contains the content for the _"Writing an OS in Rust"_ blog.
## 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
https://www.apache.org/licenses/LICENSE-2.0)

View File

@@ -1,5 +1,4 @@
+++
title = "Second Edition"
template = "redirect-to-frontpage.html"
aliases = ["second-edition/index.html"]
+++

View 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>

View File

@@ -0,0 +1,4 @@
+++
title = "Chapters"
render = false
+++

View 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.

View 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.

View File

@@ -0,0 +1,585 @@
+++
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/nightly/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/nightly/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/nightly/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
[`rustup`]: https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html
```
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 we're still depending on the Rust runtime. To remove that dependency, we can use the `#[no_main]` attribute.
### 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
### `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.
### `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/

View File

@@ -0,0 +1,779 @@
+++
title = "Booting"
weight = 2
path = "booting"
date = 0000-01-01
draft = true
[extra]
chapter = "Bare Bones"
icon = '''
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-power" viewBox="0 0 16 16">
<path d="M7.5 1v7h1V1h-1z"/>
<path d="M3 8.812a4.999 4.999 0 0 1 2.578-4.375l-.485-.874A6 6 0 1 0 11 3.616l-.501.865A5 5 0 1 1 3 8.812z"/>
</svg>
'''
extra_content = ["uefi/index.md"]
+++
In this post, we explore the boot process on both BIOS and UEFI-based systems.
We combine the [minimal kernel] created in the previous post with a bootloader to create a bootable disk image.
We then show how this image can be started in the [QEMU] emulator and run on real hardware.
[minimal kernel]: @/edition-3/posts/01-minimal-kernel/index.md
[QEMU]: https://www.qemu.org/
<!-- 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.2`][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.2
<!-- toc -->
## The Boot Process
When you turn on a computer, it begins executing firmware code that is stored in motherboard [ROM].
This code performs a [power-on self-test], detects available RAM, and pre-initializes the CPU and other hardware.
Afterwards it looks for a bootable disk and starts booting the operating system kernel.
[ROM]: https://en.wikipedia.org/wiki/Read-only_memory
[power-on self-test]: https://en.wikipedia.org/wiki/Power-on_self-test
On x86, there are two firmware standards: the “Basic Input/Output System“ (**[BIOS]**) and the newer “Unified Extensible Firmware Interface” (**[UEFI]**).
The BIOS standard is outdated and not standardized, but relatively simple and supported on almost any x86 machine since the 1980s.
UEFI, in contrast, is more modern and has much more features, but also more complex and only runs on fairly recent hardware (built since ~2012).
[BIOS]: https://en.wikipedia.org/wiki/BIOS
[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
### BIOS
Almost all x86 systems have support for BIOS booting, including most UEFI-based machines that support an emulated BIOS.
This is great, because you can use the same boot logic across all machines from the last centuries.
The drawback is that the standard is very old, for example the CPU is put into a 16-bit compatibility mode called [real mode] before booting so that archaic bootloaders from the 1980s would still work.
Also, BIOS-compatibility will be slowly removed on newer UEFI machines over the next years (see below).
#### Boot Process
When you turn on a BIOS-based computer, it first loads the BIOS firmware from some special flash memory located on the motherboard.
The BIOS runs self test and initialization routines of the hardware, then it looks for bootable disks.
For that it loads the first disk sector (512 bytes) of each disk into memory, which contains the [_master boot record_] (MBR) structure.
This structure has the following general format:
[_master boot record_]: https://en.wikipedia.org/wiki/Master_boot_record
| Offset | Field | Size |
| ------ | ----------------- | ---- |
| 0 | bootstrap code | 446 |
| 446 | partition entry 1 | 16 |
| 462 | partition entry 2 | 16 |
| 478 | partition entry 3 | 16 |
| 444 | partition entry 4 | 16 |
| 510 | boot signature | 2 |
The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel.
The four partition entries describe the [disk partitions] such as the `C:` partition on Windows.
The boot signature field at the end of the structure specifies whether this disk is bootable or not.
If it is bootable, the signature field must be set to the [magic bytes] `0xaa55`.
It's worth noting that there are [many extensions][mbr-extensions] of the MBR format, which for example include a 5th partition entry or a disk signature.
[disk partitions]: https://en.wikipedia.org/wiki/Disk_partitioning
[magic bytes]: https://en.wikipedia.org/wiki/Magic_number_(programming)
[mbr-extensions]: https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout
The BIOS itself only cares for the boot signature field.
If it finds a disk with a boot signature equal to `0xaa55`, it directly passes control to the bootloader code stored at the beginning of the disk.
This bootloader is then responsible for multiple things:
- **Loading the kernel from disk:** The bootloader has to determine the location of the kernel image on the disk and load it into memory.
- **Initializing the CPU:** As noted above, all `x86_64` CPUs start up in a 16-bit [real mode] to be compatible with older operating systems.
So in order to run current 64-bit operating systems, the bootloader needs to switch the CPU from the 16-bit [real mode] first to the 32-bit [protected mode], and then to the 64-bit [long mode], where all CPU registers and the complete main memory are available.
- **Querying system information:** The third job of the bootloader is to query certain information from the BIOS and pass it to the OS kernel.
This, for example, includes information about the available main memory and graphical output devices.
- **Setting up an execution environment:** Kernels are typically stored as normal executable files (e.g. in the [ELF] or [PE] format), which require some loading procedure.
This includes setting up a [call stack] and a [page table].
[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
[PE]: https://en.wikipedia.org/wiki/Portable_Executable
[call stack]: https://en.wikipedia.org/wiki/Call_stack
[real mode]: https://en.wikipedia.org/wiki/Real_mode
[protected mode]: https://en.wikipedia.org/wiki/Protected_mode
[long mode]: https://en.wikipedia.org/wiki/Long_mode
[memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation
[page table]: https://en.wikipedia.org/wiki/Page_table
Some bootloaders also include a basic user interface for [choosing between multiple installed OSs][multi-booting] or entering a recovery mode.
Since it is not possible to do all that within the available 446 bytes, most bootloaders are split into a small first stage, which is as small as possible, and a second stage, which is subsequently loaded by the first stage.
[multi-booting]: https://en.wikipedia.org/wiki/Multi-booting
Writing a BIOS bootloader is cumbersome as it requires assembly language and a lot of non insightful steps like _“write this magic value to this processor register”_.
Therefore we don't cover bootloader creation in this post and instead use the existing [`bootloader`] crate to make our kernel bootable.
(If you are interested in building your own BIOS bootloader, you can look through the [BIOS source code] of the `bootloader` crate on GitHub, which is mostly written in Rust and has only about 50 lines of assembly code.)
[BIOS source code]: https://github.com/rust-osdev/bootloader/tree/main/bios
#### The Future of BIOS
As noted above, most modern systems still support booting operating systems written for the legacy BIOS firmware for backwards-compatibility.
However, there are [plans to remove this support soon][end-bios-support].
Thus, it is strongly recommended to make operating system kernels compatible with the newer UEFI standard too.
Fortunately, it is possible to create a kernel that supports booting on both BIOS (for older systems) and UEFI (for modern systems).
[end-bios-support]: https://arstechnica.com/gadgets/2017/11/intel-to-kill-off-the-last-vestiges-of-the-ancient-pc-bios-by-2020/
### UEFI
The Unified Extensible Firmware Interface (UEFI) replaces the classical BIOS firmware on most modern computers.
The specification provides lots of useful features that make bootloader implementations much simpler:
- It supports initializing the CPU directly into 64-bit mode, instead of starting in a DOS-compatible 16-bit mode like the BIOS firmware.
- It understands disk partitions and executable files.
Thus it is able to fully load the bootloader from disk into memory (no 512-byte "first stage" is required anymore).
- A standardized [specification][uefi-specification] minimizes the differences between systems.
This isn't the case for the legacy BIOS firmware, so that bootloaders often have to try different methods because of hardware differences.
- The specification is independent of the CPU architecture, so that the same interface can be used to boot on `x86_64` and e.g. `ARM` CPUs.
- It natively supports network booting without requiring additional drivers.
[uefi-specification]: https://uefi.org/specifications
The UEFI standard also tries to make the boot process safer through a so-called _"secure boot"_ mechanism.
The idea is that the firmware only allows loading bootloaders that are signed by a trusted [digital signature].
Thus, malware should be prevented from compromising the early boot process.
[digital signature]: https://en.wikipedia.org/wiki/Digital_signature
#### Issues & Criticism
While most of the UEFI specification sounds like a good idea, there are also many issues with the standard.
The main issue for most people is the fear that the _secure boot_ mechanism could be used to lock users into a specific operating system (e.g. Windows) and thus prevent the installation of alternative operating systems.
Another point of criticism is that the large number of features make the UEFI firmware very complex, which increases the chance that there are some bugs in the firmware implementation itself.
This can lead to security problems because the firmware has complete control over the hardware.
For example, a vulnerability in the built-in network stack of an UEFI implementation can allow attackers to compromise the system and e.g. silently observe all I/O data.
The fact that most UEFI implementations are not open-source makes this issue even more problematic, since there is no way to audit the firmware code for potential bugs.
While there are open firmware projects such as [coreboot] that try to solve these problems, there is no way around the UEFI standard on most modern consumer computers.
So we have to live with these drawbacks for now if we want to build a widely compatible bootloader and operating system kernel.
[coreboot]: https://www.coreboot.org/
#### Boot Process
The UEFI boot process works in the following way:
- After powering on and self-testing all components, the UEFI firmware starts looking for special bootable disk partitions called [EFI system partitions].
These partitions must be formatted with the [FAT file system] and assigned a special ID that indicates them as EFI system partition.
The UEFI standard understands both the [MBR] and [GPT] partition table formats for this, at least theoretically.
In practice, some UEFI implementations seem to [directly switch to BIOS-style booting when an MBR partition table is used][mbr-csm], so it is recommended to only use the GPT format with UEFI.
- If the firmware finds an EFI system partition, it looks for an executable file named `efi\boot\bootx64.efi` (on x86_64 systems).
This executable must use the [Portable Executable (PE)] format, which is common in the Windows world.
- It then loads the executable from disk to memory, sets up the execution environment (CPU state, page tables, etc.) in a standardized way, and finally jumps to the entry point of the loaded executable.
[MBR]: https://en.wikipedia.org/wiki/Master_boot_record
[GPT]: https://en.wikipedia.org/wiki/GUID_Partition_Table
[mbr-csm]: https://bbs.archlinux.org/viewtopic.php?id=142637
[EFI system partitions]: https://en.wikipedia.org/wiki/EFI_system_partition
[FAT file system]: https://en.wikipedia.org/wiki/File_Allocation_Table
[Portable Executable (PE)]: https://en.wikipedia.org/wiki/Portable_Executable
From this point on, the loaded executable has control.
Typically, this executable is a bootloader that then loads the actual operating system kernel.
Theoretically, it would also be possible to let the UEFI firmware load the kernel directly without a bootloader in between, but this would make it more difficult to port the kernel to other architectures.
Bootloaders and kernels typically need additional information about the system, for example the amount of available memory.
For this reason, the UEFI firmware passes a pointer to a special _system table_ as an argument when invoking the bootloader entry point function.
Using this table, the bootloader can query various system information and even invoke special functions provided by the UEFI firmware, for example for accessing the hard disk.
#### How we will use UEFI
As it is probably clear at this point, the UEFI interface is very powerful and complex.
The wide range of functionality makes it even possible to write an operating system directly as an UEFI application, using the UEFI services provided by the system table instead of creating own drivers.
In practice, however, most operating systems use UEFI only for the bootloader since own drivers give you better performance and more control over the system.
We will also follow this path for our OS implementation.
To keep this post focused, we won't cover the creation of an UEFI bootloader here.
Instead, we will use the already mentioned [`bootloader`] crate, which allows loading our kernel on both UEFI and BIOS systems.
If you're interested in how to create an UEFI bootloader yourself, check out our extra post about [**UEFI Booting**].
[**UEFI Booting**]: @/edition-3/posts/02-booting/uefi/index.md
### The Multiboot Standard
To avoid that every operating system implements its own bootloader that is only compatible with a single OS, the [Free Software Foundation] created an open bootloader standard called [Multiboot] in 1995.
The standard defines an interface between the bootloader and operating system, so that any Multiboot compliant bootloader can load any Multiboot compliant operating system on both BIOS and UEFI systems.
The reference implementation is [GNU GRUB], which is the most popular bootloader for Linux systems.
[Free Software Foundation]: https://en.wikipedia.org/wiki/Free_Software_Foundation
[Multiboot]: https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html
[GNU GRUB]: https://en.wikipedia.org/wiki/GNU_GRUB
To make a kernel Multiboot compliant, one just needs to insert a so-called [Multiboot header] at the beginning of the kernel file.
This makes it very easy to boot an OS in GRUB.
However, GRUB and the Multiboot standard have some issues too:
[Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format
- The standard is designed to make the bootloader simple instead of the kernel.
For example, the kernel needs to be linked with an [adjusted default page size], because GRUB can't find the Multiboot header otherwise.
Another example is that the [boot information], which is passed to the kernel, contains lots of architecture-dependent structures instead of providing clean abstractions.
- The standard supports only the 32-bit protected mode on BIOS systems.
This means that you still have to do the CPU configuration to switch to the 64-bit long mode.
- For UEFI systems, the standard provides very little added value as it simply exposes the normal UEFI interface to kernels.
- Both GRUB and the Multiboot standard are only sparsely documented.
- GRUB needs to be installed on the host system to create a bootable disk image from the kernel file.
This makes development on Windows or Mac more difficult.
[adjusted default page size]: https://wiki.osdev.org/Multiboot#Multiboot_2
[boot information]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
Because of these drawbacks we decided to not use GRUB or the Multiboot standard for this series.
However, we might add Multiboot support to our [`bootloader`] crate at some point, so that it becomes possible to load your kernel on a GRUB system too.
If you're interested in writing a Multiboot compliant kernel, check out the [first edition] of this blog series.
[first edition]: @/edition-1/_index.md
## Bootable Disk Image
We now know that most operating system kernels are loaded by bootloaders, which are small programs that initialize the hardware to reasonable defaults, load the kernel from disk, and provide it with some fundamental information about the underlying system.
In this section, we will learn how to combine the [minimal kernel] we created in the previous post with the `bootloader` crate in order to create a bootable disk image.
The [`bootloader`] crate supports both BIOS and UEFI booting on `x86_64` and creates a reasonable default execution environment for our kernel.
This way, we can focus on the actual kernel design in the following posts instead of spending a lot of time on system initialization.
### The `bootloader_api` Crate
In order to make our kernel compatible with the `bootloader` crate, we first need to add a dependency on the [`bootloader_api`] crate:
[`bootloader`]: https://docs.rs/bootloader/latest/bootloader/
[`bootloader_api`]: https://docs.rs/bootloader_api/latest/bootloader_api/
```toml,hl_lines=4
# in Cargo.toml
[dependencies]
bootloader_api = "0.11.2"
```
Now we need to replace our custom `_start` entry point function with [`bootloader_api::entry_point`] macro. This macro instructs the compiler to create a special `.bootloader-config` section with encoded configuration options in the resulting executable, which is later read by the bootloader implementation.
[`bootloader_api::entry_point`]: https://docs.rs/bootloader_api/latest/bootloader_api/macro.entry_point.html
We will take a closer look at the `entry_point` macro and the different configuration options later. For now, we just use the default setup:
```rust,hl_lines=3 6-8
// in main.rs
bootloader_api::entry_point!(kernel_main);
// ↓ this replaces the `_start` function ↓
fn kernel_main(bootinfo: &'static mut bootloader_api::BootInfo) -> ! {
loop {}
}
```
There are a few notable things:
- The `kernel_main` function is just a normal Rust function with an arbitrary name. No `#[no_mangle]` attribute is needed anymore since the `entry_point` macro handles this internally.
- Like before, our entry point function is [diverging], i.e. it must never return. We ensure this by looping endlessly.
- There is a new [`BootInfo`] argument, which the bootloader fills with various system information. We will use this argument later.
- The `entry_point` macro verifies that the `kernel_main` function has the correct arguments and return type, otherwise a compile error will occur. This is important because undefined behavior might occur when the function signature does not match the bootloader's expectations.
[diverging]: https://doc.rust-lang.org/rust-by-example/fn/diverging.html
[`BootInfo`]: https://docs.rs/bootloader_api/latest/bootloader_api/info/struct.BootInfo.html
To verify that the `entry_point` macro worked as expected, we can use the `objdump` tool as [described in the previous post][objdump-prev]. First, we recompile using `cargo build --target x86_64-unknown-none`, then we inspect the section headers using `objdump` or `rust-objdump`:
[objdump-prev]: @/edition-3/posts/01-minimal-kernel/index.md#objdump
```bash,hl_lines=8
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 .bootloader-config 0000007c 0000000000200120 DATA
2 .text 00000075 00000000002011a0 TEXT
3 .debug_abbrev 000001c8 0000000000000000 DEBUG
4 .debug_info 00000b56 0000000000000000 DEBUG
5 .debug_aranges 00000090 0000000000000000 DEBUG
6 .debug_ranges 00000040 0000000000000000 DEBUG
7 .debug_str 00000997 0000000000000000 DEBUG
8 .debug_pubnames 0000014c 0000000000000000 DEBUG
9 .debug_pubtypes 00000548 0000000000000000 DEBUG
10 .debug_frame 000000b0 0000000000000000 DEBUG
11 .debug_line 0000012c 0000000000000000 DEBUG
12 .comment 00000013 0000000000000000
13 .symtab 000000a8 0000000000000000
14 .shstrtab 000000b8 0000000000000000
15 .strtab 000000cd 0000000000000000
```
We see that there is indeed a new `.bootloader-config` section of size `0x7c` in our kernel executable.
This means that we can now look into how to create a bootable disk image from our kernel.
### Creating a Disk Image
Now that our kernel is compatible with the `bootloader` crate, we can turn it into a bootable disk image.
To do that, we need to create a disk image file with an [MBR] or [GPT] partition table and create a new [FAT][FAT file system] boot partition there.
Then we can copy our compiled kernel and the compiled bootloader implementation there.
While we could perform these steps manually using platform-specific tools (e.g. [`mkfs`] on Linux), this would not be cumbersome and fragile.
Fortunately, the `bootloader` crate provides a [`DiskImageBuilder`] to construct both BIOS and UEFI disk images in a simple way.
It works on Windows, macOS, and Linux without any additional dependencies.
We just need to pass path to our kernel executable and then call `create_bios_image` and/or `create_uefi_image` with our desired target path.
[`mkfs`]: https://www.man7.org/linux/man-pages/man8/mkfs.fat.8.html
[`DiskImageBuilder`]: https://docs.rs/bootloader/0.11.3/bootloader/struct.DiskImageBuilder.html
By using the `DiskImageBuilder` together with some advanced features of `cargo`, we can combine the kernel build and disk image creation steps.
This way, we also don't need to pass the `--target x86_64-unknown-none` argument anymore.
In the next sections, we will implement following steps to achieve this:
- Create a [`cargo` workspace] with an empty root package.
- Add an [_artifact dependency_] to include the compiled kernel binary in the root package.
- Create a [build script] for the root package that invokes the `bootloader::DiskImageBuilder`.
[`cargo` workspace]: https://doc.rust-lang.org/cargo/reference/workspaces.html
[_artifact dependency_]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies
[build script]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
Don't worry if that sounds a bit complex!
We will explain each of these steps in detail.
#### Creating a Workspace
TODO
#### Adding an Artifact Dependency
TODO
#### Creating a Build Script
TODO
> For building the bootloader, you need to have the `llvm-tools-preview` rustup component installed.
> You can do so by executing `rustup component add llvm-tools-preview`.
#### Making `rust-analyzer` happy
TODO
## Running our Kernel
After creating a bootable disk image for our kernel, we are finally able to run it.
Before we learn how to run it on real hardware, we start by running it inside the [QEMU] system emulator.
This has multiple advantages:
- We can't break anything: Our kernel has full hardware access, so that a bug might have serious consequences on real hardware.
- We don't need a separate computer: QEMU runs as a normal program on our development computer.
- The edit-test cycle is much faster: We don't need to copy the disk image to bootable usb stick on every kernel change.
- It's possible to debug our kernel via QEMU's debug tools and GDB.
We will still learn how to boot our kernel on real hardware later in this post, but for now we focus on QEMU.
For that you need to install QEMU on your machine as described on the [QEMU download page].
[QEMU download page]: https://www.qemu.org/download/
### Running in QEMU
After installing QEMU, you can run `qemu-system-x86_64 --version` in a terminal to verify that it is installed.
Then you can run the BIOS disk image of our kernel through the following command:
```
qemu-system-x86_64 -drive \
format=raw,file=bootimage-bios-blog_os.img
```
As a result, you should see a window open that looks like this:
![QEMU printing several `INFO:` log messages](qemu-bios.png)
This output comes from the bootloader.
As we see, the last line is _"Jumping to kernel entry point at […]"_.
This is the point where the `_start` function of our kernel is called.
Since we currently only `loop {}` in that function nothing else happens, so it is expected that we don't see any additional output.
Running the UEFI disk image works in a similar way, but we need to pass some additional files to QEMU to emulate an UEFI firmware.
This is necessary because QEMU does not support emulating an UEFI firmware natively.
The files that we need are provided by the [Open Virtual Machine Firmware (OVMF)][OVMF] project, which is a sub-project of [TianoCore] and implements UEFI support for virtual machines.
Unfortunately, the project is only [sparsely documented][ovmf-whitepaper] and does not even have a clear homepage.
[OVMF]: https://github.com/tianocore/tianocore.github.io/wiki/OVMF
[TianoCore]: https://www.tianocore.org/
[ovmf-whitepaper]: https://www.linux-kvm.org/downloads/lersek/ovmf-whitepaper-c770f8c.txt
The easiest way to work with OVMF is to download pre-built images of the code.
We provide such images in the [`rust-osdev/ovmf-prebuilt`] repository, which is updated daily from [Gerd Hoffman's RPM builds](https://www.kraxel.org/repos/).
The compiled OVMF are provided as [GitHub releases][ovmf-prebuilt-releases].
[`rust-osdev/ovmf-prebuilt`]: https://github.com/rust-osdev/ovmf-prebuilt/
[ovmf-prebuilt-releases]: https://github.com/rust-osdev/ovmf-prebuilt/releases/latest
To run our UEFI disk image in QEMU, we need the `OVMF_pure-efi.fd` file (other files might work as well).
After downloading it, we can then run our UEFI disk image using the following command:
```
qemu-system-x86_64 -drive \
format=raw,file=bootimage-uefi-blog_os.img \
-bios /path/to/OVMF_pure-efi.fd,
```
If everything works, this command opens a window with the following content:
![QEMU printing several `INFO:` log messages](qemu-uefi.png)
The output is a bit different than with the BIOS disk image.
Among other things, it explicitly mentions that this is an UEFI boot right on top.
### Using `cargo run`
TODO
### Screen Output
While we see some screen output from the bootloader, our kernel still does nothing.
Let's fix this by trying to output something to the screen from our kernel too.
Screen output works through a so-called [_framebuffer_].
A framebuffer is a memory region that contains the pixels that should be shown on the screen.
The graphics card automatically reads the contents of this region on every screen refresh and updates the shown pixels accordingly.
[_framebuffer_]: https://en.wikipedia.org/wiki/Framebuffer
Since the size, pixel format, and memory location of the framebuffer can vary between different systems, we need to find out these parameters first.
The easiest way to do this is to read it from the [boot information structure][`BootInfo`] that the bootloader passes as argument to our kernel entry point:
```rust
// in src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_ref() {
let info = framebuffer.info();
let buffer = framebuffer.buffer();
}
loop {}
}
```
Even though most systems support a framebuffer, some might not.
The [`BootInfo`] type reflects this by specifying its `framebuffer` field as an [`Option`].
Since screen output won't be essential for our kernel (there are other possible communication channels such as serial ports), we use an [`if let`] statement to run the framebuffer code only if a framebuffer is available.
[`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html
[`if let`]: https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions
The [`FrameBuffer`] type provides two methods: The `info` method returns a [`FrameBufferInfo`] instance with all kinds of information about the framebuffer format, including the pixel type and the screen resolution.
The `buffer` method returns the actual framebuffer content in form of a mutable byte [slice].
[`FrameBuffer`]: https://docs.rs/bootloader/0.11.0/bootloader/boot_info/struct.FrameBuffer.html
[`FrameBufferInfo`]: https://docs.rs/bootloader/0.11.0/bootloader/boot_info/struct.FrameBufferInfo.html
[slice]: https://doc.rust-lang.org/std/primitive.slice.html
We will look into programming the framebuffer in detail in the next post.
For now, let's just try setting the whole screen to some color.
For this, we just set every pixel in the byte slice to some fixed value:
```rust
// in 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 {}
}
```
While it depends on the pixel color format how these values are interpreted, the result will likely be some shade of gray since we set the same value for every color channel (e.g. in the RGB color format).
After running `cargo kbuild` and then our `boot` script again, we can boot the new version in QEMU.
We see that our guess that the whole screen would turn gray was right:
![QEMU showing a gray screen](qemu-gray.png)
We finally see some output from our own little kernel!
You can try experimenting with the pixel bytes if you like, for example by increasing the pixel value on each loop iteration:
```rust
// in src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let mut value = 0x90;
for byte in framebuffer.buffer_mut() {
*byte = value;
value = value.wrapping_add(1);
}
}
loop {}
}
```
We use the [`wrapping_add`] method here because Rust panics on implicit integer overflow (at least in debug mode).
By adding a prime number, we try to add some variety.
The result looks as follows:
![QEMU showing repeating gradient columns](qemu-wrapping-add.png)
### Booting on Real Hardware
To boot on real hardware, you first need to write either the `bootimage-uefi-blog_os.img` or the `bootimage-bios-blog_os.img` disk image to an USB stick.
This deletes everything on the stick, so be careful.
The actual steps to do this depend on your operating system.
#### Unix-like
On any Unix-like host OS (including both Linux and macOS), you can use the `dd` command to write the disk image directly to a USB drive.
First run either `sudo fdisk -l` (on Linux) or `diskutil list` (on a Mac) to get info about where in `/dev` the file representing your device is located.
After that, open a terminal window and run either of the following commands:
##### Linux
```
# replace /dev/sdX with device filename as revealed by "sudo fdisk -l"
$ sudo dd if=boot-uefi-blog_os.img of=/dev/sdX
```
##### macOS
```
# replace /dev/diskX with device filename as revealed by "diskutil list"
$ sudo dd if=boot-uefi-blog_os.img of=/dev/diskX
```
**WARNING**: Be very careful when running this command.
If you specify the wrong device as the `of=` parameter, you could end up wiping your system clean, so make sure the device you run it on is a removable one.
#### Windows
On Windows, you can use the [Rufus] tool, which is developed as an open-source project [on GitHub][rufus-github].
After downloading it you can directly run it, there's no installation necessary.
In the interface, you select the USB stick you want to write to.
[Rufus]: https://rufus.ie/
[rufus-github]: https://github.com/pbatard/rufus
### OLD
The [docs of the `bootloader` crate][`bootloader` docs] describe how to create a bootable disk image for a kernel.
The first step is to find the directory where cargo placed the source code of the `bootloader` dependency.
Then, a special build command needs to be executed in that directory, passing the paths to the kernel binary and its `Cargo.toml` as arguments.
This will result in multiple disk image files as output, which can be used to boot the kernel on BIOS and UEFI systems.
[`bootloader` docs]: https://docs.rs/bootloader/0.11.0/bootloader/
#### A `boot` crate
Since following these steps manually is cumbersome, we create a script to automate it.
For that we create a new `boot` crate in a subdirectory, in which we will implement the build steps:
```
cargo new --bin boot
```
This command creates a new `boot` subfolder with a `Cargo.toml` and a `src/main.rs` in it.
Since this new cargo project will be tightly coupled with our main project, it makes sense to combine the two crates as a [cargo workspace].
This way, they will share the same `Cargo.lock` for their dependencies and place their compilation artifacts in a common `target` folder.
To create such a workspace, we add the following to the `Cargo.toml` of our main project:
[cargo workspace]: https://doc.rust-lang.org/cargo/reference/workspaces.html
```toml
# in Cargo.toml
[workspace]
members = ["boot"]
```
After creating the workspace, we can begin the implementation of the `boot` crate.
Note that the crate will be invoked as part as our build process, so it can be a normal Rust executable that runs on our host system.
This means that is has a classical `main` function and can use standard library types such as [`Path`] or [`Command`] without problems.
[`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
[`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
#### Artifact Dependencies
The first step in creating the bootable disk image is to enable support for [artifact dependencies](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) from inside your kernel's `.cargo/config.toml` because we're going to need that support later:
```toml
# in .cargo/config.toml
[unstable]
bindeps = true
```
After this, you need to add an artifact dependency on your kernel from inside the boot crate. This tells the bootloader crate where the source code to your kernel resides:
```toml
# in boot/Cargo.toml
[dependencies]
kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" }
```
Finally, you need to add a dependency on the main `bootloader` crate. Previous versions used `bootloader_locator` instead, but now, thanks to artifact dependencies, that is no longer necessary.
```toml
# in boot/Cargo.toml
[dependencies]
bootloader = "0.11.0"
```
We can see how this works by printing the Cargo-generated environment variable pointing to the absolute path of the kernel binary
```rust
// in boot/src/main.rs
use std::path::Path; // new
pub fn main() {
let kernel_binary = Path::new(env!("CARGO_BIN_FILE_KERNEL_kernel"));
dbg!(kernel_binary);
}
```
The `CARGO_BIN_FILE_KERNEL_kernel` environment variable is defined by Cargo as the absolute path to the binary file created after compiling an artifact dependency — and in this case, the binary file it points to is your kernel's binary. This makes it very easy to begin the process of boot image creation, as explained in detail below.
[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html
To run the `boot` crate from our workspace root (i.e. the kernel directory), we can pass a [`--package`] argument to `cargo run`:
[`--package`]: https://doc.rust-lang.org/cargo/commands/cargo-run.html#package-selection
```
> cargo run --package boot
[boot/src/main.rs:5] kernel_path = "/.../target/x86_64-unknown-none/debug/deps/artifact/kernel-.../bin/kernel-..."
```
It worked! We see that the kernel binary lives somewhere in the dependency tree of our `boot` crate.
By depending on the kernel as a binary dependency of `boot`, we ensure that the bootloader and the kernel use the exact same version of the `BootInfo` type.
This is important because the `BootInfo` type is not stable yet, so undefined behavior can occur when when using different `BootInfo` versions.
#### Building a Boot Image
The next step is to actually build the boot image.
From the [`bootloader` docs] we learn that the crate defines two completely unique bootloader objects: `BiosBoot` for BIOS and `UefiBoot` for UEFI. To keep it simple, we will support both, although it's possible to choose which to exclusively support later to keep your workflow streamlined as your kernel becomes more complex.
```toml
# in boot/Cargo.toml
[dependencies]
bootloader = "0.11.0"
kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" }
```
Once all dependencies are accounted for, it's time to put everything together:
```rust
// in boot/src/main.rs
// new
use bootloader::{BiosBoot, UefiBoot}
use std::{path::Path, process::exit};
pub fn main() {
// new code below
let kernel_dir = todo!();
let bios_image = todo!();
let uefi_image = todo!();
// invoke UEFI boot image builder
let uefi = UefiBoot::new(&kernel_binary);
// invoke BIOS boot image builder
let bios = BiosBoot::new(&kernel_binary);
// attempt to create UEFI boot image
if let Err(e) = uefi.create_disk_image(&uefi_path) {
eprintln!("{:#?}", &e);
exit(1)
}
// attempt to create BIOS boot image
if let Err(e) = bios.create_disk_image(&bios_path) {
eprintln!("{:#?}", &e);
exit(1)
}
}
```
We use both the `UefiBoot` and `BiosBoot` types to create disk images for the BIOS and UEFI implementations, respectively. By using the `if let` syntax, we can exit the build gracefully whenever an error occurs.
After creating the `UefiBoot` and `BiosBoot` types using the `CARGO_BIN_FILE_KERNEL_kernel` environment variable that we went over previously as the constructor argument for both, we now are ready for the next step.
#### Filling in the Blanks
We still need to fill in the paths we marked as `todo!` above. Like with the kernel binary, we can also use the `env!()` builtin for this, since another environment variable can also be used as a reference point for determining the filenames for the disk images:
```rust
// in `main` in boot/src/main.rs
// we know that the kernel lives in the parent directory of the `boot` crate
let kernel_dir = Path::new(env!("CARGO_MANIFEST_DIR")).manifest_dir.parent().unwrap();
// use the above as a target folder in which to place both the BIOS and UEFI disk images
let bios_image = kernel_dir.join("bootimage-bios-blog_os.img");
let uefi_image = kernel_dir.join("bootimage-uefi-blog_os.img");
```
The [`CARGO_MANIFEST_DIR`] environment variable always points to the `boot` directory, even if the crate is built from a different directory (e.g. via cargo's `--manifest-path` argument).
This gives use a good starting point for creating the paths we care about since we know that our kernel lives in the [parent][`Path::parent`] directory.
[`Path::parent`]: https://doc.rust-lang.org/std/path/struct.Path.html
[`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
From the `kernel_dir`, we can then construct the `bios_image` and `uefi_image` paths using the [`Path::join`] method.
[`Path::join`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.join
#### Creating the Disk Images
There is one last step before we can create the bootable disk images: The `bootloader` build requires the [rustup component] `llvm-tools-preview`.
To install it, we can either run `rustup component add llvm-tools-preview` or specify it in our `rust-toolchain.toml` file:
[rustup component]: https://rust-lang.github.io/rustup/concepts/components.html
```toml
# in rust-toolchain.toml
[toolchain]
channel = "nightly"
components = ["rust-src", "rustfmt", "clippy", "llvm-tools-preview"]
```
After that can finally use our `boot` crate to create some bootable disk images from our kernel:
```bash
> cargo run --package boot
```
Because we're using artifact dependencies, when you run the `boot` package, the kernel is automatically pulled in and compiled as a dependency. Previously, in version 0.10 of the bootloader crate, you had to build the kernel binary first, but now, thanks to artifact dependencies, this is no longer required.
Note that the command will only work from the root directory of our project.
This is because we hardcoded the `kernel_binary` path in our `main` function.
We will fix this later in the post, but first it is time to actually run our kernel!
Note also that we specified names for the image files. Although we used `bootimage-bios-blog_os.img` and `bootimage-uefi-blog_os.img` for compatibility, they can now be given whatever names you see fit.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,25 @@
+++
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>'''
+++
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
<!-- TODO: update relative link in 02-booting/uefi/index.md when this post is finished -->

View 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

View 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

View 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

View File

@@ -0,0 +1,7 @@
+++
title = "Posts"
sort_by = "weight"
insert_anchor_links = "left"
render = false
page_template = "edition-3/page.html"
+++

View File

@@ -22,13 +22,13 @@
*/
/* Fonts */
@font-face {
font-family: "Iosevka";
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-family: 'Iosevka Web';
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,
pre {
font-family: "Iosevka", monospace;
font-family: "Iosevka Web", monospace;
}
code {
padding: 0.25em 0.5em;

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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")
}
}

View File

@@ -3,10 +3,10 @@
{% block title %}Page not found | {{ config.title }}{% endblock title %}
{% block main %}
<h1>Page not found</h1>
<p>Sorry, this address is not valid.</p>
<h1>Page not found</h1>
<p>Sorry, this address is not valid.</p>
<p><a href="{{ config.base_url | safe }}">Go to the index page</a>.</p>
<p><a href="{{ config.base_url | safe }}">Go to the index page</a>.</p>
<p>If you followed a link on this site, please <a href="https://github.com/phil-opp/blog_os/issues">report it</a>!</p>
<p>If you followed a link on this site, please <a href="https://github.com/phil-opp/blog_os/issues">report it</a>!</p>
{% endblock main %}

View File

@@ -26,7 +26,7 @@
<script async src="/js/edition-2/main.js"></script>
<title>{% block title %}{% endblock title %}</title>
<title>{% block title %}{% endblock title %} (Second Edition)</title>
</head>
<body>
@@ -34,7 +34,7 @@
<header class="masthead">
<div style="position:relative">
<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>
<p><small>{{ config.extra.subtitle | replace(from=" ", to="&nbsp;") | safe }}</small></p>
{% block header %}{% endblock header %}

View File

@@ -69,7 +69,9 @@
{% block after_main %}
<aside class="page-aside-right">
<div class="block" id="language-selector">
<h2>Other Languages</h2>
{% if section.translations -%}
<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 -%}
{%- set translation = translations[lang_code].0 -%}
@@ -78,7 +80,9 @@
{{ trans(key="lang_name", lang = translation.lang) }}
</a></li>
{%- endif -%}
{% endfor %}</ul>
{% endfor %}</ul>
</div>
{%- endif %}
</div>
<div class="block">
<h2>Recent Updates</h2>

View File

@@ -42,3 +42,4 @@
</ul>
</details>
{% endmacro toc %}

View 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 %}

View 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="&nbsp;") | safe }}
&nbsp;Third&nbsp;Edition&nbsp;(Alpha&nbsp;Release)</small></p>
{% block header %}{% endblock header %}
</div>
</header>
{% endblock masthead %}

View 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 %}

View 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>
&copy; <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>

View 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&nbsp;the&nbsp;second edition&nbsp;»</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&nbsp;the&nbsp;first edition&nbsp;»</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 %}

View 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&nbsp;more&nbsp;»</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 %}

View 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 }}">&laquo; {{ page.lower.title }}</a>
{% endif %}
{% if page.higher %}
<a class="next" href="{{ page.higher.path | safe }}">{{ page.higher.title }} &raquo;</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 %}

View File

@@ -0,0 +1 @@
{{ page.content | safe }}

View File

@@ -1,25 +1 @@
{% extends "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 %}
{% extends "edition-2/news-page.html" %}

View File

@@ -10,16 +10,16 @@
<div class="posts neutral">
{% for page in section.pages %}
<div>
<h2 class="post-title"><a href="{{ page.path | safe }}">{{ page.title }}</a></h2>
<time datetime="{{ page.date | date(format="%Y-%m-%d") }}" class="post-date" style="margin-bottom: 0.5rem;">
{{ page.date | date(format="%b %d, %Y") }}
</time>
<div class="post-summary" style="margin-bottom: 2rem;">
{{ page.summary | safe}}
<a class="read-more" href="{{ page.path | safe }}">read more…</a>
</div>
<div>
<h2 class="post-title"><a href="{{ page.path | safe }}">{{ page.title }}</a></h2>
<time datetime="{{ page.date | date(format=" %Y-%m-%d") }}" class="post-date" style="margin-bottom: 0.5rem;">
{{ page.date | date(format="%b %d, %Y") }}
</time>
<div class="post-summary" style="margin-bottom: 2rem;">
{{ page.summary | safe}}
<a class="read-more" href="{{ page.path | safe }}">read more…</a>
</div>
</div>
{% endfor %}
</div>

View File

@@ -3,6 +3,6 @@
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
{% block main %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock main %}

View File

@@ -1,8 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<link rel="canonical" href="{{ config.base_url | safe }}" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="refresh" content="0;url={{ config.base_url | safe }}" />
</head>
</html>
<head>
<link rel="canonical" href="{{ config.base_url | safe }}" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="refresh" content="0;url={{ config.base_url | safe }}" />
</head>
</html>

View File

@@ -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.
</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.
</p>
<p>
Thank you!
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>
{% endmacro support %}

View File

@@ -5,34 +5,37 @@
{% 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 }}
<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 }}
<div>
<h2>Thank You!</h2>
<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>
</div>
<div>
<h2>Thank You!</h2>
<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>
</div>
{% endblock main %}
{% block after_main %}
<hr>
<div class="PageNavigation">
{% if page.lower %}
<a class="prev" href="{{ page.lower.path | safe }}">&laquo; {{ page.lower.title }}</a>
{% endif %}
{% if page.higher %}
<a class="next" href="{{ page.higher.path | safe }}">{{ page.higher.title }} &raquo;</a>
{% endif %}
</div>
<hr>
<section>
<h2 id="comments">Comments</h2>
{{ snippets::giscus(search_term=page.title, lang=page.lang) }}
</section>
<hr>
<div class="PageNavigation">
{% if page.lower %}
<a class="prev" href="{{ page.lower.path | safe }}">&laquo; {{ page.lower.title }}</a>
{% endif %}
{% if page.higher %}
<a class="next" href="{{ page.higher.path | safe }}">{{ page.higher.title }} &raquo;</a>
{% endif %}
</div>
<hr>
<section>
<h2 id="comments">Comments</h2>
{{ snippets::giscus(search_term=page.title, lang=page.lang) }}
</section>
{% endblock after_main %}

View File

@@ -10,11 +10,13 @@
<p>{{ section.description }}</p>
{% endblock introduction %}
<div class="status-update-list"><ul>
{% include "auto/status-updates.html" %}
{% for page in section.pages %}
<div class="status-update-list">
<ul>
{% include "auto/status-updates.html" %}
{% for page in section.pages %}
<li><b><a href="{{ page.path | safe }}">{{ page.title }}</a></b></li>
{% endfor %}
</ul></div>
{% endfor %}
</ul>
</div>
{% endblock main %}