Compare commits

..

167 Commits

Author SHA1 Message Date
Philipp Oppermann
bd3550ea87 Merge pull request #1410 from v4zha/edition-3
loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars
2025-05-09 15:51:53 +02:00
V4zha
ecb60ec326 Update index.md
change to_str().unwrap() to display() in format! args
2025-04-24 18:48:52 +05:30
v4zha
8a1267477a loading UEFI using ovmf_prebuilt=0.2.3 with ovmf_code and ovmf_vars 2025-04-24 16:58:59 +05:30
Philipp Oppermann
ce01059620 Fix typos
Fixes #1339
2024-08-26 07:56:55 +02:00
Philipp Oppermann
4d0c3ac188 Merge pull request #1333 from proudmuslim-dev/patch-5
Move import to sensible location in chapter 3
2024-07-25 22:57:03 +02:00
proudmuslim-dev
d565cd125b Move import to sensible location in chapter 3
It served no purpose in the previous code block and would only confuse the user
2024-07-04 19:15:21 +00:00
Philipp Oppermann
ca86085360 Merge pull request #1299 from spocino/patch-1
fix typo'd variable name in post 3 (doesn't compile)
2024-02-27 07:52:00 +01:00
Samuel Pocino
5f3d38884c fix typo'd variable name in post 3 (doesn't compile) 2024-02-24 20:32:28 -05:00
Philipp Oppermann
f557d1c698 Merge pull request #1276 from proudmuslim-dev/patch-4
Fix `embedded_graphics` code + typo in chapter 3
2024-02-04 17:09:00 +01:00
proudmuslim-dev
0c248d027e Fix embedded_graphics code + correct typo in chapter 3
Compiles now
2024-01-31 12:55:20 -08:00
Philipp Oppermann
2cf0675a2d Merge pull request #1269 from proudmuslim-dev/patch-3
Fix typos in code for `embedded_graphics` crate in chapter 3
2024-01-28 12:18:43 +01:00
Philipp Oppermann
916ad36e78 Merge pull request #1270 from lachsdachs/patch-1
fix a lil typo
2024-01-28 12:09:11 +01:00
lachsdachs
3c2e91fa4e fix a lil typo
sturcts -> structs
2024-01-27 21:23:35 +01:00
proudmuslim-dev
c9683a2cd9 Fix typos in code for embedded_graphics crate in chapter 3
This still won't compile on account of the fact that the `Point` type apparently doesn't implement `Into<(usize, usize)>`. Attempting to change the type just results in more issues
2024-01-27 00:21:18 +00:00
Philipp Oppermann
514736b1d8 Merge pull request #1265 from proudmuslim-dev/patch-1
Fix typo in chapter 2
2024-01-24 10:01:44 +01:00
Philipp Oppermann
647b509971 Merge pull request #1266 from proudmuslim-dev/patch-2
Fix formatting in chapter 2
2024-01-22 11:27:39 +01:00
proudmuslim-dev
1118350b16 Fix formatting in chapter 2 2024-01-21 22:00:19 +00:00
proudmuslim-dev
fb096a7484 Fix typo in chapter 2 2024-01-21 21:34:52 +00:00
Philipp Oppermann
8a41fd65bf Polish section about drawing blue pixels and squares 2023-12-29 18:57:01 +01:00
Philipp Oppermann
50802c8332 Continue work on framebuffer post 2023-12-28 20:12:07 +01:00
Philipp Oppermann
ba410f40ba Fix source path 2023-12-28 20:11:46 +01:00
Philipp Oppermann
a119d36cc9 Tweak heading style 2023-12-28 17:59:23 +01:00
Philipp Oppermann
9080e69a09 Improve headings 2023-12-28 17:59:13 +01:00
Philipp Oppermann
2676d69c31 Remove striped output example
Doesn't really add much value.
2023-07-09 11:28:40 +02:00
Philipp Oppermann
c31f3c2728 Start describing framebuffer bitmap layout 2023-07-09 11:26:34 +02:00
Philipp Oppermann
5799263124 Start working on 'Screen Output' post 2023-07-08 13:49:41 +02:00
Kenny Strawn
ded60de8d0 Submit rough draft of Edition-3-Post-3 (#1223) 2023-07-08 13:21:56 +02:00
Philipp Oppermann
171956adc8 Merge branch 'main' into edition-3 2023-07-08 13:21:06 +02:00
Philipp Oppermann
5681d3f0f7 Finish rest of post 2023-05-01 15:06:05 +02:00
Philipp Oppermann
68d0c946f4 Set default rustup profile 2023-04-30 17:16:11 +02:00
Philipp Oppermann
9baab55788 Add rust-src component 2023-04-30 17:14:24 +02:00
Philipp Oppermann
2bc74ce8f7 Write sections about creating bootable disk image 2023-04-30 17:09:37 +02:00
Philipp Oppermann
7ce9ae1caf Remove profile overrides again after compiling for custom target 2023-04-30 16:54:38 +02:00
Philipp Oppermann
646e5ba502 Add a short introduction to rustup 2023-04-30 16:53:45 +02:00
Philipp Oppermann
bb754eadba Link to stable Rust docs 2023-04-30 16:53:25 +02:00
Philipp Oppermann
f377050605 Tweak colors for note blocks 2023-04-30 16:52:47 +02:00
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
85 changed files with 5562 additions and 1923 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

@@ -8,7 +8,7 @@ from github import Github
g = Github()
one_month_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=32)
one_month_ago = datetime.datetime.now() - datetime.timedelta(days=32)
def filter_date(issue):
return issue.closed_at > one_month_ago

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"]
@@ -49,13 +49,6 @@ translated_content_notice = "This is a community translation of the <strong><a h
translated_by = "Translation by"
translation_contributors = "With contributions from"
word_separator = "and"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# Chinese (simplified)
[languages.zh-CN]
@@ -74,13 +67,6 @@ translated_content_notice = "这是对原文章 <strong><a href=\"_original.perm
translated_by = "翻译者:"
translation_contributors = "With contributions from"
word_separator = "和"
support_me = """
<h2>支持我</h2>
<p>创建和维护这个博客以及相关的库带来了十分庞大的工作量,即便我十分热爱它们,仍然需要你们的支持。通过赞助我,可以让我有能投入更多时间与精力在创造新内容,开发新功能上。赞助我最好的办法是通过<a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. 十分感谢各位!</p>
"""
comment_note = """
你有问题需要解决,想要分享反馈,或者讨论更多的想法吗?请随时在这里留下评论!请使用尽量使用英文并遵循 Rust 的 <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. 这个讨论串将与 <a href="_discussion_url_"><em>discussion on GitHub</em></a> 直接连接,所以你也可以直接在那边发表评论
"""
# Chinese (traditional)
[languages.zh-TW]
@@ -99,13 +85,6 @@ translated_content_notice = "這是對原文章 <strong><a href=\"_original.perm
translated_by = "翻譯者:"
translation_contributors = "With contributions from"
word_separator = "和"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# Japanese
[languages.ja]
@@ -124,13 +103,6 @@ translated_content_notice = "この記事は<strong><a href=\"_original.permalin
translated_by = "翻訳者:"
translation_contributors = "With contributions from"
word_separator = "及び"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# Persian
[languages.fa]
@@ -149,13 +121,6 @@ translated_content_notice = "این یک ترجمه از جامعه کاربرا
translated_by = "ترجمه توسط"
translation_contributors = "With contributions from"
word_separator = "و"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# Russian
[languages.ru]
@@ -174,13 +139,6 @@ translated_content_notice = "Это перевод сообщества пост
translated_by = "Перевод сделан"
translation_contributors = "With contributions from"
word_separator = "и"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# French
[languages.fr]
@@ -199,13 +157,6 @@ translated_content_notice = "Ceci est une traduction communautaire de l'article
translated_by = "Traduit par : "
translation_contributors = "With contributions from"
word_separator = "et"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""
# Korean
[languages.ko]
@@ -224,10 +175,3 @@ translated_content_notice = "이것은 커뮤니티 멤버가 <strong><a href=\"
translated_by = "번역한 사람 : "
translation_contributors = "With contributions from"
word_separator = "와"
support_me = """
<h2>Support Me</h2>
<p>Creating and maintaining 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. 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>
"""
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
"""

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

@@ -505,7 +505,7 @@ A minimal target specification that describes the `x86_64-unknown-linux-gnu` tar
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
@@ -527,7 +527,7 @@ In order to disable the multimedia extensions, we create a new target named `x86
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",

View File

@@ -2,7 +2,6 @@
title = "Set Up GDB"
template = "plain.html"
path = "set-up-gdb"
aliases = ["set-up-gdb.html"]
weight = 4
+++

View File

@@ -98,7 +98,7 @@ Rust allows us to define [custom targets] through a JSON configuration file. A m
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"linker-flavor": "gcc",
"target-endian": "little",
"target-pointer-width": "64",
@@ -133,7 +133,7 @@ For our target system, we define the following JSON configuration in a file name
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"linker-flavor": "gcc",
"target-endian": "little",
"target-pointer-width": "64",

View File

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

View File

@@ -119,7 +119,7 @@ error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
```
現在編譯告訴我們缺少 `#[panic_handler]` 函式以及 _language item_
現在編譯告訴我們缺少 `#[panic_handler]` 函式以及 _language item_
## 實作 panic 處理函式
@@ -220,9 +220,9 @@ pub extern "C" fn _start() -> ! {
}
```
我們使用 `no_mangle` 屬性來停用[名稱重整][name mangling],確保 Rust 編譯器輸出的函式名稱會是 `_start`。沒有這個屬性的話,編譯器會產生符號像是 `_ZN3blog_os4_start7hb173fedf945531caE` 來讓每個函式的名稱都是獨一無二的。我們會需要這項屬性的原因是因為我們接下來希望連結器能夠呼叫入口點函式的名稱。
我們使用 `no_mangle` 屬性來停用[名字修飾][name mangling],確保 Rust 編譯器輸出的函式名稱會是 `_start`。沒有這個屬性的話,編譯器會產生符號像是 `_ZN3blog_os4_start7hb173fedf945531caE` 來讓每個函式的名稱都是獨一無二的。我們會需要這項屬性的原因是因為我們接下來希望連結器能夠呼叫入口點函式的名稱。
我們還將函式標記為 `extern "C"` 來告訴編譯器這個函式應當使用 [C 的呼叫慣例][C calling convention],而不是 Rust 的呼叫慣例。而函式名稱選用 `_start` 的原因是因為這是大多數系統的預設入口點名稱。
我們還將函式標記為 `extern "C"` 來告訴編譯器這個函式應當使用 [C 的調用約定][C calling convention],而不是 Rust 的調用約定。而函式名稱選用 `_start` 的原因是因為這是大多數系統的預設入口點名稱。
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
@@ -511,7 +511,7 @@ cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
```
注意這只是最小的 Rust 獨立執行檔範例,它還是會賴一些事情,像是當 `_start` 函式呼叫時堆疊已經初始化完畢。**所以如果想真的使用這樣的執行檔的話還需要更多步驟。**
注意這只是最小的 Rust 獨立執行檔範例,它還是會賴一些事情發生,像是當 `_start` 函式呼叫時堆疊已經初始化完畢。**所以如果想真的使用這樣的執行檔的話還需要更多步驟。**
## 接下來呢?

View File

@@ -122,7 +122,7 @@ rtl = true
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -145,7 +145,7 @@ rtl = true
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -204,7 +204,7 @@ For more information, see our post on [disabling SIMD](@/edition-2/posts/02-mini
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -414,7 +414,7 @@ pub extern "C" fn _start() -> ! {
# in Cargo.toml
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
افزودن بوت‌لودر به عنوان وابستگی برای ایجاد یک دیسک ایمیج قابل بوت کافی نیست. مشکل این است که ما باید هسته خود را با بوت لودر پیوند دهیم، اما کارگو از [اسکریپت های بعد از بیلد] پشتیبانی نمی‌کند.

View File

@@ -118,7 +118,7 @@ Pour notre système cible toutefois, nous avons besoin de paramètres de configu
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -141,7 +141,7 @@ Nous pouvons aussi cibler les systèmes `x86_64` avec notre noyau, donc notre sp
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -201,7 +201,7 @@ Notre fichier de spécification de cible ressemble maintenant à ceci :
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",

View File

@@ -9,7 +9,7 @@ chapter = "Bare Bones"
# Please update this when updating the translation
translation_based_on_commit = "7212ffaa8383122b1eb07fe1854814f99d2e1af4"
# GitHub usernames of the people that translated this post
translators = ["swnakamura", "JohnTitor"]
translators = ["woodyZootopia", "JohnTitor"]
+++
この記事では、Rustで最小限の64bitカーネルを作ります。前の記事で作った[フリースタンディングなRustバイナリ][freestanding Rust binary]を下敷きにして、何かを画面に出力する、ブータブルディスクイメージを作ります。
@@ -116,7 +116,7 @@ Cargoは`--target`パラメータを使ってさまざまなターゲットを
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -139,7 +139,7 @@ Cargoは`--target`パラメータを使ってさまざまなターゲットを
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -198,7 +198,7 @@ SIMDを無効化することによる問題に、`x86_64`における浮動小
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -411,7 +411,7 @@ pub extern "C" fn _start() -> ! {
# in Cargo.toml
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
bootloaderを依存として加えることだけでブータブルディスクイメージが実際に作れるわけではなく、私達のカーネルをコンパイル後にブートローダーにリンクする必要があります。問題は、cargoが[<ruby>ビルド後<rp> (</rp><rt>post-build</rt><rp>) </rp></ruby>にスクリプトを走らせる機能][post-build scripts]を持っていないことです。

View File

@@ -124,7 +124,7 @@ Cargo는 `--target` 인자를 통해 여러 컴파일 대상 시스템들을 지
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -148,7 +148,7 @@ Cargo는 `--target` 인자를 통해 여러 컴파일 대상 시스템들을 지
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -209,7 +209,7 @@ SIMD 레지스터 값들을 메모리에 백업하고 또 다시 복구하는
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -418,7 +418,7 @@ pub extern "C" fn _start() -> ! {
# Cargo.toml 에 들어갈 내용
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
부트로더를 의존 크레이트로 추가하는 것만으로는 부팅 가능한 디스크 이미지를 만들 수 없습니다. 커널 컴파일이 끝난 후 커널을 부트로더와 함께 링크할 수 있어야 하는데, cargo는 현재 [빌드 직후 스크립트 실행][post-build scripts] 기능을 지원하지 않습니다.

View File

@@ -112,7 +112,7 @@ For our target system, however, we require some special configuration parameters
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -135,7 +135,7 @@ We also target `x86_64` systems with our kernel, so our target specification wil
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -195,7 +195,7 @@ Our target specification file now looks like this:
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -260,7 +260,7 @@ That's where the [`build-std` feature] of cargo comes in. It allows to recompile
[`build-std` feature]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
[nightly Rust compilers]: #installing-rust-nightly
To use the feature, we need to create a local [cargo configuration] file at `.cargo/config.toml` (the `.cargo` folder should be next to your `src` folder) with the following content:
To use the feature, we need to create a [cargo configuration] file at `.cargo/config.toml` with the following content:
```toml
# in .cargo/config.toml
@@ -403,16 +403,14 @@ Instead of writing our own bootloader, which is a project on its own, we use the
# in Cargo.toml
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
**Note:** This post is only compatible with `bootloader v0.9`. Newer versions use a different build system and will result in build errors when following this post.
Adding the bootloader as a dependency is not enough to actually create a bootable disk image. The problem is that we need to link our kernel with the bootloader after compilation, but cargo has no support for [post-build scripts].
[post-build scripts]: https://github.com/rust-lang/cargo/issues/545
To solve this problem, we created a tool named `bootimage` that first compiles the kernel and bootloader, and then links them together to create a bootable disk image. To install the tool, go into your home directory (or any directory outside of your cargo project) and execute the following command in your terminal:
To solve this problem, we created a tool named `bootimage` that first compiles the kernel and bootloader, and then links them together to create a bootable disk image. To install the tool, execute the following command in your terminal:
```
cargo install bootimage
@@ -420,7 +418,7 @@ cargo install bootimage
For running `bootimage` and 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`.
After installing `bootimage` and adding the `llvm-tools-preview` component, you can create a bootable disk image by going back into your cargo project directory and executing:
After installing `bootimage` and adding the `llvm-tools-preview` component, we can create a bootable disk image by executing:
```
> cargo bootimage

View File

@@ -119,7 +119,7 @@ Cargo поддерживает различные целевые системы
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -142,7 +142,7 @@ Cargo поддерживает различные целевые системы
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -202,7 +202,7 @@ Cargo поддерживает различные целевые системы
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -411,7 +411,7 @@ pub extern "C" fn _start() -> ! {
# in Cargo.toml
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
Добавление загрузчика в качестве зависимости недостаточно для создания загрузочного образа диска. Проблема в том, что нам нужно связать наше ядро с загрузчиком после компиляции, но в cargo нет поддержки [скриптов после сборки][post-build scripts].

View File

@@ -15,7 +15,7 @@ translation_contributors = ["JiangengDong"]
在这篇文章中,我们将基于 **x86架构**the x86 architecture使用 Rust 语言,编写一个最小化的 64 位内核。我们将从上一章中构建的[独立式可执行程序][freestanding-rust-binary]开始,构建自己的内核;它将向显示器打印字符串,并能被打包为一个能够引导启动的**磁盘映像**disk image
[freestanding-rust-binary]: @/edition-2/posts/01-freestanding-rust-binary/index.md
[freestanding Rust binary]: @/edition-2/posts/01-freestanding-rust-binary/index.md
<!-- more -->
@@ -69,7 +69,7 @@ x86 架构支持两种固件标准: **BIOS**[Basic Input/Output System](htt
## 最小内核
现在我们已经明白电脑是如何启动的那也是时候编写我们自己的内核了。我们的小目标是创建一个内核的磁盘映像它能够在启动时向屏幕输出一行“Hello World!”;我们的工作将基于上一章构建的[独立式可执行程序][freestanding-rust-binary]。
现在我们已经明白电脑是如何启动的那也是时候编写我们自己的内核了。我们的小目标是创建一个内核的磁盘映像它能够在启动时向屏幕输出一行“Hello World!”;我们的工作将基于上一章构建的[独立式可执行程序][freestanding Rust binary]。
如果读者还有印象的话,在上一章,我们使用 `cargo` 构建了一个独立的二进制程序;但这个程序依然基于特定的操作系统平台:因平台而异,我们需要定义不同名称的函数,且使用不同的编译指令。这是因为在默认情况下,`cargo` 会为特定的**宿主系统**host system构建源码比如为你正在运行的系统构建源码。这并不是我们想要的因为我们的内核不应该基于另一个操作系统——我们想要编写的就是这个操作系统。确切地说我们想要的是编译为一个特定的**目标系统**target system
@@ -92,7 +92,7 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -112,7 +112,7 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -157,16 +157,14 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
禁用 SIMD 产生的一个问题是,`x86_64` 架构的浮点数指针运算默认依赖于 SIMD 寄存器。我们的解决方法是,启用 `soft-float` 特征,它将使用基于整数的软件功能,模拟浮点数指针运算。
为了让读者的印象更清晰,我们撰写了一篇关于 [禁用 SIMD][disabling SIMD] 的短文。
[disabling SIMD]: @/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.zh-CN.md
为了让读者的印象更清晰,我们撰写了一篇关于 [禁用 SIMD][disabling SIMD](@/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.zh-CN.md) 的短文。
现在,我们将各个配置项整合在一起。我们的目标配置清单应该长这样:
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
@@ -227,10 +225,10 @@ error[E0463]: can't find crate for `core`
#### `build-std` 选项
此时就到了cargo中 [`build-std` 特性][`build-std` feature] 登场的时刻,该特性允许你按照自己的需要重编译 `core` 等标准crate而不需要使用Rust安装程序内置的预编译版本。 但是该特性是全新的功能,到目前为止尚未完全完成,所以它被标记为 "unstable" 且仅被允许在 [Nightly Rust 编译器][Nightly Rust compilers] 环境下调用。
此时就到了cargo中 [`build-std` 特性][`build-std` feature] 登场的时刻,该特性允许你按照自己的需要重编译 `core` 等标准crate而不需要使用Rust安装程序内置的预编译版本。 但是该特性是全新的功能,到目前为止尚未完全完成,所以它被标记为 "unstable" 且仅被允许在 [nightly Rust 编译器][nightly Rust compilers] 环境下调用。
[`build-std` feature]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
[Nightly Rust compilers]:https://os.phil-opp.com/zh-CN/minimal-rust-kernel/#an-zhuang-nightly-rust
[nightly Rust compilers]: #安装 Nightly Rust
要启用该特性,你需要创建一个 [cargo 配置][cargo configuration] 文件,即 `.cargo/config.toml`,并写入以下语句:
@@ -349,7 +347,7 @@ pub extern "C" fn _start() -> ! {
使用 `unsafe` 语句块要求程序员有足够的自信,所以必须强调的一点是,**肆意使用 unsafe 语句块并不是 Rust 编程的一贯方式**。在缺乏足够经验的前提下,直接在 `unsafe` 语句块内操作裸指针,非常容易把事情弄得很糟糕;比如,在不注意的情况下,我们很可能会意外地操作缓冲区以外的内存。
在这样的前提下,我们希望最小化 `unsafe` 语句块的使用。使用 Rust 语言,我们能够将不安全操作将包装为一个安全的抽象模块。举个例子,我们可以创建一个 VGA 缓冲区类型,把所有的不安全语句封装起来,来确保从类型外部操作时,无法写出不安全的代码:通过这种方式,我们只需要最少的 `unsafe` 语句块来确保我们不破坏**内存安全**[memory safety](https://en.wikipedia.org/wiki/Memory_safety))。在下一篇文章中,我们将会创建这样的 VGA 缓冲区封装。
在这样的前提下,我们希望最小化 `unsafe ` 语句块的使用。使用 Rust 语言,我们能够将不安全操作将包装为一个安全的抽象模块。举个例子,我们可以创建一个 VGA 缓冲区类型,把所有的不安全语句封装起来,来确保从类型外部操作时,无法写出不安全的代码:通过这种方式,我们只需要最少的 `unsafe` 语句块来确保我们不破坏**内存安全**[memory safety](https://en.wikipedia.org/wiki/Memory_safety))。在下一篇文章中,我们将会创建这样的 VGA 缓冲区封装。
## 启动内核
@@ -365,11 +363,9 @@ pub extern "C" fn _start() -> ! {
# in Cargo.toml
[dependencies]
bootloader = "0.9"
bootloader = "0.9.23"
```
** 注意:** 当前环境仅兼容 `bootloader v0.9` 版本。较新的版本需考虑使用其他的构建工具,否则会导致构建出现未知错误。
只添加引导程序为依赖项,并不足以创建一个可引导的磁盘映像;我们还需要内核编译完成之后,将内核和引导程序组合在一起。然而,截至目前,原生的 cargo 并不支持在编译完成后添加其它步骤(详见[这个 issue](https://github.com/rust-lang/cargo/issues/545))。
为了解决这个问题,我们建议使用 `bootimage` 工具——它将会在内核编译完毕后,将它和引导程序组合在一起,最终创建一个能够引导的磁盘映像。我们可以使用下面的命令来安装这款工具:
@@ -400,7 +396,7 @@ cargo install bootimage
### 在 QEMU 中启动内核
现在我们可以在虚拟机中启动内核了。为了在[QEMU](https://www.qemu.org/) 中启动内核,我们使用下面的命令:
现在我们可以在虚拟机中启动内核了。为了在[ QEMU](https://www.qemu.org/) 中启动内核,我们使用下面的命令:
```bash
> qemu-system-x86_64 -drive format=raw,file=target/x86_64-blog_os/debug/bootimage-blog_os.bin
@@ -430,7 +426,7 @@ warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
要让在 QEMU 中运行内核更轻松,我们可以设置在 cargo 配置文件中设置 `runner` 配置项:
```toml
# in .cargo/config.toml
# in .cargo/config
[target.'cfg(target_os = "none")']
runner = "bootimage runner"

View File

@@ -9,7 +9,7 @@ chapter = "Bare Bones"
# Please update this when updating the translation
translation_based_on_commit = "bd6fbcb1c36705b2c474d7fcee387bfea1210851"
# GitHub usernames of the people that translated this post
translators = ["swnakamura", "JohnTitor"]
translators = ["woodyZootopia", "JohnTitor"]
+++
[VGAテキストモード][VGA text mode]は画面にテキストを出力するシンプルな方法です。この記事では、すべてのunsafeな要素を別のモジュールにカプセル化することで、それを安全かつシンプルに扱えるようにするインターフェースを作ります。また、Rustの[フォーマッティングマクロ][formatting macros]のサポートも実装します。

View File

@@ -169,7 +169,7 @@ pub struct Writer {
}
```
我们将让这个 `Writer` 类型将字符写入屏幕的最后一行,并在一行写满或接收到换行符 `\n` 的时候,将所有的字符向上位移一行。`column_position` 变量将跟踪光标在最后一行的位置。当前字符的前景和背景色将由 `color_code` 变量指定;另外,我们存入一个 VGA 字符缓冲区的可变借用到`buffer`变量中。需要注意的是,这里我们对借用使用**显式生命周期**[explicit lifetime](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax)),告诉编译器这个借用在何时有效:我们使用 `'static` 生命周期(['static lifetime](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime)),意味着这个借用应该在整个程序的运行期间有效;这对一个全局有效的 VGA 字符缓冲区来说,是非常合理的。
我们将让这个 `Writer` 类型将字符写入屏幕的最后一行,并在一行写满或接收到换行符 `\n` 的时候,将所有的字符向上位移一行。`column_position` 变量将跟踪光标在最后一行的位置。当前字符的前景和背景色将由 `color_code` 变量指定;另外,我们存入一个 VGA 字符缓冲区的可变借用到`buffer`变量中。需要注意的是,这里我们对借用使用**显式生命周期**[explicit lifetime](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax)),告诉编译器这个借用在何时有效:我们使用** `'static` 生命周期 **['static lifetime](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime)),意味着这个借用应该在整个程序的运行期间有效;这对一个全局有效的 VGA 字符缓冲区来说,是非常合理的。
### 打印字符

View File

@@ -77,7 +77,7 @@ error[E0463]: can't find crate for `test`
#![test_runner(crate::test_runner)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();

View File

@@ -9,7 +9,7 @@ chapter = "Bare Bones"
# Please update this when updating the translation
translation_based_on_commit = "dce5c9825bd4e7ea6c9530e999c9d58f80c585cc"
# GitHub usernames of the people that translated this post
translators = ["swnakamura", "JohnTitor"]
translators = ["woodyZootopia", "JohnTitor"]
+++
この記事では、`no_std`な実行環境における<ruby>単体テスト<rp> (</rp><rt>unit test</rt><rp>) </rp></ruby>と<ruby>結合テスト<rp> (</rp><rt>integration test</rt><rp>) </rp></ruby>について学びます。Rustではカスタムテストフレームワークがサポートされているので、これを使ってカーネルの中でテスト関数を実行します。QEMUの外へとテストの結果を通知するため、QEMUと`bootimage`の様々な機能を使います。
@@ -81,7 +81,7 @@ error[E0463]: can't find crate for `test`
#![test_runner(crate::test_runner)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();

View File

@@ -73,7 +73,7 @@ To implement a custom test framework for our kernel, we add the following to our
#![test_runner(crate::test_runner)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();

View File

@@ -77,7 +77,7 @@ error[E0463]: can't find crate for `test`
#![test_runner(crate::test_runner)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();
@@ -642,7 +642,7 @@ fn test_println_output() {
如你所想我们可以创建更多的测试函数例如一个用来测试当打印一个很长的且包装正确的行时是否会发生panic的函数或是一个用于测试换行符、不可打印字符、非unicode字符是否能被正确处理的函数。
在这篇文章的剩余部分,我们还会解释如何创建一个 _集成测试_ 以测试不同组建之间的交互。
在这篇文章的剩余部分,我们还会解释如何创建一个_集成测试_以测试不同组建之间的交互。
## 集成测试
@@ -989,7 +989,7 @@ harness = false
#![no_main]
use core::panic::PanicInfo;
use blog_os::{QemuExitCode, exit_qemu, serial_println, serial_print};
use blog_os::{QemuExitCode, exit_qemu, serial_println};
#[no_mangle]
pub extern "C" fn _start() -> ! {
@@ -1028,4 +1028,4 @@ fn panic(_info: &PanicInfo) -> ! {
## 下期预告
在下一篇文章中,我们将会探索 _CPU异常_。这些异常将在一些非法事件发生时由CPU抛出例如抛出除以零或是访问没有映射的内存页通常也被称为 `page fault` 即页异常)。能够捕获和检查这些异常,对将来的调试来说是非常重要的。异常处理与键盘支持所需的硬件中断处理十分相似。
在下一篇文章中,我们将会探索_CPU异常_。这些异常将在一些非法事件发生时由CPU抛出例如抛出除以零或是访问没有映射的内存页通常也被称为 `page fault` 即页异常)。能够捕获和检查这些异常,对将来的调试来说是非常重要的。异常处理与键盘支持所需的硬件中断处理十分相似。

View File

@@ -9,7 +9,7 @@ chapter = "Interrupts"
# Please update this when updating the translation
translation_based_on_commit = "a8a6b725cff2e485bed76ff52ac1f18cec08cc7b"
# GitHub usernames of the people that translated this post
translators = ["swnakamura"]
translators = ["woodyZootopia"]
+++
CPU例外は、例えば無効なメモリアドレスにアクセスしたときやゼロ除算したときなど、様々なミスによって発生します。それらに対処するために、ハンドラ関数を提供する **<ruby>割り込み記述子表<rp> (</rp><rt>interrupt descriptor table</rt><rp>) </rp></ruby>** を設定しなくてはなりません。この記事を読み終わる頃には、私達のカーネルは[ブレークポイント例外][breakpoint exceptions]を捕捉し、その後通常の実行を継続できるようになっているでしょう。

View File

@@ -9,7 +9,7 @@ chapter = "Interrupts"
# Please update this when updating the translation
translation_based_on_commit = "81d4f49f153eb5f390681f5c13018dd2aa6be0b1"
# GitHub usernames of the people that translated this post
translators = ["shimomura1004", "swnakamura"]
translators = ["shimomura1004", "woodyZootopia"]
+++
この記事では、ハードウェア割り込みを正しく CPU に転送するためにプログラム可能な割り込みコントローラの設定を行います。これらの割り込みに対処するため、例外ハンドラのときに行ったのと同じように割り込み記述子表に新しいエントリを追加しなくてはいけません。ここでは周期タイマ割り込みの受け方と、キーボードからの入力の受け方を学びます。

View File

@@ -9,7 +9,7 @@ chapter = "Memory Management"
# Please update this when updating the translation
translation_based_on_commit = "3315bfe2f63571f5e6e924d58ed32afd8f39f892"
# GitHub usernames of the people that translated this post
translators = ["swnakamura", "JohnTitor"]
translators = ["woodyZootopia", "JohnTitor"]
+++
この記事では**ページング**を紹介します。これは、私達のオペレーティングシステムにも使う、とても一般的なメモリ管理方式です。なぜメモリの分離が必要なのか、**セグメンテーション**がどういう仕組みなのか、**仮想メモリ**とは何なのか、ページングがいかにしてメモリ<ruby>断片化<rp> (</rp><rt>フラグメンテーション</rt><rp>) </rp></ruby>の問題を解決するのかを説明します。また、x86_64アーキテクチャにおける、マルチレベルページテーブルのレイアウトについても説明します。

View File

@@ -1,421 +0,0 @@
+++
title = "페이징 소개"
weight = 8
path = "ko/paging-introduction"
date = 2019-01-14
[extra]
chapter = "Memory Management"
# Please update this when updating the translation
translation_based_on_commit = "ac943091147a57fcac8bde8876776c7aaff5c3d8"
# GitHub usernames of the people that translated this post
translators = ["potatogim"]
# GitHub usernames of the people that contributed to this translation
translation_contributors = []
+++
이 포스트에서는 우리가 만들 운영체제에서도 사용할 매우 일반적인 메모리 관리 방법인 _페이징_ 기법을 소개합니다. 왜 메모리 격리가 필요한지, _세그먼테이션_이 어떻게 동작하는지, _가상 메모리_가 무엇인지, 페이징이 어떻게 메모리 단편화 문제를 해결하는지를 설명합니다. 또한 x86_64 아키텍처에서 멀티 레벨 페이지 테이블의 레이아웃을 살펴봅니다.
<!-- more -->
이 블로그는 [GitHub 저장소][GitHub]에서 오픈 소스로 개발되고 있으니, 문제나 문의사항이 있다면 저장소의 'Issue' 기능을 이용해 제보해주세요. [페이지 맨 아래][at the bottom]에 댓글을 남기실 수도 있습니다. 이 포스트와 관련된 모든 소스 코드는 저장소의 [`post-07 브랜치`][post 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-08
<!-- toc -->
## 메모리 보호
운영체제의 주요 작업 중 하나는 프로그램을 서로 격리하는 것입니다. 웹 브라우저가 텍스트 편집기를 방해할 수 없어야 한다는 것이 이러한 예입니다. 운영 체제는 이러한 목표를 달성하기 위해 하드웨어 기능을 활용하여 한 프로세스의 메모리 영역에 다른 프로세스가 접근할 수 없도록하며, 하드웨어와 운영체제 구현에 따라 이러한 구현의 접근 방식이 상이합니다.
예를 들어, 임베디드 시스템에 사용되는 일부 ARM Cortex-M 프로세서에는 [_메모리 보호 장치_](MPU; Memory Protection Unit)가 있어 서로 다른 접근 권한(예: 권한 없음, 읽기 전용, 읽기-쓰기)을 갖는 소수의 메모리 영역(예: 8개)을 정의할 수 있습니다. MPU는 메모리 접근 요청이 발생하면 요청된 영역에 위치한 메모리 주소에 대해 올바른 권한이 있는지 확인하고 그렇지 않으면 예외를 발생시킵니다. 운영체제는 프로세스가 전환될 때에 메모리 영역과 접근 권한도 같이 전환하여 각 프로세스가 자신의 메모리에만 접근하도록함으로써 프로세스를 서로 격리할 수 있습니다.
[_메모리 보호 장치_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu
x86 아키텍처에서는 [세그먼테이션]과 [페이징]이라는 2가지의 다른 접근법을 제공합니다:
[세그먼테이션]: https://en.wikipedia.org/wiki/X86_memory_segmentation
[페이징]: https://en.wikipedia.org/wiki/Virtual_memory#Paged_virtual_memory
## 세그먼테이션
세그먼테이션은 주소 지정을 할 수 있는 메모리의 크기를 늘리기 위해 1978년에 이미 도입되었습니다. 당시에는 CPU가 16비트 주소만 사용했기 때문에 주소 지정 가능한 메모리의 크기가 64&nbsp;KiB로 제한되었습니다. 이 64&nbsp;KiB라는 제한을 극복하기 위해 오프셋 주소를 포함하는 세그먼트 레지스터가 추가적으로 도입되었고, CPU는 각 메모리 접근에 이 오프셋을 자동으로 추가해서 최대 1&nbsp;MiB 크기의 메모리에 접근할 수 있게 되었습니다.
세그먼트 레지스터는 메모리 접근 유형에 따라 CPU에 의해 자동으로 선택됩니다. 인출 명령에는 코드 세그먼트 `CS`가 사용되고, 스택 작업(push/pop)에는 스택 세그먼트 `SS`가 사용됩니다. 다른 명령어는 데이터 세그먼트 `DS` 또는 추가 세그먼트 `ES`를 사용합니다. 나중에는 자유롭게 사용할 수 있는 `FS``GS`라는 2개의 세그먼트 레지스터가 추가되었습니다.
세그먼테이션의 첫 번째 버전에서는 세그먼트 레지스터가 오프셋을 직접 포함했으며 접근 제어가 수행되지 않았습니다. 이는 나중에 [_보호 모드_] 도입과 함께 변경되었습니다. CPU가 보호 모드에서 실행될 때 세그먼트 디스크립터에는 로컬 또는 글로벌 [_디스크립터 테이블_]의 인덱스가 포함되며 여기에는 오프셋 주소 외에도 세그먼트 크기 및 접근 권한이 포함됩니다. 각 프로세스별로 별도의 전역/로컬 디스크립터 테이블을 적재함으로써 각 프로세스는 해당 프로세스에 할당된 메모리 영역으로 메모리 접근을 한정하게 되며, 운영체제는 프로세스를 서로 격리할 수 있습니다.
[_보호 모드_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode
[_디스크립터 테이블_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
실제로 메모리에 접근하기 전에 메모리 주소를 수정한다는 관점에서 세그먼테이션은 이제는 거의 모든 곳에서 사용되고 있는 기술인 _가상 메모리_를 차용했다고 볼 수 있습니다.
### 가상 메모리
가상 메모리의 기본 발상은 하위 계층의 물리적 저장 장치로부터 메모리 주소를 추상화하는 것입니다. 저장 장치에 직접 접근하는 대신 변환 단계가 먼저 수행됩니다. 세그먼테이션의 경우 변환 단계는 활성 세그먼트의 오프셋 주소를 추가하는 것입니다. 오프셋이 `0x1111000`인 세그먼트에서 메모리 주소 `0x1234000`에 접근하는 프로그램을 상상해보겠습니다. 이 경우, 실제로 접근되는 주소는 `0x2345000`입니다.
두 가지 주소 유형을 구분하기 위해서 변환하기 전 주소를 _가상 주소_라고 하고 변환하고 난 뒤의 주소를 _물리 주소_라고 합니다. 이 두 종류의 주소 사이의 한 가지 중요한 차이점은 물리 주소는 항상 동일한 별개의 메모리 위치를 참조하는 고유한 주소이고, 가상 주소는 변환 방식에 따라 다른 위치를 참조한다는 점입니다. 즉, 두 개의 서로 다른 가상 주소가 동일한 물리 주소를 참조하는 것 또한 가능하다는 말입니다. 또한 동일한 가상 주소가 서로 다른 변환 방식을 사용한다면 사용한 변환 방식에 따라 각기 다른 물리 주소를 참조할 수도 있습니다.
동일한 프로그램을 병렬로 두 번 실행하는 경우가 이러한 특성이 유용한 예입니다:
![주소가 0-150인 2개의 가상 주소 공간이 하나는 100-250으로, 하나는 300-450으로 변환](segmentation-same-program-twice.svg)
여기서는 동일한 프로그램이 두 번 실행되지만 변환 방식이 다릅니다. 첫 번째 인스턴스는 세그먼트 오프셋이 100이므로 가상 주소 0150이 물리 주소 100250으로 변환됩니다. 두 번째 인스턴스의 오프셋은 300이며 가상 주소 0150을 물리적 주소 300450으로 변환합니다. 이를 통해 두 프로그램은 서로 간섭하지 않고 동일한 코드를 실행하고 동일한 가상 주소를 사용할 수 있습니다.
또 다른 장점은 이제 프로그램들이 완전히 다른 가상 주소를 사용하더라도 임의의 물리적 메모리 위치에 배치할 수 있다는 것입니다. 따라서 운영체제는 프로그램을 다시 컴파일할 필요 없이 사용 가능한 메모리를 최대한 활용할 수 있습니다.
### 단편화
세그먼테이션은 가상 주소와 물리 주소를 구분함으로써 정말 강력해지지만, 이로 인해 단편화 문제가 생깁니다. 예를 들어, 위에서 본 프로그램의 세 번째 사본을 실행하고 싶다고 상상해보겠습니다.:
![3개의 가상 주소 공간이 있지만 세 번째 프로세스의 연속적인 메모리 공간이 부족함](segmentation-fragmentation.svg)
유휴 메모리 공간이 충분하지만 프로그램의 세 번째 인스턴스를 겹치지 않고 가상 메모리에 매핑할 방법이 없습니다. 문제는 _연속적인_ 메모리가 필요하지만 유휴 메모리 공간을 사용할 수 없다는 점입니다.
이러한 단편화를 제거하는 한 가지 방법은 실행을 일시 중지한 뒤에 사용된 메모리들을 인접하도록 이동하고 변환을 업데이트한 뒤에 실행을 재개하는 것입니다:
![단편화 제거 후 3개의 가상 주소 공간](segmentation-fragmentation-compacted.svg)
이제 프로그램의 세 번째 인스턴스를 시작하기에 충분한 연속 공간이 있습니다.
이 단편화 제거 절차의 단점은 많은 양의 메모리를 복사해야 하므로 성능이 저하된다는 것과 메모리가 과도하게 단편화되기 전에 정기적으로 수행해야 한다는 것입니다. 이로 인해 프로그램이 임의의 시간에 일시 중지되고 응답하지 않을 수 있으므로 성능을 예측할 수 없습니다.
이러한 단편화 문제는 대부분의 시스템에서 더 이상 세그먼테이션이 사용되지 않는 이유 중 하나입니다. 실제로 x86의 64비트 모드에서는 세그먼테이션이 더 이상 지원되지 않습니다. 대신 단편화 문제를 완전히 방지하는 _페이징_이 사용됩니다.
## 페이징
페이징의 기본 발상은 가상/물리 메모리 공간을 작은 고정 크기 블록으로 나누는 것입니다. 가상 메모리 공간의 블록을 _페이지_라고 하고 물리 주소 공간의 블록을 _프레임_이라고 합니다. 각 페이지는 프레임에 개별적으로 매핑될 수 있으므로 더 큰 메모리 영역을 비연속적인 물리적 프레임으로 분할할 수 있습니다.
The advantage of this becomes visible if we recap the example of the fragmented memory space, but use paging instead of segmentation this time:
단편화된 메모리 공간의 예를 다시 떠올려보면 페이징의 이점이 도드라져 보이겠지만, 여기에선 세그먼테이션 대신 페이징을 사용합니다.:
![페이징을 통해 세 번째 프로그램 인스턴스를 여러 개의 작은 물리 영역으로 분할](paging-fragmentation.svg)
이 예에서 페이지 크기는 50바이트이며 이는 각 메모리 영역이 세 페이지로 분할됨을 의미합니다. 각 페이지는 개별적으로 프레임에 매핑되므로 연속적인 가상 메모리 영역을 비연속적인 물리적 프레임에 매핑할 수 있습니다. 이를 통해 이전에 단편화 제거를 수행하지 않고 프로그램의 세 번째 인스턴스를 시작할 수 있습니다.
### 숨겨진 단편화
페이징은 세그먼테이션이 가변적인 크기를 갖는 다수의 메모리 공간을 사용하는 것에 비해 적은 개수의 작고 고정된 크기의 메모리 영역을 많이 사용합니다. 모든 프레임의 크기가 같기 때문에 사용하기에 너무 작은 프레임이 없으므로 단편화가 발생하지 않습니다.
또는 단편화가 발생하지 않는 것처럼 _보입니다_. 소위 _내부 단편화_라고 하는 일부 숨겨진 종류의 단편화는 여전히 있습니다. 내부 단편화는 모든 메모리 영역이 페이지 크기의 정확한 배수가 아니기 때문에 발생합니다. 위의 예에서 크기가 101인 프로그램을 상상해보겠습니다. 여전히 크기가 50인 세 페이지가 필요하므로 필요한 것보다 49바이트를 더 많이 차지합니다. 이러한 두 종류의 단편화를 구별하기 위해 세그먼테이션을 사용할 때 발생하는 단편화의 종류를 _외부 단편화_라고 합니다.
내부 단편화는 안타까운 일이지만 세그먼테이션에서 발생하는 외부 단편화보다는 나은 경우가 많습니다. 여전히 메모리를 낭비하지만 단편화 제거가 필요하지 않으며 단편화된 양을 예측할 수 있게 해줍니다 (대체적으로는 메모리 영역당 절반 페이지 정도).
### 페이지 테이블
We saw that each of the potentially millions of pages is individually mapped to a frame. This mapping information needs to be stored somewhere. Segmentation uses an individual segment selector register for each active memory region, which is not possible for paging since there are way more pages than registers. Instead, paging uses a table structure called _page table_ to store the mapping information.
For our above example, the page tables would look like this:
![Three page tables, one for each program instance. For instance 1, the mapping is 0->100, 50->150, 100->200. For instance 2, it is 0->300, 50->350, 100->400. For instance 3, it is 0->250, 50->450, 100->500.](paging-page-tables.svg)
We see that each program instance has its own page table. A pointer to the currently active table is stored in a special CPU register. On `x86`, this register is called `CR3`. It is the job of the operating system to load this register with the pointer to the correct page table before running each program instance.
On each memory access, the CPU reads the table pointer from the register and looks up the mapped frame for the accessed page in the table. This is entirely done in hardware and completely invisible to the running program. To speed up the translation process, many CPU architectures have a special cache that remembers the results of the last translations.
Depending on the architecture, page table entries can also store attributes such as access permissions in a flags field. In the above example, the "r/w" flag makes the page both readable and writable.
### Multilevel Page Tables
The simple page tables we just saw have a problem in larger address spaces: they waste memory. For example, imagine a program that uses the four virtual pages `0`, `1_000_000`, `1_000_050`, and `1_000_100` (we use `_` as a thousands separator):
![Page 0 mapped to frame 0 and pages `1_000_000``1_000_150` mapped to frames 100250](single-level-page-table.svg)
It only needs 4 physical frames, but the page table has over a million entries. We can't omit the empty entries because then the CPU would no longer be able to jump directly to the correct entry in the translation process (e.g., it is no longer guaranteed that the fourth page uses the fourth entry).
To reduce the wasted memory, we can use a **two-level page table**. The idea is that we use different page tables for different address regions. An additional table called _level 2_ page table contains the mapping between address regions and (level 1) page tables.
This is best explained by an example. Let's define that each level 1 page table is responsible for a region of size `10_000`. Then the following tables would exist for the above example mapping:
![Page 0 points to entry 0 of the level 2 page table, which points to the level 1 page table T1. The first entry of T1 points to frame 0; the other entries are empty. Pages `1_000_000``1_000_150` point to the 100th entry of the level 2 page table, which points to a different level 1 page table T2. The first three entries of T2 point to frames 100250; the other entries are empty.](multilevel-page-table.svg)
Page 0 falls into the first `10_000` byte region, so it uses the first entry of the level 2 page table. This entry points to level 1 page table T1, which specifies that page `0` points to frame `0`.
The pages `1_000_000`, `1_000_050`, and `1_000_100` all fall into the 100th `10_000` byte region, so they use the 100th entry of the level 2 page table. This entry points to a different level 1 page table T2, which maps the three pages to frames `100`, `150`, and `200`. Note that the page address in level 1 tables does not include the region offset. For example, the entry for page `1_000_050` is just `50`.
We still have 100 empty entries in the level 2 table, but much fewer than the million empty entries before. The reason for these savings is that we don't need to create level 1 page tables for the unmapped memory regions between `10_000` and `1_000_000`.
The principle of two-level page tables can be extended to three, four, or more levels. Then the page table register points to the highest level table, which points to the next lower level table, which points to the next lower level, and so on. The level 1 page table then points to the mapped frame. The principle in general is called a _multilevel_ or _hierarchical_ page table.
Now that we know how paging and multilevel page tables work, we can look at how paging is implemented in the x86_64 architecture (we assume in the following that the CPU runs in 64-bit mode).
## Paging on x86_64
The x86_64 architecture uses a 4-level page table and a page size of 4&nbsp;KiB. Each page table, independent of the level, has a fixed size of 512 entries. Each entry has a size of 8 bytes, so each table is 512 * 8&nbsp;B = 4&nbsp;KiB large and thus fits exactly into one page.
The page table index for each level is derived directly from the virtual address:
![Bits 012 are the page offset, bits 1221 the level 1 index, bits 2130 the level 2 index, bits 3039 the level 3 index, and bits 3948 the level 4 index](x86_64-table-indices-from-address.svg)
We see that each table index consists of 9 bits, which makes sense because each table has 2^9 = 512 entries. The lowest 12 bits are the offset in the 4&nbsp;KiB page (2^12 bytes = 4&nbsp;KiB). Bits 48 to 64 are discarded, which means that x86_64 is not really 64-bit since it only supports 48-bit addresses.
Even though bits 48 to 64 are discarded, they can't be set to arbitrary values. Instead, all bits in this range have to be copies of bit 47 in order to keep addresses unique and allow future extensions like the 5-level page table. This is called _sign-extension_ because it's very similar to the [sign extension in two's complement]. When an address is not correctly sign-extended, the CPU throws an exception.
[sign extension in two's complement]: https://en.wikipedia.org/wiki/Two's_complement#Sign_extension
It's worth noting that the recent "Ice Lake" Intel CPUs optionally support [5-level page tables] to extend virtual addresses from 48-bit to 57-bit. Given that optimizing our kernel for a specific CPU does not make sense at this stage, we will only work with standard 4-level page tables in this post.
[5-level page tables]: https://en.wikipedia.org/wiki/Intel_5-level_paging
### Example Translation
Let's go through an example to understand how the translation process works in detail:
![An example of a 4-level page hierarchy with each page table shown in physical memory](x86_64-page-table-translation.svg)
The physical address of the currently active level 4 page table, which is the root of the 4-level page table, is stored in the `CR3` register. Each page table entry then points to the physical frame of the next level table. The entry of the level 1 table then points to the mapped frame. Note that all addresses in the page tables are physical instead of virtual, because otherwise the CPU would need to translate those addresses too (which could cause a never-ending recursion).
The above page table hierarchy maps two pages (in blue). From the page table indices, we can deduce that the virtual addresses of these two pages are `0x803FE7F000` and `0x803FE00000`. Let's see what happens when the program tries to read from address `0x803FE7F5CE`. First, we convert the address to binary and determine the page table indices and the page offset for the address:
![The sign extension bits are all 0, the level 4 index is 1, the level 3 index is 0, the level 2 index is 511, the level 1 index is 127, and the page offset is 0x5ce](x86_64-page-table-translation-addresses.png)
With these indices, we can now walk the page table hierarchy to determine the mapped frame for the address:
- We start by reading the address of the level 4 table out of the `CR3` register.
- The level 4 index is 1, so we look at the entry with index 1 of that table, which tells us that the level 3 table is stored at address 16&nbsp;KiB.
- We load the level 3 table from that address and look at the entry with index 0, which points us to the level 2 table at 24&nbsp;KiB.
- The level 2 index is 511, so we look at the last entry of that page to find out the address of the level 1 table.
- Through the entry with index 127 of the level 1 table, we finally find out that the page is mapped to frame 12&nbsp;KiB, or 0x3000 in hexadecimal.
- The final step is to add the page offset to the frame address to get the physical address 0x3000 + 0x5ce = 0x35ce.
![The same example 4-level page hierarchy with 5 additional arrows: "Step 0" from the CR3 register to the level 4 table, "Step 1" from the level 4 entry to the level 3 table, "Step 2" from the level 3 entry to the level 2 table, "Step 3" from the level 2 entry to the level 1 table, and "Step 4" from the level 1 table to the mapped frames.](x86_64-page-table-translation-steps.svg)
The permissions for the page in the level 1 table are `r`, which means read-only. The hardware enforces these permissions and would throw an exception if we tried to write to that page. Permissions in higher level pages restrict the possible permissions in lower levels, so if we set the level 3 entry to read-only, no pages that use this entry can be writable, even if lower levels specify read/write permissions.
It's important to note that even though this example used only a single instance of each table, there are typically multiple instances of each level in each address space. At maximum, there are:
- one level 4 table,
- 512 level 3 tables (because the level 4 table has 512 entries),
- 512 * 512 level 2 tables (because each of the 512 level 3 tables has 512 entries), and
- 512 * 512 * 512 level 1 tables (512 entries for each level 2 table).
### Page Table Format
Page tables on the x86_64 architecture are basically an array of 512 entries. In Rust syntax:
```rust
#[repr(align(4096))]
pub struct PageTable {
entries: [PageTableEntry; 512],
}
```
As indicated by the `repr` attribute, page tables need to be page-aligned, i.e., aligned on a 4&nbsp;KiB boundary. This requirement guarantees that a page table always fills a complete page and allows an optimization that makes entries very compact.
Each entry is 8 bytes (64 bits) large and has the following format:
Bit(s) | Name | Meaning
------ | ---- | -------
0 | present | the page is currently in memory
1 | writable | it's allowed to write to this page
2 | user accessible | if not set, only kernel mode code can access this page
3 | write-through caching | writes go directly to memory
4 | disable cache | no cache is used for this page
5 | accessed | the CPU sets this bit when this page is used
6 | dirty | the CPU sets this bit when a write to this page occurs
7 | huge page/null | must be 0 in P1 and P4, creates a 1&nbsp;GiB page in P3, creates a 2&nbsp;MiB page in P2
8 | global | page isn't flushed from caches on address space switch (PGE bit of CR4 register must be set)
9-11 | available | can be used freely by the OS
12-51 | physical address | the page aligned 52bit physical address of the frame or the next page table
52-62 | available | can be used freely by the OS
63 | no execute | forbid executing code on this page (the NXE bit in the EFER register must be set)
We see that only bits 1251 are used to store the physical frame address. The remaining bits are used as flags or can be freely used by the operating system. This is possible because we always point to a 4096-byte aligned address, either to a page-aligned page table or to the start of a mapped frame. This means that bits 011 are always zero, so there is no reason to store these bits because the hardware can just set them to zero before using the address. The same is true for bits 5263, because the x86_64 architecture only supports 52-bit physical addresses (similar to how it only supports 48-bit virtual addresses).
Let's take a closer look at the available flags:
- The `present` flag differentiates mapped pages from unmapped ones. It can be used to temporarily swap out pages to disk when the main memory becomes full. When the page is accessed subsequently, a special exception called _page fault_ occurs, to which the operating system can react by reloading the missing page from disk and then continuing the program.
- The `writable` and `no execute` flags control whether the contents of the page are writable or contain executable instructions, respectively.
- The `accessed` and `dirty` flags are automatically set by the CPU when a read or write to the page occurs. This information can be leveraged by the operating system, e.g., to decide which pages to swap out or whether the page contents have been modified since the last save to disk.
- The `write-through caching` and `disable cache` flags allow the control of caches for every page individually.
- The `user accessible` flag makes a page available to userspace code, otherwise, it is only accessible when the CPU is in kernel mode. This feature can be used to make [system calls] faster by keeping the kernel mapped while a userspace program is running. However, the [Spectre] vulnerability can allow userspace programs to read these pages nonetheless.
- The `global` flag signals to the hardware that a page is available in all address spaces and thus does not need to be removed from the translation cache (see the section about the TLB below) on address space switches. This flag is commonly used together with a cleared `user accessible` flag to map the kernel code to all address spaces.
- The `huge page` flag allows the creation of pages of larger sizes by letting the entries of the level 2 or level 3 page tables directly point to a mapped frame. With this bit set, the page size increases by factor 512 to either 2&nbsp;MiB = 512 * 4&nbsp;KiB for level 2 entries or even 1&nbsp;GiB = 512 * 2&nbsp;MiB for level 3 entries. The advantage of using larger pages is that fewer lines of the translation cache and fewer page tables are needed.
[system calls]: https://en.wikipedia.org/wiki/System_call
[Spectre]: https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)
The `x86_64` crate provides types for [page tables] and their [entries], so we don't need to create these structures ourselves.
[page tables]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTable.html
[entries]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html
### The Translation Lookaside Buffer
A 4-level page table makes the translation of virtual addresses expensive because each translation requires four memory accesses. To improve performance, the x86_64 architecture caches the last few translations in the so-called _translation lookaside buffer_ (TLB). This allows skipping the translation when it is still cached.
Unlike the other CPU caches, the TLB is not fully transparent and does not update or remove translations when the contents of page tables change. This means that the kernel must manually update the TLB whenever it modifies a page table. To do this, there is a special CPU instruction called [`invlpg`] (“invalidate page”) that removes the translation for the specified page from the TLB, so that it is loaded again from the page table on the next access. The TLB can also be flushed completely by reloading the `CR3` register, which simulates an address space switch. The `x86_64` crate provides Rust functions for both variants in the [`tlb` module].
[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html
[`tlb` module]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/tlb/index.html
It is important to remember to flush the TLB on each page table modification because otherwise, the CPU might keep using the old translation, which can lead to non-deterministic bugs that are very hard to debug.
## Implementation
One thing that we did not mention yet: **Our kernel already runs on paging**. The bootloader that we added in the ["A minimal Rust Kernel"] post has already set up a 4-level paging hierarchy that maps every page of our kernel to a physical frame. The bootloader does this because paging is mandatory in 64-bit mode on x86_64.
["A minimal Rust kernel"]: @/edition-2/posts/02-minimal-rust-kernel/index.md#creating-a-bootimage
This means that every memory address that we used in our kernel was a virtual address. Accessing the VGA buffer at address `0xb8000` only worked because the bootloader _identity mapped_ that memory page, which means that it mapped the virtual page `0xb8000` to the physical frame `0xb8000`.
Paging makes our kernel already relatively safe, since every memory access that is out of bounds causes a page fault exception instead of writing to random physical memory. The bootloader even sets the correct access permissions for each page, which means that only the pages containing code are executable and only data pages are writable.
### Page Faults
Let's try to cause a page fault by accessing some memory outside of our kernel. First, we create a page fault handler and register it in our IDT, so that we see a page fault exception instead of a generic [double fault]:
[double fault]: @/edition-2/posts/06-double-faults/index.md
```rust
// in src/interrupts.rs
lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
[]
idt.page_fault.set_handler_fn(page_fault_handler); // new
idt
};
}
use x86_64::structures::idt::PageFaultErrorCode;
use crate::hlt_loop;
extern "x86-interrupt" fn page_fault_handler(
stack_frame: InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
use x86_64::registers::control::Cr2;
println!("EXCEPTION: PAGE FAULT");
println!("Accessed Address: {:?}", Cr2::read());
println!("Error Code: {:?}", error_code);
println!("{:#?}", stack_frame);
hlt_loop();
}
```
The [`CR2`] register is automatically set by the CPU on a page fault and contains the accessed virtual address that caused the page fault. We use the [`Cr2::read`] function of the `x86_64` crate to read and print it. The [`PageFaultErrorCode`] type provides more information about the type of memory access that caused the page fault, for example, whether it was caused by a read or write operation. For this reason, we print it too. We can't continue execution without resolving the page fault, so we enter a [`hlt_loop`] at the end.
[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2
[`Cr2::read`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr2.html#method.read
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html
[LLVM bug]: https://github.com/rust-lang/rust/issues/57270
[`hlt_loop`]: @/edition-2/posts/07-hardware-interrupts/index.md#the-hlt-instruction
Now we can try to access some memory outside our kernel:
```rust
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");
blog_os::init();
// new
let ptr = 0xdeadbeaf as *mut u32;
unsafe { *ptr = 42; }
// as before
#[cfg(test)]
test_main();
println!("It did not crash!");
blog_os::hlt_loop();
}
```
When we run it, we see that our page fault handler is called:
![EXCEPTION: Page Fault, Accessed Address: VirtAddr(0xdeadbeaf), Error Code: CAUSED_BY_WRITE, InterruptStackFrame: {…}](qemu-page-fault.png)
The `CR2` register indeed contains `0xdeadbeaf`, the address that we tried to access. The error code tells us through the [`CAUSED_BY_WRITE`] that the fault occurred while trying to perform a write operation. It tells us even more through the [bits that are _not_ set][`PageFaultErrorCode`]. For example, the fact that the `PROTECTION_VIOLATION` flag is not set means that the page fault occurred because the target page wasn't present.
[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
We see that the current instruction pointer is `0x2031b2`, so we know that this address points to a code page. Code pages are mapped read-only by the bootloader, so reading from this address works but writing causes a page fault. You can try this by changing the `0xdeadbeaf` pointer to `0x2031b2`:
```rust
// Note: The actual address might be different for you. Use the address that
// your page fault handler reports.
let ptr = 0x2031b2 as *mut u32;
// read from a code page
unsafe { let x = *ptr; }
println!("read worked");
// write to a code page
unsafe { *ptr = 42; }
println!("write worked");
```
By commenting out the last line, we see that the read access works, but the write access causes a page fault:
![QEMU with output: "read worked, EXCEPTION: Page Fault, Accessed Address: VirtAddr(0x2031b2), Error Code: PROTECTION_VIOLATION | CAUSED_BY_WRITE, InterruptStackFrame: {…}"](qemu-page-fault-protection.png)
We see that the _"read worked"_ message is printed, which indicates that the read operation did not cause any errors. However, instead of the _"write worked"_ message, a page fault occurs. This time the [`PROTECTION_VIOLATION`] flag is set in addition to the [`CAUSED_BY_WRITE`] flag, which indicates that the page was present, but the operation was not allowed on it. In this case, writes to the page are not allowed since code pages are mapped as read-only.
[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
### Accessing the Page Tables
Let's try to take a look at the page tables that define how our kernel is mapped:
```rust
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");
blog_os::init();
use x86_64::registers::control::Cr3;
let (level_4_page_table, _) = Cr3::read();
println!("Level 4 page table at: {:?}", level_4_page_table.start_address());
[] // test_main(), println(…), and hlt_loop()
}
```
The [`Cr3::read`] function of the `x86_64` returns the currently active level 4 page table from the `CR3` register. It returns a tuple of a [`PhysFrame`] and a [`Cr3Flags`] type. We are only interested in the frame, so we ignore the second element of the tuple.
[`Cr3::read`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr3.html#method.read
[`PhysFrame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/frame/struct.PhysFrame.html
[`Cr3Flags`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr3Flags.html
When we run it, we see the following output:
```
Level 4 page table at: PhysAddr(0x1000)
```
So the currently active level 4 page table is stored at address `0x1000` in _physical_ memory, as indicated by the [`PhysAddr`] wrapper type. The question now is: how can we access this table from our kernel?
[`PhysAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.PhysAddr.html
Accessing physical memory directly is not possible when paging is active, since programs could easily circumvent memory protection and access the memory of other programs otherwise. So the only way to access the table is through some virtual page that is mapped to the physical frame at address `0x1000`. This problem of creating mappings for page table frames is a general problem since the kernel needs to access the page tables regularly, for example, when allocating a stack for a new thread.
Solutions to this problem are explained in detail in the next post.
## Summary
This post introduced two memory protection techniques: segmentation and paging. While the former uses variable-sized memory regions and suffers from external fragmentation, the latter uses fixed-sized pages and allows much more fine-grained control over access permissions.
Paging stores the mapping information for pages in page tables with one or more levels. The x86_64 architecture uses 4-level page tables and a page size of 4&nbsp;KiB. The hardware automatically walks the page tables and caches the resulting translations in the translation lookaside buffer (TLB). This buffer is not updated transparently and needs to be flushed manually on page table changes.
We learned that our kernel already runs on top of paging and that illegal memory accesses cause page fault exceptions. We tried to access the currently active page tables, but we weren't able to do it because the CR3 register stores a physical address that we can't access directly from our kernel.
## What's next?
The next post explains how to implement support for paging in our kernel. It presents different ways to access physical memory from our kernel, which makes it possible to access the page tables that our kernel runs on. At this point, we are able to implement functions for translating virtual to physical addresses and for creating new mappings in the page tables.

View File

@@ -149,7 +149,7 @@ x86_64 平台使用4级页表页大小为4KiB无论层级每个页表
![Bits 012 are the page offset, bits 1221 the level 1 index, bits 2130 the level 2 index, bits 3039 the level 3 index, and bits 3948 the level 4 index](x86_64-table-indices-from-address.svg)
我们可以看到,每个表索引号占据 9 个比特,这当然是有道理的,每个表都有 2^9 = 512 个条目低12位用来表示内存页的偏移量2^12 bytes = 4KiB而上文提到页大小为4KiB。第 48-64 位毫无用处,这也就意味着 x86_64 并非真正的 64 位,因为它实际上支持 48 位地址。
我们可以看到,每个表索引号占据9个字节,这当然是有道理的,每个表都有 2^9 = 512 个条目低12位用来表示内存页的偏移量2^12 bytes = 4KiB而上文提到页大小为4KiB。第48-64位毫无用处这也就意味着 x86_64 并非真正的64位因为它实际上支持48位地址。
[5-level page table]: https://en.wikipedia.org/wiki/Intel_5-level_paging
@@ -191,7 +191,7 @@ x86_64 平台使用4级页表页大小为4KiB无论层级每个页表
- 1个4级页表
- 512个3级页表因为4级页表可以有512个条目
- 512*512个2级页表因为每个3级页表可以有512个条目
- 512\*512\*512个1级页表因为每个2级页表可以有512个条目
- 512*512*512个1级页表因为每个2级页表可以有512个条目
### 页表格式
@@ -225,7 +225,7 @@ pub struct PageTable {
| 63 | no execute | 禁止在该页中运行代码EFER寄存器中的NXE比特位必须一同被设置 |
我们可以看到仅1251位会用于存储页帧地址或页表地址其余比特都用于存储标志位或由操作系统自由使用。
其原因就是该地址总是指向一个4096字节对齐的地址、页表或者页帧的起始地址。
其原因就是该地址总是指向一个4096比特对齐的地址、页表或者页帧的起始地址。
这也就意味着0-11位始终为0没有必要存储这些东西硬件层面在使用该地址之前也会将这12位比特设置为052-63位同理因为x86_64平台仅支持52位物理地址类似于上文中提到的仅支持48位虚拟地址的原因
进一步说明一下可用的标志位:

View File

@@ -7,7 +7,7 @@ date = 2019-03-14
[extra]
chapter = "Memory Management"
translation_based_on_commit = "27ab4518acbb132e327ed4f4f0508393e9d4d684"
translators = ["swnakamura", "garasubo"]
translators = ["woodyZootopia", "garasubo"]
+++
この記事では私達のカーネルをページングに対応させる方法についてお伝えします。まずページテーブルの物理フレームにカーネルがアクセスできるようにする様々な方法を示し、それらの利点と欠点について議論します。次にアドレス変換関数を、ついで新しい<ruby>対応付け<rp> (</rp><rt>マッピング</rt><rp>) </rp></ruby>を作るための関数を実装します。
@@ -281,7 +281,7 @@ frame.map(|frame| frame.start_address() + u64::from(addr.page_offset()))
```toml
[dependencies]
bootloader = { version = "0.9", features = ["map_physical_memory"]}
bootloader = { version = "0.9.23", features = ["map_physical_memory"]}
```
この機能を有効化すると、ブートローダは物理メモリの全体を、ある未使用の仮想アドレス空間にマッピングします。この仮想アドレスの範囲をカーネルに伝えるために、ブートローダは**boot information**構造体を渡します。
@@ -291,7 +291,7 @@ bootloader = { version = "0.9", features = ["map_physical_memory"]}
`bootloader`クレートは、カーネルに渡されるすべての情報を格納する[`BootInfo`]構造体を定義しています。この構造体はまだ開発の初期段階にあり、将来の[対応していないsemverの][semver-incompatible]ブートローダのバージョンに更新した際には、うまく動かなくなることが予想されます。`map_physical_memory` featureが有効化されているので、いまこれは`memory_map``physical_memory_offset`という2つのフィールドを持っています
[`BootInfo`]: https://docs.rs/bootloader/0.9/bootloader/bootinfo/struct.BootInfo.html
[`BootInfo`]: https://docs.rs/bootloader/0.9.3/bootloader/bootinfo/struct.BootInfo.html
[semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements
- `memory_map`フィールドは、利用可能な物理メモリの情報の概要を保持しています。システムの利用可能な物理メモリがどのくらいかや、どのメモリ領域がVGAハードウェアのようなデバイスのために予約されているかをカーネルに伝えます。これらのメモリマッピングはBIOSやUEFIファームウェアから取得できますが、それが可能なのはブートのごく初期に限られます。そのため、これらをカーネルが後で取得することはできないので、ブートローダによって提供する必要があるわけです。このメモリマッピングは後で必要となります。

View File

@@ -278,7 +278,7 @@ We choose the first approach for our kernel since it is simple, platform-indepen
```toml
[dependencies]
bootloader = { version = "0.9", features = ["map_physical_memory"]}
bootloader = { version = "0.9.23", features = ["map_physical_memory"]}
```
With this feature enabled, the bootloader maps the complete physical memory to some unused virtual address range. To communicate the virtual address range to our kernel, the bootloader passes a _boot information_ structure.
@@ -287,7 +287,7 @@ With this feature enabled, the bootloader maps the complete physical memory to s
The `bootloader` crate defines a [`BootInfo`] struct that contains all the information it passes to our kernel. The struct is still in an early stage, so expect some breakage when updating to future [semver-incompatible] bootloader versions. With the `map_physical_memory` feature enabled, it currently has the two fields `memory_map` and `physical_memory_offset`:
[`BootInfo`]: https://docs.rs/bootloader/0.9/bootloader/bootinfo/struct.BootInfo.html
[`BootInfo`]: https://docs.rs/bootloader/0.9.3/bootloader/bootinfo/struct.BootInfo.html
[semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements
- The `memory_map` field contains an overview of the available physical memory. This tells our kernel how much physical memory is available in the system and which memory regions are reserved for devices such as the VGA hardware. The memory map can be queried from the BIOS or UEFI firmware, but only very early in the boot process. For this reason, it must be provided by the bootloader because there is no way for the kernel to retrieve it later. We will need the memory map later in this post.

View File

@@ -18,7 +18,7 @@ translation_contributors = ["liuyuran"]
<!-- more -->
这个系列的 blog 在[GitHub]上开放开发,如果你有任何问题,请在这里开一个 issue 来讨论。当然你也可以在[底部][at the bottom]留言。你可以在[`post-09`][post branch]找到这篇文章的完整源码。
这个系列的 blog 在[GitHub]上开放开发,如果你有任何问题,请在这里开一个 issue 来讨论。当然你也可以在[底部][at the bottom]留言。你可以在[`post-08`][post branch]找到这篇文章的完整源码。
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
@@ -62,7 +62,7 @@ translation_contributors = ["liuyuran"]
在这个例子中我们看到各种直接映射的页表框架。页表的物理地址也是有效的虚拟地址这样我们就可以很容易地访问从CR3寄存器开始的各级页表。
然而它使虚拟地址空间变得杂乱无章并使寻找较大尺寸的连续内存区域更加困难。例如想象一下我们想在上述图形中创建一个大小为1000&nbsp;KiB的虚拟内存区域例如 [memory-mapping a file]我们不能在`28 KiB`处开始区域,因为它将与`1004 KiB`处已经映射的页面相撞。所以我们必须进一步寻找,直到找到一个足够大的未映射区域,例如在`1008 KiB`。这是一个类似于[segmentation]的碎片化问题。
然而它使虚拟地址空间变得杂乱无章并使寻找较大尺寸的连续内存区域更加困难。例如想象一下我们想在上述图形中创建一个大小为1000&nbsp;KiB的虚拟内存区域例如 [memory-mapping a file] . 我们不能在`28 KiB`处开始区域,因为它将与`1004 KiB`处已经映射的页面相撞。所以我们必须进一步寻找,直到找到一个足够大的未映射区域,例如在`1008 KiB`。这是一个类似于[segmentation]的碎片化问题。
[memory-mapping a file]: https://en.wikipedia.org/wiki/Memory-mapped_file
[segmentation]: @/edition-2/posts/08-paging-introduction/index.md#fragmentation
@@ -277,18 +277,18 @@ frame.map(|frame| frame.start_address() + u64::from(addr.page_offset()))
所有这些方法的设置都需要对页表进行修改。例如需要创建物理内存的映射或者需要对4级表的一个条目进行递归映射。问题是如果没有访问页表的现有方法我们就无法创建这些所需的映射。
这意味着我们需要 bootloader 的帮助bootloader 创建了内核运行的页表。Bootloader 可以访问页表,所以它可以创建内核需要的任何映射。在目前的实现中,bootloader 工具箱支持上述两种方法,通过 [cargo features] 进行控制。
这意味着我们需要 bootloader 的帮助bootloader 创建了内核运行的页表。Bootloader 可以访问页表,所以它可以创建内核需要的任何映射。在目前的实现中,"bootloader "工具箱支持上述两种方法,通过 [cargo features] 进行控制。
[cargo features]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section
- `map_physical_memory` 功能将某处完整的物理内存映射到虚拟地址空间。因此,内核可以访问所有的物理内存,并且可以遵循[_映射完整物理内存_](#ying-she-wan-zheng-de-wu-li-nei-cun)的方法。
- 有了 recursive_page_table 功能bootloader会递归地映射4级page table的一个条目。这允许内核访问页表如[_递归页表_](#di-gui-ye-biao)部分所述。
- 有了 "recursive_page_table "功能bootloader会递归地映射4级page table的一个条目。这允许内核访问页表如[_递归页表_](#di-gui-ye-biao)部分所述。
我们为我们的内核选择了第一种方法,因为它很简单,与平台无关,而且更强大(它还允许访问非页表框架)。为了启用所需的引导程序支持,我们在 引导程序 的依赖中加入了 "map_physical_memory"功能。
我们为我们的内核选择了第一种方法,因为它很简单,与平台无关,而且更强大(它还允许访问非页表框架)。为了启用所需的引导程序支持,我们在 "引导程序 "的依赖中加入了 "map_physical_memory"功能。
```toml
[dependencies]
bootloader = { version = "0.9", features = ["map_physical_memory"]}
bootloader = { version = "0.9.23", features = ["map_physical_memory"]}
```
启用这个功能后bootloader 将整个物理内存映射到一些未使用的虚拟地址范围。为了将虚拟地址范围传达给我们的内核bootloader 传递了一个 _启动信息_ 结构。
@@ -296,16 +296,16 @@ bootloader = { version = "0.9", features = ["map_physical_memory"]}
### 启动信息
`Bootloader` 板块定义了一个[`BootInfo`]结构,包含了它传递给我们内核的所有信息。这个结构还处于早期阶段,所以在更新到未来的 [semver-incompatible] bootloader 版本时,可能会出现一些故障。在启用 "map_physical_memory" 功能后,它目前有两个字段 "memory_map" 和 "physical_memory_offset"。
Bootloader "板块定义了一个[`BootInfo`]结构,包含了它传递给我们内核的所有信息。这个结构还处于早期阶段,所以在更新到未来的[semver-incompatible]bootloader版本时可能会出现一些故障。在启用 "map_physical_memory "功能后,它目前有两个字段 "memory_map "和 "physical_memory_offset"。
[`BootInfo`]: https://docs.rs/bootloader/0.9/bootloader/bootinfo/struct.BootInfo.html
[`BootInfo`]: https://docs.rs/bootloader/0.9.3/bootloader/bootinfo/struct.BootInfo.html
[semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements
- `memory_map`字段包含了可用物理内存的概览。它告诉我们的内核系统中有多少物理内存可用哪些内存区域被保留给设备如VGA硬件。内存图可以从BIOS或UEFI固件中查询但只能在启动过程的早期查询。由于这个原因它必须由引导程序提供因为内核没有办法在以后检索到它。在这篇文章的后面我们将需要内存图。
- `physical_memory_offset`告诉我们物理内存映射的虚拟起始地址。通过把这个偏移量加到物理地址上,我们得到相应的虚拟地址。这使得我们可以从我们的内核中访问任意的物理内存。
- physical_memory_offset`告诉我们物理内存映射的虚拟起始地址。通过把这个偏移量加到物理地址上,我们得到相应的虚拟地址。这使得我们可以从我们的内核中访问任意的物理内存。
- 这个物理内存偏移可以通过在Cargo.toml中添加一个`[package.metadata.bootloader]`表并设置`physical-memory-offset = "0x0000f00000000000"`或任何其他值来定制。然而请注意如果bootloader遇到物理地址值开始与偏移量以外的空间重叠也就是说它以前会映射到其他早期的物理地址的区域就会出现恐慌。所以一般来说这个值越高>1 TiB越好。
Bootloader将 `BootInfo` 结构以 `&'static BootInfo`参数的形式传递给我们的内核,并传递给我们的`_start`函数。我们的函数中还没有声明这个参数,所以让我们添加它。
Bootloader将 "BootInfo "结构以"&'static BootInfo "参数的形式传递给我们的内核,并传递给我们的"_start "函数。我们的函数中还没有声明这个参数,所以让我们添加它。
```rust
// in src/main.rs
@@ -415,7 +415,7 @@ pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr)
首先,我们从`CR3`寄存器中读取活动的4级表的物理帧。然后我们取其物理起始地址将其转换为`u64`,并将其添加到`physical_memory_offset`中,得到页表框架映射的虚拟地址。最后,我们通过`as_mut_ptr`方法将虚拟地址转换为`*mut PageTable`原始指针,然后不安全地从它创建一个`&mut PageTable`引用。我们创建一个`&mut`引用,而不是`&`引用,因为我们将在本篇文章的后面对页表进行突变。
我们不需要在这里使用不安全块因为Rust把一个 `不安全 fn` 的完整主体当作一个大的 `不安全`块。这使得我们的代码更加危险,因为我们可能会在不知不觉中在前几行引入不安全操作。这也使得在安全操作之间发现不安全操作的难度大大增加。有一个[RFC](https://github.com/rust-lang/rfcs/pull/2585)可以改变这种行为。
我们不需要在这里使用不安全块因为Rust把一个 "不安全 "fn的完整主体当作一个大的 "不安全 "块。这使得我们的代码更加危险,因为我们可能会在不知不觉中在前几行引入不安全操作。这也使得在安全操作之间发现不安全操作的难度大大增加。有一个[RFC]https://github.com/rust-lang/rfcs/pull/2585可以改变这种行为。
现在我们可以用这个函数来打印第4级表格的条目。
@@ -555,9 +555,9 @@ fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: VirtAddr)
我们没有重复使用`active_level_4_table`函数,而是再次从`CR3`寄存器读取4级帧。我们这样做是因为它简化了这个原型的实现。别担心我们一会儿就会创建一个更好的解决方案。
`VirtAddr`结构已经提供了计算四级页面表索引的方法。我们将这些索引存储在一个小数组中,因为它允许我们使用`for`循环遍历页表。在循环之外,我们记住了最后访问的`frame`,以便以后计算物理地址。`frame`在迭代时指向页表框架在最后一次迭代后指向映射的框架也就是在跟随第1级条目之后。
`VirtAddr`结构已经提供了计算四级页面表索引的方法。我们将这些索引存储在一个小数组中,因为它允许我们使用`for`循环遍历页表。在循环之外,我们记住了最后访问的`frame',以便以后计算物理地址。`frame`在迭代时指向页表框架在最后一次迭代后指向映射的框架也就是在跟随第1级条目之后。
在这个循环中,我们再次使用`physical_memory_offset`将帧转换为页表引用。然后我们读取当前页表的条目,并使用[`PageTableEntry::frame`]函数来检索映射的框架。如果该条目没有映射到一个框架,我们返回`None`。如果该条目映射了一个巨大的2&nbsp;MiB或1&nbsp;GiB页面我们就暂时慌了。
在这个循环中,我们再次使用`physical_memory_offset`将帧转换为页表引用。然后我们读取当前页表的条目,并使用[`PageTableEntry::frame`]函数来检索映射的框架。如果该条目没有映射到一个框架,我们返回`None'。如果该条目映射了一个巨大的2&nbsp;MiB或1&nbsp;GiB页面我们就暂时慌了。
[`PageTableEntry::frame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame
@@ -621,11 +621,11 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
[`translate_addr`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Translate.html#method.translate_addr
[`translate`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Translate.html#tymethod.translate
特质只定义接口,不提供任何实现。`x86_64`板块目前提供了三种类型来实现不同要求的特征。[`OffsetPageTable`] 类型假设完整的物理内存被映射到虚拟地址空间的某个偏移处。[`MappedPageTable`]更灵活一些。它只要求每个页表帧在一个可计算的地址处被映射到虚拟地址空间。最后,[`递归页表`]类型可以用来通过[递归页表](#di-gui-ye-biao)访问页表框架。
特质只定义接口,不提供任何实现。`x86_64`板块目前提供了三种类型来实现不同要求的特征。[`OffsetPageTable`] 类型假设完整的物理内存被映射到虚拟地址空间的某个偏移处。[`MappedPageTable`]更灵活一些。它只要求每个页表帧在一个可计算的地址处被映射到虚拟地址空间。最后,[递归页表]类型可以用来通过[递归页表](#di-gui-ye-biao)访问页表框架。
[`OffsetPageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html
[`MappedPageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MappedPageTable.html
[`递归页表`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
[`RecursivePageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
在我们的例子中bootloader在`physical_memory_offset`变量指定的虚拟地址上映射完整的物理内存,所以我们可以使用`OffsetPageTable`类型。为了初始化它,我们在`memory`模块中创建一个新的`init`函数。
@@ -648,11 +648,11 @@ unsafe fn active_level_4_table(physical_memory_offset: VirtAddr)
{}
```
该函数接受 "physical_memory_offset "作为参数,并返回一个新的 "OffsetPageTable "实例,该实例具有 "静态 "寿命。这意味着该实例在我们内核的整个运行时间内保持有效。在函数体中,我们首先调用 "active_level_4_table "函数来获取4级页表的可变引用。然后我们用这个引用调用[`OffsetPageTable::new`] 函数。作为第二个参数,`new`函数希望得到物理内存映射开始的虚拟地址,该地址在`physical_memory_offset`变量中给出。
该函数接受 "physical_memory_offset "作为参数,并返回一个新的 "OffsetPageTable "实例,该实例具有 "静态 "寿命。这意味着该实例在我们内核的整个运行时间内保持有效。在函数体中,我们首先调用 "active_level_4_table "函数来获取4级页表的可变引用。然后我们用这个引用调用[`OffsetPageTable::new`] 函数。作为第二个参数,`new'函数希望得到物理内存映射开始的虚拟地址,该地址在`physical_memory_offset'变量中给出。
[`OffsetPageTable::new`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html#method.new
从现在开始,`active_level_4_table`函数只能从`init`函数中调用,因为它在多次调用时很容易导致别名的可变引用,这可能导致未定义的行为。出于这个原因,我们通过删除`pub`指定符使该函数成为私有的。
从现在开始,`active_level_4_table'函数只能从`init'函数中调用,因为它在多次调用时很容易导致别名的可变引用,这可能导致未定义的行为。出于这个原因,我们通过删除`pub`指定符使该函数成为私有的。
我们现在可以使用`Translate::translate_addr`方法而不是我们自己的`memory::translate_addr`函数。我们只需要在`kernel_main`中修改几行。
@@ -760,9 +760,9 @@ pub fn create_example_mapping(
#### 一个假的 `FrameAllocator`
为了能够调用`create_example_mapping`,我们需要首先创建一个实现`FrameAllocator`特质的类型。如上所述,如果`map_to`需要新的页表,该特质负责为其分配框架。
为了能够调用`create_example_mapping`,我们需要首先创建一个实现`FrameAllocator`特质的类型。如上所述,如果`map_to'需要新的页表,该特质负责为其分配框架。
让我们从简单的情况开始,假设我们不需要创建新的页面表。对于这种情况,一个总是返回 "无 "的框架分配器就足够了。我们创建这样一个`空框架分配器`来测试我们的映射函数。
让我们从简单的情况开始,假设我们不需要创建新的页面表。对于这种情况,一个总是返回 "无 "的框架分配器就足够了。我们创建这样一个`空框架分配器'来测试我们的映射函数。
```rust
// in src/memory.rs
@@ -787,7 +787,7 @@ unsafe impl FrameAllocator<Size4KiB> for EmptyFrameAllocator {
图中左边是虚拟地址空间,右边是物理地址空间,中间是页表。页表被存储在物理内存框架中,用虚线表示。虚拟地址空间包含一个地址为`0x803fe00000`的单一映射页用蓝色标记。为了将这个页面转换到它的框架CPU在4级页表上行走直到到达地址为36&nbsp;KiB的框架。
此外该图用红色显示了VGA文本缓冲区的物理帧。我们的目标是使用`create_example_mapping`函数将一个先前未映射的虚拟页映射到这个帧。由于我们的`EmptyFrameAllocator`总是返回`None`,我们想创建映射,这样就不需要分配器提供额外的帧。这取决于我们为映射选择的虚拟页。
此外该图用红色显示了VGA文本缓冲区的物理帧。我们的目标是使用`create_example_mapping`函数将一个先前未映射的虚拟页映射到这个帧。由于我们的`EmptyFrameAllocator'总是返回`None',我们想创建映射,这样就不需要分配器提供额外的帧。这取决于我们为映射选择的虚拟页。
图中显示了虚拟地址空间中的两个候选页,都用黄色标记。一个页面在地址`0x803fdfd000`比映射的页面蓝色早3页。虽然4级和3级页表的索引与蓝色页相同但2级和1级的索引不同见[上一篇][页表-索引]。2级表的不同索引意味着这个页面使用了一个不同的1级表。由于这个1级表还不存在如果我们选择该页作为我们的例子映射我们就需要创建它这就需要一个额外的未使用的物理帧。相比之下地址为`0x803fe02000`的第二个候选页就没有这个问题因为它使用了与蓝色页面相同的1级页表。因此所有需要的页表都已经存在。
@@ -828,9 +828,9 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
我们首先通过调用 "create_example_mapping "函数为地址为0的页面创建映射并为 "mapper "和 "frame_allocator "实例提供一个可变的引用。这将页面映射到VGA文本缓冲区框架所以我们应该在屏幕上看到对它的任何写入。
然后我们将页面转换为原始指针,并写一个值到偏移量`400`。我们不写到页面的开始因为VGA缓冲区的顶行被下一个`println`直接移出了屏幕。我们写值`0x_f021_f077_f065_f04e`,表示白色背景上的字符串 _"New!"_ 。正如我们[在 _"VGA文本模式"_ 帖子中]所学到的对VGA缓冲区的写入应该是不稳定的所以我们使用[`write_volatile`]方法。
然后我们将页面转换为原始指针,并写一个值到偏移量`400`。我们不写到页面的开始因为VGA缓冲区的顶行被下一个`println`直接移出了屏幕。我们写值`0x_f021_f077_f065_f04e`,表示白色背景上的字符串 _"New!"_ 。正如我们[在_"VGA文本模式"_帖子中]所学到的对VGA缓冲区的写入应该是不稳定的所以我们使用[`write_volatile`]方法。
[在 _"VGA文本模式"_ 帖子中]: @/edition-2/posts/03-vga-text-buffer/index.md#volatile
[在_"VGA文本模式"_帖子中]: @/edition-2/posts/03-vga-text-buffer/index.md#volatile
[`write_volatile`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile
当我们在QEMU中运行它时我们看到以下输出。
@@ -896,7 +896,7 @@ impl BootInfoFrameAllocator {
#### 一个 `usable_frames` 方法
在我们实现`FrameAllocator`特性之前,我们添加一个辅助方法,将内存映射转换为可用帧的迭代器。
在我们实现`FrameAllocator'特性之前,我们添加一个辅助方法,将内存映射转换为可用帧的迭代器。
```rust
// in src/memory.rs
@@ -923,7 +923,7 @@ impl BootInfoFrameAllocator {
这个函数使用迭代器组合方法将初始的`MemoryMap`转化为可用的物理帧的迭代器。
- 首先,我们调用`iter`方法,将内存映射转换为多个[`MemoryRegion`]的迭代器。
- 首先,我们调用`iter`方法,将内存映射转换为[`MemoryRegion`]s的迭代器。
- 然后我们使用[`filter`]方法跳过任何保留或其他不可用的区域。Bootloader为它创建的所有映射更新了内存地图所以被我们的内核使用的帧代码、数据或堆栈或存储启动信息的帧已经被标记为`InUse`或类似的。因此,我们可以确定 "可使用" 的帧没有在其他地方使用。
- 之后,我们使用[`map`]组合器和Rust的[range语法]将我们的内存区域迭代器转化为地址范围的迭代器。
- 接下来,我们使用[`flat_map`]将地址范围转化为帧起始地址的迭代器,使用[`step_by`]选择每4096个地址。由于4096字节=4&nbsp;KiB是页面大小我们得到了每个帧的起始地址。Bootloader对所有可用的内存区域进行页对齐所以我们在这里不需要任何对齐或舍入代码。通过使用[`flat_map`]而不是`map`,我们得到一个`Iterator<Item = u64>`而不是`Iterator<Item = Iterator<Item = u64>`。
@@ -961,7 +961,7 @@ unsafe impl FrameAllocator<Size4KiB> for BootInfoFrameAllocator {
[`Iterator::nth`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.nth
这个实现不是很理想,因为它在每次分配时都会重新创建`usable_frame`分配器。最好的办法是直接将迭代器存储为一个结构域。这样我们就不需要`nth`方法了,可以在每次分配时直接调用[`next`]。这种方法的问题是,目前不可能将 "impl Trait "类型存储在一个结构字段中。当 [_named existential types_] 完全实现时,它可能会在某一天发挥作用。
这个实现不是很理想,因为它在每次分配时都会重新创建`usable_frame`分配器。最好的办法是直接将迭代器存储为一个结构域。这样我们就不需要`nth`方法了,可以在每次分配时直接调用[`next`]。这种方法的问题是,目前不可能将 "impl Trait "类型存储在一个结构字段中。当[_name existential types_]完全实现时,它可能会在某一天发挥作用。
[`next`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#tymethod.next
[_named existential types_]: https://github.com/rust-lang/rfcs/pull/2071
@@ -992,7 +992,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
虽然我们的`create_example_mapping`函数只是一些示例代码,但我们现在能够为任意的页面创建新的映射。这对于分配内存或在未来的文章中实现多线程是至关重要的。
此时,我们应该再次删除`create_example_mapping`函数,以避免意外地调用未定义的行为,正如 [上面](#create-example-mapping-han-shu) 所解释的那样。
此时,我们应该再次删除`create_example_mapping`函数,以避免意外地调用未定义的行为,正如[上面](#create_example_mapping 函数)所解释的那样。
## 总结

View File

@@ -9,7 +9,7 @@ chapter = "Memory Management"
# Please update this when updating the translation
translation_based_on_commit = "afeed7477bb19a29d94a96b8b0620fd241b0d55f"
# GitHub usernames of the people that translated this post
translators = ["swnakamura", "garasubo"]
translators = ["woodyZootopia", "garasubo"]
+++
この記事では、私たちのカーネルにヒープ<ruby>割り当て<rp> (</rp><rt>アロケーション</rt><rp>) </rp></ruby>の機能を追加します。まず動的メモリの基礎を説明し、どのようにして借用チェッカがありがちなアロケーションエラーを防いでくれるのかを示します。その後Rustの基本的なアロケーションインターフェースを実装し、ヒープメモリ領域を作成し、アロケータクレートを設定します。この記事を終える頃には、Rustに組み込みの`alloc`クレートのすべてのアロケーション・コレクション型が私たちのカーネルで利用可能になっているでしょう。

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ chapter = "Multitasking"
# Please update this when updating the translation
translation_based_on_commit = "bf4f88107966c7ab1327c3cdc0ebfbd76bad5c5f"
# GitHub usernames of the authors of this translation
translators = ["kahirokunn", "garasubo", "sozysozbot", "swnakamura"]
translators = ["kahirokunn", "garasubo", "sozysozbot", "woodyZootopia"]
# GitHub usernames of the people that contributed to this translation
translation_contributors = ["asami-kawasaki", "Foo-x"]
+++
@@ -469,9 +469,9 @@ ExampleStateMachine::End(_) => {
Futureは `Poll::Ready` を返した後、再びポーリングされるべきではありません。したがって、すでに `End` の状態にあるときに `poll` が呼ばれるとパニックするようにしましょう。
コンパイラが生成するステートマシンとその `Future` traitの実装はこのようになっている**かもしれません**。実際には、コンパイラは異なる方法でコードを生成しています。 (一応、現在は[_coroutines_]をベースにした実装になっていますが、これはあくまでも実装の詳細です。)
コンパイラが生成するステートマシンとその `Future` traitの実装はこのようになっている**かもしれません**。実際には、コンパイラは異なる方法でコードを生成しています。 (一応、現在は[_generators_]をベースにした実装になっていますが、これはあくまでも実装の詳細です。)
[_coroutines_]: https://doc.rust-lang.org/stable/unstable-book/language-features/coroutines.html
[_generators_]: https://doc.rust-lang.org/nightly/unstable-book/language-features/generators.html
パズルの最後のピースは、生成された `example` 関数自体のコードです。関数のヘッダは次のように定義されていたことを思い出してください:

View File

@@ -462,9 +462,9 @@ ExampleStateMachine::End(_) => {
Futures should not be polled again after they returned `Poll::Ready`, so we panic if `poll` is called while we are already in the `End` state.
We now know what the compiler-generated state machine and its implementation of the `Future` trait _could_ look like. In practice, the compiler generates code in a different way. (In case you're interested, the implementation is currently based on [_coroutines_], but this is only an implementation detail.)
We now know what the compiler-generated state machine and its implementation of the `Future` trait _could_ look like. In practice, the compiler generates code in a different way. (In case you're interested, the implementation is currently based on [_generators_], but this is only an implementation detail.)
[_coroutines_]: https://doc.rust-lang.org/stable/unstable-book/language-features/coroutines.html
[_generators_]: https://doc.rust-lang.org/nightly/unstable-book/language-features/generators.html
The last piece of the puzzle is the generated code for the `example` function itself. Remember, the function header was defined like this:

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,606 @@
+++
title = "Minimal Kernel"
weight = 1
path = "minimal-kernel"
date = 0000-01-01
draft = true
[extra]
chapter = "Bare Bones"
icon = '''
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark-binary" viewBox="0 0 16 16">
<path d="M7.05 11.885c0 1.415-.548 2.206-1.524 2.206C4.548 14.09 4 13.3 4 11.885c0-1.412.548-2.203 1.526-2.203.976 0 1.524.79 1.524 2.203zm-1.524-1.612c-.542 0-.832.563-.832 1.612 0 .088.003.173.006.252l1.559-1.143c-.126-.474-.375-.72-.733-.72zm-.732 2.508c.126.472.372.718.732.718.54 0 .83-.563.83-1.614 0-.085-.003-.17-.006-.25l-1.556 1.146zm6.061.624V14h-3v-.595h1.181V10.5h-.05l-1.136.747v-.688l1.19-.786h.69v3.633h1.125z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
</svg>
'''
+++
The first step in creating our own operating system kernel is to create a [bare metal] Rust executable that does not depend on an underlying operating system.
For that we need to disable most of Rust's standard library and adjust various compilation settings.
The result is a minimal operating system kernel that forms the base for the following posts of this series.
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
<!-- more -->
This blog is openly developed on [GitHub].
If you have any problems or questions, please open an issue there.
You can also leave comments [at the bottom].
The complete source code for this post can be found in the [`post-3.1`][post branch] branch.
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
[post branch]: https://github.com/phil-opp/blog_os/tree/post-3.1
<!-- toc -->
## Introduction
Kernels are the heart of an operating system.
They provide all the fundamental building blocks that are required for building higher-level programs.
Typical building blocks are threads, files, heap memory, timers, or sockets.
Other important tasks of a kernel are the isolation of different programs and the multiplexing of resources.
When writing an operating system kernel, we need to provide all of these building blocks ourselves.
This means that we can't use most of the [Rust standard library].
However, there are still a lot of Rust features that we _can_ use.
For example, we can use [iterators], [closures], [pattern matching], [`Option`] and [`Result`], [string formatting], and of course the [ownership system].
These features make it possible to write a kernel in a very expressive, high level way and worry less about [undefined behavior] or [memory safety].
[`Option`]: https://doc.rust-lang.org/core/option/
[`Result`]: https://doc.rust-lang.org/core/result/
[Rust standard library]: https://doc.rust-lang.org/std/
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
[ownership system]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
In this post, we create a minimal OS kernel that can be run without an underlying operating system.
Such an executable is often called a “freestanding” or “bare-metal” executable.
We then make this executable compatible with the early-boot environment of the `x86_64` architecture so that we can boot it as an operating system kernel.
## Disabling the Standard Library
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking.
It also depends on the C standard library `libc`, which closely interacts with OS services.
Since our plan is to write an operating system, we cannot use any OS-dependent libraries.
So we have to disable the automatic inclusion of the standard library, which we can do through the [`no_std` attribute].
[standard library]: https://doc.rust-lang.org/std/
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
We start by creating a new cargo application project.
The easiest way to do this is through the command line:
```
cargo new kernel --bin --edition 2021
```
We name the project `kernel` here, but of course you can choose your own name.
The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2021` flag specifies that we want to use the [2021 edition] of Rust for our crate.
When we run the command, cargo creates the following directory structure for us:
[2021 edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
```
kernel
├── Cargo.toml
└── src
└── main.rs
```
The `Cargo.toml` contains the crate configuration, for example the crate name, the [semantic version] number, and dependencies.
The `src/main.rs` file contains the root module of our crate and our `main` function.
You can compile your crate through `cargo build` and then run the compiled `kernel` binary in the `target/debug` subfolder.
[semantic version]: https://semver.org/
### The `no_std` Attribute
Right now our crate implicitly links the standard library.
Let's try to disable this by adding the [`no_std` attribute]:
```rust,hl_lines=3
// main.rs
#![no_std]
fn main() {
println!("Hello, world!");
}
```
When we try to build it now (by running `cargo build`), the following errors occur:
```
error: cannot find macro `println!` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
[...]
```
The reason for the first error is that the [`println` macro] is part of the standard library, which we no longer include.
So we can no longer print things.
This makes sense, since `println` writes to [standard output], which is a special file descriptor provided by the operating system.
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
So let's remove the printing and try again with an empty main function:
```rust,hl_lines=5
// main.rs
#![no_std]
fn main() {}
```
```
cargo build
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`
[...]
```
The `println` error is gone, but the compiler is still missing a `#[panic_handler]` function and a _language item_.
### Panic Implementation
The `panic_handler` attribute defines the function that the compiler should invoke when a [panic] occurs.
The standard library provides its own panic handler function, but in a `no_std` environment we need to define one ourselves:
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
```rust,hl_lines=3 9-13
// in main.rs
use core::panic::PanicInfo;
#![no_std]
fn main() {}
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
```
The [`PanicInfo` parameter][PanicInfo] contains the file and line where the panic happened and the optional panic message.
The handler function should never return, so it is marked as a [diverging function] by returning the [“never” type] `!`.
There is not much we can do in this function for now, so we just loop indefinitely.
[PanicInfo]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html
[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
[“never” type]: https://doc.rust-lang.org/std/primitive.never.html
After defining a panic handler, only the `eh_personality` language item error remains:
```
cargo build
error: language item required, but not found: `eh_personality`
|
= note: this can occur when a binary crate with `#![no_std]` is compiled for a
target where `eh_personality` is defined in the standard library
= help: you may be able to compile for a target that doesn't need `eh_personality`,
specify a target with `--target` or in `.cargo/config`
```
### Disabling Unwinding
Language items are special functions and types that are required internally by the compiler.
They are normally provided by the standard library, which we disabled using the `#![no_std]` attribute.
The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding].
By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic].
This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution.
Unwinding, however, is a complex process and requires some OS-specific libraries, such as [libunwind] on Linux or [structured exception handling] on Windows.
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
[libunwind]: https://www.nongnu.org/libunwind/
[structured exception handling]: https://docs.microsoft.com/de-de/windows/win32/debug/structured-exception-handling
While unwinding is very useful, it also has some drawbacks.
For example, it increases the size of the compiled executable because it requires additional context at runtime.
Because of these drawbacks, Rust provides an option to [abort on panic] instead.
[abort on panic]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html#unwinding-the-stack-or-aborting-in-response-to-a-panic
We already use a custom panic handler that never returns, so we don't need unwinding for our kernel.
By disabling it, the `eh_personality` language item won't be required anymore.
There are multiple ways to set the panic strategy, the easiest is to use [cargo profiles]:
[cargo profiles]: https://doc.rust-lang.org/cargo/reference/profiles.html
```toml,hl_lines=3-7
# in Cargo.toml
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
```
This sets the panic strategy to `abort` for both the `dev` profile (used for `cargo build`) and the `release` profile (used for `cargo build --release`).
Now the `eh_personality` language item should no longer be required.
When we try to compile our kernel now, a new error occurs:
```
cargo build
error: requires `start` lang_item
```
Our kernel is missing the `start` language item, which defines the _entry point_ of the executable.
## Setting the Entry Point
The [entry point] of a program is the function that is called when the executable is started.
One might think that the `main` function is the first function called, however, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go).
This runtime needs to be called before `main`, since it needs to initialize itself.
[entry point]: https://en.wikipedia.org/wiki/Entry_point
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
In a typical Rust binary that links the standard library, execution starts in a C runtime library called [`crt0`] (“C runtime zero”), which sets up the environment for a C application.
This includes creating a [call stack] and placing the command line arguments in the right CPU registers.
The C runtime then invokes the [entry point of the Rust runtime][rt::lang_start], which is marked by the `start` language item.
Rust only has a very minimal runtime, which takes care of some small things such as setting up stack overflow guards or printing a backtrace on panic.
The runtime then finally calls the `main` function.
[`crt0`]: https://en.wikipedia.org/wiki/Crt0
[call stack]: https://en.wikipedia.org/wiki/Call_stack
[rt::lang_start]: hhttps://github.com/rust-lang/rust/blob/0d97f7a96877a96015d70ece41ad08bb7af12377/library/std/src/rt.rs#L59-L70
Since we're building an operating system kernel that should run without any underlying operating system, we don't want our kernel to depend on any Rust or C runtime.
To remove these dependencies, we need to do two things:
1. Instruct the compiler that we want to build for a bare-metal target environment. This removes the dependency on the C library.
2. Disable the Rust main function to remove the Rust runtime.
### Bare-Metal Target
By default Rust tries to build an executable that is able to run in your current system environment.
For example, if you're using Windows and an `x86_64` CPU, Rust tries to build a `.exe` Windows executable that uses `x86_64` instructions.
This environment is called your "host" system.
To describe different environments, Rust uses a string called [_target triple_].
You can see the target triple for your host system by running `rustc --version --verbose`:
[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
```
rustc 1.68.1 (8460ca823 2023-03-20)
binary: rustc
commit-hash: 8460ca823e8367a30dda430efda790588b8c84d3
commit-date: 2023-03-20
host: x86_64-unknown-linux-gnu
release: 1.68.1
LLVM version: 15.0.6
```
The above output is from a `x86_64` Linux system.
We see that the `host` triple is `x86_64-unknown-linux-gnu`, which includes the CPU architecture (`x86_64`), the vendor (`unknown`), the operating system (`linux`), and the [ABI] (`gnu`).
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that uses the C runtime by default, which requires the `start` language item.
To avoid the runtimes, we can compile for a different environment with no underlying operating system.
#### The `x86_64-unknown-none` Target
Rust supports a [variety of target systems][platform-support], including some bare-metal targets.
For example, the `thumbv7em-none-eabihf` target triple can be used to compile for an [embedded] [ARM] system with a `Cortex M4F` CPU, as used in the [Rust Embedded Book].
[platform-support]: https://doc.rust-lang.org/rustc/platform-support.html
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
[Rust Embedded Book]: https://docs.rust-embedded.org/book/intro/index.html
Our kernel should run on a bare-metal `x86_64` system, so the suitable target triple is [`x86_64-unknown-none`].
The `-none` suffix indicates that there is no underlying operating system.
To be able to compile for this target, we need to add it using [`rustup`].
[`x86_64-unknown-none`]: https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html
<div class = "note"><details>
<summary><em>What is <code>rustup</code></em>?</summary>
The [`rustup`] tool is the [officially recommended] way of installing Rust.
It supports having multiple versions of Rust installed simultaneously and makes upgrading Rust easy.
It also provides access to optional tools and components such as [`rustfmt`] or [`rust-analyzer`].
This guide requires `rustup`, so please install it if you haven't already.
[`rustup`]: https://rustup.rs/
[officially recommended]: https://www.rust-lang.org/learn/get-started
[`rustfmt`]: https://github.com/rust-lang/rustfmt/
[`rust-analyzer`]: https://github.com/rust-lang/rust-analyzer
</details></div>
To download and set up the `x86_64-unknown-none` target, we use the following `rustup` command:
```
rustup target add x86_64-unknown-none
```
This downloads a pre-compiled copy of the `core` library for the target.
Afterwards, we can [cross compile] our executable for a bare metal environment by passing a `--target` argument:
[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
```
cargo build --target x86_64-unknown-none
Compiling kernel v0.1.0 (/<...>/kernel)
error: requires `start` lang_item
```
We still get the error about a missing `start` language item because the custom target only removed the dependency on the C library.
To remove the dependency on the Rust runtime as well, we can use the `#[no_main]` attribute.
Before that, we can do a small cleanup.
The `x86_64-unknown-none` target defaults to `panic = "abort"`, so the we can remove the `profile.dev` and `profile.release` tables from our `Cargo.toml` again.
### The `#[no_main]` Attribute
To tell the Rust compiler that we don't want to use the normal entry point chain, we add the `#![no_main]` attribute.
```rust,hl_lines=4
// main.rs
#![no_std]
#![no_main]
use core::panic::PanicInfo;
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
```
You might notice that we removed the `main` function.
The reason is that a `main` doesn't make sense without an underlying runtime that calls it.
Instead, we are now overwriting the operating system entry point with our own `_start` function:
```rust,hl_lines=3-6
// in main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
```
By using the `#[no_mangle]` attribute we disable the [name mangling] to ensure that the Rust compiler really outputs a function with the name `_start`.
Without the attribute, the compiler would generate some cryptic `_ZN3kernel4_start7hb173fedf945531caE` symbol to give every function an unique name.
The reason for naming the function `_start` is that this is the default entry point name for most systems.
[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
We mark the function as `extern "C"` to tell the compiler that it should use the [C calling convention] for this function (instead of the unspecified Rust calling convention).
[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
Like in our panic handler, the `!` return type means that the function is diverging, i.e. not allowed to ever return.
This is required because the entry point is not called by any function, but invoked directly by the operating system or bootloader.
So instead of returning, the entry point should e.g. invoke the [`exit` system call] of the operating system.
In our case, shutting down the machine could be a reasonable action, since there's nothing left to do if a freestanding binary returns.
For now, we fulfill the requirement by looping endlessly.
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
When we run `cargo build --target x86_64-unknown-none` now, it should finally compile without any errors:
```
cargo build --target x86_64-unknown-none
Compiling kernel v0.1.0 (/<...>/kernel)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
```
We successfully created a minimal bare-metal kernel executable! The compiled executable can be found at `target/x86_64-unknown-none/debug/kernel`.
There is no `.exe` extension even if you're on Windows because the `x86_64-unknown-none` target uses UNIX standards.
To build the kernel with optimizations, we can run:
```
cargo build --target x86_64-unknown-none --release
```
The compiled executable is placed at `target/x86_64-unknown-none/release/kernel` in this case.
In the next post we will cover how to turn this kernel into a bootable disk image that can be run in a virtual machine or on real hardware.
In the rest of this post, we will introduce some tools for examining our kernel executable.
These tools are very useful for debugging future issues, so it's good to know about them.
## Useful Tools
In this section, we will examine our kernel executable using the [`objdump`], [`nm`], and [`size`] tools.
[`objdump`]: https://www.man7.org/linux/man-pages/man1/objdump.1.html
[`nm`]: https://man7.org/linux/man-pages/man1/nm.1.html
[`size`]: https://man7.org/linux/man-pages/man1/size.1.html
If you're on a UNIX system, you might already have the above tools installed.
Otherwise (and on Windows), you can use the LLVM binutils shipped by `rustup` through the [`cargo-binutils`] crate.
To install it, run **`cargo install cargo-binutils`** and **`rustup component add llvm-tools-preview`**.
Afterwards, you can run the tools through `rust-nm`, `rust-objdump`, and `rust-strip`.
[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
### List Symbols using `nm`
We defined a `_start` function as the entry point of our kernel.
To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
```
rust-nm target/x86_64-unknown-none/debug/kernel
0000000000201120 T _start
```
If we comment out the `_start` function or if we remove the `#[no_mangle]` attribute, the `_start` symbol is no longer there after recompiling:
```
rust-nm target/x86_64-unknown-none/debug/kernel
```
This way we can ensure that we set the `_start` function correctly.
### Inspect ELF File using `objdump`
The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable.
[ELF file format]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
#### File Headers
Among other things, the ELF [file header] specifies the target architecture and the entry point address of the executable files.
To print the file header, we can use `objdump -f`:
[file header]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
```
rust-objdump -f target/x86_64-unknown-none/debug/kernel
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
architecture: x86_64
start address: 0x0000000000001210
```
As expected, our kernel targets the `x86_64` CPU architecture.
The start address specifies the memory address of our `_start` function.
Here the function name `_start` becomes important.
If we rename the function to something else (e.g., `_start_here`) and recompile, we see that no start address is set in the ELF file anymore:
```bash,hl_lines=5
rust-objdump -f target/x86_64-unknown-none/debug/kernel
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
architecture: x86_64
start address: 0x0000000000000000
```
#### Sections
Using `objdump -h`, we can print the various sections of our kernel executable:
```bash,hl_lines=12
rust-objdump -h target/x86_64-unknown-none/debug/kernel
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
Sections:
Idx Name Size VMA Type
0 00000000 0000000000000000
1 .dynsym 00000018 00000000000001c8
2 .gnu.hash 0000001c 00000000000001e0
3 .hash 00000010 00000000000001fc
4 .dynstr 00000001 000000000000020c
5 .text 00000004 0000000000001210 TEXT
6 .dynamic 000000a0 0000000000002218
7 .debug_abbrev 0000010c 0000000000000000 DEBUG
8 .debug_info 000005ce 0000000000000000 DEBUG
9 .debug_aranges 00000040 0000000000000000 DEBUG
10 .debug_ranges 00000030 0000000000000000 DEBUG
11 .debug_str 00000492 0000000000000000 DEBUG
12 .debug_pubnames 000000bc 0000000000000000 DEBUG
13 .debug_pubtypes 0000036c 0000000000000000 DEBUG
14 .debug_frame 00000050 0000000000000000 DEBUG
15 .debug_line 00000059 0000000000000000 DEBUG
16 .comment 00000013 0000000000000000
17 .symtab 00000060 0000000000000000
18 .shstrtab 000000ce 0000000000000000
19 .strtab 00000022 0000000000000000
```
The `.text` section contains the program code, the other sections are not important right now.
The section dump is useful for debugging, for example for checking which section a pointer points to.
Most of the sections only contain debug information and are not needed for execution.
We can remove this debug information using `rust-strip`:
```
rust-strip target/x86_64-unknown-none/debug/kernel
rust-objdump -h target/x86_64-unknown-none/debug/kernel
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
Sections:
Idx Name Size VMA Type
0 00000000 0000000000000000
1 .dynsym 00000018 00000000000001c8
2 .gnu.hash 0000001c 00000000000001e0
3 .hash 00000010 00000000000001fc
4 .dynstr 00000001 000000000000020c
5 .text 00000004 0000000000001210 TEXT
6 .dynamic 000000a0 0000000000002218
7 .shstrtab 00000034 0000000000000000
```
#### Disassembling
Sometimes we need to check the [assembly code] that certain functions compile to.
We can use the `objdump -d` command to print the `.text` section of an executable in assembly language:
[assembly code]: https://en.wikipedia.org/wiki/X86_assembly_language
```
rust-objdump -d target/x86_64-unknown-none/debug/kernel
target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
Disassembly of section .text:
0000000000001210 <_start>:
1210: eb 00 jmp 0x1212 <_start+0x2>
1212: eb fe jmp 0x1212 <_start+0x2>
```
We see that our `_start` function consists of just two [`jmp` instructions], which jump to the given address.
The first `jmp` command jumps to the second `jmp` command at address `1212`.
The second `jmp` command jumps to itself again, thereby representing the infinite loop that we've written in our `_start` function.
[`jmp` instructions]: https://www.felixcloutier.com/x86/jmp
As you probably noticed, the first `jmp` command is not really needed.
Such inefficiencies can happen in debug builds because the compiler does not optimize them.
If we disassemble the optimized release build, we see that the compiler indeed removed the unneeded `jmp`:
```
cargo build --target x86_64-unknown-none --release
rust-objdump -d target/x86_64-unknown-none/release/kernel
target/x86_64-unknown-none/release/kernel: file format elf64-x86-64
Disassembly of section .text:
0000000000001210 <_start>:
1210: eb fe jmp 0x1210 <_start>
```
We will use continue to use the above tools in future posts, as they're quite useful for debugging issues.
## What's next?
In the [next post], we will learn how to turn our minimal kernel in a bootable disk image, which can then be started in the [QEMU] virtual machine and on real hardware.
For this, we'll explore the boot process of `x86_64` systems and learn about the differences between UEFI and the legacy BIOS firmware.
[next post]: @/edition-3/posts/02-booting/index.md
[QEMU]: https://www.qemu.org/

File diff suppressed because it is too large Load Diff

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.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,491 @@
+++
title = "Screen Output"
weight = 3
path = "screen-output"
date = 0000-01-01
draft = true
[extra]
chapter = "Basic I/O"
icon = '''<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-display" viewBox="0 0 16 16">
<path d="M0 4s0-2 2-2h12s2 0 2 2v6s0 2-2 2h-4c0 .667.083 1.167.25 1.5H11a.5.5 0 0 1 0 1H5a.5.5 0 0 1 0-1h.75c.167-.333.25-.833.25-1.5H2s-2 0-2-2V4zm1.398-.855a.758.758 0 0 0-.254.302A1.46 1.46 0 0 0 1 4.01V10c0 .325.078.502.145.602.07.105.17.188.302.254a1.464 1.464 0 0 0 .538.143L2.01 11H14c.325 0 .502-.078.602-.145a.758.758 0 0 0 .254-.302 1.464 1.464 0 0 0 .143-.538L15 9.99V4c0-.325-.078-.502-.145-.602a.757.757 0 0 0-.302-.254A1.46 1.46 0 0 0 13.99 3H2c-.325 0-.502.078-.602.145z"/>
</svg>'''
+++
In this post we focus on the [framebuffer], a special memory region that controls the screen output.
Using an [external crate], we will create functions for writing individual pixels, lines, and various shapes.
In the the second half of this post, we will explore text rendering and learn how to print the obligatory _["Hello, World!"]_.
[framebuffer]: https://en.wikipedia.org/wiki/Framebuffer
[external crate]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
["Hello, World!"]: https://en.wikipedia.org/wiki/Hello_world
<!-- more -->
This blog is openly developed on [GitHub].
If you have any problems or questions, please open an issue there.
You can also leave comments [at the bottom].
The complete source code for this post can be found in the [`post-3.3`][post branch] branch.
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
<!-- fix for zola anchor checker (target is in template): <a id="comments"> -->
[post branch]: https://github.com/phil-opp/blog_os/tree/post-3.3
<!-- toc -->
## Bitmap Images
In the [previous post], we learned how to make our minimal kernel bootable.
Using the [`BootInfo`] provided by the bootloader, we were able to access a special memory region called the _[framebuffer]_, which controls the screen output.
We wrote some example code to display a gray background:
[previous post]: @/edition-3/posts/02-booting/index.md
[`BootInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.BootInfo.html
```rust
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
for byte in framebuffer.buffer_mut() {
*byte = 0x90;
}
}
loop {}
}
```
The reason that the above code affects the screen output is because the graphics card interprets the framebuffer memory as a [bitmap] image.
A bitmap describes an image through a predefined number of bytes per pixel.
The pixels are laid out line by line, typically starting at the top.
[bitmap]: https://en.wikipedia.org/wiki/Bitmap
[RGB]: https://en.wikipedia.org/wiki/Rgb
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
<table style = "width: fit-content;"><tbody>
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
<tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
<tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
</tbody></table>
So top left pixel is stored at offset 0 in the bitmap array.
The pixel on its right is at pixel offset 1.
The first pixel of the next line starts at pixel offset `line_length`, which is 10 in this case.
The last line starts at pixel offset 20, which is `line_length * 2`.
### Padding
Depending on the hardware and GPU firmware, it is often more efficient to make lines start at well-aligned offsets.
Because of this, there is often some additional padding at the end of each line.
So the actual memory layout of the 10x3 example image might look like this, with the padding marked as yellow:
<table style = "width: fit-content;"><tbody>
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td style="background-color:yellow;">10</td><td style="background-color:yellow;">11</td></tr>
<tr><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td><td>20</td><td>21</td><td style="background-color:yellow;">22</td><td style="background-color:yellow;">23</td></tr>
<tr><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>31</td><td>32</td><td>33</td><td style="background-color:yellow;">34</td><td style="background-color:yellow;">35</td></tr>
</tbody></table>
So now the second line starts at pixel offset 12.
The two pixels at the end of each line are considered as padding and ignored.
So if we want to set the first pixel of the second line, we need to be aware of the additional padding and set the pixel at offset 12 instead of offset 10.
The line length plus the padding bytes is typically called the _stride_ or _pitch_ of the buffer.
In the example above, the stride is 12 and the line length is 10.
Since the amount of padding depends on the hardware, the stride is only known at runtime.
The `bootloader` crate queries the framebuffer parameters from the UEFI or BIOS firmware and reports them as part of the `BootInfo`.
It provides the stride of the framebuffer, among other parameters, in form of a [`FrameBufferInfo`] struct that can be created using the [`FrameBuffer::info`] method.
[`FrameBufferInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html
[`FrameBuffer::info`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBuffer.html#method.info
### Color formats
The [`FrameBufferInfo`] also specifies the [`PixelFormat`] of the framebuffer, which also depends on the underlying hardware.
Using this information, we can set pixels to different colors.
For example, the [`PixelFormat::Rgb`] variant specifies that each pixel is represented in the [RGB color space], which stores the red, green, and blue parts of the pixel as separate bytes.
In this model, the color red would be represented as the three bytes `[255, 0, 0]`, or `0xff0000` in [hexadecimal representation].
The color yellow is represented the addition of red and green, which results in `[255, 255, 0]` (or `0xffff00` in hexadecimal representation).
[`PixelFormat`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html
[`PixelFormat::Rgb`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html#variant.Rgb
[RGB color space]: https://en.wikipedia.org/wiki/RGB_color_spaces
[hexadecimal representation]: https://en.wikipedia.org/wiki/RGB_color_model#Numeric_representations
While the `Rgb` format is most common, there are also framebuffers that use a different color format.
For example, the [`PixelFormat::Bgr`] stores the three colors in inverted order, i.e. blue first and red last.
There are also buffers that don't support colors at all and can represent only grayscale pixels.
The `bootloader_api` crate reports such buffers as [`PixelFormat::U8`].
[`PixelFormat::Bgr`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.Bgr
[`PixelFormat::U8`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.U8
Note that there might be some additional padding at the pixel-level as well.
For example, an `Rgb` pixel might be stored as 4 bytes instead of 3 to ensure 32-bit alignment.
The number of bytes per pixel is reported by the bootloader in the [`FrameBufferInfo::bytes_per_pixel`] field.
[`FrameBufferInfo::bytes_per_pixel`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html#structfield.bytes_per_pixel
## Setting specific Pixels
Based on this above details, we can now create a function to set a specific pixel to a certain color.
We start by creating a new `framebuffer` [module]:
[module]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
```rust ,hl_lines=3-5
// in kernel/src/main.rs
// declare a submodule -> the compiler will automatically look
// for a file named `framebuffer.rs` or `framebuffer/mod.rs`
mod framebuffer;
```
In the new module, we create basic structs for representing pixel positions and colors:
```rust ,hl_lines=3-16
// in new kernel/src/framebuffer.rs file
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub x: usize,
pub y: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
```
By marking the structs and their fields as `pub`, we make them accessible from the parent `kernel` module.
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
These traits allow us to duplicate, compare, and print the structs.
[`Debug`]: https://doc.rust-lang.org/stable/core/fmt/trait.Debug.html
[`Clone`]: https://doc.rust-lang.org/stable/core/clone/trait.Clone.html
[`Copy`]: https://doc.rust-lang.org/stable/core/marker/trait.Copy.html
[`PartialEq`]: https://doc.rust-lang.org/stable/core/cmp/trait.PartialEq.html
[`Eq`]: https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html
Next, we create a function for setting a specific pixel in the framebuffer to a given color:
```rust ,hl_lines=3 5-39
// in new kernel/src/framebuffer.rs file
use bootloader_api::info::{FrameBuffer, PixelFormat};
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
let info = framebuffer.info();
// calculate offset to first byte of pixel
let byte_offset = {
// use stride to calculate pixel offset of target line
let line_offset = position.y * info.stride;
// add x position to get the absolute pixel offset in buffer
let pixel_offset = line_offset + position.x;
// convert to byte offset
pixel_offset * info.bytes_per_pixel
};
// set pixel based on color format
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
match info.pixel_format {
PixelFormat::Rgb => {
pixel_buffer[0] = color.red;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.blue;
}
PixelFormat::Bgr => {
pixel_buffer[0] = color.blue;
pixel_buffer[1] = color.green;
pixel_buffer[2] = color.red;
}
PixelFormat::U8 => {
// use a simple average-based grayscale transform
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
pixel_buffer[0] = gray;
}
other => panic!("unknown pixel format {other:?}"),
}
}
```
The first step is to calculate the byte offset within the framebuffer slice at which the pixel starts.
For this, we first calculate the pixel offset of the line by multiplying the `y` position with the stride of the framebuffer, i.e. its line width plus the line padding.
We then add the `x` position to get the absolute index of the pixel.
As the framebuffer slice is a byte slice, we need to transform the pixel index to a byte offset by multiplying it with the number of `bytes_per_pixel`.
[`FrameBuffer::buffer_mut`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/struct.FrameBuffer.html#method.buffer_mut
The second step is to set the pixel to the desired color.
We first use the [`FrameBuffer::buffer_mut`] method to get access to the actual bytes of the framebuffer in form of a slice.
Then, we use the slicing operator `[byte_offset..]` to get a sub-slice starting at the `byte_offset` of the target pixel.
As the write operation depends on the pixel format, we use a [`match`] statement:
[`match`]: https://doc.rust-lang.org/stable/std/keyword.match.html
- For `Rgb` framebuffers, we write three bytes; first red, then green, then blue.
- For `Bgr` framebuffers, we also write three bytes, but blue first and red last.
- For `U8` framebuffers, we first convert the color to grayscale by taking the average of the three color channels.
Note that there are multiple [different ways to convert colors to grayscale], so you can also use different factors here.
- For all other framebuffer formats, we [panic] for now.
[different ways to convert colors to grayscale]: https://www.baeldung.com/cs/convert-rgb-to-grayscale#bd-convert-rgb-to-grayscale
[panic]: https://doc.rust-lang.org/stable/core/macro.panic.html
Let's try to use our new function to write a blue pixel in our `kernel_main` function:
```rust ,hl_lines=5-11
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let position = framebuffer::Position { x: 20, y: 100 };
let color = framebuffer::Color {
red: 0,
green: 0,
blue: 255,
};
framebuffer::set_pixel_in(framebuffer, position, color);
}
loop {}
}
```
When we run our code in QEMU using `cargo run --bin qemu-bios` (or `--bin qemu-uefi`) and look _very closely_, we can see the blue pixel.
It's really difficult to see, so I marked with an arrow below:
![QEMU the bootloader text output with one pixel set to blue. An annotated arrow points to the pixel](qemu-blue-pixel.png)
As this single pixel is too difficult to see, let's draw a filled square of 100x100 pixels instead:
```rust ,hl_lines=10-18
// in kernel/src/main.rs
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
let color = framebuffer::Color {
red: 0,
green: 0,
blue: 255,
};
for x in 0..100 {
for y in 0..100 {
let position = framebuffer::Position {
x: 20 + x,
y: 100 + y,
};
framebuffer::set_pixel_in(framebuffer, position, color);
}
}
}
loop {}
}
```
Now we clearly see that our code works as intended:
![QEMU showing a blue square above the bootloader text output](qemu-blue-square.png)
Feel free to experiment with different positions and colors if you like.
You can also try to draw a circle instead of a square, or a line with a certain thickness.
As you can probably imagine, it would be a lot of work to draw more complex shapes this way.
One example for such complex shapes is _text_, i.e. the rendering of letters and punctuation.
Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate, which provides draw functions for text, various shapes, and image data.
[`embedded-graphics`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/index.html
## The `embedded-graphics` crate
### Implementing `DrawTarget`
```rust ,hl_lines=3
// in kernel/src/framebuffer.rs
use embedded_graphics::{
Pixel,
draw_target::DrawTarget,
geometry::{OriginDimensions, Size},
pixelcolor::{Rgb888, RgbColor},
};
pub struct Display<'f> {
framebuffer: &'f mut FrameBuffer,
}
impl<'f> Display<'f> {
pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
Display { framebuffer }
}
fn draw_pixel(&mut self, Pixel(coordinates, color): Pixel<Rgb888>) {
// ignore any out of bounds pixels
let (width, height) = {
let info = self.framebuffer.info();
(info.width, info.height)
};
let (x, y) = {
let c: (i32, i32) = coordinates.into();
(c.0 as usize, c.1 as usize)
};
if (0..width).contains(&x) && (0..height).contains(&y) {
let color = Color { red: color.r(), green: color.g(), blue: color.b() };
set_pixel_in(self.framebuffer, Position { x, y }, color);
}
}
}
impl<'f> DrawTarget for Display<'f> {
type Color = Rgb888;
/// Drawing operations can never fail.
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for pixel in pixels.into_iter() {
self.draw_pixel(pixel);
}
Ok(())
}
}
impl<'f> OriginDimensions for Display<'f> {
fn size(&self) -> Size {
let info = self.framebuffer.info();
Size::new(info.width as u32, info.height as u32)
}
}
```
---
So far, we have drawn shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
In most other programming languages, implementing this behind the scenes can be a daunting task. With Rust, however, we have a toolset at our disposal that can pave the way for setting up proper framebuffer logging using very little code of our own.
# The `log` crate
Rust developers used to writing user-mode code will recognize the `log` crate from a mile away:
```toml
# in Cargo.toml
[dependencies]
log = { version = "0.4.17", default-features = false }
```
This crate has both a set of macros for logging either to the console or to a log file for later reading and a trait — also called `Log` with a capital L — that can be implemented to provide a backend, called a `Logger` in Rust parlance. Loggers are provided by a myriad of crates for a wide variety of use cases, and some of them even run on bare metal. We already used one such extant logger in the UEFI booting module when we used the logger provided by the `uefi` crate to print text to the UEFI console. That won't work in the kernel, however, because UEFI boot services need to be active in order for the UEFI logger to be usable.
If you were paying attention to the post before that one, however, you may have noticed that the bootloader is itself able to log directly to the framebuffer as it did when we booted the barebones kernel for the first time, and unlike the UEFI console logger, this logger is usable long after UEFI boot services are exited. It's this logger, therefore, that provides the easiest means of implementation on our end.
## `bootloader-x86_64-common`
In version 0.11.x of the bootloader crate, each component is separate, unlike in 0.10.x where the bootloader was a huge monolith. This is fantastic as it means that a lot of the APIs that the bootloader uses behind the scenes are also free for kernels to use, including, of course, the logger. The set of APIs that the logger belongs to are in a crate called `bootloader-x86_64-common` which also contains some other useful abstractions related to things like memory management that will come in handy later:
```toml
# in Cargo.toml
[dependencies]
bootloader-x86_64-common = "0.11.3"
```
For now, however, only the logger will be used. If you are curious as to how this logger is written behind the scenes, however, don't worry; a sub-module of this chapter will include a tutorial on how to write a custom logger from scratch.
# Putting it all together
Before we use the bootloader's logger, we first need to initialize it. This requires creating a static instance, since it needs to live for as long as the kernel lives — which would mean for as long as the computer is powered on. Unfortunately, this is easier said than done, as Rust statics can be rather finicky — understandably so for security reasons. Luckily, there's a crate for this too.
## The `conquer_once` crate
Those used to using the standard library know that it provides a `OnceCell` which is exactly what it sounds like: you write to it only once, and then after that it's just there to use whenever. We're in a kernel and don't have access to the standard library, however, so is there a crate on crates.io that provides a replacement? Ah, yes there is:
```toml
# in Cargo.toml
[dependencies]
conquer-once = { version = "0.4.0", default-features = false }
```
Note that we need to add `default-features = false` to our `conquer-once` dependency —that's because the [`conquer-once` crate](https://crates.io/crates/conquer-once) tries to pull in the standard library by default, which in the kernel will result in compilation errors.
Now that we've added our two dependencies, it's time to use them:
```rust
// in src/main.rs
use conquer_once::spin::OnceCell;
use bootloader_x86_64_common::logger::LockedLogger;
// ...
pub(crate) static LOGGER: OnceCell<LockedLogger> = OnceCell::uninit();
```
By setting the logger up as a static `OnceCell` it becomes much easier to initialize. We use `pub(crate)` to ensure that the kernel can see it but nothing else can.
After this, it's time to actually initialize it. To do that, we use a function:
```rust
// in src/main.rs
use bootloader_api::info::FrameBufferInfo;
// ...
pub(crate) fn init_logger(buffer: &'static mut [u8], info: FrameBufferInfo) {
let logger = LOGGER.get_or_init(move || LockedLogger::new(buffer, info, true, false));
log::set_logger(logger).expect("Logger already set");
log::set_max_level(log::LevelFilter::Trace);
log::info!("Hello, Kernel Mode!");
}
```
This function takes two parameters: a byte slice representing a raw framebuffer and a `FrameBufferInfo` structure containing information about the first parameter. Getting those parameters, however, requires jumping through some hoops to satisfy the borrow checker:
```rust
// in src/main.rs
fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
// ...
// free the doubly wrapped framebuffer from the boot info struct
let frame_buffer_optional = &mut boot_info.framebuffer;
// free the wrapped framebuffer from the FFI-safe abstraction provided by bootloader_api
let frame_buffer_option = frame_buffer_optional.as_mut();
// unwrap the framebuffer
let frame_buffer_struct = frame_buffer_option.unwrap();
// extract the framebuffer info and, to satisfy the borrow checker, clone it
let frame_buffer_info = frame_buffer_struct.info().clone();
// get the framebuffer's mutable raw byte slice
let raw_frame_buffer = frame_buffer_struct.buffer_mut();
// finally, initialize the logger using the last two variables
init_logger(raw_frame_buffer, frame_buffer_info);
// ...
}
```
Any one of these steps, if skipped, will cause the borrow checker to throw a hissy fit due to the use of the `move ||` closure by the initializer function. With this, however, you're done, and you'll know the logger has been initialized when you see "Hello, Kernel Mode!" printed on the screen.
<!-- more -->
<!-- toc -->
<!-- TODO: update relative link in 02-booting/uefi/index.md when this post is finished -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

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

@@ -61,14 +61,17 @@
</div>
<div class="">
{{ trans(key="support_me", lang=lang) | safe }}
<h2>Support Me</h2>
{{ snippets::support() }}
</div>
{% endblock main %}
{% 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 -%}
@@ -77,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

@@ -91,7 +91,8 @@
</div>
<div class="post-footer-support{% if page.extra.rtl %} right-to-left{% endif %}">
{{ trans(key="support_me", lang=lang) | safe }}
<h2>Support Me</h2>
{{ snippets::support() }}
</div>
<hr>

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

@@ -1,3 +1,12 @@
{% macro support() %}
<p>
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>. Thank you!
</p>
{% endmacro support %}
{% macro giscus(search_term, lang) %}
{% if lang != "en" %}
{% set category = "Post Comments (translated)" %}
@@ -12,12 +21,12 @@
{% if search_term is number %}
{% set discussion_url = "https://github.com/phil-opp/blog_os/discussions/" ~ search_term %}
{% else %}
{% set search_term_encoded = `"` ~ search_term ~ `"` ~ ` in:title` | urlencode %}
{% set search_term_encoded = `"` ~ search_term ~ `"` | urlencode %}
{% set discussion_url = `https://github.com/phil-opp/blog_os/discussions/categories/` ~ category_path ~ `?discussions_q=` ~ search_term_encoded %}
{% endif %}
<p class="comment-note">
{{ trans(key="comment_note", lang=lang) | replace(from="_discussion_url_", to=discussion_url) | safe }}
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="{{ discussion_url | safe }}"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
</p>
<div class="giscus"></div>

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

4
bors.toml Normal file
View File

@@ -0,0 +1,4 @@
status = [
"Zola Build", "Check Spelling"
]
delete_merged_branches = true