From 987d695ed29e509b21b00666d0d951dca992692b Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Wed, 13 Jan 2021 20:00:48 +0900 Subject: [PATCH 001/125] Just copied the file --- .../posts/05-cpu-exceptions/index.ja.md | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md new file mode 100644 index 00000000..daf46fa0 --- /dev/null +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md @@ -0,0 +1,467 @@ ++++ +title = "CPU Exceptions" +weight = 5 +path = "cpu-exceptions" +date = 2018-06-17 + +[extra] +chapter = "Interrupts" ++++ + +CPU exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To react to them we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards. + +[breakpoint exceptions]: https://wiki.osdev.org/Exceptions#Breakpoint + + + +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-05`][post branch] branch. + +[GitHub]: https://github.com/phil-opp/blog_os +[at the bottom]: #comments +[post branch]: https://github.com/phil-opp/blog_os/tree/post-05 + + + +## Overview +An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type. + +On x86 there are about 20 different CPU exception types. The most important are: + +- **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page. +- **Invalid Opcode**: This exception occurs when the current instruction is invalid, for example when we try to use newer [SSE instructions] on an old CPU that does not support them. +- **General Protection Fault**: This is the exception with the broadest range of causes. It occurs on various kinds of access violations such as trying to execute a privileged instruction in user level code or writing reserved fields in configuration registers. +- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered for an exception. +- **Triple Fault**: If an exception occurs while the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. + +[SSE instructions]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions + +For the full list of exceptions check out the [OSDev wiki][exceptions]. + +[exceptions]: https://wiki.osdev.org/Exceptions + +### The Interrupt Descriptor Table +In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure: + +Type| Name | Description +----|--------------------------|----------------------------------- +u16 | Function Pointer [0:15] | The lower bits of the pointer to the handler function. +u16 | GDT selector | Selector of a code segment in the [global descriptor table]. +u16 | Options | (see below) +u16 | Function Pointer [16:31] | The middle bits of the pointer to the handler function. +u32 | Function Pointer [32:63] | The remaining bits of the pointer to the handler function. +u32 | Reserved | + +[global descriptor table]: https://en.wikipedia.org/wiki/Global_Descriptor_Table + +The options field has the following format: + +Bits | Name | Description +------|-----------------------------------|----------------------------------- +0-2 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called. +3-7 | Reserved | +8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. +9-11 | must be one | +12 | must be zero | +13‑14 | Descriptor Privilege Level (DPL) | The minimal privilege level required for calling this handler. +15 | Present | + +Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column. + +When an exception occurs, the CPU roughly does the following: + +1. Push some registers on the stack, including the instruction pointer and the [RFLAGS] register. (We will use these values later in this post.) +2. Read the corresponding entry from the Interrupt Descriptor Table (IDT). For example, the CPU reads the 14-th entry when a page fault occurs. +3. Check if the entry is present. Raise a double fault if not. +4. Disable hardware interrupts if the entry is an interrupt gate (bit 40 not set). +5. Load the specified [GDT] selector into the CS segment. +6. Jump to the specified handler function. + +[RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register +[GDT]: https://en.wikipedia.org/wiki/Global_Descriptor_Table + +Don't worry about steps 4 and 5 for now, we will learn about the global descriptor table and hardware interrupts in future posts. + +## An IDT Type +Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this: + +[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html + +``` rust +#[repr(C)] +pub struct InterruptDescriptorTable { + pub divide_by_zero: Entry, + pub debug: Entry, + pub non_maskable_interrupt: Entry, + pub breakpoint: Entry, + pub overflow: Entry, + pub bound_range_exceeded: Entry, + pub invalid_opcode: Entry, + pub device_not_available: Entry, + pub double_fault: Entry, + pub invalid_tss: Entry, + pub segment_not_present: Entry, + pub stack_segment_fault: Entry, + pub general_protection_fault: Entry, + pub page_fault: Entry, + pub x87_floating_point: Entry, + pub alignment_check: Entry, + pub machine_check: Entry, + pub simd_floating_point: Entry, + pub virtualization: Entry, + pub security_exception: Entry, + // some fields omitted +} +``` + +The fields have the type [`idt::Entry`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`]. + +[`idt::Entry`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.Entry.html +[`HandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFunc.html +[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFuncWithErrCode.html +[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.PageFaultHandlerFunc.html + +Let's look at the `HandlerFunc` type first: + +```rust +type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); +``` + +It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention? + +[type alias]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases +[foreign calling convention]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions + +## The Interrupt Calling Convention +Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the called function and executes it. Afterwards the CPU jumps to the return address and continues the execution of the parent function. + +However, there is a major difference between exceptions and function calls: A function call is invoked voluntary by a compiler inserted `call` instruction, while an exception might occur at _any_ instruction. In order to understand the consequences of this difference, we need to examine function calls in more detail. + +[Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]): + +[Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention +[System V ABI]: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf + +- the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` +- additional arguments are passed on the stack +- results are returned in `rax` and `rdx` + +Note that Rust does not follow the C ABI (in fact, [there isn't even a Rust ABI yet][rust abi]), so these rules apply only to functions declared as `extern "C" fn`. + +[rust abi]: https://github.com/rust-lang/rfcs/issues/600 + +### Preserved and Scratch Registers +The calling convention divides the registers in two parts: _preserved_ and _scratch_ registers. + +The values of _preserved_ registers must remain unchanged across function calls. So a called function (the _“callee”_) is only allowed to overwrite these registers if it restores their original values before returning. Therefore these registers are called _“callee-saved”_. A common pattern is to save these registers to the stack at the function's beginning and restore them just before returning. + +In contrast, a called function is allowed to overwrite _scratch_ registers without restrictions. If the caller wants to preserve the value of a scratch register across a function call, it needs to backup and restore it before the function call (e.g. by pushing it to the stack). So the scratch registers are _caller-saved_. + +On x86_64, the C calling convention specifies the following preserved and scratch registers: + +preserved registers | scratch registers +---|--- +`rbp`, `rbx`, `rsp`, `r12`, `r13`, `r14`, `r15` | `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`, `r9`, `r10`, `r11` +_callee-saved_ | _caller-saved_ + +The compiler knows these rules, so it generates the code accordingly. For example, most functions begin with a `push rbp`, which backups `rbp` on the stack (because it's a callee-saved register). + +### Preserving all Registers +In contrast to function calls, exceptions can occur on _any_ instruction. In most cases we don't even know at compile time if the generated code will cause an exception. For example, the compiler can't know if an instruction causes a stack overflow or a page fault. + +Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for exception handlers. Instead, we need a calling convention means that preserves _all registers_. The `x86-interrupt` calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return. + +Note that this does not mean that all registers are saved to the stack at function entry. Instead, the compiler only backs up the registers that are overwritten by the function. This way, very efficient code can be generated for short functions that only use a few registers. + +### The Interrupt Stack Frame +On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this: + +![function stack frame](function-stack-frame.svg) + +For exception and interrupt handlers, however, pushing a return address would not suffice, since interrupt handlers often run in a different context (stack pointer, CPU flags, etc.). Instead, the CPU performs the following steps when an interrupt occurs: + +1. **Aligning the stack pointer**: An interrupt can occur at any instructions, so the stack pointer can have any value, too. However, some CPU instructions (e.g. some SSE instructions) require that the stack pointer is aligned on a 16 byte boundary, therefore the CPU performs such an alignment right after the interrupt. +2. **Switching stacks** (in some cases): A stack switch occurs when the CPU privilege level changes, for example when a CPU exception occurs in an user mode program. It is also possible to configure stack switches for specific interrupts using the so-called _Interrupt Stack Table_ (described in the next post). +3. **Pushing the old stack pointer**: The CPU pushes the values of the stack pointer (`rsp`) and the stack segment (`ss`) registers at the time when the interrupt occurred (before the alignment). This makes it possible to restore the original stack pointer when returning from an interrupt handler. +4. **Pushing and updating the `RFLAGS` register**: The [`RFLAGS`] register contains various control and status bits. On interrupt entry, the CPU changes some bits and pushes the old value. +5. **Pushing the instruction pointer**: Before jumping to the interrupt handler function, the CPU pushes the instruction pointer (`rip`) and the code segment (`cs`). This is comparable to the return address push of a normal function call. +6. **Pushing an error code** (for some exceptions): For some specific exceptions such as page faults, the CPU pushes an error code, which describes the cause of the exception. +7. **Invoking the interrupt handler**: The CPU reads the address and the segment descriptor of the interrupt handler function from the corresponding field in the IDT. It then invokes this handler by loading the values into the `rip` and `cs` registers. + +[`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register + +So the _interrupt stack frame_ looks like this: + +![interrupt stack frame](exception-stack-frame.svg) + +In the `x86_64` crate, the interrupt stack frame is represented by the [`InterruptStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument. + +[`InterruptStackFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptStackFrame.html + +### Behind the Scenes +The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of: + +- **Retrieving the arguments**: Most calling conventions expect that the arguments are passed in registers. This is not possible for exception handlers, since we must not overwrite any register values before backing them up on the stack. Instead, the `x86-interrupt` calling convention is aware that the arguments already lie on the stack at a specific offset. +- **Returning using `iretq`**: Since the interrupt stack frame completely differs from stack frames of normal function calls, we can't return from handlers functions through the normal `ret` instruction. Instead, the `iretq` instruction must be used. +- **Handling the error code**: The error code, which is pushed for some exceptions, makes things much more complex. It changes the stack alignment (see the next point) and needs to be popped off the stack before returning. The `x86-interrupt` calling convention handles all that complexity. However, it doesn't know which handler function is used for which exception, so it needs to deduce that information from the number of function arguments. That means that the programmer is still responsible to use the correct function type for each exception. Luckily, the `InterruptDescriptorTable` type defined by the `x86_64` crate ensures that the correct function types are used. +- **Aligning the stack**: There are some instructions (especially SSE instructions) that require a 16-byte stack alignment. The CPU ensures this alignment whenever an exception occurs, but for some exceptions it destroys it again later when it pushes an error code. The `x86-interrupt` calling convention takes care of this by realigning the stack in this case. + +If you are interested in more details: We also have a series of posts that explains exception handling using [naked functions] linked [at the end of this post][too-much-magic]. + +[naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md +[too-much-magic]: #too-much-magic + +## Implementation +Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We'll start by creating a new interrupts module in `src/interrupts.rs`, that first creates an `init_idt` function that creates a new `InterruptDescriptorTable`: + +``` rust +// in src/lib.rs + +pub mod interrupts; + +// in src/interrupts.rs + +use x86_64::structures::idt::InterruptDescriptorTable; + +pub fn init_idt() { + let mut idt = InterruptDescriptorTable::new(); +} +``` + +Now we can add handler functions. We start by adding a handler for the [breakpoint exception]. The breakpoint exception is the perfect exception to test exception handling. Its only purpose is to temporarily pause a program when the breakpoint instruction `int3` is executed. + +[breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint + +The breakpoint exception is commonly used in debuggers: When the user sets a breakpoint, the debugger overwrites the corresponding instruction with the `int3` instruction so that the CPU throws the breakpoint exception when it reaches that line. When the user wants to continue the program, the debugger replaces the `int3` instruction with the original instruction again and continues the program. For more details, see the ["_How debuggers work_"] series. + +["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints + +For our use case, we don't need to overwrite any instructions. Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program. So let's create a simple `breakpoint_handler` function and add it to our IDT: + +```rust +// in src/interrupts.rs + +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use crate::println; + +pub fn init_idt() { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); +} + +extern "x86-interrupt" fn breakpoint_handler( + stack_frame: &mut InterruptStackFrame) +{ + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} +``` + +Our handler just outputs a message and pretty-prints the interrupt stack frame. + +When we try to compile it, the following error occurs: + +``` +error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180) + --> src/main.rs:53:1 + | +53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) { +54 | | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +55 | | } + | |_^ + | + = help: add #![feature(abi_x86_interrupt)] to the crate attributes to enable +``` + +This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `lib.rs`. + +### Loading the IDT +In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it: + +[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt +[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load + +```rust +// in src/interrupts.rs + +pub fn init_idt() { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + idt.load(); +} +``` + +When we try to compile it now, the following error occurs: + +``` +error: `idt` does not live long enough + --> src/interrupts/mod.rs:43:5 + | +43 | idt.load(); + | ^^^ does not live long enough +44 | } + | - borrowed value only lives until here + | + = note: borrowed value must be valid for the static lifetime... +``` + +So the `load` methods expects a `&'static self`, that is a reference that is valid for the complete runtime of the program. The reason is that the CPU will access this table on every interrupt until we load a different IDT. So using a shorter lifetime than `'static` could lead to use-after-free bugs. + +In fact, this is exactly what happens here. Our `idt` is created on the stack, so it is only valid inside the `init` function. Afterwards the stack memory is reused for other functions, so the CPU would interpret random stack memory as IDT. Luckily, the `InterruptDescriptorTable::load` method encodes this lifetime requirement in its function definition, so that the Rust compiler is able to prevent this possible bug at compile time. + +In order to fix this problem, we need to store our `idt` at a place where it has a `'static` lifetime. To achieve this we could allocate our IDT on the heap using [`Box`] and then convert it to a `'static` reference, but we are writing an OS kernel and thus don't have a heap (yet). + +[`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html + + +As an alternative we could try to store the IDT as a `static`: + +```rust +static IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); + +pub fn init_idt() { + IDT.breakpoint.set_handler_fn(breakpoint_handler); + IDT.load(); +} +``` + +However, there is a problem: Statics are immutable, so we can't modify the breakpoint entry from our `init` function. We could solve this problem by using a [`static mut`]: + +[`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable + +```rust +static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); + +pub fn init_idt() { + unsafe { + IDT.breakpoint.set_handler_fn(breakpoint_handler); + IDT.load(); + } +} +``` + +This variant compiles without errors but it's far from idiomatic. `static mut`s are very prone to data races, so we need an [`unsafe` block] on each access. + +[`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers + +#### Lazy Statics to the Rescue +Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values. + +We already imported the `lazy_static` crate when we [created an abstraction for the VGA text buffer][vga text buffer lazy static]. So we can directly use the `lazy_static!` macro to create our static IDT: + +[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics + +```rust +// in src/interrupts.rs + +use lazy_static::lazy_static; + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + idt + }; +} + +pub fn init_idt() { + IDT.load(); +} +``` + +Note how this solution requires no `unsafe` blocks. The `lazy_static!` macro does use `unsafe` behind the scenes, but it is abstracted away in a safe interface. + +### Running it + +The last step for making exceptions work in our kernel is to call the `init_idt` function from our `main.rs`. Instead of calling it directly, we introduce a general `init` function in our `lib.rs`: + +```rust +// in src/lib.rs + +pub fn init() { + interrupts::init_idt(); +} +``` + +With this function we now have a central place for initialization routines that can be shared between the different `_start` functions in our `main.rs`, `lib.rs`, and integration tests. + +Now we can update the `_start` function of our `main.rs` to call `init` and then trigger a breakpoint exception: + +```rust +// in src/main.rs + +#[no_mangle] +pub extern "C" fn _start() -> ! { + println!("Hello World{}", "!"); + + blog_os::init(); // new + + // invoke a breakpoint exception + x86_64::instructions::interrupts::int3(); // new + + // as before + #[cfg(test)] + test_main(); + + println!("It did not crash!"); + loop {} +} +``` + +When we run it in QEMU now (using `cargo run`), we see the following: + +![QEMU printing `EXCEPTION: BREAKPOINT` and the interrupt stack frame](qemu-breakpoint-exception.png) + +It works! The CPU successfully invokes our breakpoint handler, which prints the message, and then returns back to the `_start` function, where the `It did not crash!` message is printed. + +We see that the interrupt stack frame tells us the instruction and stack pointers at the time when the exception occurred. This information is very useful when debugging unexpected exceptions. + +### Adding a Test + +Let's create a test that ensures that the above continues to work. First, we update the `_start` function to also call `init`: + +```rust +// in src/lib.rs + +/// Entry point for `cargo test` +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + init(); // new + test_main(); + loop {} +} +``` + +Remember, this `_start` function is used when running `cargo test --lib`, since Rust's tests the `lib.rs` completely independent of the `main.rs`. We need to call `init` here to set up an IDT before running the tests. + +Now we can create a `test_breakpoint_exception` test: + +```rust +// in src/interrupts.rs + +#[test_case] +fn test_breakpoint_exception() { + // invoke a breakpoint exception + x86_64::instructions::interrupts::int3(); +} +``` + +The test invokes the `int3` function to trigger a breakpoint exception. By checking that the execution continues afterwards, we verify that our breakpoint handler is working correctly. + +You can try this new test by running `cargo test` (all tests) or `cargo test --lib` (only tests of `lib.rs` and its modules). You should see the following in the output: + +``` +blog_os::interrupts::test_breakpoint_exception... [ok] +``` + +## Too much Magic? +The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date. + +[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md +[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[first edition]: @/edition-1/_index.md + +## What's next? +We've successfully caught our first exception and returned from it! The next step is to ensure that we catch all exceptions, because an uncaught exception causes a fatal [triple fault], which leads to a system reset. The next post explains how we can avoid this by correctly catching [double faults]. + +[triple fault]: https://wiki.osdev.org/Triple_Fault +[double faults]: https://wiki.osdev.org/Double_Fault#Double_Fault From 07797673555e15f33766be865f019b8aa465cc5d Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Mon, 8 Mar 2021 15:34:09 +0900 Subject: [PATCH 002/125] Finish translation --- .../posts/05-cpu-exceptions/index.ja.md | 253 +++++++++--------- 1 file changed, 130 insertions(+), 123 deletions(-) diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md index daf46fa0..b47b69f3 100644 --- a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md @@ -1,20 +1,24 @@ +++ -title = "CPU Exceptions" +title = "CPU例外" weight = 5 -path = "cpu-exceptions" +path = "ja/cpu-exceptions" date = 2018-06-17 [extra] chapter = "Interrupts" +# Please update this when updating the translation +translation_based_on_commit = "904d203f3696ebefa7ca037d403a391ccbb2a867" +# GitHub usernames of the people that translated this post +translators = ["woodyZootopia"] +++ -CPU exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To react to them we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards. +CPU例外は、例えば無効なメモリアドレスにアクセスしたときやゼロ除算したときなど、様々なミスによって発生します。それらに対して反応するために、ハンドラ関数を提供する **割り込み記述子表 (interrupt descriptor table) ** を設定しなくてはなりません。この記事を読み終わる頃には、私達のカーネルは[ブレークポイント例外][breakpoint exceptions]を捕捉し、その後通常の実行を継続できるようになっているでしょう。 [breakpoint exceptions]: https://wiki.osdev.org/Exceptions#Breakpoint -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-05`][post branch] branch. +このブログの内容は [GitHub] 上で公開・開発されています。何か問題や質問などがあれば issue をたててください (訳注: リンクは原文(英語)のものになります)。また[こちら][at the bottom]にコメントを残すこともできます。この記事の完全なソースコードは[`post-05` ブランチ][post branch]にあります。 [GitHub]: https://github.com/phil-opp/blog_os [at the bottom]: #comments @@ -22,67 +26,67 @@ This blog is openly developed on [GitHub]. If you have any problems or questions -## Overview -An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type. +## 概要 +例外とは、今実行している命令はなにかおかしいぞ、ということを示すものです。例えば、現在の命令が0で割ろうとするときにCPUは例外を発します。例外が起こったら、CPUは現在行われている作業に割り込み、例外の種類に従って、即座に特定の例外ハンドラ関数を呼びます。 -On x86 there are about 20 different CPU exception types. The most important are: +x86には20種類のCPU例外があります。中でも重要なものは: -- **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page. -- **Invalid Opcode**: This exception occurs when the current instruction is invalid, for example when we try to use newer [SSE instructions] on an old CPU that does not support them. -- **General Protection Fault**: This is the exception with the broadest range of causes. It occurs on various kinds of access violations such as trying to execute a privileged instruction in user level code or writing reserved fields in configuration registers. -- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered for an exception. -- **Triple Fault**: If an exception occurs while the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. +- **ページフォルト (Page Fault) **: ページフォルトは不正なメモリアクセスの際に発生します。例えば、現在の命令がマップされていないページから読み込もうとしたり、読み込み専用のページに書き込もうとしたときに生じます。 +- **無効な (Invalid) 命令コード (Opcode) **: この例外は現在の命令が無効であるときに発生します。例えば、[SSE命令][SSE instructions]という新しい命令をサポートしていない旧式のCPU上でこれを実行しようとしたときに生じます。 +- **一般保護違反 (General Protection Fault) **: これは、例外の中でも、最もいろいろな理由で発生しうるものです。ユーザーレベルのコードで特権命令 (privileged instruction) を実行しようとしたときや、設定レジスタの保護領域に書き込もうとしたときなど、様々な種類のアクセス違反によって生じます。 +- **ダブルフォルト (Double Fault) **: 例外が起こったとき、CPUは対応するハンドラ関数を呼び出そうとします。 この例外ハンドラを **呼び出している間に** 別の例外が起こった場合、CPUはダブルフォルト例外を出します。この例外はまた、ある例外に対してハンドラ関数が登録されていないときにも起こります。 +- **トリプルフォルト (Triple Fault) **: CPUがダブルフォルトのハンドラ関数を呼び出そうとしている間に例外が発生したら、CPUは **トリプルフォルト** という致命的な例外を発します。トリプルフォルトを捕捉したり処理したりすることはできません。これが起こると、多くのプロセッサは自らをリセットしてOSを再起動することで対応します。 [SSE instructions]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions -For the full list of exceptions check out the [OSDev wiki][exceptions]. +例外の完全な一覧を見たい場合は、[OSDev wiki][exceptions]を見てください。 [exceptions]: https://wiki.osdev.org/Exceptions -### The Interrupt Descriptor Table -In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure: +### 割り込み記述子表 +例外を捕捉し処理するためには、いわゆる割り込み記述子表 (interrupt descriptor table) (IDT) を設定しないといけません。この表にそれぞれのCPU例外に対するハンドラ関数を指定することができます。ハードウェアはこの表を直接使うので、決められたフォーマットに従わないといけません。それぞれのエントリは以下の16バイトの構造を持たなければなりません: -Type| Name | Description +型 | 名前 | 説明 ----|--------------------------|----------------------------------- -u16 | Function Pointer [0:15] | The lower bits of the pointer to the handler function. -u16 | GDT selector | Selector of a code segment in the [global descriptor table]. -u16 | Options | (see below) -u16 | Function Pointer [16:31] | The middle bits of the pointer to the handler function. -u32 | Function Pointer [32:63] | The remaining bits of the pointer to the handler function. -u32 | Reserved | +u16 | 関数ポインタ [0:15] | ハンドラ関数へのポインタの下位ビット。 +u16 | GDTセレクタ | [大域記述子表 (global descriptor table) ][global descriptor table]におけるコードセグメントを選ぶ。 +u16 | オプション | (下を参照) +u16 | 関数ポインタ [16:31] | ハンドラ関数へのポインタの中位ビット。 +u32 | 関数ポインタ [32:63] | ハンドラ関数へのポインタの上位ビット。 +u32 | 予約済 | [global descriptor table]: https://en.wikipedia.org/wiki/Global_Descriptor_Table -The options field has the following format: +オプション部は以下のフォーマットになっています: -Bits | Name | Description -------|-----------------------------------|----------------------------------- -0-2 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called. -3-7 | Reserved | -8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. -9-11 | must be one | -12 | must be zero | -13‑14 | Descriptor Privilege Level (DPL) | The minimal privilege level required for calling this handler. -15 | Present | +ビット | 名前 | 説明 +--------|------------------------------------------------------------------------------------------------|------------------------------------- +0-2 | 割り込みスタックテーブルインデックス | 0ならスタックを変えない。1から7なら、ハンドラが呼ばれたとき、割り込みスタック表のその数字のスタックに変える。 +3-7 | 予約済 | +8 | 0: 割り込みゲート、1: トラップゲート | 0なら、このハンドラが呼ばれたとき割り込みは無効化される。 +9-11 | 1にしておかないといけない | +12 | 0にしておかないといけない | +13‑14 | 記述子の特権レベル (Descriptor Privilege Level) (DPL) | このハンドラを呼ぶ際に必要になる最低限の特権レベル。 +15 | Present | -Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column. +それぞれの例外がIDTの何番目に対応するかは事前に定義されています。例えば、"無効な命令コード"例外は6番目で、"ページフォルト"例外は14番目です。これにより、ハードウェアがそれぞれの例外に対応するIDTの設定を(特に設定の必要なく)自動的に読み出せるというわけです。OSDev wikiの[「例外表」][exceptions]の "Vector nr." 列にすべての例外のIDTインデックスが記されています。 -When an exception occurs, the CPU roughly does the following: +例外が起こると、ざっくりCPUは以下のことを行います: -1. Push some registers on the stack, including the instruction pointer and the [RFLAGS] register. (We will use these values later in this post.) -2. Read the corresponding entry from the Interrupt Descriptor Table (IDT). For example, the CPU reads the 14-th entry when a page fault occurs. -3. Check if the entry is present. Raise a double fault if not. -4. Disable hardware interrupts if the entry is an interrupt gate (bit 40 not set). -5. Load the specified [GDT] selector into the CS segment. -6. Jump to the specified handler function. +1. 命令ポインタと[RFLAGS]レジスタ(これらの値は後で使います)を含むレジスタをスタックにプッシュする。 +2. 割り込み記述子表から対応するエントリを読む。例えば、ページフォルトが起こったときはCPUは14番目のエントリを読む。 +3. エントリが存在しているのかチェックする。そうでなければダブルフォルトを起こす。 +4. エントリが割り込みゲートなら(40番目のビットが0なら)ハードウェア割り込みを無効にする。 +5. 指定された[GDT]セレクタをCSセグメントに読み込む。 +6. 指定されたハンドラ関数にジャンプする。 [RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register [GDT]: https://en.wikipedia.org/wiki/Global_Descriptor_Table -Don't worry about steps 4 and 5 for now, we will learn about the global descriptor table and hardware interrupts in future posts. +ステップ4と5について今深く考える必要はありません。今後の記事で大域記述子表 (Global Descriptor Table, 略してGDT) とハードウェア割り込みについては学んでいきます。 -## An IDT Type -Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this: +## IDT型 +自前でIDTの型を作る代わりに、`x86_64`クレートの[`InterruptDescriptorTable`構造体][`InterruptDescriptorTable` struct]を使います。こんな見た目をしています: [`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html @@ -109,109 +113,109 @@ pub struct InterruptDescriptorTable { pub simd_floating_point: Entry, pub virtualization: Entry, pub security_exception: Entry, - // some fields omitted + // いくつかのフィールドは省略している } ``` -The fields have the type [`idt::Entry`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`]. +この構造体のフィールドは[`idt::Entry`]という型を持っています。これはIDTのエントリのフィールド(上の表を見てください)を表す構造体です。型パラメータ`F`は、期待されるハンドラ関数の型を表します。エントリの中には、[`HandlerFunc`]型を要求するものや、[`HandlerFuncWithErrCode`]型を要求するものがあることがわかります。ページフォルトに至っては、[`PageFaultHandlerFunc`]という自分専用の型を要求していますね。 [`idt::Entry`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.Entry.html [`HandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFunc.html [`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFuncWithErrCode.html [`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.PageFaultHandlerFunc.html -Let's look at the `HandlerFunc` type first: +まず`HandlerFunc`型を見てみましょう: ```rust type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); ``` -It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention? +これは、`extern "x86-interrupt" fn`型への[型エイリアス][type alias]です。`extern`は[外部呼び出し規約][foreign calling convention]に従う関数を定義するのに使われ、おもにC言語のコードとの間で通信をしたいときに使われます (`extern "C" fn`) 。しかし、`x86-interrupt`呼び出し規約とは何なのでしょう? [type alias]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases [foreign calling convention]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions -## The Interrupt Calling Convention -Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the called function and executes it. Afterwards the CPU jumps to the return address and continues the execution of the parent function. +## 例外の呼び出し規約 +例外は関数呼び出しと非常に似ています。CPUが呼び出された関数の最初の命令にジャンプし、それを実行します。その後、CPUはリターンアドレスにジャンプし、親関数の実行を続けます。 -However, there is a major difference between exceptions and function calls: A function call is invoked voluntary by a compiler inserted `call` instruction, while an exception might occur at _any_ instruction. In order to understand the consequences of this difference, we need to examine function calls in more detail. +しかし、例外と関数呼び出しには大きな違いが一つあるのです:関数呼び出しはコンパイラによって挿入された`call`命令によって自発的に引き起こされますが、例外は **どんな命令の実行中でも** 起こる可能性があるのです。この違いの結果を理解するためには、関数呼び出しについてより詳しく見ていく必要があります。 -[Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]): +[呼び出し規約][Calling conventions]は関数呼び出しについて事細かく指定しています。例えば、関数のパラメータがどこに置かれるべきか(例えば、レジスタなのかスタックなのか)や、結果がどのように返されるべきかを指定しています。x86_64上のLinuxでは、C言語の関数に関しては以下のルールが適用されます(これは[System V ABI]で指定されています): [Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention [System V ABI]: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf -- the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` -- additional arguments are passed on the stack -- results are returned in `rax` and `rdx` +- 最初の6つの整数引数は、レジスタ`rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`で渡される +- 追加の引数はスタックで渡される +- 結果は`rax`と`rdx`で返される -Note that Rust does not follow the C ABI (in fact, [there isn't even a Rust ABI yet][rust abi]), so these rules apply only to functions declared as `extern "C" fn`. +注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustのABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。 [rust abi]: https://github.com/rust-lang/rfcs/issues/600 -### Preserved and Scratch Registers -The calling convention divides the registers in two parts: _preserved_ and _scratch_ registers. +### PreservedレジスタとScratchレジスタ +呼び出し規約はレジスタを2種類に分けています:preserved (保存) レジスタとscratch (下書き) レジスタです。 -The values of _preserved_ registers must remain unchanged across function calls. So a called function (the _“callee”_) is only allowed to overwrite these registers if it restores their original values before returning. Therefore these registers are called _“callee-saved”_. A common pattern is to save these registers to the stack at the function's beginning and restore them just before returning. +preservedレジスタの値は関数呼び出しの前後で変化してはいけません。ですので、呼び出された関数(訳注:callの受け身で"callee"と呼ばれます)は、リターンする前にその値をもとに戻す場合に限り、その値を上書きできます。そのため、これらのレジスタはcallee-saved (呼び出し先によって保存される) と呼ばれます。よくやる方法は、関数の最初でそのレジスタをスタックに保存し、リターンする直前にその値をもとに戻すことです。 -In contrast, a called function is allowed to overwrite _scratch_ registers without restrictions. If the caller wants to preserve the value of a scratch register across a function call, it needs to backup and restore it before the function call (e.g. by pushing it to the stack). So the scratch registers are _caller-saved_. +それとは対照的に、呼び出された関数はscratchレジスタを何の制限もなく上書きすることができます。呼び出し元の関数がscratchレジスタの値を関数呼び出しの間保存しておきたいなら、関数呼び出しの前に自分で(スタックにプッシュするなどして)バックアップしておいて、もとに戻す必要があります。なので、scratchレジスタはcaller-saved (呼び出し元によって保存される) です。 -On x86_64, the C calling convention specifies the following preserved and scratch registers: +x86_64においては、C言語の呼び出し規約は以下のpreservedとscratchレジスタを指定します: -preserved registers | scratch registers ----|--- +preservedレジスタ | scratchレジスタ +--- | --- `rbp`, `rbx`, `rsp`, `r12`, `r13`, `r14`, `r15` | `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`, `r9`, `r10`, `r11` -_callee-saved_ | _caller-saved_ +_callee-saved_ | _caller-saved_ -The compiler knows these rules, so it generates the code accordingly. For example, most functions begin with a `push rbp`, which backups `rbp` on the stack (because it's a callee-saved register). +コンパイラはこれらのルールを知っているので、それにしたがってコードを生成します。例えば、ほとんどの関数は`push rbp`から始まるのですが、これは`rbp`をスタックにバックアップしているのです(`rbp`はcallee-savedなレジスタであるため)。 -### Preserving all Registers -In contrast to function calls, exceptions can occur on _any_ instruction. In most cases we don't even know at compile time if the generated code will cause an exception. For example, the compiler can't know if an instruction causes a stack overflow or a page fault. +### すべてのレジスタを保存する +関数呼び出しとは対象的に、例外は **どんな命令の最中にも** 起きる可能性があります。多くの場合、生成されたコードが例外を引き起こすのかどうかは、コンパイル時には見当も付きません。例えば、コンパイラはある命令がスタックオーバーフローやページフォルトを起こすのか知ることができません。 -Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for exception handlers. Instead, we need a calling convention means that preserves _all registers_. The `x86-interrupt` calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return. +いつ例外が起きるのかわからない以上、レジスタを事前にバックアップしておくことは不可能です。つまり、caller-savedレジスタを利用する呼び出し規約は、例外ハンドラには使えないということです。代わりに、 **すべてのレジスタを** 保存する規約を使わないといけません。`x86-interrupt`呼び出し規約はそのような呼び出し規約なので、関数が戻るときにすべてのレジスタが元の値に戻されることを保証してくれるというわけです。 -Note that this does not mean that all registers are saved to the stack at function entry. Instead, the compiler only backs up the registers that are overwritten by the function. This way, very efficient code can be generated for short functions that only use a few registers. +これは、関数の初めにすべてのレジスタがスタックに保存されるということを意味しないことに注意してください。その代わりに、コンパイラは関数によって上書きされてしまうレジスタのみをバックアップします。こうすれば、数個のレジスタしか使わない短い関数に対して、とても効率的なコードが生成できるでしょう。 ### The Interrupt Stack Frame -On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this: +通常の関数呼び出し(`call`命令を使います)においては、CPUは対象の関数にジャンプする前にリターンアドレスをプッシュします。関数がリターンするとき(`ret`命令を使います)、CPUはこのリターンアドレスをポップし、そこにジャンプします。そのため、通常の関数呼び出しの際のスタックフレームは以下のようになっています: ![function stack frame](function-stack-frame.svg) -For exception and interrupt handlers, however, pushing a return address would not suffice, since interrupt handlers often run in a different context (stack pointer, CPU flags, etc.). Instead, the CPU performs the following steps when an interrupt occurs: +しかし、例外と割り込みハンドラについては、リターンアドレスをプッシュするだけではだめです。なぜなら、割り込みハンドラはしばしば(スタックポインタや、CPUフラグなどが)異なる状況で実行されるからです。ですので、代わりに、CPUは割り込みが起こると以下の手順を実行します。 -1. **Aligning the stack pointer**: An interrupt can occur at any instructions, so the stack pointer can have any value, too. However, some CPU instructions (e.g. some SSE instructions) require that the stack pointer is aligned on a 16 byte boundary, therefore the CPU performs such an alignment right after the interrupt. -2. **Switching stacks** (in some cases): A stack switch occurs when the CPU privilege level changes, for example when a CPU exception occurs in an user mode program. It is also possible to configure stack switches for specific interrupts using the so-called _Interrupt Stack Table_ (described in the next post). -3. **Pushing the old stack pointer**: The CPU pushes the values of the stack pointer (`rsp`) and the stack segment (`ss`) registers at the time when the interrupt occurred (before the alignment). This makes it possible to restore the original stack pointer when returning from an interrupt handler. -4. **Pushing and updating the `RFLAGS` register**: The [`RFLAGS`] register contains various control and status bits. On interrupt entry, the CPU changes some bits and pushes the old value. -5. **Pushing the instruction pointer**: Before jumping to the interrupt handler function, the CPU pushes the instruction pointer (`rip`) and the code segment (`cs`). This is comparable to the return address push of a normal function call. -6. **Pushing an error code** (for some exceptions): For some specific exceptions such as page faults, the CPU pushes an error code, which describes the cause of the exception. -7. **Invoking the interrupt handler**: The CPU reads the address and the segment descriptor of the interrupt handler function from the corresponding field in the IDT. It then invokes this handler by loading the values into the `rip` and `cs` registers. +1. **スタックポインタをアラインする**: 割り込みはあらゆる命令において発生しうるので、スタックポインタもあらゆる値を取る可能性があります。しかし、CPU命令のうちいくつか(例えばSSE命令の一部など)はスタックポインタが16バイトの倍数になっていることを要求するので、そうなるようにCPUは割り込みの直後にスタックポインタを揃え (アラインし) ます。 +2. (場合によっては)**スタックを変更する**: スタックの変更は、例えばCPU例外がユーザーモードのプログラムで起こったときに、CPUの特権レベルを変更するときに起こります。いわゆる割り込みスタック表 (Interrupt Stack Table) を使うことで、特定の割り込みに対しスタックを変更するよう設定することも可能です。割り込みスタック表については次の記事で説明します。 +3. **古いスタックポインタをプッシュする**: CPUは、割り込みが発生した際の(アラインされる前の)スタックポインタレジスタ(`rsp`)とスタックセグメントレジスタ(`ss`)の値をプッシュします。これにより、割り込みハンドラからリターンしてきたときにもとのスタックポインタを復元することが可能になります。 +4. **`RFLAGS`レジスタをプッシュして更新する**: [`RFLAGS`]レジスタは状態や制御のための様々なビットを保持しています。割り込みに入るとき、CPUはビットのうちいくつかを変更し古い値をプッシュしておきます。 +5. **命令ポインタをプッシュする**: 割り込みハンドラ関数にジャンプする前に、CPUは命令ポインタ(`rip`)とコードセグメント(`cs`)をプッシュします。これは通常の関数呼び出しにおける戻り値のプッシュに対応します。 +6. **エラーコードをプッシュする** (for some exceptions): ページフォルトのような特定の例外の場合、CPUはエラーコードをプッシュします。これは、例外の原因を説明するものです。 +7. **割り込みハンドラを呼び出す**: CPUは割り込みハンドラ関数のアドレスとセグメント記述子 (segment descriptor) をIDTの対応するフィールドから読み出します。そして、この値を`rip`と`cs`レジスタに書き出してから、ハンドラを呼び出します。 [`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register -So the _interrupt stack frame_ looks like this: +ですので、割り込み時のスタックフレーム (interrupt stack frame) は以下のようになります: ![interrupt stack frame](exception-stack-frame.svg) -In the `x86_64` crate, the interrupt stack frame is represented by the [`InterruptStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument. +`x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡すことができ、これを使うことで例外の原因に関して追加で情報を手に入れることができます。エラーコードは例外のうちいくつかしかプッシュしないので、構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。 [`InterruptStackFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptStackFrame.html -### Behind the Scenes -The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of: +### 舞台裏では何が +`x86-interrupt`呼び出し規約は、この例外処理 (ハンドル) プロセスのややこしいところをほぼ全て隠蔽してくれる、強力な抽象化です。しかし、その後ろで何が起こっているのかを知っておいたほうが良いこともあるでしょう。以下に、`x86-interrupt`呼び出し規約がやってくれることを簡単なリストにして示しました。 -- **Retrieving the arguments**: Most calling conventions expect that the arguments are passed in registers. This is not possible for exception handlers, since we must not overwrite any register values before backing them up on the stack. Instead, the `x86-interrupt` calling convention is aware that the arguments already lie on the stack at a specific offset. -- **Returning using `iretq`**: Since the interrupt stack frame completely differs from stack frames of normal function calls, we can't return from handlers functions through the normal `ret` instruction. Instead, the `iretq` instruction must be used. -- **Handling the error code**: The error code, which is pushed for some exceptions, makes things much more complex. It changes the stack alignment (see the next point) and needs to be popped off the stack before returning. The `x86-interrupt` calling convention handles all that complexity. However, it doesn't know which handler function is used for which exception, so it needs to deduce that information from the number of function arguments. That means that the programmer is still responsible to use the correct function type for each exception. Luckily, the `InterruptDescriptorTable` type defined by the `x86_64` crate ensures that the correct function types are used. -- **Aligning the stack**: There are some instructions (especially SSE instructions) that require a 16-byte stack alignment. The CPU ensures this alignment whenever an exception occurs, but for some exceptions it destroys it again later when it pushes an error code. The `x86-interrupt` calling convention takes care of this by realigning the stack in this case. +- **引数を取得する**: 多くの呼び出し規約においては、引数はレジスタを使って渡されることを想定しています。例外ハンドラにおいては、スタックにバックアップする前にレジスタの値を上書きしてはいけないので、これは不可能です。代わりに、`x86-interrupt`呼び出し規約は、引数が既に特定のオフセットでスタック上にあることを認識しています。 +- **`iretq`を使ってリターンする**: 割り込み時のスタックフレームは通常の関数呼び出しのスタックフレームとは全く異なるため、通常の `ret` 命令を使ってハンドラ関数から戻ることはできません。その代わりに、`iretq` 命令を使う必要があります。 +- **エラーコードを処理する**: いくつかの例外の場合、エラーコードがプッシュされるのですが、これが状況をより複雑にします。エラーコードはスタックのアラインメントを変更し(次の箇条を参照)、リターンする前にスタックからポップされる必要があります。`x86-interrupt`呼び出し規約は、このややこしい仕組みをすべて処理してくれます。しかし、どのハンドラ関数がどの例外に使われているかは呼び出し規約側にはわからないので、関数の引数の数からその情報を推測する必要があります。つまり、プログラマはやはりそれぞれの例外に対して正しい関数型を使う責任があるということです。幸いにも、`x86_64`クレートで定義されている`InterruptDescriptorTable`型が、正しい関数型が確実に使われるようにしてくれます。 +- **スタックをアラインする**: 一部の命令(特にSSE命令)には、16バイトのスタックアラインメントを必要とするものがあります。CPUは例外が発生したときには必ずこのようにスタックが整列 (アライン) されることを保証しますが、例外の中には、エラーコードをプッシュしたとき再びスタックの整列を壊してしまうものもあります。この場合、`x86-interrupt`の呼び出し規約は、スタックを再整列させることでこの問題を解決します。 -If you are interested in more details: We also have a series of posts that explains exception handling using [naked functions] linked [at the end of this post][too-much-magic]. +もしより詳しく知りたい場合は、例外の処理について[naked function][naked functions]を使って説明する一連の記事があります。[この記事の最下部][too-much-magic]にそこへのリンクがあります。 [naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md -[too-much-magic]: #too-much-magic +[too-much-magic]: #sasuganijian-dan-sugi -## Implementation -Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We'll start by creating a new interrupts module in `src/interrupts.rs`, that first creates an `init_idt` function that creates a new `InterruptDescriptorTable`: +## 実装 +理論を理解したところで、私達のカーネルでCPUの例外を実際に処理していきましょう。まず、`src/interrupts.rs`に新しい割り込みのためのモジュールを作ります。このモジュールはまず、`init_idt`関数という、新しい`InterruptDescriptorTable`を作る関数を定義します。 ``` rust // in src/lib.rs @@ -227,15 +231,15 @@ pub fn init_idt() { } ``` -Now we can add handler functions. We start by adding a handler for the [breakpoint exception]. The breakpoint exception is the perfect exception to test exception handling. Its only purpose is to temporarily pause a program when the breakpoint instruction `int3` is executed. +これで、ハンドラ関数を追加していくことができます。まず、[ブレークポイント例外][breakpoint exception]のハンドラを追加するところから始めましょう。ブレークポイント例外は、例外処理のテストをするのにうってつけの例外なのです。この例外の唯一の目的は、ブレークポイント命令`int3`が実行された時、プログラムを一時停止させるということです。 [breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint -The breakpoint exception is commonly used in debuggers: When the user sets a breakpoint, the debugger overwrites the corresponding instruction with the `int3` instruction so that the CPU throws the breakpoint exception when it reaches that line. When the user wants to continue the program, the debugger replaces the `int3` instruction with the original instruction again and continues the program. For more details, see the ["_How debuggers work_"] series. +ブレークポイント例外はよくデバッガによって使われます。ユーザーがブレークポイントを設定すると、デバッガが対応する命令を`int3`命令で置き換え、その行に到達したときにCPUがブレークポイント例外を投げるようにするのです。ユーザがプログラムを続行したい場合は、デバッガは`int3`命令をもとの命令に戻してプログラムを再開します。より詳しく知るには、[How debuggers work]["_How debuggers work_"]というシリーズ記事を読んでください。 ["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints -For our use case, we don't need to overwrite any instructions. Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program. So let's create a simple `breakpoint_handler` function and add it to our IDT: +今回の場合、命令を上書きする必要はありません。ブレークポイント命令が実行された時、メッセージを表示したうえで実行を継続したいだけです。ですので、単純な`breakpoint_handler`関数を作ってIDTに追加してみましょう。 ```rust // in src/interrupts.rs @@ -255,9 +259,9 @@ extern "x86-interrupt" fn breakpoint_handler( } ``` -Our handler just outputs a message and pretty-prints the interrupt stack frame. +私達のハンドラは、ただメッセージを出力し、割り込みスタックフレームを整形して出力するだけです。 -When we try to compile it, the following error occurs: +これをコンパイルしようとすると、以下のエラーが起こります: ``` error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180) @@ -271,10 +275,10 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue = help: add #![feature(abi_x86_interrupt)] to the crate attributes to enable ``` -This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `lib.rs`. +このエラーは、`x86-interrupt`呼び出し規約がまだ不安定なために発生します。これを強制的に使うためには、`lib.rs`の最初に`#![feature(abi_x86_interrupt)]`を追記して、この機能を明示的に有効化してやる必要があります。 -### Loading the IDT -In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it: +### IDTを読み込む +CPUがこの割り込みディスクリプタテーブル(IDT)を使用するためには、[`lidt`]命令を使ってこれを読み込む必要があります。`x86_64`の`InterruptDescriptorTable`構造体には、そのための[`load`][InterruptDescriptorTable::load]というメソッド関数が用意されています。それを使ってみましょう: [`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt [InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load @@ -289,7 +293,7 @@ pub fn init_idt() { } ``` -When we try to compile it now, the following error occurs: +これをコンパイルしようとすると、以下のエラーが発生します: ``` error: `idt` does not live long enough @@ -303,16 +307,16 @@ error: `idt` does not live long enough = note: borrowed value must be valid for the static lifetime... ``` -So the `load` methods expects a `&'static self`, that is a reference that is valid for the complete runtime of the program. The reason is that the CPU will access this table on every interrupt until we load a different IDT. So using a shorter lifetime than `'static` could lead to use-after-free bugs. +`load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、use-after-free (解放後にアクセス) バグが発生する可能性があります。 -In fact, this is exactly what happens here. Our `idt` is created on the stack, so it is only valid inside the `init` function. Afterwards the stack memory is reused for other functions, so the CPU would interpret random stack memory as IDT. Luckily, the `InterruptDescriptorTable::load` method encodes this lifetime requirement in its function definition, so that the Rust compiler is able to prevent this possible bug at compile time. +実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはランダムに中身が変更されたスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。 -In order to fix this problem, we need to store our `idt` at a place where it has a `'static` lifetime. To achieve this we could allocate our IDT on the heap using [`Box`] and then convert it to a `'static` reference, but we are writing an OS kernel and thus don't have a heap (yet). +この問題を解決するには、`idt`を`'static`なライフタイムの場所に格納する必要があります。これを達成するには、[`Box`]を使ってIDTをヒープに割当て、続いてそれを`'static`な参照に変換すればよいです。しかし、私達はOSのカーネルを書いている途中であり、(まだ)ヒープを持っていません。 [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html -As an alternative we could try to store the IDT as a `static`: +別の方法として、IDTを`static`として保存してみましょう: ```rust static IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); @@ -323,7 +327,7 @@ pub fn init_idt() { } ``` -However, there is a problem: Statics are immutable, so we can't modify the breakpoint entry from our `init` function. We could solve this problem by using a [`static mut`]: +しかし、問題が発生します:staticは不変 (イミュータブル) なので、`init`関数でエントリを変更することができません。これは[`static mut`]を使って解決できそうです: [`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable @@ -338,14 +342,14 @@ pub fn init_idt() { } ``` -This variant compiles without errors but it's far from idiomatic. `static mut`s are very prone to data races, so we need an [`unsafe` block] on each access. +このように変更するとエラーなくコンパイルできますが、このような書き方は全く慣用的ではありません。`static mut`はデータ競合を非常に起こしやすいので、アクセスするたびに[unsafeブロック][`unsafe` block]が必要になります。 [`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers -#### Lazy Statics to the Rescue -Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values. +#### Lazy Staticsならきっとなんとかしてくれる +幸いにも、例の`lazy_static`マクロが存在します。このマクロは`static`をコンパイル時に評価する代わりに、最初に参照されたときに初期化を行います。このため、初期化時にはほとんどすべてのことができ、実行時にのみ決定する値を読み込むこともできます。 -We already imported the `lazy_static` crate when we [created an abstraction for the VGA text buffer][vga text buffer lazy static]. So we can directly use the `lazy_static!` macro to create our static IDT: +[VGAテキストバッファの抽象化をした][vga text buffer lazy static]ときに、すでに`lazy_static`クレートはインポートしました。そのため、すぐに`lazy_static!`マクロを使って静的なIDTを作ることができます。 [vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics @@ -367,11 +371,11 @@ pub fn init_idt() { } ``` -Note how this solution requires no `unsafe` blocks. The `lazy_static!` macro does use `unsafe` behind the scenes, but it is abstracted away in a safe interface. +この解決法では、`unsafe`ブロックが必要ないことに注目してください。`lazy_static!`マクロはその内部で確かに`unsafe`を使っているのですが、安全なインターフェースの中に抽象化されているのです。 -### Running it +### 実行する -The last step for making exceptions work in our kernel is to call the `init_idt` function from our `main.rs`. Instead of calling it directly, we introduce a general `init` function in our `lib.rs`: +カーネルで例外を動作させるための最後のステップは、`main.rs`から`init_idt`関数を呼び出すことです。直接呼び出す代わりに、より一般的な`init`関数を`lib.rs`に導入します: ```rust // in src/lib.rs @@ -382,8 +386,9 @@ pub fn init() { ``` With this function we now have a central place for initialization routines that can be shared between the different `_start` functions in our `main.rs`, `lib.rs`, and integration tests. +この関数により、`main.rs`、`lib.rs`、それに結合テストにおける異なる`_start`関数で共有できる、初期化ルーチンの中心地ができました。 -Now we can update the `_start` function of our `main.rs` to call `init` and then trigger a breakpoint exception: +`main.rs`内の`_start`関数を更新して、`init`を呼び出し、そのあとブレークポイント例外を発生させるようにしてみましょう: ```rust // in src/main.rs @@ -406,18 +411,20 @@ pub extern "C" fn _start() -> ! { } ``` -When we run it in QEMU now (using `cargo run`), we see the following: +(`cargo run`を使って)QEMU内でこれを実行すると、以下のようになります ![QEMU printing `EXCEPTION: BREAKPOINT` and the interrupt stack frame](qemu-breakpoint-exception.png) -It works! The CPU successfully invokes our breakpoint handler, which prints the message, and then returns back to the `_start` function, where the `It did not crash!` message is printed. +うまくいきました!CPUは私達のブレークポイントハンドラを呼び出すのに成功し、これがメッセージを出力し、そのあと`_start`関数に戻って、`It did not crash!`のメッセージを出力しました。 -We see that the interrupt stack frame tells us the instruction and stack pointers at the time when the exception occurred. This information is very useful when debugging unexpected exceptions. +割り込みスタックフレームは、例外が発生した時の命令とスタックポインタを教えてくれることがわかります。これは、予期せぬ例外をデバッグする際に非常に便利です。 -### Adding a Test +### テストを追加する Let's create a test that ensures that the above continues to work. First, we update the `_start` function to also call `init`: +上記の動作が継続することを確認するテストを作成してみましょう。まず、`_start` 関数を更新して `init` を呼び出すようにします。 + ```rust // in src/lib.rs @@ -431,9 +438,9 @@ pub extern "C" fn _start() -> ! { } ``` -Remember, this `_start` function is used when running `cargo test --lib`, since Rust's tests the `lib.rs` completely independent of the `main.rs`. We need to call `init` here to set up an IDT before running the tests. +Rustのテストでは、`main.rs`とは全く無関係に`lib.rs`をテストするので、この`_start`関数は`cargo test --lib`を実行する際に使用されることを思い出してください。テストを実行する前にIDTを設定するために、ここで`init`を呼び出す必要があります。 -Now we can create a `test_breakpoint_exception` test: +では、`test_breakpoint_exception`テストを作ってみましょう: ```rust // in src/interrupts.rs @@ -445,23 +452,23 @@ fn test_breakpoint_exception() { } ``` -The test invokes the `int3` function to trigger a breakpoint exception. By checking that the execution continues afterwards, we verify that our breakpoint handler is working correctly. +このテストでは、`int3`関数を呼び出してブレークポイント例外を発生させます。その後も実行が続くことを確認することで、ブレークポイントハンドラが正しく動作していることを保証します。 -You can try this new test by running `cargo test` (all tests) or `cargo test --lib` (only tests of `lib.rs` and its modules). You should see the following in the output: +この新しいテストを試すには、`cargo test`(すべてのテストを試したい場合)または`cargo test --lib`(`lib.rs`とそのモジュールのテストのみの場合)を実行すればよいです。出力は以下のようになるはずです: ``` blog_os::interrupts::test_breakpoint_exception... [ok] ``` -## Too much Magic? -The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date. +## さすがに簡単すぎ? +`x86-interrupt`呼び出し規約と[`InterruptDescriptorTable`]型のおかげで、例外処理のプロセスは比較的わかりやすく、面倒なところはありませんでした。「これではさすがに簡単すぎる、例外処理の闇をすべて学び尽くしたい」というあなた向けの記事もあります:私達の[Handling Exceptions with Naked Functions][“Handling Exceptions with Naked Functions”]シリーズ(未訳)では、`x86-interrupt`呼び出し規約を使わずに例外を処理する方法を学び、さらには独自のIDT型を定義します。`x86-interrupt`呼び出し規約や、`x86_64`クレートが存在する前は、これらの記事が主な例外処理に関する記事でした。なお、これらの記事はこのブログの[第1版][first edition]をもとにしているので、内容が古くなっている可能性があることに注意してください。 [“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md [`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html [first edition]: @/edition-1/_index.md -## What's next? -We've successfully caught our first exception and returned from it! The next step is to ensure that we catch all exceptions, because an uncaught exception causes a fatal [triple fault], which leads to a system reset. The next post explains how we can avoid this by correctly catching [double faults]. +## 次は? +例外を捕捉し、そこから戻ってくることに成功しました!次のステップは、すべての例外を捕捉できるようにすることです。なぜなら、補足されなかった例外は致命的な[トリプルフォルト][triple fault]を引き起こし、これはシステムリセットにつながってしまうからです。次の記事では、[ダブルフォルト][double faults]を正しく捕捉することで、これを回避できることを説明します。 [triple fault]: https://wiki.osdev.org/Triple_Fault [double faults]: https://wiki.osdev.org/Double_Fault#Double_Fault From 616376fbd76c7de1e1ad51b3513372284237f746 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Mon, 8 Mar 2021 15:45:10 +0900 Subject: [PATCH 003/125] Rebased to the current HEAD --- .../posts/05-cpu-exceptions/index.ja.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md index b47b69f3..3c7c8827 100644 --- a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md @@ -7,7 +7,7 @@ date = 2018-06-17 [extra] chapter = "Interrupts" # Please update this when updating the translation -translation_based_on_commit = "904d203f3696ebefa7ca037d403a391ccbb2a867" +translation_based_on_commit = "a8a6b725cff2e485bed76ff52ac1f18cec08cc7b" # GitHub usernames of the people that translated this post translators = ["woodyZootopia"] +++ @@ -88,7 +88,7 @@ u32 | 予約済 | ## IDT型 自前でIDTの型を作る代わりに、`x86_64`クレートの[`InterruptDescriptorTable`構造体][`InterruptDescriptorTable` struct]を使います。こんな見た目をしています: -[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html ``` rust #[repr(C)] @@ -119,10 +119,10 @@ pub struct InterruptDescriptorTable { この構造体のフィールドは[`idt::Entry`]という型を持っています。これはIDTのエントリのフィールド(上の表を見てください)を表す構造体です。型パラメータ`F`は、期待されるハンドラ関数の型を表します。エントリの中には、[`HandlerFunc`]型を要求するものや、[`HandlerFuncWithErrCode`]型を要求するものがあることがわかります。ページフォルトに至っては、[`PageFaultHandlerFunc`]という自分専用の型を要求していますね。 -[`idt::Entry`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.Entry.html -[`HandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFunc.html -[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFuncWithErrCode.html -[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.PageFaultHandlerFunc.html +[`idt::Entry`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.Entry.html +[`HandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFunc.html +[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html +[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html まず`HandlerFunc`型を見てみましょう: @@ -199,7 +199,7 @@ _callee-saved_ | _caller-saved_ `x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡すことができ、これを使うことで例外の原因に関して追加で情報を手に入れることができます。エラーコードは例外のうちいくつかしかプッシュしないので、構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。 -[`InterruptStackFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptStackFrame.html +[`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html ### 舞台裏では何が `x86-interrupt`呼び出し規約は、この例外処理 (ハンドル) プロセスのややこしいところをほぼ全て隠蔽してくれる、強力な抽象化です。しかし、その後ろで何が起こっているのかを知っておいたほうが良いこともあるでしょう。以下に、`x86-interrupt`呼び出し規約がやってくれることを簡単なリストにして示しました。 @@ -281,7 +281,7 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue CPUがこの割り込みディスクリプタテーブル(IDT)を使用するためには、[`lidt`]命令を使ってこれを読み込む必要があります。`x86_64`の`InterruptDescriptorTable`構造体には、そのための[`load`][InterruptDescriptorTable::load]というメソッド関数が用意されています。それを使ってみましょう: [`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt -[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load +[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load ```rust // in src/interrupts.rs @@ -464,7 +464,7 @@ blog_os::interrupts::test_breakpoint_exception... [ok] `x86-interrupt`呼び出し規約と[`InterruptDescriptorTable`]型のおかげで、例外処理のプロセスは比較的わかりやすく、面倒なところはありませんでした。「これではさすがに簡単すぎる、例外処理の闇をすべて学び尽くしたい」というあなた向けの記事もあります:私達の[Handling Exceptions with Naked Functions][“Handling Exceptions with Naked Functions”]シリーズ(未訳)では、`x86-interrupt`呼び出し規約を使わずに例外を処理する方法を学び、さらには独自のIDT型を定義します。`x86-interrupt`呼び出し規約や、`x86_64`クレートが存在する前は、これらの記事が主な例外処理に関する記事でした。なお、これらの記事はこのブログの[第1版][first edition]をもとにしているので、内容が古くなっている可能性があることに注意してください。 [“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md -[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html [first edition]: @/edition-1/_index.md ## 次は? From be2e130de90fe924211d3eed10136ffa1ab6b6c3 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Mon, 8 Mar 2021 19:08:58 +0900 Subject: [PATCH 004/125] Refined translation --- .../posts/05-cpu-exceptions/index.ja.md | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md index 3c7c8827..a605e9c3 100644 --- a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md @@ -44,12 +44,12 @@ x86には20種類のCPU例外があります。中でも重要なものは: [exceptions]: https://wiki.osdev.org/Exceptions ### 割り込み記述子表 -例外を捕捉し処理するためには、いわゆる割り込み記述子表 (interrupt descriptor table) (IDT) を設定しないといけません。この表にそれぞれのCPU例外に対するハンドラ関数を指定することができます。ハードウェアはこの表を直接使うので、決められたフォーマットに従わないといけません。それぞれのエントリは以下の16バイトの構造を持たなければなりません: +例外を捕捉し処理するためには、いわゆる割り込み記述子表 (Interrupt Descriptor Table, IDT) を設定しないといけません。この表にそれぞれのCPU例外に対するハンドラ関数を指定することができます。ハードウェアはこの表を直接使うので、決められたフォーマットに従わないといけません。それぞれのエントリは以下の16バイトの構造を持たなければなりません: 型 | 名前 | 説明 ----|--------------------------|----------------------------------- u16 | 関数ポインタ [0:15] | ハンドラ関数へのポインタの下位ビット。 -u16 | GDTセレクタ | [大域記述子表 (global descriptor table) ][global descriptor table]におけるコードセグメントを選ぶ。 +u16 | GDTセレクタ | [大域記述子表 (Global Descriptor Table)][global descriptor table] におけるコードセグメントを選ぶ。 u16 | オプション | (下を参照) u16 | 関数ポインタ [16:31] | ハンドラ関数へのポインタの中位ビット。 u32 | 関数ポインタ [32:63] | ハンドラ関数へのポインタの上位ビット。 @@ -69,7 +69,7 @@ u32 | 予約済 | 13‑14 | 記述子の特権レベル (Descriptor Privilege Level) (DPL) | このハンドラを呼ぶ際に必要になる最低限の特権レベル。 15 | Present | -それぞれの例外がIDTの何番目に対応するかは事前に定義されています。例えば、"無効な命令コード"例外は6番目で、"ページフォルト"例外は14番目です。これにより、ハードウェアがそれぞれの例外に対応するIDTの設定を(特に設定の必要なく)自動的に読み出せるというわけです。OSDev wikiの[「例外表」][exceptions]の "Vector nr." 列にすべての例外のIDTインデックスが記されています。 +それぞれの例外がIDTの何番目に対応するかは事前に定義されています。例えば、「無効な命令コード」の例外は6番目で、「ページフォルト」例外は14番目です。これにより、ハードウェアがそれぞれの例外に対応するIDTの設定を(特に設定の必要なく)自動的に読み出せるというわけです。OSDev wikiの[「例外表」][exceptions]の "Vector nr." 列に、すべての例外についてIDTの何番目かが記されています。 例外が起こると、ざっくりCPUは以下のことを行います: @@ -130,7 +130,7 @@ pub struct InterruptDescriptorTable { type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); ``` -これは、`extern "x86-interrupt" fn`型への[型エイリアス][type alias]です。`extern`は[外部呼び出し規約][foreign calling convention]に従う関数を定義するのに使われ、おもにC言語のコードとの間で通信をしたいときに使われます (`extern "C" fn`) 。しかし、`x86-interrupt`呼び出し規約とは何なのでしょう? +これは、`extern "x86-interrupt" fn`型への[型エイリアス][type alias]です。`extern`は[外部呼び出し規約][foreign calling convention]に従う関数を定義するのに使われ、おもにC言語のコードと連携したいときに使われます (`extern "C" fn`) 。しかし、`x86-interrupt`呼び出し規約とは何なのでしょう? [type alias]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases [foreign calling convention]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions @@ -149,18 +149,18 @@ type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); - 追加の引数はスタックで渡される - 結果は`rax`と`rdx`で返される -注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustのABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。 +注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustにはABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。 [rust abi]: https://github.com/rust-lang/rfcs/issues/600 ### PreservedレジスタとScratchレジスタ 呼び出し規約はレジスタを2種類に分けています:preserved (保存) レジスタとscratch (下書き) レジスタです。 -preservedレジスタの値は関数呼び出しの前後で変化してはいけません。ですので、呼び出された関数(訳注:callの受け身で"callee"と呼ばれます)は、リターンする前にその値をもとに戻す場合に限り、その値を上書きできます。そのため、これらのレジスタはcallee-saved (呼び出し先によって保存される) と呼ばれます。よくやる方法は、関数の最初でそのレジスタをスタックに保存し、リターンする直前にその値をもとに戻すことです。 +preservedレジスタの値は関数呼び出しの前後で変化してはいけません。ですので、呼び出された関数(訳注:callの受け身で"callee"と呼ばれます)は、リターンする前にその値をもとに戻す場合に限り、その値を上書きできます。そのため、これらのレジスタはcallee-saved (呼び出し先によって保存される) と呼ばれます。よくとられる方法は、関数の最初でそのレジスタをスタックに保存し、リターンする直前にその値をもとに戻すことです。 -それとは対照的に、呼び出された関数はscratchレジスタを何の制限もなく上書きすることができます。呼び出し元の関数がscratchレジスタの値を関数呼び出しの間保存しておきたいなら、関数呼び出しの前に自分で(スタックにプッシュするなどして)バックアップしておいて、もとに戻す必要があります。なので、scratchレジスタはcaller-saved (呼び出し元によって保存される) です。 +それとは対照的に、呼び出された関数はscratchレジスタを何の制限もなく上書きすることができます。呼び出し元の関数がscratchレジスタの値を関数呼び出しの前後で保存したいなら、関数呼び出しの前に自分で(スタックにプッシュするなどして)バックアップしておいて、もとに戻す必要があります。なので、scratchレジスタはcaller-saved (呼び出し元によって保存される) です。 -x86_64においては、C言語の呼び出し規約は以下のpreservedとscratchレジスタを指定します: +x86_64においては、C言語の呼び出し規約は以下のpreservedレジスタとscratchレジスタを指定します: preservedレジスタ | scratchレジスタ --- | --- @@ -176,7 +176,7 @@ _callee-saved_ | _caller-saved_ これは、関数の初めにすべてのレジスタがスタックに保存されるということを意味しないことに注意してください。その代わりに、コンパイラは関数によって上書きされてしまうレジスタのみをバックアップします。こうすれば、数個のレジスタしか使わない短い関数に対して、とても効率的なコードが生成できるでしょう。 -### The Interrupt Stack Frame +### 割り込み時のスタックフレーム 通常の関数呼び出し(`call`命令を使います)においては、CPUは対象の関数にジャンプする前にリターンアドレスをプッシュします。関数がリターンするとき(`ret`命令を使います)、CPUはこのリターンアドレスをポップし、そこにジャンプします。そのため、通常の関数呼び出しの際のスタックフレームは以下のようになっています: ![function stack frame](function-stack-frame.svg) @@ -193,21 +193,21 @@ _callee-saved_ | _caller-saved_ [`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register -ですので、割り込み時のスタックフレーム (interrupt stack frame) は以下のようになります: +ですので、割り込み時のスタックフレーム (interrupt stack frame) は以下のようになります: ![interrupt stack frame](exception-stack-frame.svg) -`x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡すことができ、これを使うことで例外の原因に関して追加で情報を手に入れることができます。エラーコードは例外のうちいくつかしかプッシュしないので、構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。 +`x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡されるため、これを使うことで例外の原因に関して追加で情報を手に入れることができます。例外のすべてがエラーコードをプッシュするわけではないので、この構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。 [`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html ### 舞台裏では何が `x86-interrupt`呼び出し規約は、この例外処理 (ハンドル) プロセスのややこしいところをほぼ全て隠蔽してくれる、強力な抽象化です。しかし、その後ろで何が起こっているのかを知っておいたほうが良いこともあるでしょう。以下に、`x86-interrupt`呼び出し規約がやってくれることを簡単なリストにして示しました。 -- **引数を取得する**: 多くの呼び出し規約においては、引数はレジスタを使って渡されることを想定しています。例外ハンドラにおいては、スタックにバックアップする前にレジスタの値を上書きしてはいけないので、これは不可能です。代わりに、`x86-interrupt`呼び出し規約は、引数が既に特定のオフセットでスタック上にあることを認識しています。 +- **引数を取得する**: 多くの呼び出し規約においては、引数はレジスタを使って渡されることを想定しています。例外ハンドラにおいては、スタックにバックアップする前にレジスタの値を上書きしてはいけないので、これは不可能です。その代わり、`x86-interrupt`呼び出し規約は、引数が既に特定のオフセットでスタック上にあることを認識しています。 - **`iretq`を使ってリターンする**: 割り込み時のスタックフレームは通常の関数呼び出しのスタックフレームとは全く異なるため、通常の `ret` 命令を使ってハンドラ関数から戻ることはできません。その代わりに、`iretq` 命令を使う必要があります。 -- **エラーコードを処理する**: いくつかの例外の場合、エラーコードがプッシュされるのですが、これが状況をより複雑にします。エラーコードはスタックのアラインメントを変更し(次の箇条を参照)、リターンする前にスタックからポップされる必要があります。`x86-interrupt`呼び出し規約は、このややこしい仕組みをすべて処理してくれます。しかし、どのハンドラ関数がどの例外に使われているかは呼び出し規約側にはわからないので、関数の引数の数からその情報を推測する必要があります。つまり、プログラマはやはりそれぞれの例外に対して正しい関数型を使う責任があるということです。幸いにも、`x86_64`クレートで定義されている`InterruptDescriptorTable`型が、正しい関数型が確実に使われるようにしてくれます。 -- **スタックをアラインする**: 一部の命令(特にSSE命令)には、16バイトのスタックアラインメントを必要とするものがあります。CPUは例外が発生したときには必ずこのようにスタックが整列 (アライン) されることを保証しますが、例外の中には、エラーコードをプッシュしたとき再びスタックの整列を壊してしまうものもあります。この場合、`x86-interrupt`の呼び出し規約は、スタックを再整列させることでこの問題を解決します。 +- **エラーコードを処理する**: いくつかの例外の場合、エラーコードがプッシュされるのですが、これが状況をより複雑にします。エラーコードはスタックのアラインメントを変更し(次の箇条を参照)、リターンする前にスタックからポップされる必要があるのです。`x86-interrupt`呼び出し規約は、このややこしい仕組みをすべて処理してくれます。しかし、どのハンドラ関数がどの例外に使われているかは呼び出し規約側にはわからないので、関数の引数の数からその情報を推測する必要があります。つまり、プログラマはやはりそれぞれの例外に対して正しい関数型を使う責任があるということです。幸いにも、`x86_64`クレートで定義されている`InterruptDescriptorTable`型が、正しい関数型が確実に使われるようにしてくれます。 +- **スタックをアラインする**: 一部の命令(特にSSE命令)には、16バイトのスタックアラインメントを必要とするものがあります。CPUは例外が発生したときには必ずこのようにスタックが整列 (アライン) されることを保証しますが、例外の中には、エラーコードをプッシュして再びスタックの整列を壊してしまうものもあります。この場合、`x86-interrupt`の呼び出し規約は、スタックを再整列させることでこの問題を解決します。 もしより詳しく知りたい場合は、例外の処理について[naked function][naked functions]を使って説明する一連の記事があります。[この記事の最下部][too-much-magic]にそこへのリンクがあります。 @@ -215,7 +215,7 @@ _callee-saved_ | _caller-saved_ [too-much-magic]: #sasuganijian-dan-sugi ## 実装 -理論を理解したところで、私達のカーネルでCPUの例外を実際に処理していきましょう。まず、`src/interrupts.rs`に新しい割り込みのためのモジュールを作ります。このモジュールはまず、`init_idt`関数という、新しい`InterruptDescriptorTable`を作る関数を定義します。 +理屈は理解したので、私達のカーネルでCPUの例外を実際に処理していきましょう。まず、`src/interrupts.rs`に割り込みのための新しいモジュールを作ります。このモジュールはまず、`init_idt`関数という、新しい`InterruptDescriptorTable`を作る関数を定義します。 ``` rust // in src/lib.rs @@ -231,7 +231,7 @@ pub fn init_idt() { } ``` -これで、ハンドラ関数を追加していくことができます。まず、[ブレークポイント例外][breakpoint exception]のハンドラを追加するところから始めましょう。ブレークポイント例外は、例外処理のテストをするのにうってつけの例外なのです。この例外の唯一の目的は、ブレークポイント命令`int3`が実行された時、プログラムを一時停止させるということです。 +これで、ハンドラ関数を追加していくことができます。まず、[ブレークポイント例外][breakpoint exception]のハンドラを追加するところから始めましょう。ブレークポイント例外は、例外処理のテストをするのにうってつけの例外なのです。この例外の唯一の目的は、ブレークポイント命令`int3`が実行された時、プログラムを一時停止させることです。 [breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint @@ -239,7 +239,7 @@ pub fn init_idt() { ["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints -今回の場合、命令を上書きする必要はありません。ブレークポイント命令が実行された時、メッセージを表示したうえで実行を継続したいだけです。ですので、単純な`breakpoint_handler`関数を作ってIDTに追加してみましょう。 +今回の場合、命令を上書きしたりする必要はありません。ブレークポイント命令が実行された時、メッセージを表示したうえで実行を継続したいだけです。ですので、単純な`breakpoint_handler`関数を作ってIDTに追加してみましょう。 ```rust // in src/interrupts.rs @@ -309,7 +309,7 @@ error: `idt` does not live long enough `load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、use-after-free (解放後にアクセス) バグが発生する可能性があります。 -実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはランダムに中身が変更されたスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。 +実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはどこかもわからないスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。 この問題を解決するには、`idt`を`'static`なライフタイムの場所に格納する必要があります。これを達成するには、[`Box`]を使ってIDTをヒープに割当て、続いてそれを`'static`な参照に変換すればよいです。しかし、私達はOSのカーネルを書いている途中であり、(まだ)ヒープを持っていません。 @@ -346,13 +346,12 @@ pub fn init_idt() { [`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers -#### Lazy Staticsならきっとなんとかしてくれる +#### Lazy Staticsにおまかせ 幸いにも、例の`lazy_static`マクロが存在します。このマクロは`static`をコンパイル時に評価する代わりに、最初に参照されたときに初期化を行います。このため、初期化時にはほとんどすべてのことができ、実行時にのみ決定する値を読み込むこともできます。 [VGAテキストバッファの抽象化をした][vga text buffer lazy static]ときに、すでに`lazy_static`クレートはインポートしました。そのため、すぐに`lazy_static!`マクロを使って静的なIDTを作ることができます。 -[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics - +[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.ja.md#dai-keta-lazy-jing-de-bian-shu ```rust // in src/interrupts.rs @@ -371,7 +370,7 @@ pub fn init_idt() { } ``` -この解決法では、`unsafe`ブロックが必要ないことに注目してください。`lazy_static!`マクロはその内部で確かに`unsafe`を使っているのですが、安全なインターフェースの中に抽象化されているのです。 +この方法では`unsafe`ブロックが必要ないことに注目してください。`lazy_static!`マクロはその内部で`unsafe`を使ってはいるのですが、これは安全なインターフェースの中に抽象化されているのです。 ### 実行する @@ -385,8 +384,7 @@ pub fn init() { } ``` -With this function we now have a central place for initialization routines that can be shared between the different `_start` functions in our `main.rs`, `lib.rs`, and integration tests. -この関数により、`main.rs`、`lib.rs`、それに結合テストにおける異なる`_start`関数で共有できる、初期化ルーチンの中心地ができました。 +この関数により、`main.rs`、`lib.rs`および結合テストにおける、異なる`_start`関数で共有される、初期化ルーチンの「中央広場」ができました。 `main.rs`内の`_start`関数を更新して、`init`を呼び出し、そのあとブレークポイント例外を発生させるようにしてみましょう: @@ -421,8 +419,6 @@ pub extern "C" fn _start() -> ! { ### テストを追加する -Let's create a test that ensures that the above continues to work. First, we update the `_start` function to also call `init`: - 上記の動作が継続することを確認するテストを作成してみましょう。まず、`_start` 関数を更新して `init` を呼び出すようにします。 ```rust From 2ce8169e641416b7382230ff04c0543f88e70476 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Mon, 8 Mar 2021 19:13:02 +0900 Subject: [PATCH 005/125] Fix Double Faults article accordingly --- blog/content/edition-2/posts/06-double-faults/index.ja.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/blog/content/edition-2/posts/06-double-faults/index.ja.md b/blog/content/edition-2/posts/06-double-faults/index.ja.md index b275e560..58244117 100644 --- a/blog/content/edition-2/posts/06-double-faults/index.ja.md +++ b/blog/content/edition-2/posts/06-double-faults/index.ja.md @@ -25,10 +25,9 @@ translators = ["garasubo"] ## ダブルフォルトとは -簡単に言うとダブルフォルトとはCPUが例外ハンドラを呼び出すことに失敗したときに起きる特別な例外です。例えば、ページフォルトが起きたが、ページフォルトハンドラが[割り込みディスクリプタテーブル][IDT](IDT: Interrupt Descriptor Table)(訳注: 翻訳当時、リンク先未訳)に登録されていないときに発生します。つまり、C++での`catch(...)`や、JavaやC#の`catch(Exception e)`ような、例外のあるプログラミング言語のcatch-allブロックのようなものです。 - -[IDT]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table +簡単に言うとダブルフォルトとはCPUが例外ハンドラを呼び出すことに失敗したときに起きる特別な例外です。例えば、ページフォルトが起きたが、ページフォルトハンドラが[割り込みディスクリプタテーブル][IDT](IDT: Interrupt Descriptor Table)に登録されていないときに発生します。つまり、C++での`catch(...)`や、JavaやC#の`catch(Exception e)`ような、例外のあるプログラミング言語のcatch-allブロックのようなものです。 +[IDT]: @/edition-2/posts/05-cpu-exceptions/index.ja.md#ge-riip-miji-shu-zi-biao ダブルフォルトは通常の例外のように振る舞います。ベクター番号`8`を持ち、IDTに通常のハンドラ関数として定義できます。ダブルフォルトがうまく処理されないと、より重大な例外である**トリプルフォルト**が起きてしまうため、ダブルフォルトハンドラを設定することはとても重要です。トリプルフォルトはキャッチできず、ほとんどのハードウェアはシステムリセットを起こします。 ### ダブルフォルトを起こす From bdf6f14b9e2899274092d63734c58350f22729bb Mon Sep 17 00:00:00 2001 From: "Shu W. Nakamura" <30687489+woodyZootopia@users.noreply.github.com> Date: Sun, 21 Mar 2021 07:47:52 +0900 Subject: [PATCH 006/125] Apply suggestions from code review Co-authored-by: Yuki Okushi --- .../posts/05-cpu-exceptions/index.ja.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md index a605e9c3..3d31b936 100644 --- a/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.ja.md @@ -12,7 +12,7 @@ translation_based_on_commit = "a8a6b725cff2e485bed76ff52ac1f18cec08cc7b" translators = ["woodyZootopia"] +++ -CPU例外は、例えば無効なメモリアドレスにアクセスしたときやゼロ除算したときなど、様々なミスによって発生します。それらに対して反応するために、ハンドラ関数を提供する **割り込み記述子表 (interrupt descriptor table) ** を設定しなくてはなりません。この記事を読み終わる頃には、私達のカーネルは[ブレークポイント例外][breakpoint exceptions]を捕捉し、その後通常の実行を継続できるようになっているでしょう。 +CPU例外は、例えば無効なメモリアドレスにアクセスしたときやゼロ除算したときなど、様々なミスによって発生します。それらに対処するために、ハンドラ関数を提供する **割り込み記述子表 (interrupt descriptor table) ** を設定しなくてはなりません。この記事を読み終わる頃には、私達のカーネルは[ブレークポイント例外][breakpoint exceptions]を捕捉し、その後通常の実行を継続できるようになっているでしょう。 [breakpoint exceptions]: https://wiki.osdev.org/Exceptions#Breakpoint @@ -27,15 +27,15 @@ CPU例外は、例えば無効なメモリアドレスにアクセスしたと ## 概要 -例外とは、今実行している命令はなにかおかしいぞ、ということを示すものです。例えば、現在の命令が0で割ろうとするときにCPUは例外を発します。例外が起こったら、CPUは現在行われている作業に割り込み、例外の種類に従って、即座に特定の例外ハンドラ関数を呼びます。 +例外とは、今実行している命令はなにかおかしいぞ、ということを示すものです。例えば、現在の命令がゼロ除算を実行しようとしているとき、CPUは例外を発します。例外が起こると、CPUは現在行われている作業に割り込み、例外の種類に従って、即座に特定の例外ハンドラ関数を呼びます。 x86には20種類のCPU例外があります。中でも重要なものは: - **ページフォルト (Page Fault) **: ページフォルトは不正なメモリアクセスの際に発生します。例えば、現在の命令がマップされていないページから読み込もうとしたり、読み込み専用のページに書き込もうとしたときに生じます。 - **無効な (Invalid) 命令コード (Opcode) **: この例外は現在の命令が無効であるときに発生します。例えば、[SSE命令][SSE instructions]という新しい命令をサポートしていない旧式のCPU上でこれを実行しようとしたときに生じます。 - **一般保護違反 (General Protection Fault) **: これは、例外の中でも、最もいろいろな理由で発生しうるものです。ユーザーレベルのコードで特権命令 (privileged instruction) を実行しようとしたときや、設定レジスタの保護領域に書き込もうとしたときなど、様々な種類のアクセス違反によって生じます。 -- **ダブルフォルト (Double Fault) **: 例外が起こったとき、CPUは対応するハンドラ関数を呼び出そうとします。 この例外ハンドラを **呼び出している間に** 別の例外が起こった場合、CPUはダブルフォルト例外を出します。この例外はまた、ある例外に対してハンドラ関数が登録されていないときにも起こります。 -- **トリプルフォルト (Triple Fault) **: CPUがダブルフォルトのハンドラ関数を呼び出そうとしている間に例外が発生したら、CPUは **トリプルフォルト** という致命的な例外を発します。トリプルフォルトを捕捉したり処理したりすることはできません。これが起こると、多くのプロセッサは自らをリセットしてOSを再起動することで対応します。 +- **ダブルフォルト (Double Fault) **: 何らかの例外が起こったとき、CPUは対応するハンドラ関数を呼び出そうとします。 この例外ハンドラを **呼び出している間に** 別の例外が起こった場合、CPUはダブルフォルト例外を出します。この例外はまた、ある例外に対してハンドラ関数が登録されていないときにも起こります。 +- **トリプルフォルト (Triple Fault) **: CPUがダブルフォルトのハンドラ関数を呼び出そうとしている間に例外が発生すると、CPUは **トリプルフォルト** という致命的な例外を発します。トリプルフォルトを捕捉したり処理したりすることはできません。これが起こると、多くのプロセッサは自らをリセットしてOSを再起動することで対応します。 [SSE instructions]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions @@ -49,7 +49,7 @@ x86には20種類のCPU例外があります。中でも重要なものは: 型 | 名前 | 説明 ----|--------------------------|----------------------------------- u16 | 関数ポインタ [0:15] | ハンドラ関数へのポインタの下位ビット。 -u16 | GDTセレクタ | [大域記述子表 (Global Descriptor Table)][global descriptor table] におけるコードセグメントを選ぶ。 +u16 | GDTセレクタ | [大域記述子表 (Global Descriptor Table)][global descriptor table] におけるコードセグメントのセレクタ。 u16 | オプション | (下を参照) u16 | 関数ポインタ [16:31] | ハンドラ関数へのポインタの中位ビット。 u32 | 関数ポインタ [32:63] | ハンドラ関数へのポインタの上位ビット。 @@ -149,7 +149,7 @@ type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); - 追加の引数はスタックで渡される - 結果は`rax`と`rdx`で返される -注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustにはABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。 +注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustにはABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用されないということです。 [rust abi]: https://github.com/rust-lang/rfcs/issues/600 @@ -184,11 +184,11 @@ _callee-saved_ | _caller-saved_ しかし、例外と割り込みハンドラについては、リターンアドレスをプッシュするだけではだめです。なぜなら、割り込みハンドラはしばしば(スタックポインタや、CPUフラグなどが)異なる状況で実行されるからです。ですので、代わりに、CPUは割り込みが起こると以下の手順を実行します。 1. **スタックポインタをアラインする**: 割り込みはあらゆる命令において発生しうるので、スタックポインタもあらゆる値を取る可能性があります。しかし、CPU命令のうちいくつか(例えばSSE命令の一部など)はスタックポインタが16バイトの倍数になっていることを要求するので、そうなるようにCPUは割り込みの直後にスタックポインタを揃え (アラインし) ます。 -2. (場合によっては)**スタックを変更する**: スタックの変更は、例えばCPU例外がユーザーモードのプログラムで起こったときに、CPUの特権レベルを変更するときに起こります。いわゆる割り込みスタック表 (Interrupt Stack Table) を使うことで、特定の割り込みに対しスタックを変更するよう設定することも可能です。割り込みスタック表については次の記事で説明します。 +2. (場合によっては)**スタックを変更する**: スタックの変更は、例えばCPU例外がユーザーモードのプログラムで起こった場合のような、CPUの特権レベルを変更するときに起こります。いわゆる割り込みスタック表 (Interrupt Stack Table) を使うことで、特定の割り込みに対しスタックを変更するよう設定することも可能です。割り込みスタック表については次の記事で説明します。 3. **古いスタックポインタをプッシュする**: CPUは、割り込みが発生した際の(アラインされる前の)スタックポインタレジスタ(`rsp`)とスタックセグメントレジスタ(`ss`)の値をプッシュします。これにより、割り込みハンドラからリターンしてきたときにもとのスタックポインタを復元することが可能になります。 4. **`RFLAGS`レジスタをプッシュして更新する**: [`RFLAGS`]レジスタは状態や制御のための様々なビットを保持しています。割り込みに入るとき、CPUはビットのうちいくつかを変更し古い値をプッシュしておきます。 5. **命令ポインタをプッシュする**: 割り込みハンドラ関数にジャンプする前に、CPUは命令ポインタ(`rip`)とコードセグメント(`cs`)をプッシュします。これは通常の関数呼び出しにおける戻り値のプッシュに対応します。 -6. **エラーコードをプッシュする** (for some exceptions): ページフォルトのような特定の例外の場合、CPUはエラーコードをプッシュします。これは、例外の原因を説明するものです。 +6. (例外によっては)**エラーコードをプッシュする**: ページフォルトのような特定の例外の場合、CPUはエラーコードをプッシュします。これは、例外の原因を説明するものです。 7. **割り込みハンドラを呼び出す**: CPUは割り込みハンドラ関数のアドレスとセグメント記述子 (segment descriptor) をIDTの対応するフィールドから読み出します。そして、この値を`rip`と`cs`レジスタに書き出してから、ハンドラを呼び出します。 [`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register @@ -307,7 +307,7 @@ error: `idt` does not live long enough = note: borrowed value must be valid for the static lifetime... ``` -`load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、use-after-free (解放後にアクセス) バグが発生する可能性があります。 +`load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、use-after-free (メモリ解放後にアクセス) バグが発生する可能性があります。 実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはどこかもわからないスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。 From 7ac5fc903bb13ffefde73ee3e6017aaf511c6d7f Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Mon, 22 Mar 2021 09:23:35 +0100 Subject: [PATCH 007/125] minor typo (#949) I think this is missing a preposition. --- blog/content/edition-2/posts/06-double-faults/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/06-double-faults/index.md b/blog/content/edition-2/posts/06-double-faults/index.md index 54542733..370c7e3c 100644 --- a/blog/content/edition-2/posts/06-double-faults/index.md +++ b/blog/content/edition-2/posts/06-double-faults/index.md @@ -330,7 +330,7 @@ The problem is that the GDT segments are not yet active because the segment and In summary, we need to do the following: -1. **Reload code segment register**: We changed our GDT, so we should reload `cs`, the code segment register. This is required since the old segment selector could point a different GDT descriptor now (e.g. a TSS descriptor). +1. **Reload code segment register**: We changed our GDT, so we should reload `cs`, the code segment register. This is required since the old segment selector could point to a different GDT descriptor now (e.g. a TSS descriptor). 2. **Load the TSS** : We loaded a GDT that contains a TSS selector, but we still need to tell the CPU that it should use that TSS. 3. **Update the IDT entry**: As soon as our TSS is loaded, the CPU has access to a valid interrupt stack table (IST). Then we can tell the CPU that it should use our new double fault stack by modifying our double fault IDT entry. From 6a17e8a7f442ac8159a06b8377207b3828db5d73 Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Mon, 22 Mar 2021 09:36:55 +0100 Subject: [PATCH 008/125] missing word (#950) --- blog/content/edition-2/posts/06-double-faults/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/06-double-faults/index.md b/blog/content/edition-2/posts/06-double-faults/index.md index 370c7e3c..bdf1a833 100644 --- a/blog/content/edition-2/posts/06-double-faults/index.md +++ b/blog/content/edition-2/posts/06-double-faults/index.md @@ -545,6 +545,6 @@ In this post we learned what a double fault is and under which conditions it occ We also enabled the hardware supported stack switching on double fault exceptions so that it also works on stack overflow. While implementing it, we learned about the task state segment (TSS), the contained interrupt stack table (IST), and the global descriptor table (GDT), which was used for segmentation on older architectures. ## What's next? -The next post explains how to handle interrupts from external devices such as timers, keyboards, or network controllers. These hardware interrupts are very similar to exceptions, e.g. they are also dispatched through the IDT. However, unlike exceptions, they don't arise directly on the CPU. Instead, an _interrupt controller_ aggregates these interrupts and forwards them to CPU depending on their priority. In the next we will explore the [Intel 8259] \(“PIC”) interrupt controller and learn how to implement keyboard support. +The next post explains how to handle interrupts from external devices such as timers, keyboards, or network controllers. These hardware interrupts are very similar to exceptions, e.g. they are also dispatched through the IDT. However, unlike exceptions, they don't arise directly on the CPU. Instead, an _interrupt controller_ aggregates these interrupts and forwards them to CPU depending on their priority. In the next post we will explore the [Intel 8259] \(“PIC”) interrupt controller and learn how to implement keyboard support. [Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259 From 5577f1985964cf180a154f8e3cf7a6a86aa4af3c Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Wed, 24 Mar 2021 12:48:26 +0100 Subject: [PATCH 009/125] Inconsistent version and grammar change (#951) Earlier in https://github.com/phil-opp/blog_os/blob/master/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md bootloader = "0.9.8" and here we are effectively downgrading to bootloader = "0.9.3" (Maybe there is an earlier feature that has been dropped that I am unaware of.) --- .../content/edition-2/posts/09-paging-implementation/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/content/edition-2/posts/09-paging-implementation/index.md b/blog/content/edition-2/posts/09-paging-implementation/index.md index a5896642..fdbad6f5 100644 --- a/blog/content/edition-2/posts/09-paging-implementation/index.md +++ b/blog/content/edition-2/posts/09-paging-implementation/index.md @@ -273,11 +273,11 @@ This means that we need the help of the bootloader, which creates the page table - The `map_physical_memory` feature maps the complete physical memory somewhere into the virtual address space. Thus, the kernel can access all physical memory and can follow the [_Map the Complete Physical Memory_](#map-the-complete-physical-memory) approach. - With the `recursive_page_table` feature, the bootloader maps an entry of the level 4 page table recursively. This allows the kernel to access the page tables as described in the [_Recursive Page Tables_](#recursive-page-tables) section. -We choose the first approach for our kernel since it is simple, platform-independent, and more powerful (it also allows to access non-page-table-frames). To enable the required bootloader support, we add the `map_physical_memory` feature to our `bootloader` dependency: +We choose the first approach for our kernel since it is simple, platform-independent, and more powerful (it also allows access to non-page-table-frames). To enable the required bootloader support, we add the `map_physical_memory` feature to our `bootloader` dependency: ```toml [dependencies] -bootloader = { version = "0.9.3", features = ["map_physical_memory"]} +bootloader = { version = "0.9.8", 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. From ec4863ff93cdad4e74f2af2c64e2ed24cb814494 Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Wed, 24 Mar 2021 13:16:45 +0100 Subject: [PATCH 010/125] Example image does not match code output (#952) The 2nd edition outputs "L4 " at the start of each line of the loop. --- .../qemu-print-level-4-table.png | Bin 15714 -> 22564 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/blog/content/edition-2/posts/09-paging-implementation/qemu-print-level-4-table.png b/blog/content/edition-2/posts/09-paging-implementation/qemu-print-level-4-table.png index 39001d2028c3e2f2a001b4d3d24908682b9ee425..8594cf5247ba4f14bb5501e46ea004bf73c4d98f 100644 GIT binary patch literal 22564 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%0A5L~YBwXQlK5eDtP9o;SYA1I zes_uN7Rzbf-<-tcEl+>vy}RM{g145JpKqM^Y^ho1_wY+5*{ikZD%8ICAULZ(ZHH*$ zwe7S2GJX?ZVSJ_hz|~u?x_+;aopa+x*oOQA|D4Juo9#0ymDzFO4Esw@c^kXeo_EqJ zv;z1K{$pA=|23<_glCVAf7>j7;2+b9?+xOLuAu4xJYLigv*gvSho4I)N4C9|H0xcT zn47cA(|U@R-1;|Lbnh?uxkNL4x;ekWkj0+P1qf3=9kcOJtq4 zcvqa3nvxqYYyHA>2DkKxnuvLDZaD57D-;ydo56LIVQYz+T`<3 zMG|i~%}!2_d7T+t|4HxplH$l&+1}S)`|Zz+=}Vnm`pwo?t~4X4Z-1d2s0#LZZZ&_& zuk7E~9_U=Ro`3)D-<{rS8)dU@e5=?N*>-jl+f?5hzujRyiHPo^1K;-j+H?Qc(~akv zPgUM|`ZVhLra20muC2{nyV~=s+4@@hRkhQuZ#3@v`bO{man~!?f8B1$h<`3GuYc!O zZQZ-@=ipLY@8F5=by`u*-hq9o*E3^`8%~=ZUFMgvcb-^o{EnRIXH7hxpE3DeJE1^C zeRE*jVx@4Q`{s&~alUb1m@|XE+v=CkoHK1jrd3%?^pay6{SN<|mU{PdMfTdWR=;PZ z$s|?AMWdXm%jV{ z7OOSUP7_bxeD*xy_Um&R>wGthxpbB6ozYTozgqW2dCaYn!(Nw-a;t6UZrx?QqT1(@ z<>mGZUl~5_6gpfN_iAm*UiKFo=6kY?(nWfU;f3I>d2)0 zmAsxRXBV|2XS#WA>D6-CtuIeadAjTbWTZ&H4ZV`s(w}|#-RI96q`MMtzsa6ulV7%Z z-D!(;;+68J%`V@FnVj?Lm_}aNvAO1-_WTn`tvwaVcj{iZ$1GX%T>JE|^9;H^6hvOD zi-^qcakK1SQ$B0k>Dk+M-KswH{pPCo)^~b8Ga0}7cT8_>#5UcNlS9t!-fo|{)9m`( zC7EnzH+S&=?_1SV`zBca`2iE%?Uiz;%^0t%`l#RCk`{YSBX7s23I&VxZ;N*BOSe>? zayiT?zSXX8x88~B)t5E+4(yRXwPc-GeZBAFjd!Hga^=>Y+`PuUw$God%Ix~wz?j)J zcO85$e?9j6V_x+0-SHI4?^>xm)T5Ij&Menz@f~$uEf>rAKZEC(`FW#r% zZ98L$!CNb#R^R%Yfw{ZSeoWhZZr`$(<$g1##(S&pJlFp!%7EA0^>5yUWgFwZ+umQh zcHNBryPI=D9h%dg1O=>Po_*V!Tt zJi474Y4$aJ)~i)#t(N|Lx8d*9GO7Fe-%eD$QDJ42*RMWd^~-(U(>dQ-b+#=oTXI)_ zu~mLg5^6sJJYKXhW{dcyA19Lb<-cf)WzRi7)2{!kiT>8YyYYxxqAhJlYs03*`i8%( zuXtYb?TCc*AtsAGWd!#jjxXt5?<0GB%}h`qg8A_CLZR)F^SG{rZrFd|Ytw61j!Spm zHX9|`H~eJ{c;C3tsS8v|Kw7RlHN3w$@80p@=dP1Fhrja7K6ZVBnc3wXJhhLL<$v4| z*LyvA*__yWHdDT^Z85p+VQ1KU6>{Tt&QfK$T_P%ZO$btF zh^(E{t+rC;(!M%z1_p)g3U600-RKeP62n{*G~uL! zXjt6t5uG!2{f5vb7tC_=HA^&ad&>69Ffb_eellUTWME)eP~f<0d#mAV{~zntzmf~( z{`u~$M^xPYrz(4Q>U;mZp?A7;Zu?%v3(ualt^;kJkiEt)&33R|@5Qf)rY`)y|83a+ zo1^sZ`?BAr@BiKZJIy#lD*kEm?lpC{@9SIe&uV|WscO0ZtYgcv1sE6(Y&o$vyM4Xb z*~lzO-&=0C+YQ)jug};%Uwp!4J#Geuglh(W?uxX&b-ZhzbEwcffEhFe-z%bMlRe|y zrKju+3<+sXCTnN>oN|7}sh#Ef><*cfGBGfmNtv)slD*Tk9!Gu;p3d%F6$taD0e0sv*UEA#*W literal 15714 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 Date: Wed, 24 Mar 2021 13:50:06 +0100 Subject: [PATCH 011/125] x86_64::VirtAddr already imported on L415 (#953) `error[E0252]: the name 'VirtAddr' is defined multiple times` --- blog/content/edition-2/posts/09-paging-implementation/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/blog/content/edition-2/posts/09-paging-implementation/index.md b/blog/content/edition-2/posts/09-paging-implementation/index.md index fdbad6f5..2f5fd7b7 100644 --- a/blog/content/edition-2/posts/09-paging-implementation/index.md +++ b/blog/content/edition-2/posts/09-paging-implementation/index.md @@ -558,9 +558,8 @@ Let's test our translation function by translating some addresses: // in src/main.rs fn kernel_main(boot_info: &'static BootInfo) -> ! { - // new imports + // new import use blog_os::memory::translate_addr; - use x86_64::VirtAddr; […] // hello world and blog_os::init From c515590c1b0fd91c9f32640aa6f6d981a216fc1e Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Sat, 27 Mar 2021 10:33:59 +0900 Subject: [PATCH 012/125] Add ja file --- .../posts/08-paging-introduction/index.ja.md | 420 ++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 blog/content/edition-2/posts/08-paging-introduction/index.ja.md diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md new file mode 100644 index 00000000..15b6f59d --- /dev/null +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -0,0 +1,420 @@ ++++ +title = "Introduction to Paging" +weight = 8 +path = "ja/paging-introduction" +date = 2019-01-14 + +[extra] +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 = ["woodyZootopia"] ++++ + +This post introduces _paging_, a very common memory management scheme that we will also use for our operating system. It explains why memory isolation is needed, how _segmentation_ works, what _virtual memory_ is, and how paging solves memory fragmentation issues. It also explores the layout of multilevel page tables on the x86_64 architecture. + + + +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-08`][post branch] branch. + +[GitHub]: https://github.com/phil-opp/blog_os +[at the bottom]: #comments +[post branch]: https://github.com/phil-opp/blog_os/tree/post-08 + + + +## Memory Protection + +One main task of an operating system is to isolate programs from each other. Your web browser shouldn't be able to interfere with your text editor, for example. To achieve this goal, operating systems utilize hardware functionality to ensure that memory areas of one process are not accessible by other processes. There are different approaches, depending on the hardware and the OS implementation. + +As an example, some ARM Cortex-M processors (used for embedded systems) have a [_Memory Protection Unit_] (MPU), which allows you to define a small number (e.g. 8) of memory regions with different access permissions (e.g. no access, read-only, read-write). On each memory access the MPU ensures that the address is in a region with correct access permissions and throws an exception otherwise. By changing the regions and access permissions on each process switch, the operating system can ensure that each process only accesses its own memory, and thus isolate processes from each other. + +[_Memory Protection Unit_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu + +On x86, the hardware supports two different approaches to memory protection: [segmentation] and [paging]. + +[segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation +[paging]: https://en.wikipedia.org/wiki/Virtual_memory#Paged_virtual_memory + +## Segmentation + +Segmentation was already introduced in 1978, originally to increase the amount of addressable memory. The situation back then was that CPUs only used 16-bit addresses, which limited the amount of addressable memory to 64KiB. To make more than these 64KiB accessible, additional segment registers were introduced, each containing an offset address. The CPU automatically added this offset on each memory access, so that up to 1MiB of memory were accessible. + +The segment register is chosen automatically by the CPU, depending on the kind of memory access: For fetching instructions the code segment `CS` is used and for stack operations (push/pop) the stack segment `SS` is used. Other instructions use data segment `DS` or the extra segment `ES`. Later two additional segment registers `FS` and `GS` were added, which can be used freely. + +In the first version of segmentation, the segment registers directly contained the offset and no access control was performed. This was changed later with the introduction of the [_protected mode_]. When the CPU runs in this mode, the segment descriptors contain an index into a local or global [_descriptor table_], which contains – in addition to an offset address – the segment size and access permissions. By loading separate global/local descriptor tables for each process that confine memory accesses to the process's own memory areas, the OS can isolate processes from each other. + +[_protected mode_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode +[_descriptor table_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table + +By modifying the memory addresses before the actual access, segmentation already employed a technique that is now used almost everywhere: _virtual memory_. + +### Virtual Memory + +The idea behind virtual memory is to abstract away the memory addresses from the underlying physical storage device. Instead of directly accessing the storage device, a translation step is performed first. For segmentation, the translation step is to add the offset address of the active segment. Imagine a program accessing memory address `0x1234000` in a segment with offset `0x1111000`: The address that is really accessed is `0x2345000`. + +To differentiate the two address types, addresses before the translation are called _virtual_ and addresses after the translation are called _physical_. One important difference between these two kinds of addresses is that physical addresses are unique and always refer to the same, distinct memory location. Virtual addresses on the other hand depend on the translation function. It is entirely possible that two different virtual addresses refer to the same physical address. Also, identical virtual addresses can refer to different physical addresses when they use different translation functions. + +An example where this property is useful is running the same program twice in parallel: + + +![Two virtual address spaces with address 0–150, one translated to 100–250, the other to 300–450](segmentation-same-program-twice.svg) + +Here the same program runs twice, but with different translation functions. The first instance has an segment offset of 100, so that its virtual addresses 0–150 are translated to the physical addresses 100–250. The second instance has offset 300, which translates its virtual addresses 0–150 to physical addresses 300–450. This allows both programs to run the same code and use the same virtual addresses without interfering with each other. + +Another advantage is that programs can be placed at arbitrary physical memory locations now, even if they use completely different virtual addresses. Thus, the OS can utilize the full amount of available memory without needing to recompile programs. + +### Fragmentation + +The differentiation between virtual and physical addresses makes segmentation really powerful. However, it has the problem of fragmentation. As an example, imagine that we want to run a third copy of the program we saw above: + +![Three virtual address spaces, but there is not enough continuous space for the third](segmentation-fragmentation.svg) + +There is no way to map the third instance of the program to virtual memory without overlapping, even though there is more than enough free memory available. The problem is that we need _continuous_ memory and can't use the small free chunks. + +One way to combat this fragmentation is to pause execution, move the used parts of the memory closer together, update the translation, and then resume execution: + +![Three virtual address spaces after defragmentation](segmentation-fragmentation-compacted.svg) + +Now there is enough continuous space to start the third instance of our program. + +The disadvantage of this defragmentation process is that is needs to copy large amounts of memory which decreases performance. It also needs to be done regularly before the memory becomes too fragmented. This makes performance unpredictable, since programs are paused at random times and might become unresponsive. + +The fragmentation problem is one of the reasons that segmentation is no longer used by most systems. In fact, segmentation is not even supported in 64-bit mode on x86 anymore. Instead _paging_ is used, which completely avoids the fragmentation problem. + +## Paging + +The idea is to divide both the virtual and the physical memory space into small, fixed-size blocks. The blocks of the virtual memory space are called _pages_ and the blocks of the physical address space are called _frames_. Each page can be individually mapped to a frame, which makes it possible to split larger memory regions across non-continuous physical frames. + +The advantage of this becomes visible if we recap the example of the fragmented memory space, but use paging instead of segmentation this time: + +![With paging the third program instance can be split across many smaller physical areas](paging-fragmentation.svg) + +In this example we have a page size of 50 bytes, which means that each of our memory regions is split across three pages. Each page is mapped to a frame individually, so a continuous virtual memory region can be mapped to non-continuous physical frames. This allows us to start the third instance of the program without performing any defragmentation before. + +### Hidden Fragmentation + +Compared to segmentation, paging uses lots of small, fixed sized memory regions instead of a few large, variable sized regions. Since every frame has the same size, there are no frames that are too small to be used so that no fragmentation occurs. + +Or it _seems_ like no fragmentation occurs. There is still some hidden kind of fragmentation, the so-called _internal fragmentation_. Internal fragmentation occurs because not every memory region is an exact multiple of the page size. Imagine a program of size 101 in the above example: It would still need three pages of size 50, so it would occupy 49 bytes more than needed. To differentiate the two types of fragmentation, the kind of fragmentation that happens when using segmentation is called _external fragmentation_. + +Internal fragmentation is unfortunate, but often better than the external fragmentation that occurs with segmentation. It still wastes memory, but does not require defragmentation and makes the amount of fragmentation predictable (on average half a page per memory region). + +### Page Tables + +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 transparent 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 100–250](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 100–250, 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 at 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, so e.g. 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 this 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 at 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 at 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 works, 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 4KiB. 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 * 8B = 4KiB large and thus fits exactly into one page. + +The page table index for level is derived directly from the virtual address: + +![Bits 0–12 are the page offset, bits 12–21 the level 1 index, bits 21–30 the level 2 index, bits 30–39 the level 3 index, and bits 39–48 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 4KiB page (2^12 bytes = 4KiB). Bits 48 to 64 are discarded, which means that x86_64 is not really 64-bit since it only supports 48-bit addresses. + +[5-level page table]: https://en.wikipedia.org/wiki/Intel_5-level_paging + +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 extends 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 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 16KiB. +- 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 24KiB. +- 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 12KiB, 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 level, 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 4KiB 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 1GiB page in P3, creates a 2MiB 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 12–51 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 0–11 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 52–63, 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 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 were modified since the last save to disk. +- The `write through caching` and `disable cache` flags allow to control the 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 an 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 to create 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 2MiB = 512 * 4KiB for level 2 entries or even 1GiB = 512 * 2MiB 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.13.2/x86_64/structures/paging/page_table/struct.PageTable.html +[entries]: https://docs.rs/x86_64/0.13.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 4 memory accesses. To improve performance, the x86_64 architecture caches the last few translations in the so-called _translation lookaside buffer_ (TLB). This allows to skip the translation when the translation 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.13.2/x86_64/instructions/tlb/index.html + +It is important to remember flushing 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 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 set 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: &mut 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.13.2/x86_64/registers/control/struct.Cr2.html#method.read +[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.13.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.13.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.13.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.13.2/x86_64/registers/control/struct.Cr3.html#method.read +[`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html +[`Cr3Flags`]: https://docs.rs/x86_64/0.13.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.13.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 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 4KiB. 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. From 8bcfc7043fca896b5b2056310e41946dd4d7510b Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Sat, 27 Mar 2021 15:25:47 +0100 Subject: [PATCH 013/125] typo (#955) --- blog/content/edition-2/posts/11-allocator-designs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/11-allocator-designs/index.md b/blog/content/edition-2/posts/11-allocator-designs/index.md index 995a3beb..b8d36611 100644 --- a/blog/content/edition-2/posts/11-allocator-designs/index.md +++ b/blog/content/edition-2/posts/11-allocator-designs/index.md @@ -1019,7 +1019,7 @@ fn list_index(layout: &Layout) -> Option { } ``` -The block must be have at least the size and alignment required by the given `Layout`. Since we defined that the block size is also its alignment, this means that the `required_block_size` is the [maximum] of the layout's [`size()`] and [`align()`] attributes. To find the next-larger block in the `BLOCK_SIZES` slice, we first use the [`iter()`] method to get an iterator and then the [`position()`] method to find the index of the first block that is as least as large as the `required_block_size`. +The block must have at least the size and alignment required by the given `Layout`. Since we defined that the block size is also its alignment, this means that the `required_block_size` is the [maximum] of the layout's [`size()`] and [`align()`] attributes. To find the next-larger block in the `BLOCK_SIZES` slice, we first use the [`iter()`] method to get an iterator and then the [`position()`] method to find the index of the first block that is as least as large as the `required_block_size`. [maximum]: https://doc.rust-lang.org/core/cmp/trait.Ord.html#method.max [`size()`]: https://doc.rust-lang.org/core/alloc/struct.Layout.html#method.size From 51f401a56ce6996d17265230c51d919772c1474c Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Sun, 28 Mar 2021 12:47:28 +0200 Subject: [PATCH 014/125] missing word (#956) --- blog/content/edition-2/posts/11-allocator-designs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/11-allocator-designs/index.md b/blog/content/edition-2/posts/11-allocator-designs/index.md index b8d36611..6e134aab 100644 --- a/blog/content/edition-2/posts/11-allocator-designs/index.md +++ b/blog/content/edition-2/posts/11-allocator-designs/index.md @@ -1178,7 +1178,7 @@ On the implementation side, there are various things that we could improve in ou - Instead of only allocating blocks lazily using the fallback allocator, it might be better to pre-fill the lists to improve the performance of initial allocations. - To simplify the implementation, we only allowed block sizes that are powers of 2 so that we could use them also as the block alignment. By storing (or calculating) the alignment in a different way, we could also allow arbitrary other block sizes. This way, we could add more block sizes, e.g. for common allocation sizes, in order to minimize the wasted memory. - We currently only create new blocks, but never free them again. This results in fragmentation and might eventually result in allocation failure for large allocations. It might make sense to enforce a maximum list length for each block size. When the maximum length is reached, subsequent deallocations are freed using the fallback allocator instead of being added to the list. -- Instead of falling back to a linked list allocator, we could a special allocator for allocations greater than 4KiB. The idea is to utilize [paging], which operates on 4KiB pages, to map a continuous block of virtual memory to non-continuous physical frames. This way, fragmentation of unused memory is no longer a problem for large allocations. +- Instead of falling back to a linked list allocator, we could have a special allocator for allocations greater than 4KiB. The idea is to utilize [paging], which operates on 4KiB pages, to map a continuous block of virtual memory to non-continuous physical frames. This way, fragmentation of unused memory is no longer a problem for large allocations. - With such a page allocator, it might make sense to add block sizes up to 4KiB and drop the linked list allocator completely. The main advantages of this would be reduced fragmentation and improved performance predictability, i.e. better worse-case performance. [paging]: @/edition-2/posts/08-paging-introduction/index.md From 3f1a451e2b0acf9a048fa7a3a31b460a008be901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nerma?= Date: Tue, 30 Mar 2021 13:54:19 +0200 Subject: [PATCH 015/125] Fix: missing word (#958) --- blog/content/edition-2/posts/10-heap-allocation/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/10-heap-allocation/index.md b/blog/content/edition-2/posts/10-heap-allocation/index.md index cdc23423..5e1e7b99 100644 --- a/blog/content/edition-2/posts/10-heap-allocation/index.md +++ b/blog/content/edition-2/posts/10-heap-allocation/index.md @@ -106,7 +106,7 @@ We see that the `z[1]` slot is free again and can be reused for the next `alloca Apart from memory leaks, which are unfortunate but don't make the program vulnerable to attackers, there are two common types of bugs with more severe consequences: -- When we accidentally continue to use a variable after calling `deallocate` on it, we have a so-called **use-after-free** vulnerability. Such a bug causes undefined behavior and can often exploited by attackers to execute arbitrary code. +- When we accidentally continue to use a variable after calling `deallocate` on it, we have a so-called **use-after-free** vulnerability. Such a bug causes undefined behavior and can often be exploited by attackers to execute arbitrary code. - When we accidentally free a variable twice, we have a **double-free** vulnerability. This is problematic because it might free a different allocation that was allocated in the same spot after the first `deallocate` call. Thus, it can lead to an use-after-free vulnerability again. These types of vulnerabilities are commonly known, so one might expect that people learned how to avoid them by now. But no, such vulnerabilities are still regularly found, for example this recent [use-after-free vulnerability in Linux][linux vulnerability] that allowed arbitrary code execution. This shows that even the best programmers are not always able to correctly handle dynamic memory in complex projects. From ff43048585c7182fc9e5e63eb5ef0b93c884f94e Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Tue, 30 Mar 2021 13:56:09 +0200 Subject: [PATCH 016/125] grammar (#957) almost done with the proofreading first pass ;-) --- blog/content/edition-2/posts/12-async-await/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index bc58ec10..d6e39754 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -91,7 +91,7 @@ However, the strong performance and memory benefits of cooperative multitasking ## Async/Await in Rust -The Rust language provides first-class support for cooperative multitasking in form of async/await. Before we can explore what async/await is and how it works, we need to understand how _futures_ and asynchronous programming work in Rust. +The Rust language provides first-class support for cooperative multitasking in the form of async/await. Before we can explore what async/await is and how it works, we need to understand how _futures_ and asynchronous programming work in Rust. ### Futures From 2d6108dc2fe9601b0ddb7ddca301837c9fffed6d Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Thu, 1 Apr 2021 10:37:57 +0200 Subject: [PATCH 017/125] missing word (#960) minor omission of a definite article. --- blog/content/edition-2/posts/12-async-await/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index d6e39754..d7df333b 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -687,7 +687,7 @@ For further reading, check out the documentation of the [`pin` module] and the [ #### Pinning and Futures -As we already saw in this post, the [`Future::poll`] method uses pinning in form of a `Pin<&mut Self>` parameter: +As we already saw in this post, the [`Future::poll`] method uses pinning in the form of a `Pin<&mut Self>` parameter: [`Future::poll`]: https://doc.rust-lang.org/nightly/core/future/trait.Future.html#tymethod.poll From 02b80acbd7a46222c7d04125e1020e07a65250ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E6=89=8B=E6=8E=89=E5=8C=85=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88?= Date: Fri, 2 Apr 2021 20:31:56 +0800 Subject: [PATCH 018/125] Translate common texts into Chinese (#962) --- blog/config.toml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/blog/config.toml b/blog/config.toml index b1565c56..207eb272 100644 --- a/blog/config.toml +++ b/blog/config.toml @@ -48,29 +48,29 @@ word_separator = "and" [translations.zh-CN] lang_name = "Chinese (simplified)" -toc = "Table of Contents" -all_posts = "« All Posts" -comments = "Comments" -comments_notice = "Please leave your comments in English if possible." -readmore = "read more »" -not_translated = "(This post is not translated yet.)" -translated_content = "Translated Content:" -translated_content_notice = "This is a community translation of the _original.title_ post. It might be incomplete, outdated or contain errors. Please report any issues!" -translated_by = "Translation by" -word_separator = "and" +toc = "目录" +all_posts = "« 所有文章" +comments = "评论" +comments_notice = "请尽可能使用英语评论。" +readmore = "更多 »" +not_translated = "(该文章还没有被翻译。)" +translated_content = "翻译内容:" +translated_content_notice = "这是对原文章 _original.title_ 的社区中文翻译。它可能不完整,过时或者包含错误。可以在 上评论和提问!" +translated_by = "翻译者:" +word_separator = "和" [translations.zh-TW] lang_name = "Chinese (traditional)" -toc = "Table of Contents" -all_posts = "« All Posts" -comments = "Comments" -comments_notice = "Please leave your comments in English if possible." -readmore = "read more »" -not_translated = "(This post is not translated yet.)" -translated_content = "Translated Content:" -translated_content_notice = "This is a community translation of the _original.title_ post. It might be incomplete, outdated or contain errors. Please report any issues!" -translated_by = "Translation by" -word_separator = "and" +toc = "目錄" +all_posts = "« 所有文章" +comments = "評論" +comments_notice = "請儘可能使用英語評論。" +readmore = "更多 »" +not_translated = "(該文章還沒有被翻譯。)" +translated_content = "翻譯內容:" +translated_content_notice = "這是對原文章 _original.title_ 的社區中文翻譯。它可能不完整,過時或者包含錯誤。可以在 上評論和提問!" +translated_by = "翻譯者:" +word_separator = "和" [translations.ja] lang_name = "Japanese" From fc81c27fec6cd7d04e074afd16854621ec9dd73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E6=89=8B=E6=8E=89=E5=8C=85=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88?= Date: Sun, 4 Apr 2021 15:50:52 +0800 Subject: [PATCH 019/125] Fix typo (#965) --- blog/config.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/config.toml b/blog/config.toml index 207eb272..f65cebdb 100644 --- a/blog/config.toml +++ b/blog/config.toml @@ -55,7 +55,7 @@ comments_notice = "请尽可能使用英语评论。" readmore = "更多 »" not_translated = "(该文章还没有被翻译。)" translated_content = "翻译内容:" -translated_content_notice = "这是对原文章 _original.title_ 的社区中文翻译。它可能不完整,过时或者包含错误。可以在 上评论和提问!" +translated_content_notice = "这是对原文章 _original.title_ 的社区中文翻译。它可能不完整,过时或者包含错误。可以在 这个 Issue 上评论和提问!" translated_by = "翻译者:" word_separator = "和" @@ -68,7 +68,7 @@ comments_notice = "請儘可能使用英語評論。" readmore = "更多 »" not_translated = "(該文章還沒有被翻譯。)" translated_content = "翻譯內容:" -translated_content_notice = "這是對原文章 _original.title_ 的社區中文翻譯。它可能不完整,過時或者包含錯誤。可以在 上評論和提問!" +translated_content_notice = "這是對原文章 _original.title_ 的社區中文翻譯。它可能不完整,過時或者包含錯誤。可以在 這個 Issue 上評論和提問!" translated_by = "翻譯者:" word_separator = "和" From f2ca9f282a77db5b20d2630e1eddca507f0b5361 Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Sun, 4 Apr 2021 09:56:34 +0200 Subject: [PATCH 020/125] Previously updated dependency (#964) Previously, in https://os.phil-opp.com/testing/ we set the dependency to `x86_64 = "0.13.2"` so this advice seems redundant. --- blog/content/edition-2/posts/12-async-await/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index d7df333b..83b9b0ee 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -1758,7 +1758,7 @@ if self.task_queue.is_empty() { In case this interrupt pushes to the `task_queue`, we put the CPU to sleep even though there is now a ready task. In the worst case, this could delay the handling of a keyboard interrupt until the next keypress or the next timer interrupt. So how do we prevent it? -The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`interrupts::enable_and_hlt`][`enable_and_hlt`] function provided by the [`x86_64`] crate. This function is only available since version 0.9.6, so you might need to update your `x86_64` dependency to use it. +The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`interrupts::enable_and_hlt`][`enable_and_hlt`] function provided by the [`x86_64`] crate. [`enable_and_hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html From d3f5bb5fcbbbbbedfb4e4e18b6fd2800a44cc78a Mon Sep 17 00:00:00 2001 From: Alexx Roche Date: Mon, 5 Apr 2021 13:04:41 +0200 Subject: [PATCH 021/125] wake_trait is now stable (#963) You probably know: `wake_trait` has been stable since 1.51.0 and no longer requires an attribute to enable --- blog/content/edition-2/posts/12-async-await/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index 83b9b0ee..691aa167 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -1643,7 +1643,7 @@ impl Wake for TaskWaker { } ``` -The trait is still unstable, so we have to add **`#![feature(wake_trait)]`** to the top of our `lib.rs` to use it. Since wakers are commonly shared between the executor and the asynchronous tasks, the trait methods require that the `Self` instance is wrapped in the [`Arc`] type, which implements reference-counted ownership. This means that we have to move our `TaskWaker` to an `Arc` in order to call them. +Since wakers are commonly shared between the executor and the asynchronous tasks, the trait methods require that the `Self` instance is wrapped in the [`Arc`] type, which implements reference-counted ownership. This means that we have to move our `TaskWaker` to an `Arc` in order to call them. The difference between the `wake` and `wake_by_ref` methods is that the latter only requires a reference to the `Arc`, while the former takes ownership of the `Arc` and thus often requires an increase of the reference count. Not all types support waking by reference, so implementing the `wake_by_ref` method is optional, however it can lead to better performance because it avoids unnecessary reference count modifications. In our case, we can simply forward both trait methods to our `wake_task` function, which requires only a shared `&self` reference. From 6d02874049c21758a7e6a9cf10bb05ffd16fb832 Mon Sep 17 00:00:00 2001 From: Mostafa Elbannan <63522515+moomoolive@users.noreply.github.com> Date: Tue, 27 Apr 2021 03:18:38 -0600 Subject: [PATCH 022/125] Suggestion for .cargo/config.toml Explanation (#974) I was confused on what the 'unstable' section of my .cargo/config.toml file should look like once I started getting linking errors when going through the third post. It turned out it was because I overwrote the previous 'unstable' configuration with the latter one. In other words, I thought we were to overwrite "build-std = ["core", "compiler_builtins"]" under the "unstable" config, with "build-std-features = ["compiler-builtins-mem"]" and NOT have both present. I think this makes it more clear that both are supposed to be present. --- blog/content/edition-2/posts/02-minimal-rust-kernel/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md index 7567da92..c4cf302c 100644 --- a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md +++ b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md @@ -306,6 +306,7 @@ Fortunately, the `compiler_builtins` crate already contains implementations for [unstable] build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins"] ``` (Support for the `compiler-builtins-mem` feature was only [added very recently](https://github.com/rust-lang/rust/pull/77284), so you need at least Rust nightly `2020-09-30` for it.) From 5d5e51400e253cf93b8a1fca75e19245dfefc66e Mon Sep 17 00:00:00 2001 From: kahirokunn Date: Tue, 4 May 2021 10:28:13 +0900 Subject: [PATCH 023/125] fix typo --- blog/content/edition-2/posts/12-async-await/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index 691aa167..9cd1bbc1 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -1025,7 +1025,7 @@ We already have some kind of asynchronicity in our system that we can use for th [_Interrupts_]: @/edition-2/posts/07-hardware-interrupts/index.md -In the following, we will create an asynchronous task based on the keyboard interrupt. The keyboard interrupt is a good candidate for this because it is both non-deterministic and latency-critical. Non-deteministic means that there is no way to predict when the next key press will occur because it is entirely dependent on the user. Latency-critical means that we want to handle the keyboard input in a timely manner, otherwise the user will feel a lag. To support such a task in an efficient way, it will be essential that the executor has proper support for `Waker` notifications. +In the following, we will create an asynchronous task based on the keyboard interrupt. The keyboard interrupt is a good candidate for this because it is both non-deterministic and latency-critical. Non-deterministic means that there is no way to predict when the next key press will occur because it is entirely dependent on the user. Latency-critical means that we want to handle the keyboard input in a timely manner, otherwise the user will feel a lag. To support such a task in an efficient way, it will be essential that the executor has proper support for `Waker` notifications. #### Scancode Queue @@ -1789,7 +1789,7 @@ Now our executor properly puts the CPU to sleep when there is nothing to do. We Our executor is now able to run tasks in an efficient way. It utilizes waker notifications to avoid polling waiting tasks and puts the CPU to sleep when there is currently no work to do. However, our executor is still quite basic and there are many possible ways to extend its functionality: -- **Scheduling:** We currently use the [`VecDeque`] type to implement a _first in first out_ (FIFO) strategy for our `task_queue`, which is often also called _round robin_ scheduling. This strategy might not be the most efficient for all workloads. For example, it might make sense to prioritize latency-critical tasks or tasks that do a lot of I/O. See the [scheduling chapter] of the [_Operating Systems: Three Easy Pieces_] book or the [Wikipedia article on scheduling][scheduling-wiki] for more information. +- **Scheduling**: We currently use the [`VecDeque`] type to implement a _first in first out_ (FIFO) strategy for our `task_queue`, which is often also called _round robin_ scheduling. This strategy might not be the most efficient for all workloads. For example, it might make sense to prioritize latency-critical tasks or tasks that do a lot of I/O. See the [scheduling chapter] of the [_Operating Systems: Three Easy Pieces_] book or the [Wikipedia article on scheduling][scheduling-wiki] for more information. - **Task Spawning**: Our `Executor::spawn` method currently requires a `&mut self` reference and is thus no longer available after starting the `run` method. To fix this, we could create an additional `Spawner` type that shares some kind of queue with the executor and allows task creation from within tasks themselves. The queue could be for example the `task_queue` directly or a separate queue that the executor checks in its run loop. - **Utilizing Threads**: We don't have support for threads yet, but we will add it in the next post. This will make it possible to launch multiple instances of the executor in different threads. The advantage of this approach is that the delay imposed by long running tasks can be reduced because other tasks can run concurrently. This approach also allows it to utilize multiple CPU cores. - **Load Balancing**: When adding threading support, it becomes important how to distribute the tasks between the executors to ensure that all CPU cores are utilized. A common technique for this is [_work stealing_]. From 178c90ad740340ed8a1d5454d96403af04ea5486 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Sun, 9 May 2021 00:16:04 +0900 Subject: [PATCH 024/125] Translate about half of the article --- .../posts/08-paging-introduction/index.ja.md | 204 +++++++++--------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index 15b6f59d..a7a10124 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -1,5 +1,5 @@ +++ -title = "Introduction to Paging" +title = "ページング入門" weight = 8 path = "ja/paging-introduction" date = 2019-01-14 @@ -12,11 +12,11 @@ translation_based_on_commit = "3315bfe2f63571f5e6e924d58ed32afd8f39f892" translators = ["woodyZootopia"] +++ -This post introduces _paging_, a very common memory management scheme that we will also use for our operating system. It explains why memory isolation is needed, how _segmentation_ works, what _virtual memory_ is, and how paging solves memory fragmentation issues. It also explores the layout of multilevel page tables on the x86_64 architecture. +この記事では**ページング**を紹介します。これは、私達のオペレーティングシステムにも使う、とても一般的なメモリ管理方式です。なぜメモリの分離 (isolation) が必要なのか、**セグメンテーション**がどのようにして働くのか、**仮想メモリ**とは何なのか、ページングがいかにしてメモリ断片化 (フラグメンテーション) の問題を解決するのかを説明します。また、x86_64アーキテクチャにおける、マルチレベルページテーブルのレイアウトについても説明します。 -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-08`][post branch] branch. +このブログの内容は [GitHub] 上で公開・開発されています。何か問題や質問などがあれば issue をたててください(訳注: リンクは原文(英語)のものになります)。また[こちら][at the bottom]にコメントを残すこともできます。この記事の完全なソースコードは[`post-08` ブランチ][post branch]にあります。 [GitHub]: https://github.com/phil-opp/blog_os [at the bottom]: #comments @@ -24,176 +24,177 @@ This blog is openly developed on [GitHub]. If you have any problems or questions -## Memory Protection +## メモリの保護 -One main task of an operating system is to isolate programs from each other. Your web browser shouldn't be able to interfere with your text editor, for example. To achieve this goal, operating systems utilize hardware functionality to ensure that memory areas of one process are not accessible by other processes. There are different approaches, depending on the hardware and the OS implementation. +オペレーティングシステムの主な役割の一つに、プログラムを互いに分離するということがあります。例えば、ウェブブラウザがテキストエディタに干渉してはいけません。この目的を達成するために、オペレーティングシステムはハードウェアの機能を利用して、あるプロセスのメモリ領域に他のプロセスがアクセスできないようにします。ハードウェアやOSの実装によって、さまざまなアプローチがあります。 -As an example, some ARM Cortex-M processors (used for embedded systems) have a [_Memory Protection Unit_] (MPU), which allows you to define a small number (e.g. 8) of memory regions with different access permissions (e.g. no access, read-only, read-write). On each memory access the MPU ensures that the address is in a region with correct access permissions and throws an exception otherwise. By changing the regions and access permissions on each process switch, the operating system can ensure that each process only accesses its own memory, and thus isolate processes from each other. +例として、ARM Cortex-Mプロセッサ(組み込みシステムに使われています)のいくつかには、[メモリ保護ユニット][_Memory Protection Unit_] (Memory Protection Unit, MPU) が搭載されており、異なるアクセス権限(例えば、アクセス不可、読み取り専用、読み書きなど)を持つメモリ領域を少数(例えば8個)定義することができます。MPUは、メモリアクセスのたびに、そのアドレスが正しいアクセス許可を持つ領域にあるかどうかを確認し、そうでなければ例外を投げます。プロセスを変更するごとにその領域とアクセス許可を変更すれば、オペレーティングシステムはそれぞれのプロセスが自身のメモリにのみアクセスすることを保証し、したがってプロセスを互いに分離することができます。 [_Memory Protection Unit_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu -On x86, the hardware supports two different approaches to memory protection: [segmentation] and [paging]. +x86においては、ハードウェアは2つの異なるメモリ保護の方法をサポートしています:[セグメンテーション][segmentation]と[ページング][paging]です。 [segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation [paging]: https://en.wikipedia.org/wiki/Virtual_memory#Paged_virtual_memory -## Segmentation +## セグメンテーション -Segmentation was already introduced in 1978, originally to increase the amount of addressable memory. The situation back then was that CPUs only used 16-bit addresses, which limited the amount of addressable memory to 64KiB. To make more than these 64KiB accessible, additional segment registers were introduced, each containing an offset address. The CPU automatically added this offset on each memory access, so that up to 1MiB of memory were accessible. +セグメンテーションは1978年にはすでに導入されており、当初の目的はアドレス可能なメモリの量を増やすためでした。当時、CPUは16bitのアドレスしか使えなかったので、アドレス可能なメモリは64KiBに限られていました。この64KiBを超えてアクセスするために、セグメントレジスタが追加され、それぞれにオフセットアドレスが設定されました。CPUがメモリにアクセスするとき、毎回このオフセットを自動的に加算するので、最大1MiBのメモリにアクセスできるようになりました。 -The segment register is chosen automatically by the CPU, depending on the kind of memory access: For fetching instructions the code segment `CS` is used and for stack operations (push/pop) the stack segment `SS` is used. Other instructions use data segment `DS` or the extra segment `ES`. Later two additional segment registers `FS` and `GS` were added, which can be used freely. +メモリアクセスの種類によって、セグメントレジスタは自動的にCPUによって選ばれます。命令の引き出し (フェッチ) にはコードセグメント`CS`が使用され、スタック操作(プッシュ・ポップ)にはスタックセグメント`SS`が使用されます。その他の命令では、データセグメント`DS`やエクストラセグメント`ES`が使用されます。その後、自由に使用できる`FS`と`GS`というセグメントレジスタも追加されました。 -In the first version of segmentation, the segment registers directly contained the offset and no access control was performed. This was changed later with the introduction of the [_protected mode_]. When the CPU runs in this mode, the segment descriptors contain an index into a local or global [_descriptor table_], which contains – in addition to an offset address – the segment size and access permissions. By loading separate global/local descriptor tables for each process that confine memory accesses to the process's own memory areas, the OS can isolate processes from each other. +セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス許可が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセス自身のメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離することができます。 [_protected mode_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode [_descriptor table_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table -By modifying the memory addresses before the actual access, segmentation already employed a technique that is now used almost everywhere: _virtual memory_. +メモリアドレスを実際にアクセスされる前に変更するという点において、セグメンテーションは今やほぼすべての場所で使われている**仮想メモリ**というテクニックをすでに採用していたと言えます。 -### Virtual Memory +### 仮想メモリ -The idea behind virtual memory is to abstract away the memory addresses from the underlying physical storage device. Instead of directly accessing the storage device, a translation step is performed first. For segmentation, the translation step is to add the offset address of the active segment. Imagine a program accessing memory address `0x1234000` in a segment with offset `0x1111000`: The address that is really accessed is `0x2345000`. +仮想メモリの背景にある考え方は、下層にある物理的なストレージデバイスからメモリアドレスを抽象化することです。ストレージデバイスに直接アクセスするのではなく、先に何らかの変換ステップが踏まれます。セグメンテーションの場合、この変換ステップとはアクティブなセグメントのオフセットアドレスを追加することです。例えば、オフセット`0x1111000`のセグメントにあるプログラムが`0x1234000`というメモリアドレスにアクセスすると、実際にアクセスされるアドレスは`0x2345000`になります。 -To differentiate the two address types, addresses before the translation are called _virtual_ and addresses after the translation are called _physical_. One important difference between these two kinds of addresses is that physical addresses are unique and always refer to the same, distinct memory location. Virtual addresses on the other hand depend on the translation function. It is entirely possible that two different virtual addresses refer to the same physical address. Also, identical virtual addresses can refer to different physical addresses when they use different translation functions. - -An example where this property is useful is running the same program twice in parallel: +この2種類のアドレスを区別するため、変換前のアドレスを **仮想(アドレス)** と、変換後のアドレスを **物理(アドレス)** と呼びます。この2種類のアドレスの重要な違いの一つは、物理アドレスは常に同じ一意なメモリ位置を指すということです。いっぽう仮想アドレス(の指す場所)は変換する関数に依存します。二つの異なる仮想アドレスが同じ物理アドレスを指すということは十分にありえます。また、変換関数が異なっていれば、同じ仮想アドレスが別の物理アドレスを示すということもありえます。 +この特性が役立つ例として、同じプログラムを2つ並行して実行するという状況が挙げられます。 ![Two virtual address spaces with address 0–150, one translated to 100–250, the other to 300–450](segmentation-same-program-twice.svg) -Here the same program runs twice, but with different translation functions. The first instance has an segment offset of 100, so that its virtual addresses 0–150 are translated to the physical addresses 100–250. The second instance has offset 300, which translates its virtual addresses 0–150 to physical addresses 300–450. This allows both programs to run the same code and use the same virtual addresses without interfering with each other. +同じプログラムを2つ実行していますが、別の変換関数が使われています。1つ目のインスタンスではセグメントのオフセットが100なので、0から150の仮想アドレスは100から250に変換されます。2つ目のインスタンスではオフセットが300なので、0から150の仮想アドレスが300から450に変換されます。これにより、プログラムが互いに干渉することなく同じコード、同じ仮想アドレスを使うことができます。 -Another advantage is that programs can be placed at arbitrary physical memory locations now, even if they use completely different virtual addresses. Thus, the OS can utilize the full amount of available memory without needing to recompile programs. +もう一つの利点は、プログラムが全く異なる仮想アドレスを使っていたとしても、物理メモリ上の任意の場所に置くことができるということです。したがって、OSはプログラムを再コンパイルすることなく、利用可能なメモリをフルに活用することができます。 -### Fragmentation +### 断片化 (fragmentation) -The differentiation between virtual and physical addresses makes segmentation really powerful. However, it has the problem of fragmentation. As an example, imagine that we want to run a third copy of the program we saw above: +物理アドレスと仮想アドレスを分けることにより、セグメンテーションは非常に強力なものとなっています。しかし、これにより断片化という問題が発生します。例として、上で見たプログラムの3つ目を実行したいとしましょう: ![Three virtual address spaces, but there is not enough continuous space for the third](segmentation-fragmentation.svg) -There is no way to map the third instance of the program to virtual memory without overlapping, even though there is more than enough free memory available. The problem is that we need _continuous_ memory and can't use the small free chunks. +開放されているメモリは十分にあるにも関わらず、プログラムのインスタンスを重ねることなく物理メモリに対応づけることはできません。ここで必要なのは **連続した** メモリであり、開放されたメモリが小さな塊であっては使えないためです。 -One way to combat this fragmentation is to pause execution, move the used parts of the memory closer together, update the translation, and then resume execution: +この断片化に対処する方法の一つは、実行を一時停止し、メモリの使用されている部分を寄せ集めて、変換関数を更新し、実行を再開することでしょう: ![Three virtual address spaces after defragmentation](segmentation-fragmentation-compacted.svg) -Now there is enough continuous space to start the third instance of our program. +これで、プログラムの3つ目のインスタンスを開始するのに十分な連続したスペースができました。 -The disadvantage of this defragmentation process is that is needs to copy large amounts of memory which decreases performance. It also needs to be done regularly before the memory becomes too fragmented. This makes performance unpredictable, since programs are paused at random times and might become unresponsive. +このデフラグメンテーションという処理の欠点は、大量のメモリをコピーしなければならず、パフォーマンスを低下させてしまうことです。また、メモリが断片化しすぎる前に定期的に実行しないといけません。すると、プログラムが時々一時停止して、反応がなくなるので、性能が予測不可能になってしまいます。 -The fragmentation problem is one of the reasons that segmentation is no longer used by most systems. In fact, segmentation is not even supported in 64-bit mode on x86 anymore. Instead _paging_ is used, which completely avoids the fragmentation problem. +ほとんどのシステムでセグメンテーションが用いられなくなった理由の一つに、この断片化の問題があります。実際、x86の64ビットモードでは、セグメンテーションはもはやサポートされていません。代わりに **ページング** が使用されており、これにより断片化の問題は完全に回避されます。 -## Paging +## ページング -The idea is to divide both the virtual and the physical memory space into small, fixed-size blocks. The blocks of the virtual memory space are called _pages_ and the blocks of the physical address space are called _frames_. Each page can be individually mapped to a frame, which makes it possible to split larger memory regions across non-continuous physical frames. +ページングの考え方は、仮想メモリ空間と物理メモリ空間の両方を、サイズの固定された小さなブロックに分割するというものです。仮想メモリ空間のブロックは **ページ** と呼ばれ、物理アドレス空間のブロックは **フレーム** と呼ばれます。各ページはフレームに独立してマッピングできるので、大きなメモリ領域を連続していない物理フレームに分割することが可能です。 -The advantage of this becomes visible if we recap the example of the fragmented memory space, but use paging instead of segmentation this time: +この方法の利点は、セグメンテーションの代わりにページングを使ってもう一度上のメモリ空間断片化の状況を見てみれば明らかになります: ![With paging the third program instance can be split across many smaller physical areas](paging-fragmentation.svg) -In this example we have a page size of 50 bytes, which means that each of our memory regions is split across three pages. Each page is mapped to a frame individually, so a continuous virtual memory region can be mapped to non-continuous physical frames. This allows us to start the third instance of the program without performing any defragmentation before. +この例では、ページサイズは50バイトなので、それぞれのメモリ領域が3つのページに分割されます。それぞれのページは個別にフレームに対応付けられるので、連続した仮想メモリ領域を非連続な物理フレームへと対応付けられるのです。これにより、デフラグを事前に実行することなく、3つ目のプログラムのインスタンスを開始することができるようになります。 -### Hidden Fragmentation +### 隠された断片化 -Compared to segmentation, paging uses lots of small, fixed sized memory regions instead of a few large, variable sized regions. Since every frame has the same size, there are no frames that are too small to be used so that no fragmentation occurs. +少ない数の可変なサイズのメモリ領域を使っていたセグメンテーションと比べると、ページングでは大量の小さい固定サイズのメモリ領域を使います。すべてのフレームが同じ大きさなので、「小さすぎて使えないフレーム」などというものは存在せず、したがって断片化も起きません。 -Or it _seems_ like no fragmentation occurs. There is still some hidden kind of fragmentation, the so-called _internal fragmentation_. Internal fragmentation occurs because not every memory region is an exact multiple of the page size. Imagine a program of size 101 in the above example: It would still need three pages of size 50, so it would occupy 49 bytes more than needed. To differentiate the two types of fragmentation, the kind of fragmentation that happens when using segmentation is called _external fragmentation_. +というより、**目に見える** 断片化は起きていない、という方が正しいでしょう。**内部 (internal) 断片化**と呼ばれる、目に見えない断片化は依然として起こっています。内部断片化は、すべてのメモリ領域がページサイズの整数倍ぴったりにはならないために生じます。例えば、上の例でサイズが101のプログラムを考えてみてください:この場合でもサイズ50のページが3つ必要で、必要な量より49バイト多く占有します。これらの2種類の断片化を区別するため、セグメンテーションを使うときに起きる断片化は **外部 (external) 断片化** と呼ばれます。 -Internal fragmentation is unfortunate, but often better than the external fragmentation that occurs with segmentation. It still wastes memory, but does not require defragmentation and makes the amount of fragmentation predictable (on average half a page per memory region). +内部断片化が起こるのは残念なことですが、セグメンテーションで発生していた外部断片化よりも優れていることが多いです。確かにメモリ領域は無駄にしますが、デフラグメンテーションをする必要がなく、また断片化の量も予想できるからです(平均するとメモリ領域ごとにページの半分)。 -### Page Tables +### ページテーブル -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. +それぞれのプログラムのインスタンスが独自のページテーブルを持っているのが分かります。現在有効なテーブルへのポインタは、特殊なCPUのレジスタに格納されます。`x86`においては、このレジスタは`CR3`と呼ばれています。それぞれのプログラムのインスタンスを実行する前に、正しいページテーブルを指すポインタをこのレジスタにロードするのはOSの役割です。 -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 transparent 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. +それぞれのメモリアクセスにおいて、CPUはテーブルへのポインタをレジスタから読み出し、テーブル内のアクセスされたページから対応するフレームを見つけ出します。これは完全にハードウェア内で行われ、実行しているプログラムからはこの動作は見えません。変換プロセスを高速化するために、多くのCPUアーキテクチャは前回の変換の結果を覚えておく専用のキャッシュを持っています。 -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. +アーキテクチャによっては、ページテーブルのエントリは"Flags"フィールドにあるアクセス許可のような属性も保持することができます。上の例では、"r/w"フラグがあることにより、このページは読み書きのどちらも可能だということを示しています。 -### Multilevel Page Tables +### 複数層 (Multilevel) ページテーブル -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): +上で見たシンプルなページテーブルには、アドレス空間が大きくなってくると問題が発生します:メモリが無駄になるのです。たとえば、`0`, `1_000_000`, `1_000_050` および `1_000_100`(3ケタごとの区切りとして`_`を用いています)の4つの仮想ページを使うプログラムを考えてみましょう。 ![Page 0 mapped to frame 0 and pages `1_000_000`–`1_000_150` mapped to frames 100–250](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). +このプログラムはたった4つしか物理フレームを必要としていないのに、テーブルには100万以上ものエントリが存在してしまっています。空のエントリを省略した場合、変換プロセスにおいてCPUが正しいエントリに直接ジャンプすることができなくなってしまうので、それはできません(たとえば、4つめのページが4つめのエントリを使っていることが保証されなくなってしまいます)。 -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. +この無駄になるメモリを減らすことができる、 **2層ページテーブル** を使ってみましょう。発想としては、それぞれのアドレス領域に異なるページテーブルを使うというものです。**レベル2** ページテーブルと呼ばれる追加のページテーブルは、アドレス領域と(レベル1の)ページテーブルのあいだの対応を格納します。 -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: +これを理解するには、例を見るのが一番です。それぞれのレベル1テーブルは大きさ`10_000`の領域に対応するとします。すると、以下のテーブルが上のマッピングの例に対応するものとなります: ![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 100–250, 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`. +ページ0は最初の`10_000`バイト領域に入るので、レベル2ページテーブルの最初のエントリを使います。このエントリはT1というレベル1ページテーブルを指し、このページテーブルはページ`0`はフレーム`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 at 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, so e.g. the entry for page `1_000_050` is just `50`. +ページ`1_000_000`, `1_000_050`および`1_000_100`はすべて、`10_000`バイトの大きさの領域100個目に入るので、レベル2ページテーブルの100個目のエントリを使います。このエントリは、T2というべつのレベル1テーブルを指しており、このレベル1テーブルはこれらの3つのページをフレーム`100`, `150`および`200`に対応させています。レベル1テーブルにおけるページアドレスには領域のオフセットは含まれていない、つまり例えば、`1_000_050`というページのエントリは単に`50`である、ということに注意してください。 -We still have 100 empty entries in the level 2 table, but much fewer than the million empty entries before. The reason for this 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`. +レベル2テーブルにはまだ100個の空のエントリがありますが、前の100万にくらべればこれはずっと少ないです。これほど節約できる理由は、`10_000`から`10_000_000`の、対応付けのないメモリ領域のためのレベル1テーブルを作る必要がないためです。 -The principle of two-level page tables can be extended to three, four, or more levels. Then the page table register points at 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 at the mapped frame. The principle in general is called a _multilevel_ or _hierarchical_ page table. +2層ページテーブルの理論は、3、4、それ以上に多くの層に拡張することができます。このとき、ページテーブルレジスタは最も高いレベルのテーブルを指し、そのテーブルは次に低いレベルのテーブルを指し、それはさらに低いレベルのものを、と続きます。そして、レベル1のテーブルは対応するフレームを指します。この理論は一般に **複数層 (multilevel) ** ページテーブルや、 **階層型 (hierarchical) ** ページテーブルと呼ばれます。 -Now that we know how paging and multilevel page tables works, 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). +ページングと複数層ページテーブルのしくみが理解できたので、x86_64アーキテクチャにおいてどのようにページングが実装されているのかについて見ていきましょう(以下では、CPUは64ビットモードで動いているとします)。 -## Paging on x86_64 +## x86_64におけるページング -The x86_64 architecture uses a 4-level page table and a page size of 4KiB. 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 * 8B = 4KiB large and thus fits exactly into one page. +x86_64アーキテクチャは4層ページテーブルを使っており、ページサイズは4KiBです。それぞれのページテーブルは、層によらず512のエントリを持っています。それぞれのエントリの大きさは8バイトなので、それぞれのテーブルは512 * 8B = 4KiBであり、よってぴったり1ページに収まります。 -The page table index for level is derived directly from the virtual address: +(各)レベルのページテーブルインデックスは、仮想アドレスから直接求められます: ![Bits 0–12 are the page offset, bits 12–21 the level 1 index, bits 21–30 the level 2 index, bits 30–39 the level 3 index, and bits 39–48 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 4KiB page (2^12 bytes = 4KiB). Bits 48 to 64 are discarded, which means that x86_64 is not really 64-bit since it only supports 48-bit addresses. +それぞれのテーブルインデックスは9ビットからなることがわかります。それぞれのテーブルに2^9 = 512エントリあることを考えるとこれは妥当です。最下位の12ビットは4KiBページ内でのオフセット(2^12バイト = 4KiB)です。48ビットから64ビットは捨てられます。つまり、x86_64は48ビットのアドレスにしか対応しておらず、そのため実際には64ビットではないということです。 [5-level page table]: https://en.wikipedia.org/wiki/Intel_5-level_paging -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. +48ビットから64ビットが捨てられるからといって、任意の値にしてよいということではありません。この範囲のすべてのビットは、アドレスを一意にし、5層ページテーブルのような将来の拡張に備えるため、47ビットの値と同じにしないといけません。これは、[2の補数における符号拡張][sign extension in two's complement]によく似ているので、 **符号 (sign) 拡張 (extension) ** とよばれています。アドレスが適切に符号拡張されていない場合、CPUは例外を投げます。 [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 extends 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. +近年発売されたIntelのIce LakeというCPUは、[5層ページテーブル][5-level page tables]にオプションで対応していて、仮想アドレスが48ビットから57ビットまで延長されているということは書いておく価値があるでしょう。いまの段階で私たちのカーネルをこの特定のCPUに最適化する意味はないので、この記事では標準の4層ページテーブルのみを使うことにします。 [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 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). +現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「 (root) 」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれません。 -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: +上のページテーブル階層構造は、最終的に(青色の)2つのページへの対応を行っています。ページテーブルのインデックスから、これらの2つのページの仮想アドレスは`0x803FE7F000`と`0x803FE00000`であると推論できます。プログラムがアドレス`0x803FE7F5CE`から読み込もうとしたときに何が起こるかを見てみましょう。まず、アドレスを2進数に変換し、アドレスのページテーブルインデックスとページオフセットが何であるかを決定します: ![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 16KiB. -- 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 24KiB. -- 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 12KiB, 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. +- まず、`CR3`レジスタからレベル4テーブルのアドレスを読み出します。 +- レベル4のインデックスは1なので、このテーブルの1つ目のインデックスを見ます。すると、レベル3テーブルはアドレス16KiBに格納されていると分かります。 +- レベル3テーブルをそのアドレスから読み出し、インデックス0のエントリを見ると、レベル2テーブルは24KiBにあると教えてくれます。 +- レベル2のインデックスは511なので、このページの最後のエントリを見て、レベル1テーブルのアドレスを見つけます。 +- レベル1テーブルの127番目のエントリを読むことで、ついに対象のページは12KiB(16進数では0x3000)のフレームに対応づけられていると分かります。 +- 最後のステップは、ページオフセットをフレームアドレスに足して、物理アドレスを得ることです。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 level, 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. +レベル1テーブルにあるこのページのパーミッション(訳注:ページテーブルにおいて、Flagsとある列)は`r`であり、これは読み込み専用という意味です。これらのようなパーミッションに対する侵害はハードウェアによって保護されており、このページに書き込もうとした場合は例外が投げられます。より高いレベルのページにおけるパーミッションは、下のレベルにおいて可能なパーミッションを制限します。たとえばレベル3エントリを読み込み専用にした場合、下のレベルで読み書きを許可したとしても、このエントリをつかうページはすべて書き込み不可になります。 -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: +この例ではそれぞれのテーブルの実体 (インスタンス) を1つずつしか使いませんでしたが、普通それぞれのアドレス空間において、各レベルに対して複数のインスタンスが使われるということは知っておく価値があるでしょう。最大で -- 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). +- 1個のレベル4テーブル +- 512個のレベル3テーブル(レベル4テーブルには512エントリあるので) +- 512 * 512個のレベル2テーブル(512個のレベル3テーブルそれぞれに512エントリあるので) +- 512 * 512 * 512個のレベル1テーブル(それぞれのレベル2テーブルに512エントリあるので) -### Page Table Format +があります。 -Page tables on the x86_64 architecture are basically an array of 512 entries. In Rust syntax: +### ページテーブルの形式 + +x86_64アーキテクチャにおけるページテーブルは詰まるところ512個のエントリの配列です。Rustの構文では: ```rust #[repr(align(4096))] @@ -202,42 +203,43 @@ pub struct PageTable { } ``` +`repr`属性で示されるように、ページテーブルはアラインされる必要があります。つまり4KiBごとの境界に揃えられる必要がある、ということです。この要求により、ページテーブルはつねにページひとつを完全に使うので、エントリをとても小さくできる最適化が可能になります。 As indicated by the `repr` attribute, page tables need to be page aligned, i.e. aligned on a 4KiB 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: +それぞれのエントリは8バイト(64ビット)の大きさであり、以下の形式です: -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 1GiB page in P3, creates a 2MiB 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) +ビット | 名前 | 意味 +------ | ---- | ------- +0 | present | このページはメモリ内にある +1 | writable | このページへの書き込みは許可されている +2 | user accessible | 0の場合、カーネルモードのみこのページにアクセスできる +3 | write through caching | 書き込みはメモリに対して直接行われる +4 | disable cache | このページにキャッシュを使わない +5 | accessed | このページが使われているとき、CPUはこのビットを1にする +6 | dirty | このページへの書き込みが行われたとき、CPUはこのビットを1にする +7 | huge page/null | P1とP4においては0で、P3においては1GiBのページを、P2においては2MiBのページを作る +8 | global | キャッシュにあるこのページはアドレス空間変更の際に初期化されない(CR4レジスタのPGEビットが1である必要がある) +9-11 | available | OSが自由に使える +12-51 | physical address | +52-62 | available | OSが自由に使える +63 | no execute | このページにおいてプログラムを実行することを禁じる(EFERレジスタのNXEビットが1である必要がある) -We see that only bits 12–51 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 0–11 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 52–63, because the x86_64 architecture only supports 52-bit physical addresses (similar to how it only supports 48-bit virtual addresses). +12-51ビットだけが物理フレームアドレスを格納するのに使われていて、残りのビットはフラグやオペレーティングシステムが自由に使うようになっていることがわかります。これが可能なのは、常に4096バイト単位のページに揃え (アライン) られたアドレス(ページテーブルか、対応づけられたフレームの先頭)を指しているからです。これは、0-11ビットは常にゼロであることを意味し、したがってこれらのビットを格納しておく必要はありません。ハードウェアがアドレスを使用する前に、それらのビットをゼロとして(追加して)やれば良いからです。同じことが52-63ビットについてもいえます。なぜならx86_64アーキテクチャは52ビットの物理アドレスしかサポートしていないからです(仮想アドレスを48ビットしかサポートしていないのと似ています)。 -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 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 were modified since the last save to disk. -- The `write through caching` and `disable cache` flags allow to control the 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 an 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 to create 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 2MiB = 512 * 4KiB for level 2 entries or even 1GiB = 512 * 2MiB for level 3 entries. The advantage of using larger pages is that fewer lines of the translation cache and fewer page tables are needed. +- `present`フラグは、対応付けられているページとそうでないページを区別します。このフラグは、メインメモリが一杯になったとき、ページを一時的にディスクにスワップしたいときに使うことができます。後でページがアクセスされたら、 **ページフォルト** という特別な例外が発生するので、オペレーティングシステムは不足しているページをディスクから読み出すことでこれに対応し、プログラムを再開します。 +- `writable`と`no execute`フラグはそれぞれ、このページの中身が書き込み可能かと、実行可能な命令であるかを制御します。 +- `accessed`と`dirty`フラグは、ページへの読み込みか書き込みが行われたときにCPUによって自動的に1にセットされます。この情報はオペレーティングシステムによって活用することができます――例えば、どのページをスワップするかや、ページの中身が最後にディスクに保存されて以降に修正されたかを確認することができます。 +- `write through caching`と`disable cache`フラグで、キャッシュの制御をページごとに独立して行うことができます。 +- `user accessible`フラグはページをユーザー空間のプログラムに利用可能にします。このフラグが1になっていない場合、CPUがカーネルモードのときにのみアクセスできます。この機能は、ユーザ空間のプログラムが実行している間もカーネル(の使用しているメモリ)を対応付けたままにしておくことで、[システムコール][system calls]を高速化するために使うことができます。しかし、[Spectre]脆弱性を使うと、この機能があるにもかかわらず、ユーザ空間プログラムがこれらのページを読むことができてしまいます。 +- `global`フラグは、このページはすべてのアドレス空間で利用可能であり、よってアドレス空間の変更時に変換キャッシュ(TLBに関する下のセクションを読んでください)から取り除く必要がないことをハードウェアに伝えます。 +- `huge page`フラグを使うと、レベル2か3のページが対応付けられたフレームを直接指すようにすることで、より大きいサイズのページを作ることができます。このビットが1のとき、ページの大きさは512倍になるので、レベル2のエントリの場合は2MiB = 512 * 4KiBに、レベル3のエントリの場合は1GiB = 512 * 2MiBにもなります。大きいページを使うことのメリットは、必要な変換キャッシュのラインの数やページテーブルの数が少なくなることです。 [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. +`x86_64`クレートが[ページテーブル][page tables]とその[エントリ][entries]のための型を提供してくれているので、これらの構造体を私達自身で作る必要はありません。 [page tables]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTable.html [entries]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html From c7deb06fe8b387ce95a7039642d87f7710b8cc68 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Tue, 11 May 2021 23:11:42 +0900 Subject: [PATCH 025/125] Translate to the end --- .../posts/08-paging-introduction/index.ja.md | 109 +++++++++--------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index a7a10124..759a10d3 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -12,7 +12,7 @@ translation_based_on_commit = "3315bfe2f63571f5e6e924d58ed32afd8f39f892" translators = ["woodyZootopia"] +++ -この記事では**ページング**を紹介します。これは、私達のオペレーティングシステムにも使う、とても一般的なメモリ管理方式です。なぜメモリの分離 (isolation) が必要なのか、**セグメンテーション**がどのようにして働くのか、**仮想メモリ**とは何なのか、ページングがいかにしてメモリ断片化 (フラグメンテーション) の問題を解決するのかを説明します。また、x86_64アーキテクチャにおける、マルチレベルページテーブルのレイアウトについても説明します。 +この記事では**ページング**を紹介します。これは、私達のオペレーティングシステムにも使う、とても一般的なメモリ管理方式です。なぜメモリの分離が必要なのか、**セグメンテーション**がどういう仕組みなのか、**仮想メモリ**とは何なのか、ページングがいかにしてメモリ断片化 (フラグメンテーション) の問題を解決するのかを説明します。また、x86_64アーキテクチャにおける、マルチレベルページテーブルのレイアウトについても説明します。 @@ -28,7 +28,7 @@ translators = ["woodyZootopia"] オペレーティングシステムの主な役割の一つに、プログラムを互いに分離するということがあります。例えば、ウェブブラウザがテキストエディタに干渉してはいけません。この目的を達成するために、オペレーティングシステムはハードウェアの機能を利用して、あるプロセスのメモリ領域に他のプロセスがアクセスできないようにします。ハードウェアやOSの実装によって、さまざまなアプローチがあります。 -例として、ARM Cortex-Mプロセッサ(組み込みシステムに使われています)のいくつかには、[メモリ保護ユニット][_Memory Protection Unit_] (Memory Protection Unit, MPU) が搭載されており、異なるアクセス権限(例えば、アクセス不可、読み取り専用、読み書きなど)を持つメモリ領域を少数(例えば8個)定義することができます。MPUは、メモリアクセスのたびに、そのアドレスが正しいアクセス許可を持つ領域にあるかどうかを確認し、そうでなければ例外を投げます。プロセスを変更するごとにその領域とアクセス許可を変更すれば、オペレーティングシステムはそれぞれのプロセスが自身のメモリにのみアクセスすることを保証し、したがってプロセスを互いに分離することができます。 +例として、ARM Cortex-Mプロセッサ(組み込みシステムに使われています)のいくつかには、[メモリ保護ユニット][_Memory Protection Unit_] (Memory Protection Unit, MPU) が搭載されており、異なるアクセス権限(例えば、アクセス不可、読み取り専用、読み書きなど)を持つメモリ領域を少数(例えば8個)定義できます。MPUは、メモリアクセスのたびに、そのアドレスが正しいアクセス許可を持つ領域にあるかどうかを確認し、そうでなければ例外を投げます。プロセスを変更するごとにその領域とアクセス許可を変更すれば、オペレーティングシステムはそれぞれのプロセスが自身のメモリにのみアクセスすることを保証し、したがってプロセスを互いに分離できます。 [_Memory Protection Unit_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu @@ -39,11 +39,11 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ## セグメンテーション -セグメンテーションは1978年にはすでに導入されており、当初の目的はアドレス可能なメモリの量を増やすためでした。当時、CPUは16bitのアドレスしか使えなかったので、アドレス可能なメモリは64KiBに限られていました。この64KiBを超えてアクセスするために、セグメントレジスタが追加され、それぞれにオフセットアドレスが設定されました。CPUがメモリにアクセスするとき、毎回このオフセットを自動的に加算するので、最大1MiBのメモリにアクセスできるようになりました。 +セグメンテーションは1978年にはすでに導入されており、当初の目的はアドレス可能なメモリの量を増やすためでした。当時、CPUは16bitのアドレスしか使えなかったので、アドレス可能なメモリは64KiBに限られていました。この64KiBを超えてアクセスするために、セグメントレジスタが追加され、このそれぞれにオフセットアドレスが格納されるようになりました。CPUがメモリにアクセスするとき、毎回このオフセットを自動的に加算するので、最大1MiBのメモリにアクセスできるようになりました。 メモリアクセスの種類によって、セグメントレジスタは自動的にCPUによって選ばれます。命令の引き出し (フェッチ) にはコードセグメント`CS`が使用され、スタック操作(プッシュ・ポップ)にはスタックセグメント`SS`が使用されます。その他の命令では、データセグメント`DS`やエクストラセグメント`ES`が使用されます。その後、自由に使用できる`FS`と`GS`というセグメントレジスタも追加されました。 -セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス許可が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセス自身のメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離することができます。 +セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス許可設定 (パーミッション) が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセスのメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離できます。 [_protected mode_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode [_descriptor table_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table @@ -60,9 +60,9 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ![Two virtual address spaces with address 0–150, one translated to 100–250, the other to 300–450](segmentation-same-program-twice.svg) -同じプログラムを2つ実行していますが、別の変換関数が使われています。1つ目のインスタンスではセグメントのオフセットが100なので、0から150の仮想アドレスは100から250に変換されます。2つ目のインスタンスではオフセットが300なので、0から150の仮想アドレスが300から450に変換されます。これにより、プログラムが互いに干渉することなく同じコード、同じ仮想アドレスを使うことができます。 +同じプログラムを2つ実行していますが、別の変換関数が使われています。1つ目の実体 (インスタンス) ではセグメントのオフセットが100なので、0から150の仮想アドレスは100から250に変換されます。2つ目のインスタンスではオフセットが300なので、0から150の仮想アドレスが300から450に変換されます。これにより、プログラムが互いに干渉することなく同じコード、同じ仮想アドレスを使うことができます。 -もう一つの利点は、プログラムが全く異なる仮想アドレスを使っていたとしても、物理メモリ上の任意の場所に置くことができるということです。したがって、OSはプログラムを再コンパイルすることなく、利用可能なメモリをフルに活用することができます。 +もう一つの利点は、プログラムが全く異なる仮想アドレスを使っていたとしても、物理メモリ上の任意の場所に置けるということです。したがって、OSはプログラムを再コンパイルすることなく利用可能なメモリをフルに活用できます。 ### 断片化 (fragmentation) @@ -70,7 +70,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ![Three virtual address spaces, but there is not enough continuous space for the third](segmentation-fragmentation.svg) -開放されているメモリは十分にあるにも関わらず、プログラムのインスタンスを重ねることなく物理メモリに対応づけることはできません。ここで必要なのは **連続した** メモリであり、開放されたメモリが小さな塊であっては使えないためです。 +開放されているメモリは十分にあるにも関わらず、プログラムのインスタンスを重ねることなく物理メモリに対応づけることはできません。ここで必要なのは **連続した** メモリであり、開放されているメモリが小さな塊であっては使えないためです。 この断片化に対処する方法の一つは、実行を一時停止し、メモリの使用されている部分を寄せ集めて、変換関数を更新し、実行を再開することでしょう: @@ -78,7 +78,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 これで、プログラムの3つ目のインスタンスを開始するのに十分な連続したスペースができました。 -このデフラグメンテーションという処理の欠点は、大量のメモリをコピーしなければならず、パフォーマンスを低下させてしまうことです。また、メモリが断片化しすぎる前に定期的に実行しないといけません。すると、プログラムが時々一時停止して、反応がなくなるので、性能が予測不可能になってしまいます。 +このデフラグメンテーションという処理の欠点は、大量のメモリをコピーしなければならず、パフォーマンスを低下させてしまうことです。また、メモリが断片化しすぎる前に定期的に実行しないといけません。そうすると、プログラムが時々一時停止して反応がなくなるので、性能が予測不可能になってしまいます。 ほとんどのシステムでセグメンテーションが用いられなくなった理由の一つに、この断片化の問題があります。実際、x86の64ビットモードでは、セグメンテーションはもはやサポートされていません。代わりに **ページング** が使用されており、これにより断片化の問題は完全に回避されます。 @@ -86,11 +86,11 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ページングの考え方は、仮想メモリ空間と物理メモリ空間の両方を、サイズの固定された小さなブロックに分割するというものです。仮想メモリ空間のブロックは **ページ** と呼ばれ、物理アドレス空間のブロックは **フレーム** と呼ばれます。各ページはフレームに独立してマッピングできるので、大きなメモリ領域を連続していない物理フレームに分割することが可能です。 -この方法の利点は、セグメンテーションの代わりにページングを使ってもう一度上のメモリ空間断片化の状況を見てみれば明らかになります: +この方法の利点は、上のメモリ空間断片化の状況をもう一度、セグメンテーションの代わりにページングを使って見てみれば明らかになります: ![With paging the third program instance can be split across many smaller physical areas](paging-fragmentation.svg) -この例では、ページサイズは50バイトなので、それぞれのメモリ領域が3つのページに分割されます。それぞれのページは個別にフレームに対応付けられるので、連続した仮想メモリ領域を非連続な物理フレームへと対応付けられるのです。これにより、デフラグを事前に実行することなく、3つ目のプログラムのインスタンスを開始することができるようになります。 +この例では、ページサイズは50バイトなので、それぞれのメモリ領域が3つのページに分割されます。それぞれのページは個別にフレームに対応付けられるので、連続した仮想メモリ領域を非連続な物理フレームへと対応付けられるのです。これにより、デフラグを事前に実行することなく、3つ目のプログラムのインスタンスを開始できるようになります。 ### 隠された断片化 @@ -112,7 +112,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 それぞれのメモリアクセスにおいて、CPUはテーブルへのポインタをレジスタから読み出し、テーブル内のアクセスされたページから対応するフレームを見つけ出します。これは完全にハードウェア内で行われ、実行しているプログラムからはこの動作は見えません。変換プロセスを高速化するために、多くのCPUアーキテクチャは前回の変換の結果を覚えておく専用のキャッシュを持っています。 -アーキテクチャによっては、ページテーブルのエントリは"Flags"フィールドにあるアクセス許可のような属性も保持することができます。上の例では、"r/w"フラグがあることにより、このページは読み書きのどちらも可能だということを示しています。 +アーキテクチャによっては、ページテーブルのエントリは"Flags"フィールドにあるアクセス許可のような属性も保持できます。上の例では、"r/w"フラグがあることにより、このページは読み書きのどちらも可能だということを示しています。 ### 複数層 (Multilevel) ページテーブル @@ -120,9 +120,9 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ![Page 0 mapped to frame 0 and pages `1_000_000`–`1_000_150` mapped to frames 100–250](single-level-page-table.svg) -このプログラムはたった4つしか物理フレームを必要としていないのに、テーブルには100万以上ものエントリが存在してしまっています。空のエントリを省略した場合、変換プロセスにおいてCPUが正しいエントリに直接ジャンプすることができなくなってしまうので、それはできません(たとえば、4つめのページが4つめのエントリを使っていることが保証されなくなってしまいます)。 +このプログラムはたった4つしか物理フレームを必要としていないのに、テーブルには100万以上ものエントリが存在してしまっています。空のエントリを省略した場合、変換プロセスにおいてCPUが正しいエントリに直接ジャンプできなくなってしまうので、それはできません(たとえば、4つめのページが4つめのエントリを使っていることが保証されなくなってしまいます)。 -この無駄になるメモリを減らすことができる、 **2層ページテーブル** を使ってみましょう。発想としては、それぞれのアドレス領域に異なるページテーブルを使うというものです。**レベル2** ページテーブルと呼ばれる追加のページテーブルは、アドレス領域と(レベル1の)ページテーブルのあいだの対応を格納します。 +この無駄になるメモリを減らせる、 **2層ページテーブル** を使ってみましょう。発想としては、それぞれのアドレス領域に異なるページテーブルを使うというものです。**レベル2** ページテーブルと呼ばれる追加のページテーブルは、アドレス領域と(レベル1の)ページテーブルのあいだの対応を格納します。 これを理解するには、例を見るのが一番です。それぞれのレベル1テーブルは大きさ`10_000`の領域に対応するとします。すると、以下のテーブルが上のマッピングの例に対応するものとなります: @@ -134,7 +134,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 レベル2テーブルにはまだ100個の空のエントリがありますが、前の100万にくらべればこれはずっと少ないです。これほど節約できる理由は、`10_000`から`10_000_000`の、対応付けのないメモリ領域のためのレベル1テーブルを作る必要がないためです。 -2層ページテーブルの理論は、3、4、それ以上に多くの層に拡張することができます。このとき、ページテーブルレジスタは最も高いレベルのテーブルを指し、そのテーブルは次に低いレベルのテーブルを指し、それはさらに低いレベルのものを、と続きます。そして、レベル1のテーブルは対応するフレームを指します。この理論は一般に **複数層 (multilevel) ** ページテーブルや、 **階層型 (hierarchical) ** ページテーブルと呼ばれます。 +2層ページテーブルの理論は、3、4、それ以上に多くの層に拡張できます。このとき、ページテーブルレジスタは最も高いレベルのテーブルを指し、そのテーブルは次に低いレベルのテーブルを指し、それはさらに低いレベルのものを、と続きます。そして、レベル1のテーブルは対応するフレームを指します。この理論は一般に **複数層 (multilevel) ** ページテーブルや、 **階層型 (hierarchical) ** ページテーブルと呼ばれます。 ページングと複数層ページテーブルのしくみが理解できたので、x86_64アーキテクチャにおいてどのようにページングが実装されているのかについて見ていきましょう(以下では、CPUは64ビットモードで動いているとします)。 @@ -164,7 +164,7 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ![An example 4-level page hierarchy with each page table shown in physical memory](x86_64-page-table-translation.svg) -現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「 (root) 」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれません。 +現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「 (root) 」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれないからです。 上のページテーブル階層構造は、最終的に(青色の)2つのページへの対応を行っています。ページテーブルのインデックスから、これらの2つのページの仮想アドレスは`0x803FE7F000`と`0x803FE00000`であると推論できます。プログラムがアドレス`0x803FE7F5CE`から読み込もうとしたときに何が起こるかを見てみましょう。まず、アドレスを2進数に変換し、アドレスのページテーブルインデックスとページオフセットが何であるかを決定します: @@ -181,7 +181,7 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ![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) -レベル1テーブルにあるこのページのパーミッション(訳注:ページテーブルにおいて、Flagsとある列)は`r`であり、これは読み込み専用という意味です。これらのようなパーミッションに対する侵害はハードウェアによって保護されており、このページに書き込もうとした場合は例外が投げられます。より高いレベルのページにおけるパーミッションは、下のレベルにおいて可能なパーミッションを制限します。たとえばレベル3エントリを読み込み専用にした場合、下のレベルで読み書きを許可したとしても、このエントリをつかうページはすべて書き込み不可になります。 +レベル1テーブルにあるこのページのパーミッション(訳注:ページテーブルにおいて、Flagsとある列)は`r`であり、これは読み込み専用という意味です。これらのようなパーミッションに対する侵害はハードウェアによって保護されており、このページに書き込もうとした場合は例外が投げられます。より高いレベルのページにおけるパーミッションは、下のレベルにおいて可能なパーミッションを制限します。たとえばレベル3エントリを読み込み専用にした場合、下のレベルで読み書きを許可したとしても、このエントリを使うページはすべて書き込み不可になります。 この例ではそれぞれのテーブルの実体 (インスタンス) を1つずつしか使いませんでしたが、普通それぞれのアドレス空間において、各レベルに対して複数のインスタンスが使われるということは知っておく価値があるでしょう。最大で @@ -203,8 +203,7 @@ pub struct PageTable { } ``` -`repr`属性で示されるように、ページテーブルはアラインされる必要があります。つまり4KiBごとの境界に揃えられる必要がある、ということです。この要求により、ページテーブルはつねにページひとつを完全に使うので、エントリをとても小さくできる最適化が可能になります。 -As indicated by the `repr` attribute, page tables need to be page aligned, i.e. aligned on a 4KiB boundary. This requirement guarantees that a page table always fills a complete page and allows an optimization that makes entries very compact. +`repr`属性で示されるように、ページテーブルはアラインされる必要があります。つまり4KiBごとの境界に揃えられる必要がある、ということです。この条件により、ページテーブルはつねにページひとつを完全に使うので、エントリをとてもコンパクトにできる最適化が可能になります。 それぞれのエントリは8バイト(64ビット)の大きさであり、以下の形式です: @@ -230,9 +229,9 @@ As indicated by the `repr` attribute, page tables need to be page aligned, i.e. - `present`フラグは、対応付けられているページとそうでないページを区別します。このフラグは、メインメモリが一杯になったとき、ページを一時的にディスクにスワップしたいときに使うことができます。後でページがアクセスされたら、 **ページフォルト** という特別な例外が発生するので、オペレーティングシステムは不足しているページをディスクから読み出すことでこれに対応し、プログラムを再開します。 - `writable`と`no execute`フラグはそれぞれ、このページの中身が書き込み可能かと、実行可能な命令であるかを制御します。 -- `accessed`と`dirty`フラグは、ページへの読み込みか書き込みが行われたときにCPUによって自動的に1にセットされます。この情報はオペレーティングシステムによって活用することができます――例えば、どのページをスワップするかや、ページの中身が最後にディスクに保存されて以降に修正されたかを確認することができます。 +- `accessed`と`dirty`フラグは、ページへの読み込みか書き込みが行われたときにCPUによって自動的に1にセットされます。この情報はオペレーティングシステムによって活用でき、例えば、どのページをスワップするかや、ページの中身が最後にディスクに保存されて以降に修正されたかを確認できます。 - `write through caching`と`disable cache`フラグで、キャッシュの制御をページごとに独立して行うことができます。 -- `user accessible`フラグはページをユーザー空間のプログラムに利用可能にします。このフラグが1になっていない場合、CPUがカーネルモードのときにのみアクセスできます。この機能は、ユーザ空間のプログラムが実行している間もカーネル(の使用しているメモリ)を対応付けたままにしておくことで、[システムコール][system calls]を高速化するために使うことができます。しかし、[Spectre]脆弱性を使うと、この機能があるにもかかわらず、ユーザ空間プログラムがこれらのページを読むことができてしまいます。 +- `user accessible`フラグはページをユーザー空間のコードが利用できるようにします。このフラグが1になっていない場合、CPUがカーネルモードのときにのみアクセスできます。この機能は、ユーザ空間のプログラムが実行している間もカーネル(の使用しているメモリ)を対応付けたままにしておくことで、[システムコール][system calls]を高速化するために使うことができます。しかし、[Spectre]脆弱性を使うと、この機能があるにもかかわらず、ユーザ空間プログラムがこれらのページを読むことができてしまいます。 - `global`フラグは、このページはすべてのアドレス空間で利用可能であり、よってアドレス空間の変更時に変換キャッシュ(TLBに関する下のセクションを読んでください)から取り除く必要がないことをハードウェアに伝えます。 - `huge page`フラグを使うと、レベル2か3のページが対応付けられたフレームを直接指すようにすることで、より大きいサイズのページを作ることができます。このビットが1のとき、ページの大きさは512倍になるので、レベル2のエントリの場合は2MiB = 512 * 4KiBに、レベル3のエントリの場合は1GiB = 512 * 2MiBにもなります。大きいページを使うことのメリットは、必要な変換キャッシュのラインの数やページテーブルの数が少なくなることです。 @@ -244,32 +243,32 @@ As indicated by the `repr` attribute, page tables need to be page aligned, i.e. [page tables]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTable.html [entries]: https://docs.rs/x86_64/0.13.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 4 memory accesses. To improve performance, the x86_64 architecture caches the last few translations in the so-called _translation lookaside buffer_ (TLB). This allows to skip the translation when the translation is still cached. +4層ページテーブルを使うと、仮想アドレスを変換するたびに4回メモリアクセスを行わないといけないので、変換のコストは大きくなります。性能改善のために、x86_64アーキテクチャは、直前数回の変換内容を **トランスレーション・ルックアサイド・バッファ (translation lookaside buffer, TLB)** と呼ばれるところにキャッシュします。これにより、前の変換がまだキャッシュされているなら、変換をスキップできます。 -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]. +他のCPUキャッシュと異なり、TLBは完全に透明ではなく、ページテーブルの内容が変わったときに変換内容を更新したり取り除いたりしてくれません(訳注:キャッシュが透明 (transparent) であるとは、利用者がキャッシュの存在を意識する必要がないという意味)。つまり、カーネルがページテーブルを変更したときは、カーネル自らTLBを更新しないといけないということです。これを行うために、[`invlpg`]("invalidate page"、ページを無効化の意)という特別なCPU命令があります。これは指定されたページの変換をTLBから取り除き、次のアクセスの際に再び読み込まれるようにします。また、TLBは`CR3`レジスタを再読み込みすることでも初期化できます。`CR3`レジスタの再読み込み、アドレス空間が変更されたという状況を模擬するのです。`x86_64`クレートの[`tlb`モジュール][`tlb` module]が、両方のやり方のRust関数を提供しています。 [`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html [`tlb` module]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tlb/index.html -It is important to remember flushing 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. +ページテーブルを修正したときは毎回TLBを初期化しないといけないことはしっかりと覚えておいてください。さもないと、CPUは古い変換を使いつづけるかもしれず、これはデバッグの非常に難しい、予測不能なバグに繋がるかもしれないためです。 -## 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 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. +ひとつ言っていなかったことがあります:**わたしたちのカーネルはすでにページングを使っています**。[Rustでつくる最小のカーネル]["A minimal Rust Kernel"]の記事で追加したブートローダは、すでに私たちのカーネルのすべてのページを物理フレームに対応付けるような4層ページ階層構造を設定しているのです。ブートローダがこれを行う理由は、x86_64の64ビットモードにおいてページングは必須となっているからです。 -["A minimal Rust kernel"]: @/edition-2/posts/02-minimal-rust-kernel/index.md#creating-a-bootimage +["A minimal Rust kernel"]: @/edition-2/posts/02-minimal-rust-kernel/index.ja.md#butoimeziwozuo-ru -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`. +つまり、私達がカーネルにおいて使ってきたすべてのメモリアドレスは、仮想アドレスだったということです。アドレス`0xb8000`にあるVGAバッファへのアクセスが上手くいっていたのは、ひとえにブートローダがこのメモリページを **恒等対応** させていた、つまり、仮想ページ`0xb8000`を物理フレーム`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 set 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]ではなくページフォルト例外が得られるように、ページフォルト処理関数 (ハンドラ) を作ってIDTに追加しましょう: -[double fault]: @/edition-2/posts/06-double-faults/index.md +[double fault]: @/edition-2/posts/06-double-faults/index.ja.md ```rust // in src/interrupts.rs @@ -303,7 +302,7 @@ extern "x86-interrupt" fn page_fault_handler( } ``` -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`]レジスタは、ページフォルト時にCPUによって自動的に設定されており、その値はアクセスされページフォルトを引き起こした仮想アドレスになっています。`x86_64`クレートの[`Cr2::read`]関数を使ってこれを読み込み出力します。[`PageFaultErrorCode`]型は、ページフォルトを引き起こしたメモリアクセスの種類についてより詳しい情報を提供します(例えば、読み込みと書き込みのどちらによるものなのか、など)。そのため、これも出力します。ページフォルトを解決して実行を継続することはできないので、最後は[`hlt_loop`]に入ります。 [`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2 [`Cr2::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr2.html#method.read @@ -311,7 +310,7 @@ The [`CR2`] register is automatically set by the CPU on a page fault and contain [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 @@ -335,26 +334,26 @@ pub extern "C" fn _start() -> ! { } ``` -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. +`CR2`レジスタは確かに私達がアクセスしようとしていたアドレスである`0xdeadbeaf`を格納しています。エラーコードが[`CAUSED_BY_WRITE`]なので、この障害 (フォルト) write (書き込み) 操作の実行中に発生したのだと分かります。更に、[1にセットされていないビット][`PageFaultErrorCode`]からも情報を得ることができます。例えば、`PROTECTION_VIOLATION`フラグが1にセットされていないことから、ページフォルトは対象のページが存在しなかったために発生したのだと分かります。 [`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.13.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`: +ページフォルトを起こした時点での命令ポインタは`0x2031b2`であるので、このアドレスはコードページを指しているとわかります。コードページはブートローダによって読み込み専用に指定されているので、このアドレスからの読み込みは大丈夫ですが、このページへの書き込みはページフォルトを起こします。`0xdeadbeaf`へのポインタを`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"); ``` @@ -363,13 +362,13 @@ By commenting out the last line, we see that the read access works, but the writ ![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. +"read worked"というメッセージが表示されますが、これは読み込み操作が何のエラーも発生させなかったことを示しています。しかし、"write worked"のメッセージではなく、ページフォルトが発生してしまいました。今回は[`CAUSED_BY_WRITE`]フラグに加えて[`PROTECTION_VIOLATION`]フラグがセットされています。これは、ページは存在していたものの、それに対する今回の操作が許可されていなかったということを示します。今回の場合、ページへの書き込みは、コードページが読み込み専用に指定されているため許可されていませんでした。 [`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.13.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 @@ -389,34 +388,34 @@ pub extern "C" fn _start() -> ! { } ``` -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. +`x86_64`クレートの[`Cr3::read`]関数は、現在有効なレベル4ページテーブルを`CR3`レジスタから(読みとって)返します。この関数は[`PhysFrame`]型と[`Cr3Flags`]型のタプルを返します。私達はフレームにしか興味がないので、タプルの2つ目の要素は無視しました。 [`Cr3::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3.html#method.read [`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html [`Cr3Flags`]: https://docs.rs/x86_64/0.13.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? +というわけで、現在有効なレベル4ページテーブルは、[`PhysAddr`]ラッパ型が示すように、 **物理** メモリのアドレス`0x1000`に格納されています。ここで疑問が生まれます:このテーブルに私達のカーネルからアクセスするにはどうすればいいのでしょう? [`PhysAddr`]: https://docs.rs/x86_64/0.13.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 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. +ページングが有効なとき、物理メモリに直接アクセスすることはできません。もしそれができたら、プログラムは容易くメモリ保護を回避して他のプログラムのメモリにアクセスできてしまうだろうからです。ですので、テーブルにアクセスする唯一の方法は、アドレス`0x1000`の物理フレームに対応づけられているような仮想ページにアクセスすることです。ページテーブルの存在するフレームへの対応づけを作るのは(実用上も必要になる)一般的な問題です。なぜなら、例えば新しいスレッドのためにスタックを割り当てるときなど、カーネルは日常的にページテーブルにアクセスする必要があるためです。 -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. +この記事では2つのメモリ保護技術を紹介しました:セグメンテーションとページングです。前者は可変サイズのメモリ領域を使用するため外部断片化の問題が存在するのに対し、後者は固定サイズのページを使用するためアクセス許可に関して遥かに細やかな制御が可能となっていました。 -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 4KiB. 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. +ページングは、(仮想メモリと物理メモリの)対応情報を1層以上のページテーブルに格納します。x86_64アーキテクチャにおいては4層ページテーブルが使用され、ページサイズは4KiBです。ハードウェアは自動的にページテーブルを辿り、変換の結果をトランスレーション・ルックアサイド・バッファ (TLB) にキャッシュします。このバッファは自動的に更新されない(「透明ではない」)ので、ページテーブルの変更時には明示的に初期化する必要があります。 -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. +私達のカーネルは既にページングによって動いており、不正なメモリアクセスはページフォルト例外を発生させるということを学びました。現在有効なページテーブルへとアクセスしたかったのですが、CR3レジスタに格納されている物理アドレスはカーネルから直接アクセスできないものであるため、それはできませんでした。 -## 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. +次の記事では、私達のカーネルをページングに対応させる方法について説明します。私達のカーネルから物理メモリにアクセスする幾つかの方法を示すので、これらを用いれば私達のカーネルが動作しているページテーブルにアクセスできます。そうすると、仮想アドレスを物理アドレスに変換する関数を実装でき、ページテーブルに新しい対応づけを作れるようになります。 From c85e40b9ab70971b2776b20a1c4e862409870ff3 Mon Sep 17 00:00:00 2001 From: "Shu W. Nakamura" <30687489+woodyZootopia@users.noreply.github.com> Date: Wed, 12 May 2021 15:00:42 +0900 Subject: [PATCH 026/125] Update blog/content/edition-2/posts/08-paging-introduction/index.ja.md --- blog/content/edition-2/posts/08-paging-introduction/index.ja.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index 759a10d3..8ea0a2b0 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -358,7 +358,7 @@ 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: +最後の2行をコメントアウトすると、読み込みアクセスだけになるので実行は成功しますが、そうしなかった場合ページフォルトが発生します: ![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) From 01e254db8c9a7644b05d72cde2bf6923cfde4045 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Wed, 12 May 2021 16:15:14 +0900 Subject: [PATCH 027/125] Refine translation --- .../posts/08-paging-introduction/index.ja.md | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index 8ea0a2b0..d4291c46 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -28,7 +28,7 @@ translators = ["woodyZootopia"] オペレーティングシステムの主な役割の一つに、プログラムを互いに分離するということがあります。例えば、ウェブブラウザがテキストエディタに干渉してはいけません。この目的を達成するために、オペレーティングシステムはハードウェアの機能を利用して、あるプロセスのメモリ領域に他のプロセスがアクセスできないようにします。ハードウェアやOSの実装によって、さまざまなアプローチがあります。 -例として、ARM Cortex-Mプロセッサ(組み込みシステムに使われています)のいくつかには、[メモリ保護ユニット][_Memory Protection Unit_] (Memory Protection Unit, MPU) が搭載されており、異なるアクセス権限(例えば、アクセス不可、読み取り専用、読み書きなど)を持つメモリ領域を少数(例えば8個)定義できます。MPUは、メモリアクセスのたびに、そのアドレスが正しいアクセス許可を持つ領域にあるかどうかを確認し、そうでなければ例外を投げます。プロセスを変更するごとにその領域とアクセス許可を変更すれば、オペレーティングシステムはそれぞれのプロセスが自身のメモリにのみアクセスすることを保証し、したがってプロセスを互いに分離できます。 +例として、ARM Cortex-Mプロセッサ(組み込みシステムに使われています)のいくつかには、[メモリ保護ユニット][_Memory Protection Unit_] (Memory Protection Unit, MPU) が搭載されており、異なるアクセス権限(例えば、アクセス不可、読み取り専用、読み書きなど)を持つメモリ領域を少数(例えば8個)定義できます。MPUは、メモリアクセスのたびに、そのアドレスが正しいアクセス権限を持つ領域にあるかどうかを確認し、そうでなければ例外を投げます。プロセスを変更するごとにその領域とアクセス権限を変更すれば、オペレーティングシステムはそれぞれのプロセスが自身のメモリにのみアクセスすることを保証し、したがってプロセスを互いに分離できます。 [_Memory Protection Unit_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu @@ -39,11 +39,11 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ## セグメンテーション -セグメンテーションは1978年にはすでに導入されており、当初の目的はアドレス可能なメモリの量を増やすためでした。当時、CPUは16bitのアドレスしか使えなかったので、アドレス可能なメモリは64KiBに限られていました。この64KiBを超えてアクセスするために、セグメントレジスタが追加され、このそれぞれにオフセットアドレスが格納されるようになりました。CPUがメモリにアクセスするとき、毎回このオフセットを自動的に加算するので、最大1MiBのメモリにアクセスできるようになりました。 +セグメンテーションは1978年にはすでに導入されており、当初の目的はアドレス可能なメモリの量を増やすことでした。当時、CPUは16bitのアドレスしか使えなかったので、アドレス可能なメモリは64KiBに限られていました。この64KiBを超えてアクセスするために、セグメントレジスタが追加され、このそれぞれにオフセットアドレスを格納するようになりました。CPUがメモリにアクセスするとき、毎回このオフセットを自動的に加算するようにすることで、最大1MiBのメモリにアクセスできるようになりました。 メモリアクセスの種類によって、セグメントレジスタは自動的にCPUによって選ばれます。命令の引き出し (フェッチ) にはコードセグメント`CS`が使用され、スタック操作(プッシュ・ポップ)にはスタックセグメント`SS`が使用されます。その他の命令では、データセグメント`DS`やエクストラセグメント`ES`が使用されます。その後、自由に使用できる`FS`と`GS`というセグメントレジスタも追加されました。 -セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス許可設定 (パーミッション) が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセスのメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離できます。 +セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス権限が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセスのメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離できます。 [_protected mode_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode [_descriptor table_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table @@ -112,11 +112,11 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 それぞれのメモリアクセスにおいて、CPUはテーブルへのポインタをレジスタから読み出し、テーブル内のアクセスされたページから対応するフレームを見つけ出します。これは完全にハードウェア内で行われ、実行しているプログラムからはこの動作は見えません。変換プロセスを高速化するために、多くのCPUアーキテクチャは前回の変換の結果を覚えておく専用のキャッシュを持っています。 -アーキテクチャによっては、ページテーブルのエントリは"Flags"フィールドにあるアクセス許可のような属性も保持できます。上の例では、"r/w"フラグがあることにより、このページは読み書きのどちらも可能だということを示しています。 +アーキテクチャによっては、ページテーブルのエントリは"Flags"フィールドにあるアクセス権限のような属性も保持できます。上の例では、"r/w"フラグがあることにより、このページは読み書きのどちらも可能だということを示しています。 ### 複数層 (Multilevel) ページテーブル -上で見たシンプルなページテーブルには、アドレス空間が大きくなってくると問題が発生します:メモリが無駄になるのです。たとえば、`0`, `1_000_000`, `1_000_050` および `1_000_100`(3ケタごとの区切りとして`_`を用いています)の4つの仮想ページを使うプログラムを考えてみましょう。 +上で見たシンプルなページテーブルは、アドレス空間が大きくなってくると問題が発生します:メモリが無駄になるのです。たとえば、`0`, `1_000_000`, `1_000_050` および `1_000_100`(3ケタごとの区切りとして`_`を用いています)の4つの仮想ページを使うプログラムを考えてみましょう。 ![Page 0 mapped to frame 0 and pages `1_000_000`–`1_000_150` mapped to frames 100–250](single-level-page-table.svg) @@ -130,13 +130,13 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ページ0は最初の`10_000`バイト領域に入るので、レベル2ページテーブルの最初のエントリを使います。このエントリはT1というレベル1ページテーブルを指し、このページテーブルはページ`0`はフレーム`0`に対応すると指定します。 -ページ`1_000_000`, `1_000_050`および`1_000_100`はすべて、`10_000`バイトの大きさの領域100個目に入るので、レベル2ページテーブルの100個目のエントリを使います。このエントリは、T2というべつのレベル1テーブルを指しており、このレベル1テーブルはこれらの3つのページをフレーム`100`, `150`および`200`に対応させています。レベル1テーブルにおけるページアドレスには領域のオフセットは含まれていない、つまり例えば、`1_000_050`というページのエントリは単に`50`である、ということに注意してください。 +ページ`1_000_000`, `1_000_050`および`1_000_100`はすべて、`10_000`バイトの大きさの領域100個目に入るので、レベル2ページテーブルの100個目のエントリを使います。このエントリは、T2という別のレベル1テーブルを指しており、このレベル1テーブルはこれらの3つのページをフレーム`100`, `150`および`200`に対応させています。レベル1テーブルにおけるページアドレスには領域のオフセットは含まれていない、つまり例えば、ページ`1_000_050`のエントリは単に`50`である、ということに注意してください。 -レベル2テーブルにはまだ100個の空のエントリがありますが、前の100万にくらべればこれはずっと少ないです。これほど節約できる理由は、`10_000`から`10_000_000`の、対応付けのないメモリ領域のためのレベル1テーブルを作る必要がないためです。 +レベル2テーブルにはまだ100個の空のエントリがありますが、前の100万にくらべればこれはずっと少ないです。このように節約できる理由は、`10_000`から`10_000_000`の、対応付けのないメモリ領域のためのレベル1テーブルを作る必要がないためです。 -2層ページテーブルの理論は、3、4、それ以上に多くの層に拡張できます。このとき、ページテーブルレジスタは最も高いレベルのテーブルを指し、そのテーブルは次に低いレベルのテーブルを指し、それはさらに低いレベルのものを、と続きます。そして、レベル1のテーブルは対応するフレームを指します。この理論は一般に **複数層 (multilevel) ** ページテーブルや、 **階層型 (hierarchical) ** ページテーブルと呼ばれます。 +2層ページテーブルの原理は、3、4、それ以上に多くの層に拡張できます。このとき、ページテーブルレジスタは最も高いレベルのテーブルを指し、そのテーブルは次に低いレベルのテーブルを指し、それはさらに低いレベルのものを、と続きます。そして、レベル1のテーブルは対応するフレームを指します。この原理は一般に **複数層 (multilevel) ** ページテーブルや、 **階層型 (hierarchical) ** ページテーブルと呼ばれます。 -ページングと複数層ページテーブルのしくみが理解できたので、x86_64アーキテクチャにおいてどのようにページングが実装されているのかについて見ていきましょう(以下では、CPUは64ビットモードで動いているとします)。 +ページングと複数層ページテーブルの仕組みが理解できたので、x86_64アーキテクチャにおいてどのようにページングが実装されているのかについて見ていきましょう(以下では、CPUは64ビットモードで動いているとします)。 ## x86_64におけるページング @@ -146,25 +146,25 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ![Bits 0–12 are the page offset, bits 12–21 the level 1 index, bits 21–30 the level 2 index, bits 30–39 the level 3 index, and bits 39–48 the level 4 index](x86_64-table-indices-from-address.svg) -それぞれのテーブルインデックスは9ビットからなることがわかります。それぞれのテーブルに2^9 = 512エントリあることを考えるとこれは妥当です。最下位の12ビットは4KiBページ内でのオフセット(2^12バイト = 4KiB)です。48ビットから64ビットは捨てられます。つまり、x86_64は48ビットのアドレスにしか対応しておらず、そのため実際には64ビットではないということです。 +それぞれのテーブルインデックスは9ビットからなることがわかります。それぞれのテーブルに2^9 = 512エントリあることを考えるとこれは妥当です。最下位の12ビットは4KiBページ内でのオフセット(2^12バイト = 4KiB)です。48ビットから64ビットは捨てられます。つまり、x86_64は48ビットのアドレスにしか対応しておらず、そのため(64ビットアーキテクチャなどとよく呼ばれるが)実際には64ビットではないということです。 [5-level page table]: https://en.wikipedia.org/wiki/Intel_5-level_paging -48ビットから64ビットが捨てられるからといって、任意の値にしてよいということではありません。この範囲のすべてのビットは、アドレスを一意にし、5層ページテーブルのような将来の拡張に備えるため、47ビットの値と同じにしないといけません。これは、[2の補数における符号拡張][sign extension in two's complement]によく似ているので、 **符号 (sign) 拡張 (extension) ** とよばれています。アドレスが適切に符号拡張されていない場合、CPUは例外を投げます。 +48ビットから64ビットが捨てられるからといって、任意の値にしてよいということではありません。アドレスを一意にし、5層ページテーブルのような将来の拡張に備えるため、この範囲のすべてのビットは47ビットの値と同じにしないといけません。これは、[2の補数における符号拡張][sign extension in two's complement]によく似ているので、 **符号 (sign) 拡張 (extension) ** とよばれています。アドレスが適切に符号拡張されていない場合、CPUは例外を投げます。 [sign extension in two's complement]: https://en.wikipedia.org/wiki/Two's_complement#Sign_extension -近年発売されたIntelのIce LakeというCPUは、[5層ページテーブル][5-level page tables]にオプションで対応していて、仮想アドレスが48ビットから57ビットまで延長されているということは書いておく価値があるでしょう。いまの段階で私たちのカーネルをこの特定のCPUに最適化する意味はないので、この記事では標準の4層ページテーブルのみを使うことにします。 +近年発売されたIntelのIce LakeというCPUは、[5層ページテーブル][5-level page tables]を使用することもでき、そうすると仮想アドレスが48ビットから57ビットまで延長されるということは書いておく価値があるでしょう。いまの段階で私たちのカーネルをこの特定のCPUに最適化する意味はないので、この記事では標準の4層ページテーブルのみを使うことにします。 [5-level page tables]: https://en.wikipedia.org/wiki/Intel_5-level_paging ### 変換の例 -この変換プロセスの仕組みをより詳細に理解するために、例を挙げてみてみましょう。 +この変換の仕組みをより詳細に理解するために、例を挙げて見てみましょう。 ![An example 4-level page hierarchy with each page table shown in physical memory](x86_64-page-table-translation.svg) -現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「 (root) 」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれないからです。 +現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「根」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれないからです。 上のページテーブル階層構造は、最終的に(青色の)2つのページへの対応を行っています。ページテーブルのインデックスから、これらの2つのページの仮想アドレスは`0x803FE7F000`と`0x803FE00000`であると推論できます。プログラムがアドレス`0x803FE7F5CE`から読み込もうとしたときに何が起こるかを見てみましょう。まず、アドレスを2進数に変換し、アドレスのページテーブルインデックスとページオフセットが何であるかを決定します: @@ -181,9 +181,9 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ![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) -レベル1テーブルにあるこのページのパーミッション(訳注:ページテーブルにおいて、Flagsとある列)は`r`であり、これは読み込み専用という意味です。これらのようなパーミッションに対する侵害はハードウェアによって保護されており、このページに書き込もうとした場合は例外が投げられます。より高いレベルのページにおけるパーミッションは、下のレベルにおいて可能なパーミッションを制限します。たとえばレベル3エントリを読み込み専用にした場合、下のレベルで読み書きを許可したとしても、このエントリを使うページはすべて書き込み不可になります。 +レベル1テーブルにあるこのページの権限は`r`であり、これは読み込み専用という意味です。これらのような権限に対する侵害はハードウェアによって保護されており、このページに書き込もうとした場合は例外が投げられます。より高いレベルのページにおける権限は、下のレベルにおいて可能な権限を制限します。たとえばレベル3エントリを読み込み専用にした場合、下のレベルで読み書きを許可したとしても、このエントリを使うページはすべて書き込み不可になります。 -この例ではそれぞれのテーブルの実体 (インスタンス) を1つずつしか使いませんでしたが、普通それぞれのアドレス空間において、各レベルに対して複数のインスタンスが使われるということは知っておく価値があるでしょう。最大で +この例ではそれぞれのテーブルの実体 (インスタンス) を1つずつしか使いませんでしたが、普通、それぞれのアドレス空間において、各レベルに対して複数のインスタンスが使われるということは知っておく価値があるでしょう。最大で - 1個のレベル4テーブル - 512個のレベル3テーブル(レベル4テーブルには512エントリあるので) @@ -194,7 +194,7 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ### ページテーブルの形式 -x86_64アーキテクチャにおけるページテーブルは詰まるところ512個のエントリの配列です。Rustの構文では: +x86_64アーキテクチャにおけるページテーブルは詰まるところ512個のエントリの配列です。Rustの構文では以下のようになります: ```rust #[repr(align(4096))] @@ -247,12 +247,18 @@ pub struct PageTable { 4層ページテーブルを使うと、仮想アドレスを変換するたびに4回メモリアクセスを行わないといけないので、変換のコストは大きくなります。性能改善のために、x86_64アーキテクチャは、直前数回の変換内容を **トランスレーション・ルックアサイド・バッファ (translation lookaside buffer, TLB)** と呼ばれるところにキャッシュします。これにより、前の変換がまだキャッシュされているなら、変換をスキップできます。 -他のCPUキャッシュと異なり、TLBは完全に透明ではなく、ページテーブルの内容が変わったときに変換内容を更新したり取り除いたりしてくれません(訳注:キャッシュが透明 (transparent) であるとは、利用者がキャッシュの存在を意識する必要がないという意味)。つまり、カーネルがページテーブルを変更したときは、カーネル自らTLBを更新しないといけないということです。これを行うために、[`invlpg`]("invalidate page"、ページを無効化の意)という特別なCPU命令があります。これは指定されたページの変換をTLBから取り除き、次のアクセスの際に再び読み込まれるようにします。また、TLBは`CR3`レジスタを再読み込みすることでも初期化できます。`CR3`レジスタの再読み込み、アドレス空間が変更されたという状況を模擬するのです。`x86_64`クレートの[`tlb`モジュール][`tlb` module]が、両方のやり方のRust関数を提供しています。 +他のCPUキャッシュと異なり、TLBは完全に透明ではなく、ページテーブルの内容が変わったときに変換内容を更新したり取り除いたりしてくれません(訳注:キャッシュが透明 (transparent) であるとは、利用者がキャッシュの存在を意識する必要がないという意味)。つまり、カーネルがページテーブルを変更したときは、カーネル自らTLBを更新しないといけないということです。これを行うために、[`invlpg`]("invalidate page"、ページを無効化の意)という特別なCPU命令があります。これは指定されたページの変換をTLBから取り除き、次のアクセスの際に再び読み込まれるようにします。また、TLBは`CR3`レジスタを再設定することでもflushできます。`CR3`レジスタの再設定は、アドレス空間が変更されたという状況を模擬するのです。`x86_64`クレートの[`tlb`モジュール][`tlb` module]が、両方のやり方のRust関数を提供しています。 + +
+ +**訳注:** flushは「(溜まった水を)どっと流す」「(トイレなどを)水で洗い流す」という意味の言葉です。そのためコンピュータサイエンスにおいて「キャッシュなどに溜められていたデータを(場合によっては適切な出力先に書き込みながら)削除する」という意味を持つようになりました。ここではどこかに出力しているわけではないので、「初期化」と同じような意味と考えて差し支えないでしょう。 + +
[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html [`tlb` module]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tlb/index.html -ページテーブルを修正したときは毎回TLBを初期化しないといけないことはしっかりと覚えておいてください。さもないと、CPUは古い変換を使いつづけるかもしれず、これはデバッグの非常に難しい、予測不能なバグに繋がるかもしれないためです。 +ページテーブルを修正したときは毎回TLBをflushしないといけないということはしっかりと覚えておいてください。これを行わないと、CPUは古い変換を使いつづけるかもしれず、これはデバッグの非常に難しい、予測不能なバグに繋がるかもしれないためです。 ## 実装 @@ -262,7 +268,7 @@ pub struct PageTable { つまり、私達がカーネルにおいて使ってきたすべてのメモリアドレスは、仮想アドレスだったということです。アドレス`0xb8000`にあるVGAバッファへのアクセスが上手くいっていたのは、ひとえにブートローダがこのメモリページを **恒等対応** させていた、つまり、仮想ページ`0xb8000`を物理フレーム`0xb8000`に対応させていたからです。 -ページングにより、境界外メモリアクセスをしてもおかしな物理メモリに書き込むのではなくページフォルト例外を起こすようになっているため、私達のカーネルはすでに比較的安全になっていました。ブートローダはそれぞれのページに正しいパーミッションを設定しさえしてくれるので、コードを含むページだけが実行可能であり、データを含むページだけが書き込み可能になっています。 +ページングにより、境界外メモリアクセスをしてもおかしな物理メモリに書き込むのではなくページフォルト例外を起こすようになっているため、私達のカーネルはすでに比較的安全になっていました。ブートローダはそれぞれのページに正しい権限を設定することさえしてくれるので、コードを含むページだけが実行可能であり、データを含むページだけが書き込み可能になっています。 ### ページフォルト @@ -279,7 +285,7 @@ lazy_static! { […] - idt.page_fault.set_handler_fn(page_fault_handler); // new + idt.page_fault.set_handler_fn(page_fault_handler); // ここを追加 idt }; @@ -321,11 +327,11 @@ pub extern "C" fn _start() -> ! { blog_os::init(); - // new + // ここを追加 let ptr = 0xdeadbeaf as *mut u32; unsafe { *ptr = 42; } - // as before + // ここはこれまでと同じ #[cfg(test)] test_main(); @@ -368,7 +374,7 @@ println!("write worked"); ### ページテーブルへのアクセス -私達のカーネルがどのように(実メモリに)対応づけられているのかを定義しているページテーブルを見てみましょう。 +私達のカーネルがどのように(物理メモリに)対応づけられているのかを定義しているページテーブルを見てみましょう。 ```rust // in src/main.rs @@ -384,11 +390,11 @@ pub extern "C" fn _start() -> ! { 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() + […] // test_main(), println(…), hlt_loop() などが続く } ``` -`x86_64`クレートの[`Cr3::read`]関数は、現在有効なレベル4ページテーブルを`CR3`レジスタから(読みとって)返します。この関数は[`PhysFrame`]型と[`Cr3Flags`]型のタプルを返します。私達はフレームにしか興味がないので、タプルの2つ目の要素は無視しました。 +`x86_64`クレートの[`Cr3::read`]関数は、現在有効なレベル4ページテーブルを`CR3`レジスタから読みとって返します。この関数は[`PhysFrame`]型と[`Cr3Flags`]型のタプルを返します。私達はフレームにしか興味がないので、タプルの2つ目の要素は無視しました。 [`Cr3::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3.html#method.read [`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html @@ -404,15 +410,15 @@ Level 4 page table at: PhysAddr(0x1000) [`PhysAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.PhysAddr.html -ページングが有効なとき、物理メモリに直接アクセスすることはできません。もしそれができたら、プログラムは容易くメモリ保護を回避して他のプログラムのメモリにアクセスできてしまうだろうからです。ですので、テーブルにアクセスする唯一の方法は、アドレス`0x1000`の物理フレームに対応づけられているような仮想ページにアクセスすることです。ページテーブルの存在するフレームへの対応づけを作るのは(実用上も必要になる)一般的な問題です。なぜなら、例えば新しいスレッドのためにスタックを割り当てるときなど、カーネルは日常的にページテーブルにアクセスする必要があるためです。 +ページングが有効なとき、物理メモリに直接アクセスすることはできません。もしそれができたとしたら、プログラムは容易くメモリ保護を回避して他のプログラムのメモリにアクセスできてしまうだろうからです。ですので、テーブルにアクセスする唯一の方法は、アドレス`0x1000`の物理フレームに対応づけられているような仮想ページにアクセスすることです。ページテーブルの存在するフレームへの対応づけは(実用上も必要になる)一般的な問題です。なぜなら、例えば新しいスレッドのためにスタックを割り当てるときなど、カーネルは日常的にページテーブルにアクセスする必要があるためです。 この問題への解決策は次の記事で詳細に論じます。 ## まとめ -この記事では2つのメモリ保護技術を紹介しました:セグメンテーションとページングです。前者は可変サイズのメモリ領域を使用するため外部断片化の問題が存在するのに対し、後者は固定サイズのページを使用するためアクセス許可に関して遥かに細やかな制御が可能となっていました。 +この記事では2つのメモリ保護技術を紹介しました:セグメンテーションとページングです。前者は可変サイズのメモリ領域を使用するため外部断片化の問題が存在するのに対し、後者は固定サイズのページを使用するためアクセス権限に関して遥かに細やかな制御が可能となっていました。 -ページングは、(仮想メモリと物理メモリの)対応情報を1層以上のページテーブルに格納します。x86_64アーキテクチャにおいては4層ページテーブルが使用され、ページサイズは4KiBです。ハードウェアは自動的にページテーブルを辿り、変換の結果をトランスレーション・ルックアサイド・バッファ (TLB) にキャッシュします。このバッファは自動的に更新されない(「透明ではない」)ので、ページテーブルの変更時には明示的に初期化する必要があります。 +ページングは、(仮想メモリと物理メモリの)対応情報を1層以上のページテーブルに格納します。x86_64アーキテクチャにおいては4層ページテーブルが使用され、ページサイズは4KiBです。ハードウェアは自動的にページテーブルを辿り、変換の結果をトランスレーション・ルックアサイド・バッファ (TLB) にキャッシュします。このバッファは自動的に更新されない(「透明ではない」)ので、ページテーブルの変更時には明示的にflushする必要があります。 私達のカーネルは既にページングによって動いており、不正なメモリアクセスはページフォルト例外を発生させるということを学びました。現在有効なページテーブルへとアクセスしたかったのですが、CR3レジスタに格納されている物理アドレスはカーネルから直接アクセスできないものであるため、それはできませんでした。 From b070fa8ee68e5b9ba86b2e7d81cd0bf33da385fb Mon Sep 17 00:00:00 2001 From: HKalbasi <45197576+HKalbasi@users.noreply.github.com> Date: Mon, 17 May 2021 13:00:11 +0430 Subject: [PATCH 028/125] Remove wrong suggestion part (#983) --- blog/content/edition-2/posts/11-allocator-designs/index.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/blog/content/edition-2/posts/11-allocator-designs/index.md b/blog/content/edition-2/posts/11-allocator-designs/index.md index 6e134aab..81bc5c96 100644 --- a/blog/content/edition-2/posts/11-allocator-designs/index.md +++ b/blog/content/edition-2/posts/11-allocator-designs/index.md @@ -172,9 +172,6 @@ Note that we don't perform any bounds checks or alignment adjustments, so this i error[E0594]: cannot assign to `self.next` which is behind a `&` reference --> src/allocator/bump.rs:29:9 | -26 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - | ----- help: consider changing this to be a mutable reference: `&mut self` -... 29 | self.next = alloc_start + layout.size(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be written ``` @@ -186,8 +183,6 @@ The error occurs because the [`alloc`] and [`dealloc`] methods of the `GlobalAll [`alloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.alloc [`dealloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.dealloc -Note that the compiler suggestion to change `&self` to `&mut self` in the method declaration does not work here. The reason is that the method signature is defined by the `GlobalAlloc` trait and can't be changed on the implementation side. (I opened an [issue](https://github.com/rust-lang/rust/issues/68049) in the Rust repository about the invalid suggestion.) - #### `GlobalAlloc` and Mutability Before we look at a possible solution to this mutability problem, let's try to understand why the `GlobalAlloc` trait methods are defined with `&self` arguments: As we saw [in the previous post][global-allocator], the global heap allocator is defined by adding the `#[global_allocator]` attribute to a `static` that implements the `GlobalAlloc` trait. Static variables are immutable in Rust, so there is no way to call a method that takes `&mut self` on the static allocator. For this reason, all the methods of `GlobalAlloc` only take an immutable `&self` reference. From 5ef39591f6d3b590b4ada3a5d338c2271d42deda Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 15:33:35 +0200 Subject: [PATCH 029/125] Update posts to use x86_64 v0.14.2 --- .../edition-2/posts/04-testing/index.md | 6 +-- .../posts/05-cpu-exceptions/index.md | 22 +++++------ .../edition-2/posts/06-double-faults/index.md | 10 ++--- .../posts/07-hardware-interrupts/index.md | 18 ++++----- .../posts/08-paging-introduction/index.md | 24 ++++++------ .../posts/09-paging-implementation/index.md | 38 +++++++++---------- .../posts/10-heap-allocation/index.md | 26 ++++++------- .../edition-2/posts/12-async-await/index.md | 8 ++-- 8 files changed, 76 insertions(+), 76 deletions(-) diff --git a/blog/content/edition-2/posts/04-testing/index.md b/blog/content/edition-2/posts/04-testing/index.md index 3b0e501c..d1770166 100644 --- a/blog/content/edition-2/posts/04-testing/index.md +++ b/blog/content/edition-2/posts/04-testing/index.md @@ -174,18 +174,18 @@ The functionality of the `isa-debug-exit` device is very simple. When a `value` Instead of manually invoking the `in` and `out` assembly instructions, we use the abstractions provided by the [`x86_64`] crate. To add a dependency on that crate, we add it to the `dependencies` section in our `Cargo.toml`: -[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/ +[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/ ```toml # in Cargo.toml [dependencies] -x86_64 = "0.13.2" +x86_64 = "0.14.2" ``` Now we can use the [`Port`] type provided by the crate to create an `exit_qemu` function: -[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html +[`Port`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/port/struct.Port.html ```rust // in src/main.rs diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.md index 5d68b59c..d27dda94 100644 --- a/blog/content/edition-2/posts/05-cpu-exceptions/index.md +++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.md @@ -84,7 +84,7 @@ Don't worry about steps 4 and 5 for now, we will learn about the global descript ## An IDT Type Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this: -[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html ``` rust #[repr(C)] @@ -115,15 +115,15 @@ pub struct InterruptDescriptorTable { The fields have the type [`idt::Entry`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`]. -[`idt::Entry`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.Entry.html -[`HandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFunc.html -[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html -[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html +[`idt::Entry`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.Entry.html +[`HandlerFunc`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.HandlerFunc.html +[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html +[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html Let's look at the `HandlerFunc` type first: ```rust -type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame); +type HandlerFunc = extern "x86-interrupt" fn(_: InterruptStackFrame); ``` It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention? @@ -195,7 +195,7 @@ So the _interrupt stack frame_ looks like this: In the `x86_64` crate, the interrupt stack frame is represented by the [`InterruptStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument. -[`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html +[`InterruptStackFrame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptStackFrame.html ### Behind the Scenes The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of: @@ -249,7 +249,7 @@ pub fn init_idt() { } extern "x86-interrupt" fn breakpoint_handler( - stack_frame: &mut InterruptStackFrame) + stack_frame: InterruptStackFrame) { println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); } @@ -263,7 +263,7 @@ When we try to compile it, the following error occurs: error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180) --> src/main.rs:53:1 | -53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) { +53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { 54 | | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); 55 | | } | |_^ @@ -277,7 +277,7 @@ This error occurs because the `x86-interrupt` calling convention is still unstab In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it: [`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt -[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load +[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load ```rust // in src/interrupts.rs @@ -457,7 +457,7 @@ blog_os::interrupts::test_breakpoint_exception... [ok] The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date. [“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md -[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html [first edition]: @/edition-1/_index.md ## What's next? diff --git a/blog/content/edition-2/posts/06-double-faults/index.md b/blog/content/edition-2/posts/06-double-faults/index.md index bdf1a833..a276837a 100644 --- a/blog/content/edition-2/posts/06-double-faults/index.md +++ b/blog/content/edition-2/posts/06-double-faults/index.md @@ -81,7 +81,7 @@ lazy_static! { // new extern "x86-interrupt" fn double_fault_handler( - stack_frame: &mut InterruptStackFrame, _error_code: u64) -> ! + stack_frame: InterruptStackFrame, _error_code: u64) -> ! { panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); } @@ -229,7 +229,7 @@ The _Privilege Stack Table_ is used by the CPU when the privilege level changes. ### Creating a TSS Let's create a new TSS that contains a separate double fault stack in its interrupt stack table. For that we need a TSS struct. Fortunately, the `x86_64` crate already contains a [`TaskStateSegment` struct] that we can use. -[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.13.2/x86_64/structures/tss/struct.TaskStateSegment.html +[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.14.2/x86_64/structures/tss/struct.TaskStateSegment.html We create the TSS in a new `gdt` module (the name will make sense later): @@ -375,8 +375,8 @@ pub fn init() { We reload the code segment register using [`set_cs`] and load the TSS using [`load_tss`]. The functions are marked as `unsafe`, so we need an `unsafe` block to invoke them. The reason is that it might be possible to break memory safety by loading invalid selectors. -[`set_cs`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/segmentation/fn.set_cs.html -[`load_tss`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tables/fn.load_tss.html +[`set_cs`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/segmentation/fn.set_cs.html +[`load_tss`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/tables/fn.load_tss.html Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT: @@ -526,7 +526,7 @@ use blog_os::{exit_qemu, QemuExitCode, serial_println}; use x86_64::structures::idt::InterruptStackFrame; extern "x86-interrupt" fn test_double_fault_handler( - _stack_frame: &mut InterruptStackFrame, + _stack_frame: InterruptStackFrame, _error_code: u64, ) -> ! { serial_println!("[ok]"); diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.md index a54b53fc..e87acff7 100644 --- a/blog/content/edition-2/posts/07-hardware-interrupts/index.md +++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.md @@ -200,7 +200,7 @@ lazy_static! { } extern "x86-interrupt" fn timer_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { print!("."); } @@ -208,7 +208,7 @@ extern "x86-interrupt" fn timer_interrupt_handler( Our `timer_interrupt_handler` has the same signature as our exception handlers, because the CPU reacts identically to exceptions and external interrupts (the only difference is that some exceptions push an error code). The [`InterruptDescriptorTable`] struct implements the [`IndexMut`] trait, so we can access individual entries through array indexing syntax. -[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html [`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we would expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed: @@ -225,7 +225,7 @@ To send the EOI, we use our static `PICS` struct again: // in src/interrupts.rs extern "x86-interrupt" fn timer_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { print!("."); @@ -333,7 +333,7 @@ pub fn _print(args: fmt::Arguments) { The [`without_interrupts`] function takes a [closure] and executes it in an interrupt-free environment. We use it to ensure that no interrupt can occur as long as the `Mutex` is locked. When we run our kernel now we see that it keeps running without hanging. (We still don't notice any dots, but this is because they're scrolling by too fast. Try to slow down the printing, e.g. by putting a `for _ in 0..10000 {}` inside the loop.) -[`without_interrupts`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.without_interrupts.html +[`without_interrupts`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/interrupts/fn.without_interrupts.html [closure]: https://doc.rust-lang.org/book/ch13-01-closures.html We can apply the same change to our serial printing function to ensure that no deadlocks occur with it either: @@ -538,7 +538,7 @@ lazy_static! { } extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { print!("k"); @@ -563,7 +563,7 @@ To find out _which_ key was pressed, we need to query the keyboard controller. W // in src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { use x86_64::instructions::port::Port; @@ -580,7 +580,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard's data port. This byte is called the [_scancode_] and is a number that represents the key press/release. We don't do anything with the scancode yet, we just print it to the screen: -[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html +[`Port`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/port/struct.Port.html [_scancode_]: https://en.wikipedia.org/wiki/Scancode ![QEMU printing scancodes to the screen when keys are pressed](qemu-printing-scancodes.gif) @@ -604,7 +604,7 @@ To translate the scancodes to keys, we can use a match statement: // in src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { use x86_64::instructions::port::Port; @@ -663,7 +663,7 @@ Now we can use this crate to rewrite our `keyboard_interrupt_handler`: // in/src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) + _stack_frame: InterruptStackFrame) { use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; use spin::Mutex; diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.md b/blog/content/edition-2/posts/08-paging-introduction/index.md index 172dc176..416c55dd 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.md @@ -235,8 +235,8 @@ Let's take a closer look at the available flags: 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.13.2/x86_64/structures/paging/page_table/struct.PageTable.html -[entries]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html +[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 @@ -245,7 +245,7 @@ A 4-level page table makes the translation of virtual addresses expensive, becau 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.13.2/x86_64/instructions/tlb/index.html +[`tlb` module]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/tlb/index.html It is important to remember flushing 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. @@ -284,7 +284,7 @@ use x86_64::structures::idt::PageFaultErrorCode; use crate::hlt_loop; extern "x86-interrupt" fn page_fault_handler( - stack_frame: &mut InterruptStackFrame, + stack_frame: InterruptStackFrame, error_code: PageFaultErrorCode, ) { use x86_64::registers::control::Cr2; @@ -300,8 +300,8 @@ extern "x86-interrupt" fn page_fault_handler( 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.13.2/x86_64/registers/control/struct.Cr2.html#method.read -[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html +[`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 @@ -335,7 +335,7 @@ When we run it, we see that our page fault handler is called: 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.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE +[`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`: @@ -359,7 +359,7 @@ By commenting out the last line, we see that the read access works, but the writ 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.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION +[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION ### Accessing the Page Tables @@ -385,9 +385,9 @@ pub extern "C" fn _start() -> ! { 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.13.2/x86_64/registers/control/struct.Cr3.html#method.read -[`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html -[`Cr3Flags`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3Flags.html +[`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: @@ -397,7 +397,7 @@ 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.13.2/x86_64/addr/struct.PhysAddr.html +[`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 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. diff --git a/blog/content/edition-2/posts/09-paging-implementation/index.md b/blog/content/edition-2/posts/09-paging-implementation/index.md index 2f5fd7b7..572b7042 100644 --- a/blog/content/edition-2/posts/09-paging-implementation/index.md +++ b/blog/content/edition-2/posts/09-paging-implementation/index.md @@ -219,7 +219,7 @@ The above code assumes that the last level 4 entry with index `0o777` (511) is r Alternatively to performing the bitwise operations by hand, you can use the [`RecursivePageTable`] type of the `x86_64` crate, which provides safe abstractions for various page table operations. For example, the code below shows how to translate a virtual address to its mapped physical address: -[`RecursivePageTable`]: https://docs.rs/x86_64/0.13.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 ```rust // in src/memory.rs @@ -437,7 +437,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { First, we convert the `physical_memory_offset` of the `BootInfo` struct to a [`VirtAddr`] and pass it to the `active_level_4_table` function. We then use the `iter` function to iterate over the page table entries and the [`enumerate`] combinator to additionally add an index `i` to each element. We only print non-empty entries because all 512 entries wouldn't fit on the screen. -[`VirtAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.VirtAddr.html +[`VirtAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.VirtAddr.html [`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate When we run it, we see the following output: @@ -550,7 +550,7 @@ The `VirtAddr` struct already provides methods to compute the indexes into the p Inside the loop, we again use the `physical_memory_offset` to convert the frame into a page table reference. We then read the entry of the current page table and use the [`PageTableEntry::frame`] function to retrieve the mapped frame. If the entry is not mapped to a frame we return `None`. If the entry maps a huge 2MiB or 1GiB page we panic for now. -[`PageTableEntry::frame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame +[`PageTableEntry::frame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame Let's test our translation function by translating some addresses: @@ -605,18 +605,18 @@ The base of the abstraction are two traits that define various page table mappin - The [`Mapper`] trait is generic over the page size and provides functions that operate on pages. Examples are [`translate_page`], which translates a given page to a frame of the same size, and [`map_to`], which creates a new mapping in the page table. - The [`Translate`] trait provides functions that work with multiple page sizes such as [`translate_addr`] or the general [`translate`]. -[`Mapper`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html -[`translate_page`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.translate_page -[`map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to -[`Translate`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Translate.html -[`translate_addr`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Translate.html#method.translate_addr -[`translate`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Translate.html#tymethod.translate +[`Mapper`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html +[`translate_page`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.translate_page +[`map_to`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to +[`Translate`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Translate.html +[`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 The traits only define the interface, they don't provide any implementation. The `x86_64` crate currently provides three types that implement the traits with different requirements. The [`OffsetPageTable`] type assumes that the complete physical memory is mapped to the virtual address space at some offset. The [`MappedPageTable`] is a bit more flexible: It only requires that each page table frame is mapped to the virtual address space at a calculable address. Finally, the [`RecursivePageTable`] type can be used to access page table frames through [recursive page tables](#recursive-page-tables). -[`OffsetPageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html -[`MappedPageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MappedPageTable.html -[`RecursivePageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html +[`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 +[`RecursivePageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html In our case, the bootloader maps the complete physical memory at a virtual address specfied by the `physical_memory_offset` variable, so we can use the `OffsetPageTable` type. To initialize it, we create a new `init` function in our `memory` module: @@ -642,7 +642,7 @@ unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) The function takes the `physical_memory_offset` as an argument and returns a new `OffsetPageTable` instance with a `'static` lifetime. This means that the instance stays valid for the complete runtime of our kernel. In the function body, we first call the `active_level_4_table` function to retrieve a mutable reference to the level 4 page table. We then invoke the [`OffsetPageTable::new`] function with this reference. As the second parameter, the `new` function expects the virtual address at which the mapping of the physical memory starts, which is given in the `physical_memory_offset` variable. -[`OffsetPageTable::new`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html#method.new +[`OffsetPageTable::new`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html#method.new The `active_level_4_table` function should be only called from the `init` function from now on because it can easily lead to aliased mutable references when called multiple times, which can cause undefined behavior. For this reason, we make the function private by removing the `pub` specifier. @@ -693,8 +693,8 @@ Until now we only looked at the page tables without modifying anything. Let's ch We will use the [`map_to`] function of the [`Mapper`] trait for our implementation, so let's take a look at that function first. The documentation tells us that it takes four arguments: the page that we want to map, the frame that the page should be mapped to, a set of flags for the page table entry, and a `frame_allocator`. The frame allocator is needed because mapping the given page might require creating additional page tables, which need unused frames as backing storage. -[`map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to -[`Mapper`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.Mapper.html +[`map_to`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to +[`Mapper`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.Mapper.html #### A `create_example_mapping` Function @@ -733,8 +733,8 @@ In addition to the `page` that should be mapped, the function expects a mutable [impl-trait-arg]: https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters [generic]: https://doc.rust-lang.org/book/ch10-00-generics.html -[`FrameAllocator`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html -[`PageSize`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/trait.PageSize.html +[`FrameAllocator`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.FrameAllocator.html +[`PageSize`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/trait.PageSize.html The [`map_to`] method is unsafe because the caller must ensure that the frame is not already in use. The reason for this is that mapping the same frame twice could result in undefined behavior, for example when two different `&mut` references point to the same physical memory location. In our case, we reuse the VGA text buffer frame, which is already mapped, so we break the required condition. However, the `create_example_mapping` function is only a temporary testing function and will be removed after this post, so it is ok. To remind us of the unsafety, we put a `FIXME` comment on the line. @@ -746,8 +746,8 @@ The [`map_to`] function can fail, so it returns a [`Result`]. Since this is just [`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html [`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect -[`MapperFlush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html -[`flush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush +[`MapperFlush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html +[`flush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush [must_use]: https://doc.rust-lang.org/std/result/#results-must-be-used #### A dummy `FrameAllocator` diff --git a/blog/content/edition-2/posts/10-heap-allocation/index.md b/blog/content/edition-2/posts/10-heap-allocation/index.md index 5e1e7b99..50328911 100644 --- a/blog/content/edition-2/posts/10-heap-allocation/index.md +++ b/blog/content/edition-2/posts/10-heap-allocation/index.md @@ -445,12 +445,12 @@ pub fn init_heap( The function takes mutable references to a [`Mapper`] and a [`FrameAllocator`] instance, both limited to 4KiB pages by using [`Size4KiB`] as generic parameter. The return value of the function is a [`Result`] with the unit type `()` as success variant and a [`MapToError`] as error variant, which is the error type returned by the [`Mapper::map_to`] method. Reusing the error type makes sense here because the `map_to` method is the main source of errors in this function. -[`Mapper`]:https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html -[`FrameAllocator`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html -[`Size4KiB`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/enum.Size4KiB.html +[`Mapper`]:https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html +[`FrameAllocator`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.FrameAllocator.html +[`Size4KiB`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/enum.Size4KiB.html [`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html -[`MapToError`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/enum.MapToError.html -[`Mapper::map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to +[`MapToError`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/enum.MapToError.html +[`Mapper::map_to`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to The implementation can be broken down into two parts: @@ -464,18 +464,18 @@ The implementation can be broken down into two parts: - We use the [`Mapper::map_to`] method for creating the mapping in the active page table. The method can fail, therefore we use the [question mark operator] again to forward the error to the caller. On success, the method returns a [`MapperFlush`] instance that we can use to update the [_translation lookaside buffer_] using the [`flush`] method. -[`VirtAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.VirtAddr.html -[`Page`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html -[`containing_address`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html#method.containing_address -[`Page::range_inclusive`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html#method.range_inclusive -[`FrameAllocator::allocate_frame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html#tymethod.allocate_frame +[`VirtAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.VirtAddr.html +[`Page`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/struct.Page.html +[`containing_address`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/struct.Page.html#method.containing_address +[`Page::range_inclusive`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/struct.Page.html#method.range_inclusive +[`FrameAllocator::allocate_frame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.FrameAllocator.html#tymethod.allocate_frame [`None`]: https://doc.rust-lang.org/core/option/enum.Option.html#variant.None -[`MapToError::FrameAllocationFailed`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/enum.MapToError.html#variant.FrameAllocationFailed +[`MapToError::FrameAllocationFailed`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/enum.MapToError.html#variant.FrameAllocationFailed [`Option::ok_or`]: https://doc.rust-lang.org/core/option/enum.Option.html#method.ok_or [question mark operator]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html -[`MapperFlush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html +[`MapperFlush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html [_translation lookaside buffer_]: @/edition-2/posts/08-paging-introduction/index.md#the-translation-lookaside-buffer -[`flush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush +[`flush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush The final step is to call this function from our `kernel_main`: diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md index 9cd1bbc1..de390f0a 100644 --- a/blog/content/edition-2/posts/12-async-await/index.md +++ b/blog/content/edition-2/posts/12-async-await/index.md @@ -1136,7 +1136,7 @@ To call the `add_scancode` function on keyboard interrupts, we update our `keybo // in src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame + _stack_frame: InterruptStackFrame ) { use x86_64::instructions::port::Port; @@ -1744,8 +1744,8 @@ impl Executor { Since we call `sleep_if_idle` directly after `run_ready_tasks`, which loops until the `task_queue` becomes empty, checking the queue again might seem unnecessary. However, a hardware interrupt might occur directly after `run_ready_tasks` returns, so there might be a new task in the queue at the time the `sleep_if_idle` function is called. Only if the queue is still empty, we put the CPU to sleep by executing the `hlt` instruction through the [`instructions::hlt`] wrapper function provided by the [`x86_64`] crate. -[`instructions::hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/fn.hlt.html -[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/index.html +[`instructions::hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/fn.hlt.html +[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/index.html Unfortunately, there is still a subtle race condition in this implementation. Since interrupts are asynchronous and can happen at any time, it is possible that an interrupt happens right between the `is_empty` check and the call to `hlt`: @@ -1760,7 +1760,7 @@ In case this interrupt pushes to the `task_queue`, we put the CPU to sleep even The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`interrupts::enable_and_hlt`][`enable_and_hlt`] function provided by the [`x86_64`] crate. -[`enable_and_hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html +[`enable_and_hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html The updated implementation of our `sleep_if_idle` function looks like this: From 248bc1cf051c9c39c4230a9882b2966c966e635f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 15:36:08 +0200 Subject: [PATCH 030/125] Switch Hardware Interrupts post to `pic8259` crate Fork of previous `pic8259_simple` crate. --- .../posts/07-hardware-interrupts/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.md index e87acff7..1eba3f90 100644 --- a/blog/content/edition-2/posts/07-hardware-interrupts/index.md +++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.md @@ -75,29 +75,29 @@ Each controller can be configured through two [I/O ports], one “command” por The default configuration of the PICs is not usable, because it sends interrupt vector numbers in the range 0–15 to the CPU. These numbers are already occupied by CPU exceptions, for example number 8 corresponds to a double fault. To fix this overlapping issue, we need to remap the PIC interrupts to different numbers. The actual range doesn't matter as long as it does not overlap with the exceptions, but typically the range 32–47 is chosen, because these are the first free numbers after the 32 exception slots. -The configuration happens by writing special values to the command and data ports of the PICs. Fortunately there is already a crate called [`pic8259_simple`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. +The configuration happens by writing special values to the command and data ports of the PICs. Fortunately there is already a crate called [`pic8259`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. -[pic crate source]: https://docs.rs/crate/pic8259_simple/0.2.0/source/src/lib.rs +[pic crate source]: https://docs.rs/crate/pic8259/0.2.0/source/src/lib.rs To add the crate as dependency, we add the following to our project: -[`pic8259_simple`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/ +[`pic8259`]: https://docs.rs/pic8259/0.10.0/pic8259/ ```toml # in Cargo.toml [dependencies] -pic8259_simple = "0.2.0" +pic8259 = "0.10.1" ``` The main abstraction provided by the crate is the [`ChainedPics`] struct that represents the primary/secondary PIC layout we saw above. It is designed to be used in the following way: -[`ChainedPics`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/struct.ChainedPics.html +[`ChainedPics`]: https://docs.rs/pic8259/0.10.1/pic8259/struct.ChainedPics.html ```rust // in src/interrupts.rs -use pic8259_simple::ChainedPics; +use pic8259::ChainedPics; use spin; pub const PIC_1_OFFSET: u8 = 32; @@ -125,7 +125,7 @@ pub fn init() { We use the [`initialize`] function to perform the PIC initialization. Like the `ChainedPics::new` function, this function is also unsafe because it can cause undefined behavior if the PIC is misconfigured. -[`initialize`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/struct.ChainedPics.html#method.initialize +[`initialize`]: https://docs.rs/pic8259/0.10.1/pic8259/struct.ChainedPics.html#method.initialize If all goes well we should continue to see the "It did not crash" message when executing `cargo run`. From 34a3066cae3d62554843c84a8912022f007f68ae Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 15:38:50 +0200 Subject: [PATCH 031/125] Update posts to `linked_list_allocator` v0.9.0 --- .../edition-2/posts/10-heap-allocation/index.md | 8 ++++---- .../edition-2/posts/11-allocator-designs/index.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blog/content/edition-2/posts/10-heap-allocation/index.md b/blog/content/edition-2/posts/10-heap-allocation/index.md index 50328911..8a290fef 100644 --- a/blog/content/edition-2/posts/10-heap-allocation/index.md +++ b/blog/content/edition-2/posts/10-heap-allocation/index.md @@ -528,7 +528,7 @@ To use the crate, we first need to add a dependency on it in our `Cargo.toml`: # in Cargo.toml [dependencies] -linked_list_allocator = "0.8.0" +linked_list_allocator = "0.9.0" ``` Then we can replace our dummy allocator with the allocator provided by the crate: @@ -548,7 +548,7 @@ The struct is named `LockedHeap` because it uses the [`spinning_top::Spinlock`] Setting the `LockedHeap` as global allocator is not enough. The reason is that we use the [`empty`] constructor function, which creates an allocator without any backing memory. Like our dummy allocator, it always returns an error on `alloc`. To fix this, we need to initialize the allocator after creating the heap: -[`empty`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.LockedHeap.html#method.empty +[`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.LockedHeap.html#method.empty ```rust // in src/allocator.rs @@ -571,8 +571,8 @@ pub fn init_heap( We use the [`lock`] method on the inner spinlock of the `LockedHeap` type to get an exclusive reference to the wrapped [`Heap`] instance, on which we then call the [`init`] method with the heap bounds as arguments. It is important that we initialize the heap _after_ mapping the heap pages, since the [`init`] function already tries to write to the heap memory. [`lock`]: https://docs.rs/lock_api/0.3.3/lock_api/struct.Mutex.html#method.lock -[`Heap`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html -[`init`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html#method.init +[`Heap`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html +[`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init After initializing the heap, we can now use all allocation and collection types of the built-in [`alloc`] crate without error: diff --git a/blog/content/edition-2/posts/11-allocator-designs/index.md b/blog/content/edition-2/posts/11-allocator-designs/index.md index 81bc5c96..131a3da8 100644 --- a/blog/content/edition-2/posts/11-allocator-designs/index.md +++ b/blog/content/edition-2/posts/11-allocator-designs/index.md @@ -963,13 +963,13 @@ impl FixedSizeBlockAllocator { The `new` function just initializes the `list_heads` array with empty nodes and creates an [`empty`] linked list allocator as `fallback_allocator`. The `EMPTY` constant is needed because to tell the Rust compiler that we want to initialize the array with a constant value. Initializing the array directly as `[None; BLOCK_SIZES.len()]` does not work because then the compiler requires that `Option<&'static mut ListNode>` implements the `Copy` trait, which is does not. This is a current limitation of the Rust compiler, which might go away in the future. -[`empty`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html#method.empty +[`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.empty If you haven't done so already for the `LinkedListAllocator` implementation, you also need to add **`#![feature(const_mut_refs)]`** to the beginning of your `lib.rs`. The reason is that any use of mutable reference types in const functions is still unstable, including the `Option<&'static mut ListNode>` array element type of the `list_heads` field (even if we set it to `None`). The unsafe `init` function only calls the [`init`] function of the `fallback_allocator` without doing any additional initialization of the `list_heads` array. Instead, we will initialize the lists lazily on `alloc` and `dealloc` calls. -[`init`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html#method.init +[`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init For convenience, we also create a private `fallback_alloc` method that allocates using the `fallback_allocator`: @@ -992,9 +992,9 @@ impl FixedSizeBlockAllocator { Since the [`Heap`] type of the `linked_list_allocator` crate does not implement [`GlobalAlloc`] (as it's [not possible without locking]). Instead, it provides an [`allocate_first_fit`] method that has a slightly different interface. Instead of returning a `*mut u8` and using a null pointer to signal an error, it returns a `Result, ()>`. The [`NonNull`] type is an abstraction for a raw pointer that is guaranteed to be not the null pointer. By mapping the `Ok` case to the [`NonNull::as_ptr`] method and the `Err` case to a null pointer, we can easily translate this back to a `*mut u8` type. -[`Heap`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html +[`Heap`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html [not possible without locking]: #globalalloc-and-mutability -[`allocate_first_fit`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html#method.allocate_first_fit +[`allocate_first_fit`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.allocate_first_fit [`NonNull`]: https://doc.rust-lang.org/nightly/core/ptr/struct.NonNull.html [`NonNull::as_ptr`]: https://doc.rust-lang.org/nightly/core/ptr/struct.NonNull.html#method.as_ptr @@ -1124,7 +1124,7 @@ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { Like in `alloc`, we first use the `lock` method to get a mutable allocator reference and then the `list_index` function to get the block list corresponding to the given `Layout`. If the index is `None`, no fitting block size exists in `BLOCK_SIZES`, which indicates that the allocation was created by the fallback allocator. Therefore we use its [`deallocate`][`Heap::deallocate`] to free the memory again. The method expects a [`NonNull`] instead of a `*mut u8`, so we need to convert the pointer first. (The `unwrap` call only fails when the pointer is null, which should never happen when the compiler calls `dealloc`.) -[`Heap::deallocate`]: https://docs.rs/linked_list_allocator/0.8.0/linked_list_allocator/struct.Heap.html#method.deallocate +[`Heap::deallocate`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.deallocate If `list_index` returns a block index, we need to add the freed memory block to the list. For that, we first create a new `ListNode` that points to the current list head (by using [`Option::take`] again). Before we write the new node into the freed memory block, we first assert that the current block size specified by `index` has the required size and alignment for storing a `ListNode`. Then we perform the write by converting the given `*mut u8` pointer to a `*mut ListNode` pointer and then calling the unsafe [`write`][`pointer::write`] method on it. The last step is to set the head pointer of the list, which is currently `None` since we called `take` on it, to our newly written `ListNode`. For that we convert the raw `new_node_ptr` to a mutable reference. From 7992ce6a2159f481615ee0cf32f95234bfc7c71d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 15:53:23 +0200 Subject: [PATCH 032/125] Fix link --- blog/content/edition-2/posts/07-hardware-interrupts/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.md index 1eba3f90..f9c8ae72 100644 --- a/blog/content/edition-2/posts/07-hardware-interrupts/index.md +++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.md @@ -77,7 +77,7 @@ The default configuration of the PICs is not usable, because it sends interrupt The configuration happens by writing special values to the command and data ports of the PICs. Fortunately there is already a crate called [`pic8259`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. -[pic crate source]: https://docs.rs/crate/pic8259/0.2.0/source/src/lib.rs +[pic crate source]: https://docs.rs/crate/pic8259/0.10.0/source/src/lib.rs To add the crate as dependency, we add the following to our project: From bbf3c03ccec2f0041947ee80ea72e60c62f71569 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 15:58:43 +0200 Subject: [PATCH 033/125] Adjust CI deploy job for rename of master branch --- .github/workflows/build-site.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml index 56a70214..9bf498e1 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/build-site.yml @@ -68,7 +68,7 @@ jobs: name: "Deploy Generated Site" runs-on: ubuntu-latest needs: [build_site, check_spelling] - if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'schedule') steps: - name: "Download Generated Site" From 54ca8bbe6792b00da934fc2d300bef5bd1fe01a9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 May 2021 16:03:08 +0200 Subject: [PATCH 034/125] Update copyright year in footer --- blog/templates/edition-2/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index a0e66b71..628f1aff 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -44,7 +44,7 @@

- © . All rights reserved. + © . All rights reserved. Contact
From 7fb46ee069f584ac0899605f3abd848bafb28f8d Mon Sep 17 00:00:00 2001 From: "Shu W. Nakamura" <30687489+woodyZootopia@users.noreply.github.com> Date: Wed, 26 May 2021 12:18:23 +0900 Subject: [PATCH 035/125] Apply suggestions from code review Co-authored-by: Yuki Okushi --- .../edition-2/posts/08-paging-introduction/index.ja.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index d4291c46..7d0348c2 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -43,7 +43,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 メモリアクセスの種類によって、セグメントレジスタは自動的にCPUによって選ばれます。命令の引き出し (フェッチ) にはコードセグメント`CS`が使用され、スタック操作(プッシュ・ポップ)にはスタックセグメント`SS`が使用されます。その他の命令では、データセグメント`DS`やエクストラセグメント`ES`が使用されます。その後、自由に使用できる`FS`と`GS`というセグメントレジスタも追加されました。 -セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで実行している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス権限が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセスのメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離できます。 +セグメンテーションの初期バージョンでは、セグメントレジスタは直接オフセットを格納しており、アクセス制御は行われていませんでした。これは後に[プロテクトモード (protected mode) ][_protected mode_]が導入されたことで変更されました。CPUがこのモードで動作している時、セグメント記述子 (ディスクリプタ) 局所 (ローカル) または大域 (グローバル) [**記述子表 (ディスクリプタテーブル) **][_descriptor table_]を格納します。これには(オフセットアドレスに加えて)セグメントのサイズとアクセス権限が格納されます。それぞれのプロセスに対し、メモリアクセスをプロセスのメモリ領域にのみ制限するような大域/局所記述子表をロードすることで、OSはプロセスを互いに隔離できます。 [_protected mode_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode [_descriptor table_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table @@ -128,7 +128,7 @@ x86においては、ハードウェアは2つの異なるメモリ保護の方 ![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 100–250, the other entries are empty.](multilevel-page-table.svg) -ページ0は最初の`10_000`バイト領域に入るので、レベル2ページテーブルの最初のエントリを使います。このエントリはT1というレベル1ページテーブルを指し、このページテーブルはページ`0`はフレーム`0`に対応すると指定します。 +ページ0は最初の`10_000`バイト領域に入るので、レベル2ページテーブルの最初のエントリを使います。このエントリはT1というレベル1ページテーブルを指し、このページテーブルはページ`0`がフレーム`0`に対応すると指定します。 ページ`1_000_000`, `1_000_050`および`1_000_100`はすべて、`10_000`バイトの大きさの領域100個目に入るので、レベル2ページテーブルの100個目のエントリを使います。このエントリは、T2という別のレベル1テーブルを指しており、このレベル1テーブルはこれらの3つのページをフレーム`100`, `150`および`200`に対応させています。レベル1テーブルにおけるページアドレスには領域のオフセットは含まれていない、つまり例えば、ページ`1_000_050`のエントリは単に`50`である、ということに注意してください。 @@ -232,7 +232,7 @@ pub struct PageTable { - `accessed`と`dirty`フラグは、ページへの読み込みか書き込みが行われたときにCPUによって自動的に1にセットされます。この情報はオペレーティングシステムによって活用でき、例えば、どのページをスワップするかや、ページの中身が最後にディスクに保存されて以降に修正されたかを確認できます。 - `write through caching`と`disable cache`フラグで、キャッシュの制御をページごとに独立して行うことができます。 - `user accessible`フラグはページをユーザー空間のコードが利用できるようにします。このフラグが1になっていない場合、CPUがカーネルモードのときにのみアクセスできます。この機能は、ユーザ空間のプログラムが実行している間もカーネル(の使用しているメモリ)を対応付けたままにしておくことで、[システムコール][system calls]を高速化するために使うことができます。しかし、[Spectre]脆弱性を使うと、この機能があるにもかかわらず、ユーザ空間プログラムがこれらのページを読むことができてしまいます。 -- `global`フラグは、このページはすべてのアドレス空間で利用可能であり、よってアドレス空間の変更時に変換キャッシュ(TLBに関する下のセクションを読んでください)から取り除く必要がないことをハードウェアに伝えます。 +- `global`フラグは、このページはすべてのアドレス空間で利用可能であり、よってアドレス空間の変更時に変換キャッシュ(TLBに関する下のセクションを読んでください)から取り除く必要がないことをハードウェアに伝えます。このフラグはカーネルコードをすべてのアドレス空間に対応付けるため、一般的に`user accsessible`フラグと一緒に使われます。 - `huge page`フラグを使うと、レベル2か3のページが対応付けられたフレームを直接指すようにすることで、より大きいサイズのページを作ることができます。このビットが1のとき、ページの大きさは512倍になるので、レベル2のエントリの場合は2MiB = 512 * 4KiBに、レベル3のエントリの場合は1GiB = 512 * 2MiBにもなります。大きいページを使うことのメリットは、必要な変換キャッシュのラインの数やページテーブルの数が少なくなることです。 [system calls]: https://en.wikipedia.org/wiki/System_call @@ -308,7 +308,7 @@ extern "x86-interrupt" fn page_fault_handler( } ``` -[`CR2`]レジスタは、ページフォルト時にCPUによって自動的に設定されており、その値はアクセスされページフォルトを引き起こした仮想アドレスになっています。`x86_64`クレートの[`Cr2::read`]関数を使ってこれを読み込み出力します。[`PageFaultErrorCode`]型は、ページフォルトを引き起こしたメモリアクセスの種類についてより詳しい情報を提供します(例えば、読み込みと書き込みのどちらによるものなのか、など)。そのため、これも出力します。ページフォルトを解決して実行を継続することはできないので、最後は[`hlt_loop`]に入ります。 +[`CR2`]レジスタは、ページフォルト時にCPUによって自動的に設定されており、その値はアクセスされページフォルトを引き起こした仮想アドレスになっています。`x86_64`クレートの[`Cr2::read`]関数を使ってこれを読み込み出力します。[`PageFaultErrorCode`]型は、ページフォルトを引き起こしたメモリアクセスの種類についてより詳しい情報を提供します(例えば、読み込みと書き込みのどちらによるものなのか、など)。そのため、これも出力します。ページフォルトを解決しない限り実行を継続することはできないので、最後は[`hlt_loop`]に入ります。 [`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2 [`Cr2::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr2.html#method.read From 8cb4cf16dfc130eb8a50e8fc0710d6d12d00f236 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Wed, 26 May 2021 12:32:40 +0900 Subject: [PATCH 036/125] Apply suggestions from code review --- .../edition-2/posts/08-paging-introduction/index.ja.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index 7d0348c2..e029f418 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -164,7 +164,7 @@ x86_64アーキテクチャは4層ページテーブルを使っており、ペ ![An example 4-level page hierarchy with each page table shown in physical memory](x86_64-page-table-translation.svg) -現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「根」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれないからです。 +現在有効なレベル4ページテーブルの物理アドレス、つまりレベル4ページテーブルの「 (root) 」は`CR3`レジスタに格納されています。それぞれのページテーブルエントリは、次のレベルのテーブルの物理フレームを指しています。そして、レベル1のテーブルは対応するフレームを指しています。なお、ページテーブル内のアドレスは全て仮想ではなく物理アドレスであることに注意してください。さもなければ、CPUは(変換プロセス中に)それらのアドレスも変換しなくてはならず、無限再帰に陥ってしまうかもしれないからです。 上のページテーブル階層構造は、最終的に(青色の)2つのページへの対応を行っています。ページテーブルのインデックスから、これらの2つのページの仮想アドレスは`0x803FE7F000`と`0x803FE00000`であると推論できます。プログラムがアドレス`0x803FE7F5CE`から読み込もうとしたときに何が起こるかを見てみましょう。まず、アドレスを2進数に変換し、アドレスのページテーブルインデックスとページオフセットが何であるかを決定します: @@ -219,7 +219,7 @@ pub struct PageTable { 7 | huge page/null | P1とP4においては0で、P3においては1GiBのページを、P2においては2MiBのページを作る 8 | global | キャッシュにあるこのページはアドレス空間変更の際に初期化されない(CR4レジスタのPGEビットが1である必要がある) 9-11 | available | OSが自由に使える -12-51 | physical address | +12-51 | physical address | ページ単位にアラインされた、フレームまたは次のページテーブルの52bit物理アドレス 52-62 | available | OSが自由に使える 63 | no execute | このページにおいてプログラムを実行することを禁じる(EFERレジスタのNXEビットが1である必要がある) From b956c0c591d087fa317a8e06dfec1222692ae041 Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Wed, 26 May 2021 12:32:47 +0900 Subject: [PATCH 037/125] Refined translation slightly --- blog/content/edition-2/posts/08-paging-introduction/index.ja.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index e029f418..4fd8b1d9 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -223,7 +223,7 @@ pub struct PageTable { 52-62 | available | OSが自由に使える 63 | no execute | このページにおいてプログラムを実行することを禁じる(EFERレジスタのNXEビットが1である必要がある) -12-51ビットだけが物理フレームアドレスを格納するのに使われていて、残りのビットはフラグやオペレーティングシステムが自由に使うようになっていることがわかります。これが可能なのは、常に4096バイト単位のページに揃え (アライン) られたアドレス(ページテーブルか、対応づけられたフレームの先頭)を指しているからです。これは、0-11ビットは常にゼロであることを意味し、したがってこれらのビットを格納しておく必要はありません。ハードウェアがアドレスを使用する前に、それらのビットをゼロとして(追加して)やれば良いからです。同じことが52-63ビットについてもいえます。なぜならx86_64アーキテクチャは52ビットの物理アドレスしかサポートしていないからです(仮想アドレスを48ビットしかサポートしていないのと似ています)。 +12-51ビットだけが物理フレームアドレスを格納するのに使われていて、残りのビットはフラグやオペレーティングシステムが自由に使うようになっていることがわかります。これが可能なのは、常に4096バイト単位のページに揃え (アライン) られたアドレス(ページテーブルか、対応づけられたフレームの先頭)を指しているからです。これは、0-11ビットは常にゼロであることを意味し、したがってこれらのビットを格納しておく必要はありません。アドレスを使用する前に、ハードウェアがそれらのビットをゼロとして(追加して)やれば良いからです。また、52-63ビットについても格納しておく必要はありません。なぜならx86_64アーキテクチャは52ビットの物理アドレスしかサポートしていないからです(仮想アドレスを48ビットしかサポートしていないのと似ています)。 上のフラグについてより詳しく見てみましょう: From b0f0a4c5e228ae3ba11cf0ac1f0ac4111690b4ca Mon Sep 17 00:00:00 2001 From: woodyZootopia Date: Wed, 26 May 2021 12:41:09 +0900 Subject: [PATCH 038/125] Add @JohnTitor as co-translator --- blog/content/edition-2/posts/08-paging-introduction/index.ja.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md index 4fd8b1d9..95d314e4 100644 --- a/blog/content/edition-2/posts/08-paging-introduction/index.ja.md +++ b/blog/content/edition-2/posts/08-paging-introduction/index.ja.md @@ -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 = ["woodyZootopia"] +translators = ["woodyZootopia", "JohnTitor"] +++ この記事では**ページング**を紹介します。これは、私達のオペレーティングシステムにも使う、とても一般的なメモリ管理方式です。なぜメモリの分離が必要なのか、**セグメンテーション**がどういう仕組みなのか、**仮想メモリ**とは何なのか、ページングがいかにしてメモリ断片化 (フラグメンテーション) の問題を解決するのかを説明します。また、x86_64アーキテクチャにおける、マルチレベルページテーブルのレイアウトについても説明します。 From a46c20ef9365ed9089afc95205998f08441e14ec Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 12 Jun 2021 16:12:38 +0200 Subject: [PATCH 039/125] Switch comments from utterances to giscus to use GitHub discussions instead of issues for comment threads. --- blog/static/css/edition-2/main.css | 8 ++++++++ blog/templates/edition-2/base.html | 2 ++ blog/templates/edition-2/extra.html | 5 +++-- blog/templates/edition-2/page.html | 4 +++- blog/templates/news-page.html | 3 ++- blog/templates/snippets.html | 26 +++++++++++++++++++++----- blog/templates/status-update-page.html | 3 ++- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/blog/static/css/edition-2/main.css b/blog/static/css/edition-2/main.css index 32587de6..8fc7c411 100644 --- a/blog/static/css/edition-2/main.css +++ b/blog/static/css/edition-2/main.css @@ -462,3 +462,11 @@ a strong { .status-update-list li { margin-bottom: .5rem; } + +#comments { + visibility: hidden; +} + +.comment-directly-on-github { + margin-top: 1rem; +} diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index 628f1aff..f7587a9e 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -20,6 +20,8 @@ {% block title %}{% endblock title %} + + {% block head %}{% endblock head %} diff --git a/blog/templates/edition-2/extra.html b/blog/templates/edition-2/extra.html index 93c98f59..03079415 100644 --- a/blog/templates/edition-2/extra.html +++ b/blog/templates/edition-2/extra.html @@ -3,6 +3,7 @@ {% import "snippets.html" as snippets %} {% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} +{% block head %}{% endblock head %} {% block description -%} {{ page.summary | safe | striptags | truncate(length=150) }} @@ -16,7 +17,7 @@ {% block after_main %}
-

Comments

- {{ snippets::utterances() }} +

Comments

+ {{ snippets::giscus() }}
{% endblock after_main %} diff --git a/blog/templates/edition-2/page.html b/blog/templates/edition-2/page.html index 799110f1..536d2ec8 100644 --- a/blog/templates/edition-2/page.html +++ b/blog/templates/edition-2/page.html @@ -4,6 +4,8 @@ {% import "snippets.html" as snippets %} {% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} +{% block head %}{% endblock head %} + {% block header %} {% if lang != "en" -%} @@ -102,7 +104,7 @@

{% endif %} - {{ snippets::utterances() }} + {{ snippets::giscus() }} {% endblock after_main %} diff --git a/blog/templates/edition-2/page.html b/blog/templates/edition-2/page.html index ba5f3e03..e1117506 100644 --- a/blog/templates/edition-2/page.html +++ b/blog/templates/edition-2/page.html @@ -130,6 +130,8 @@ {% endfor %} {%- endif %} + + {{ snippets::dark_mode_note() }} {% endblock main %} diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index 452fed9a..96dbcf57 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -45,3 +45,12 @@ Instead of authenticating the giscus application, you can also comment directly on GitHub.

{% endmacro giscus %} + +{% macro dark_mode_note() %} +
+

Dark Mode is Experimental

+

+ We're still working on adjusting text colors, fixing images, and removing inconsistencies. If you have any problems, please file an issue. +

+ +{% endmacro dark_mode_note %} From adaaa3238ae1a825142089e0ada85c6faa49f1ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Oct 2021 17:27:48 +0200 Subject: [PATCH 102/125] Add meta tag that we support both light and dark mode --- blog/templates/edition-2/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index 61762d4d..1a2ab2e7 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -6,6 +6,7 @@ + From 96ab77fd1e0c787902652d4f63ba751c18ecf322 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 14:18:08 +0200 Subject: [PATCH 103/125] Implement a switch for switching between light and dark mode --- blog/sass/css/edition-2/main.scss | 46 ++++++++++++++++++++++++++++++ blog/static/js/edition-2/main.js | 24 ++++++++++++++++ blog/templates/edition-2/base.html | 2 ++ 3 files changed, 72 insertions(+) diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index f4712bbe..c8ca740f 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -961,6 +961,7 @@ img { background-color: white; } +/* Note that dark mode is experimental */ .dark-mode-note { display: none; @@ -978,3 +979,48 @@ body.dark .dark-mode-note { display: none; } } + +/* Manual switch between dark and light mode */ + +@mixin light-switch-light { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004' class='bi bi-moon' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M14.53 10.53a7 7 0 0 1-9.058-9.058A7.003 7.003 0 0 0 8 15a7.002 7.002 0 0 0 6.53-4.47z'/%3E%3C/svg%3E"); +} + +@mixin light-switch-dark { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff9' class='bi bi-brightness-high-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z'/%3E%3C/svg%3E"); +} + +.light-switch { + @include light-switch-light(); +} + +body.dark .light-switch { + @include light-switch-dark(); +} + +@media (prefers-color-scheme: dark) { + .light-switch { + @include light-switch-dark(); + } + + body.light .light-switch { + @include light-switch-light(); + } +} + +.light-switch { + position: fixed; + left: 2rem; + bottom: 2rem; + background-repeat: no-repeat; + width: 2rem; + height: 2rem; + cursor: pointer; + opacity: 0.6; +} + +.light-switch:hover { + transform: scale(1.3); + transition: 200ms ease-out; + opacity: 1; +} diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index dfa1dd4d..513fcd9d 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -61,3 +61,27 @@ function toc_scroll_position(container) { current_toc_item.classList.add("active"); } } + +function toggle_lights() { + var body = document.querySelector("body"); + var comment_form = document.querySelector("iframe.giscus-frame"); + if (body != null) { + if (body.classList.contains("dark")) { + body.classList.replace("dark", "light"); + if (comment_form != null) { + comment_form.contentWindow.postMessage({ + giscus: { setConfig: { theme: 'light' } } + }, "https://giscus.app") + } + } else { + body.classList.remove("light"); + body.classList.add("dark"); + if (comment_form != null) { + comment_form.contentWindow.postMessage({ + giscus: { setConfig: { theme: 'dark' } } + }, "https://giscus.app") + } + } + console.log(body) + } +} diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index 1a2ab2e7..f7baa22d 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -50,6 +50,8 @@
+
+ From 69917e234cc53d48e3b987a83cdc07db61f60b90 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 14:51:00 +0200 Subject: [PATCH 104/125] Remember chosen theme in localStorage This way, the selected theme is kept when changing pages, and for subsequent visits. To prevent flickering, we set the selected theme in a blocking script directly on load. To speed things up further, we now use a `data-theme` attribute instead of classes on the body tag, this way we don't need to wait until the body element is loaded. --- blog/sass/css/edition-2/main.scss | 12 +++--- blog/static/js/edition-2/main.js | 64 +++++++++++++++--------------- blog/templates/edition-2/base.html | 7 ++++ 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index c8ca740f..03633ae8 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -92,7 +92,7 @@ body { @include set-colors-light(); } -body.dark { +[data-theme="dark"] body { @include set-colors-dark(); } @@ -103,7 +103,7 @@ body.dark { @include set-colors-dark(); } /* Override dark mode with light mode styles if the user decides to swap */ - body.light { + [data-theme="light"] body { @include set-colors-light(); } } @@ -966,7 +966,7 @@ img { .dark-mode-note { display: none; } -body.dark .dark-mode-note { +[data-theme="dark"] .dark-mode-note { display: block; } @media (prefers-color-scheme: dark) { @@ -975,7 +975,7 @@ body.dark .dark-mode-note { display: block; } /* Override dark mode with light mode styles if the user decides to swap */ - body.light .dark-mode-note { + [data-theme="light"] .dark-mode-note { display: none; } } @@ -994,7 +994,7 @@ body.dark .dark-mode-note { @include light-switch-light(); } -body.dark .light-switch { +[data-theme="dark"] .light-switch { @include light-switch-dark(); } @@ -1003,7 +1003,7 @@ body.dark .light-switch { @include light-switch-dark(); } - body.light .light-switch { + [data-theme="light"] .light-switch { @include light-switch-light(); } } diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index 513fcd9d..3a18428f 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -1,17 +1,21 @@ -window.onload = function() { - var container = document.querySelector('#toc-aside'); - +window.onload = function () { + let theme = localStorage.getItem("theme"); + if (theme != null) { + set_theme(theme) + } + + let container = document.querySelector('#toc-aside'); if (container != null) { resize_toc(container); toc_scroll_position(container); - window.onscroll = function() { toc_scroll_position(container) }; + window.onscroll = function () { toc_scroll_position(container) }; } } function resize_toc(container) { - var containerHeight = container.clientHeight; + let containerHeight = container.clientHeight; - var resize = function() { + let resize = function () { if (containerHeight > document.documentElement.clientHeight - 100) { container.classList.add('coarse'); } else { @@ -20,8 +24,8 @@ function resize_toc(container) { }; resize(); - var resizeId; - window.onresize = function() { + let resizeId; + window.onresize = function () { clearTimeout(resizeId); resizeId = setTimeout(resize, 300); }; @@ -32,7 +36,6 @@ function toc_scroll_position(container) { // skip computation if ToC is not visible return; } - var items = container.querySelectorAll("li") // remove active class for all items for (item of container.querySelectorAll("li")) { @@ -40,15 +43,15 @@ function toc_scroll_position(container) { } // look for active item - var site_offset = document.documentElement.scrollTop; - var current_toc_item = null; + 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; } - var anchor = item.firstElementChild.getAttribute("href"); - var heading = document.querySelector(anchor); + 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 { @@ -63,25 +66,20 @@ function toc_scroll_position(container) { } function toggle_lights() { - var body = document.querySelector("body"); - var comment_form = document.querySelector("iframe.giscus-frame"); - if (body != null) { - if (body.classList.contains("dark")) { - body.classList.replace("dark", "light"); - if (comment_form != null) { - comment_form.contentWindow.postMessage({ - giscus: { setConfig: { theme: 'light' } } - }, "https://giscus.app") - } - } else { - body.classList.remove("light"); - body.classList.add("dark"); - if (comment_form != null) { - comment_form.contentWindow.postMessage({ - giscus: { setConfig: { theme: 'dark' } } - }, "https://giscus.app") - } - } - console.log(body) + if (document.documentElement.getAttribute("data-theme") === "dark") { + set_theme("light") + } else { + set_theme("dark") } } + +function set_theme(theme) { + let comment_form = document.querySelector("iframe.giscus-frame"); + document.documentElement.setAttribute("data-theme", theme); + if (comment_form != null) { + comment_form.contentWindow.postMessage({ + giscus: { setConfig: { theme: theme } } + }, "https://giscus.app") + } + localStorage.setItem("theme", theme); +} diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index f7baa22d..80d99688 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -17,6 +17,13 @@ + + {% block title %}{% endblock title %} From 76b6c445e46349bbd786d203de8bb54538a3dc05 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 15:40:48 +0200 Subject: [PATCH 105/125] Add a switch for going back to system theme --- blog/sass/css/edition-2/main.scss | 58 ++++++++++++++++++++++++++++++ blog/static/js/edition-2/main.js | 29 ++++++++++----- blog/templates/edition-2/base.html | 1 + 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index 03633ae8..c5f8ed52 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -983,10 +983,12 @@ img { /* Manual switch between dark and light mode */ @mixin light-switch-light { + // icon: https://icons.getbootstrap.com/icons/moon-fill/ (MIT licensed) background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004' class='bi bi-moon' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M14.53 10.53a7 7 0 0 1-9.058-9.058A7.003 7.003 0 0 0 8 15a7.002 7.002 0 0 0 6.53-4.47z'/%3E%3C/svg%3E"); } @mixin light-switch-dark { + // icon: https://icons.getbootstrap.com/icons/brightness-high-fill/ (MIT licensed) background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff9' class='bi bi-brightness-high-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z'/%3E%3C/svg%3E"); } @@ -1024,3 +1026,59 @@ img { transition: 200ms ease-out; opacity: 1; } + +/* Clear theme override and go back to system theme */ + +@mixin light-switch-reset-light { + // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); +} + +@mixin light-switch-reset-dark { + // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23999' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); +} + +.light-switch-reset { + @include light-switch-reset-light(); +} + +.light-switch-reset { + display: none; +} + +[data-theme="light"] .light-switch-reset { + display: block; +} + +[data-theme="dark"] .light-switch-reset { + @include light-switch-reset-dark(); + display: block; +} + +@media (prefers-color-scheme: dark) { + .light-switch-reset { + @include light-switch-reset-dark(); + } + + [data-theme="light"] .light-switch-reset { + @include light-switch-reset-light(); + } +} + +.light-switch-reset { + position: fixed; + left: 5rem; + bottom: 1.5rem; + background-repeat: no-repeat; + width: 2rem; + height: 2rem; + cursor: pointer; + opacity: 0.6; +} + +.light-switch-reset:hover { + transform: scale(1.1); + transition: 200ms ease-out; + opacity: 1; +} diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index 3a18428f..059bafc8 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -3,7 +3,7 @@ window.onload = function () { if (theme != null) { set_theme(theme) } - + let container = document.querySelector('#toc-aside'); if (container != null) { resize_toc(container); @@ -74,12 +74,23 @@ function toggle_lights() { } function set_theme(theme) { - let comment_form = document.querySelector("iframe.giscus-frame"); - document.documentElement.setAttribute("data-theme", theme); - if (comment_form != null) { - comment_form.contentWindow.postMessage({ - giscus: { setConfig: { theme: theme } } - }, "https://giscus.app") - } - localStorage.setItem("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") + } +} + diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index 80d99688..da415b22 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -58,6 +58,7 @@
+
From 5ff1aab7b55be014a15e1725bbfb070bfb2ebfa5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 16:34:24 +0200 Subject: [PATCH 106/125] Improve layout on mobile and clean up sass code --- blog/sass/css/edition-2/main.scss | 143 ++++++++++++++--------------- blog/templates/edition-2/base.html | 8 +- 2 files changed, 73 insertions(+), 78 deletions(-) diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index c5f8ed52..d8b8867b 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -982,103 +982,96 @@ img { /* Manual switch between dark and light mode */ -@mixin light-switch-light { - // icon: https://icons.getbootstrap.com/icons/moon-fill/ (MIT licensed) - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004' class='bi bi-moon' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M14.53 10.53a7 7 0 0 1-9.058-9.058A7.003 7.003 0 0 0 8 15a7.002 7.002 0 0 0 6.53-4.47z'/%3E%3C/svg%3E"); -} +.theme-switch { + margin-bottom: 1rem; -@mixin light-switch-dark { - // icon: https://icons.getbootstrap.com/icons/brightness-high-fill/ (MIT licensed) - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff9' class='bi bi-brightness-high-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z'/%3E%3C/svg%3E"); + @media (min-width: 80rem) { + position: fixed; + left: 2rem; + bottom: 2rem; + margin-bottom: 0rem; + } } .light-switch { + @mixin light-switch-light { + // icon: https://icons.getbootstrap.com/icons/moon-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004' class='bi bi-moon' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M14.53 10.53a7 7 0 0 1-9.058-9.058A7.003 7.003 0 0 0 8 15a7.002 7.002 0 0 0 6.53-4.47z'/%3E%3C/svg%3E"); + } + + @mixin light-switch-dark { + // icon: https://icons.getbootstrap.com/icons/brightness-high-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff9' class='bi bi-brightness-high-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z'/%3E%3C/svg%3E"); + } + + display: inline-block; @include light-switch-light(); -} -[data-theme="dark"] .light-switch { - @include light-switch-dark(); -} - -@media (prefers-color-scheme: dark) { - .light-switch { - @include light-switch-dark(); - } - - [data-theme="light"] .light-switch { - @include light-switch-light(); - } -} - -.light-switch { - position: fixed; - left: 2rem; - bottom: 2rem; background-repeat: no-repeat; width: 2rem; height: 2rem; cursor: pointer; opacity: 0.6; -} -.light-switch:hover { - transform: scale(1.3); - transition: 200ms ease-out; - opacity: 1; + &:hover { + transform: scale(1.3); + transition: 200ms ease-out; + opacity: 1; + } + + [data-theme="dark"] & { + @include light-switch-dark(); + } + + @media (prefers-color-scheme: dark) { + @include light-switch-dark(); + + [data-theme="light"] & { + @include light-switch-light(); + } + } } /* Clear theme override and go back to system theme */ -@mixin light-switch-reset-light { - // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); -} - -@mixin light-switch-reset-dark { - // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23999' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); -} - .light-switch-reset { + @mixin light-switch-reset-light { + // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); + } + + @mixin light-switch-reset-dark { + // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed) + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23999' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); + } + @include light-switch-reset-light(); -} - -.light-switch-reset { - display: none; -} - -[data-theme="light"] .light-switch-reset { - display: block; -} - -[data-theme="dark"] .light-switch-reset { - @include light-switch-reset-dark(); - display: block; -} - -@media (prefers-color-scheme: dark) { - .light-switch-reset { - @include light-switch-reset-dark(); - } - - [data-theme="light"] .light-switch-reset { - @include light-switch-reset-light(); - } -} - -.light-switch-reset { - position: fixed; - left: 5rem; - bottom: 1.5rem; + vertical-align: bottom; + margin-left: 0.5rem; background-repeat: no-repeat; width: 2rem; height: 2rem; cursor: pointer; opacity: 0.6; -} -.light-switch-reset:hover { - transform: scale(1.1); - transition: 200ms ease-out; - opacity: 1; + display: none; + [data-theme="light"] & { + display: inline-block; + } + [data-theme="dark"] & { + @include light-switch-reset-dark(); + display: inline-block; + } + + @media (min-width: 80rem) { + position: fixed; + left: 4.5rem; + bottom: 2rem; + } + + &:hover { + transform: scale(1.1); + transition: 200ms ease-out; + opacity: 1; + } } diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index da415b22..d2b9f81a 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -41,6 +41,11 @@ +
+
+
+
+
{% block toc_aside %}{% endblock toc_aside %}
{% block main %}{% endblock main %}
@@ -57,9 +62,6 @@
-
-
- From 0fa31a0e153e5bda52cc0ca9569ebfcf207db9da Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 16:34:59 +0200 Subject: [PATCH 107/125] Fix: don't assume that light mode is active on initial theme switch --- blog/static/js/edition-2/main.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index 059bafc8..1ebee250 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -68,8 +68,10 @@ function toc_scroll_position(container) { function toggle_lights() { if (document.documentElement.getAttribute("data-theme") === "dark") { set_theme("light") - } else { + } else if (document.documentElement.getAttribute("data-theme") === "light") { set_theme("dark") + } else { + set_theme(window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "dark") } } From 53e3578e34e221a6ba49316b7f075fc31cea305e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 17:16:02 +0200 Subject: [PATCH 108/125] Use `crate-ci/typos` action to check for typos The misspell tool that we used previously has no exclude switch for ignoring translated files. Also, it looks like it is not maintained anymore. In addition to changing our spell checker, this commit renames the `Build Site` workflow to `Blog` (to be consistent with our `Code` workflow). --- .github/workflows/{build-site.yml => blog.yml} | 10 +++++----- blog/typos.toml | 13 +++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) rename .github/workflows/{build-site.yml => blog.yml} (94%) create mode 100644 blog/typos.toml diff --git a/.github/workflows/build-site.yml b/.github/workflows/blog.yml similarity index 94% rename from .github/workflows/build-site.yml rename to .github/workflows/blog.yml index 9bf498e1..882b8fad 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/blog.yml @@ -1,4 +1,4 @@ -name: Build Site +name: Blog on: push: @@ -59,10 +59,10 @@ jobs: steps: - uses: actions/checkout@v1 - - run: curl -L https://git.io/misspell | bash - name: "Install misspell" - - run: bin/misspell -error blog/content - name: "Check for common typos" + - name: Typo Check + uses: crate-ci/typos@v1.1.9 + with: + files: blog deploy_site: name: "Deploy Generated Site" diff --git a/blog/typos.toml b/blog/typos.toml new file mode 100644 index 00000000..16e52eb3 --- /dev/null +++ b/blog/typos.toml @@ -0,0 +1,13 @@ +[files] +extend-exclude = [ + "*.svg", + "*.zh-CN.md", + "*.ja.md", +] + +[default.extend-words] +IST = "IST" # Interrupt Stack Table + +[default.extend-identifiers] +TheBegining = "TheBegining" # GitHub user mentioned in status reports +h015bf61815bb8afe = "h015bf61815bb8afe" # mangled name used in code example From a41d3236b897634b1ca6ea40d4794033db4c23e7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 17:37:55 +0200 Subject: [PATCH 109/125] Check translated files too --- .../naked-exceptions/02-better-exception-messages/index.md | 2 +- .../naked-exceptions/03-returning-from-exceptions/index.md | 2 +- blog/content/edition-1/posts/08-kernel-heap/index.md | 2 +- blog/content/edition-1/posts/09-handling-exceptions/index.md | 2 +- .../content/edition-2/posts/07-hardware-interrupts/index.fa.md | 2 +- blog/content/edition-2/posts/07-hardware-interrupts/index.md | 2 +- blog/content/status-update/2019-12-02.md | 2 +- blog/typos.toml | 3 +-- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md b/blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md index 17289421..9f98c962 100644 --- a/blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md +++ b/blog/content/edition-1/extra/naked-exceptions/02-better-exception-messages/index.md @@ -628,7 +628,7 @@ bitflags! { - When the `PROTECTION_VIOLATION` flag is set, the page fault was caused e.g. by a write to a read-only page. If it's not set, it was caused by accessing a non-present page. - The `CAUSED_BY_WRITE` flag specifies if the fault was caused by a write (if set) or a read (if not set). -- The `USER_MODE` flag is set when the fault occurred in non-priviledged mode. +- The `USER_MODE` flag is set when the fault occurred in non-privileged mode. - The `MALFORMED_TABLE` flag is set when the page table entry has a 1 in a reserved field. - When the `INSTRUCTION_FETCH` flag is set, the page fault occurred while fetching the next instruction. diff --git a/blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md b/blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md index 5ce204a0..b5ad8907 100644 --- a/blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md +++ b/blog/content/edition-1/extra/naked-exceptions/03-returning-from-exceptions/index.md @@ -426,7 +426,7 @@ The page fault is gone and we see the _“It did not crash”_ message again! So the page fault occurred because our exception handler didn't preserve the scratch register `rax`. Our new `handler!` macro fixes this problem by saving all scratch registers (including `rax`) before calling exception handlers. Thus, `rax` still contains the valid memory address when `rust-main` continues execution. ## Multimedia Registers -When we discussed calling conventions above, we assummed that a x86_64 CPU only has the following 16 registers: `rax`, `rbx`, `rcx`, `rdx`, `rsi`, `rdi`, `rsp`, `rbp`, `r8`, `r9`, `r10`, `r11`.`r12`, `r13`, `r14`, and `r15`. These registers are called _general purpose registers_ since each of them can be used for arithmetic and load/store instructions. +When we discussed calling conventions above, we assumed that a x86_64 CPU only has the following 16 registers: `rax`, `rbx`, `rcx`, `rdx`, `rsi`, `rdi`, `rsp`, `rbp`, `r8`, `r9`, `r10`, `r11`.`r12`, `r13`, `r14`, and `r15`. These registers are called _general purpose registers_ since each of them can be used for arithmetic and load/store instructions. However, modern CPUs also have a set of _special purpose registers_, which can be used to improve performance in several use cases. On x86_64, the most important set of special purpose registers are the _multimedia registers_. These registers are larger than the general purpose registers and can be used to speed up audio/video processing or matrix calculations. For example, we could use them to add two 4-dimensional vectors _in a single CPU instruction_: diff --git a/blog/content/edition-1/posts/08-kernel-heap/index.md b/blog/content/edition-1/posts/08-kernel-heap/index.md index 3b3e5dbd..82686151 100644 --- a/blog/content/edition-1/posts/08-kernel-heap/index.md +++ b/blog/content/edition-1/posts/08-kernel-heap/index.md @@ -415,7 +415,7 @@ pub fn init(boot_info: &BootInformation) { We've just moved the code to a new function. However, we've sneaked some improvements in: - An additional `.filter(|s| s.is_allocated())` in the calculation of `kernel_start` and `kernel_end`. This ignores all sections that aren't loaded to memory (such as debug sections). Thus, the kernel end address is no longer artificially increased by such sections. -- We use the `start_address()` and `end_address()` methods of `boot_info` instead of calculating the adresses manually. +- We use the `start_address()` and `end_address()` methods of `boot_info` instead of calculating the addresses manually. - We use the alternate `{:#x}` form when printing kernel/multiboot addresses. Before, we used `0x{:x}`, which leads to the same result. For a complete list of these “alternate” formatting forms, check out the [std::fmt documentation]. [std::fmt documentation]: https://doc.rust-lang.org/nightly/std/fmt/index.html#sign0 diff --git a/blog/content/edition-1/posts/09-handling-exceptions/index.md b/blog/content/edition-1/posts/09-handling-exceptions/index.md index 09685ae4..e3b896ed 100644 --- a/blog/content/edition-1/posts/09-handling-exceptions/index.md +++ b/blog/content/edition-1/posts/09-handling-exceptions/index.md @@ -379,7 +379,7 @@ Note how this solution requires no `unsafe` blocks or `unwrap` calls. > ##### Aside: How does the `lazy_static!` macro work? > -> The macro generates a `static` of type `Once`. The [`Once`][spin::Once] type is provided by the `spin` crate and allows deferred one-time initialization. It is implemented using an [`AtomicUsize`] for synchronization and an [`UnsafeCell`] for storing the (possibly unitialized) value. So this solution also uses `unsafe` behind the scenes, but it is abstracted away in a safe interface. +> The macro generates a `static` of type `Once`. The [`Once`][spin::Once] type is provided by the `spin` crate and allows deferred one-time initialization. It is implemented using an [`AtomicUsize`] for synchronization and an [`UnsafeCell`] for storing the (possibly uninitialized) value. So this solution also uses `unsafe` behind the scenes, but it is abstracted away in a safe interface. [spin::Once]: https://docs.rs/spin/0.4.5/spin/struct.Once.html [`AtomicUsize`]: https://doc.rust-lang.org/nightly/core/sync/atomic/struct.AtomicUsize.html diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md index 089c60c0..d3a91b3b 100644 --- a/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md +++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md @@ -251,7 +251,7 @@ extern "x86-interrupt" fn timer_interrupt_handler( ### پیکربندی تایمر -تایمر سخت افزاری که ما از آن استفاده می کنیم ، _Progammable Interval Timer_ یا به اختصار PIT نامیده می شود. همانطور که از نام آن مشخص است ، می توان فاصله بین دو وقفه را پیکربندی کرد. ما در اینجا به جزئیات نمی پردازیم زیرا به زودی به [تایمر APIC] سوییچ خواهیم کرد، اما ویکی OSDev مقاله مفصلی درباره [پیکربندی PIT] دارد. +تایمر سخت افزاری که ما از آن استفاده می کنیم ، _Programmable Interval Timer_ یا به اختصار PIT نامیده می شود. همانطور که از نام آن مشخص است ، می توان فاصله بین دو وقفه را پیکربندی کرد. ما در اینجا به جزئیات نمی پردازیم زیرا به زودی به [تایمر APIC] سوییچ خواهیم کرد، اما ویکی OSDev مقاله مفصلی درباره [پیکربندی PIT] دارد. [تایمر APIC]: https://wiki.osdev.org/APIC_timer [پیکربندی PIT]: https://wiki.osdev.org/Programmable_Interval_Timer diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.md index 5d1267b5..f99cae90 100644 --- a/blog/content/edition-2/posts/07-hardware-interrupts/index.md +++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.md @@ -246,7 +246,7 @@ When we now execute `cargo run` we see dots periodically appearing on the screen ### Configuring the Timer -The hardware timer that we use is called the _Progammable Interval Timer_ or PIT for short. Like the name says, it is possible to configure the interval between two interrupts. We won't go into details here because we will switch to the [APIC timer] soon, but the OSDev wiki has an extensive article about the [configuring the PIT]. +The hardware timer that we use is called the _Programmable Interval Timer_ or PIT for short. Like the name says, it is possible to configure the interval between two interrupts. We won't go into details here because we will switch to the [APIC timer] soon, but the OSDev wiki has an extensive article about the [configuring the PIT]. [APIC timer]: https://wiki.osdev.org/APIC_timer [configuring the PIT]: https://wiki.osdev.org/Programmable_Interval_Timer diff --git a/blog/content/status-update/2019-12-02.md b/blog/content/status-update/2019-12-02.md index b366c0a9..5d7e4fca 100644 --- a/blog/content/status-update/2019-12-02.md +++ b/blog/content/status-update/2019-12-02.md @@ -15,7 +15,7 @@ We also have other news: We plan to add [Experimental Support for Community Tran ## `bootloader` -- [Change the way the kernel entry point is called to honor alignement ABI](https://github.com/rust-osdev/bootloader/pull/81) by [@GuillaumeDIDIER](https://github.com/GuillaumeDIDIER) (published as version 0.8.2) +- [Change the way the kernel entry point is called to honor alignment ABI](https://github.com/rust-osdev/bootloader/pull/81) by [@GuillaumeDIDIER](https://github.com/GuillaumeDIDIER) (published as version 0.8.2) - [Add support for Github Actions](https://github.com/rust-osdev/bootloader/pull/82) - [Remove unnecessary `extern C` on panic handler to fix not-ffi-safe warning](https://github.com/rust-osdev/bootloader/pull/85) by [@cmsd2](https://github.com/cmsd2) (published as version 0.8.3) diff --git a/blog/typos.toml b/blog/typos.toml index 16e52eb3..099243e3 100644 --- a/blog/typos.toml +++ b/blog/typos.toml @@ -1,12 +1,11 @@ [files] extend-exclude = [ "*.svg", - "*.zh-CN.md", - "*.ja.md", ] [default.extend-words] IST = "IST" # Interrupt Stack Table +SEH = "SEH" # structured exception handling [default.extend-identifiers] TheBegining = "TheBegining" # GitHub user mentioned in status reports From 195d40080a94d0183ffbba14b029ca4619920eb6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 18:10:09 +0200 Subject: [PATCH 110/125] Don't spellcheck french translations (lots of false positives) --- blog/typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/blog/typos.toml b/blog/typos.toml index 099243e3..e38c44b3 100644 --- a/blog/typos.toml +++ b/blog/typos.toml @@ -1,6 +1,7 @@ [files] extend-exclude = [ "*.svg", + "*.fr.md", ] [default.extend-words] From ebbc6381d7a0c92ab4bfc9adb6d8b6989b80e216 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 18:19:54 +0200 Subject: [PATCH 111/125] Fix front matter syntax --- .../edition-2/posts/01-freestanding-rust-binary/index.fr.md | 1 - 1 file changed, 1 deletion(-) diff --git a/blog/content/edition-2/posts/01-freestanding-rust-binary/index.fr.md b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.fr.md index 80f0ee0e..0982bbf9 100644 --- a/blog/content/edition-2/posts/01-freestanding-rust-binary/index.fr.md +++ b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.fr.md @@ -11,7 +11,6 @@ translation_based_on_commit = "3e87916b6c2ed792d1bdb8c0947906aef9013ac1" # GitHub usernames of the people that translated this post translators = ["Alekzus"] +++ -+++ La première étape pour créer notre propre noyau de système d'exploitation est de créer un exécutable Rust qui ne relie pas la bibliothèque standard. Cela rend possible l'exécution du code Rust sur la ["bare machine"][machine nue] sans système d'exploitation sous-jacent. From 7eb1426e1f14b22985e7b1983ed6afc2506fa698 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 18:31:40 +0200 Subject: [PATCH 112/125] Use onload handler to set giscus theme as soon as it's loaded --- blog/static/js/edition-2/main.js | 11 ++++++----- blog/templates/snippets.html | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index 1ebee250..e1fb157f 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -1,9 +1,4 @@ window.onload = function () { - let theme = localStorage.getItem("theme"); - if (theme != null) { - set_theme(theme) - } - let container = document.querySelector('#toc-aside'); if (container != null) { resize_toc(container); @@ -96,3 +91,9 @@ function set_giscus_theme(theme) { } } +function giscus_loaded() { + let theme = localStorage.getItem("theme"); + if (theme != null) { + set_giscus_theme(theme) + } +} diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index 96dbcf57..fc596c8c 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -38,7 +38,8 @@ data-emit-metadata="1" data-theme="preferred_color_scheme" crossorigin="anonymous" - async> + async + onload="giscus_loaded()">

From d6bf1b22710278ba0013551fc559e650062bbdeb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 18:35:45 +0200 Subject: [PATCH 113/125] Revert "Use onload handler to set giscus theme as soon as it's loaded" This reverts commit 7eb1426e1f14b22985e7b1983ed6afc2506fa698. --- blog/static/js/edition-2/main.js | 11 +++++------ blog/templates/snippets.html | 3 +-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index e1fb157f..1ebee250 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -1,4 +1,9 @@ window.onload = function () { + let theme = localStorage.getItem("theme"); + if (theme != null) { + set_theme(theme) + } + let container = document.querySelector('#toc-aside'); if (container != null) { resize_toc(container); @@ -91,9 +96,3 @@ function set_giscus_theme(theme) { } } -function giscus_loaded() { - let theme = localStorage.getItem("theme"); - if (theme != null) { - set_giscus_theme(theme) - } -} diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index fc596c8c..96dbcf57 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -38,8 +38,7 @@ data-emit-metadata="1" data-theme="preferred_color_scheme" crossorigin="anonymous" - async - onload="giscus_loaded()"> + async>

From 5c750985a6bff3220af7152e31d5b5e2c170680b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 18:41:24 +0200 Subject: [PATCH 114/125] Wait 500ms before changing giscus theme on load to make sure that it's ready --- blog/static/js/edition-2/main.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js index 1ebee250..f4cb57c2 100644 --- a/blog/static/js/edition-2/main.js +++ b/blog/static/js/edition-2/main.js @@ -1,15 +1,17 @@ window.onload = function () { - let theme = localStorage.getItem("theme"); - if (theme != null) { - set_theme(theme) - } - 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) { From 44f51402f7038054074c7cd0df80a8af8f4525e8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Oct 2021 21:06:57 +0200 Subject: [PATCH 115/125] Upgrade to zola 0.14.1 --- .github/workflows/blog.yml | 4 +- blog/config.toml | 64 +++++++++++++------ .../03-returning-from-exceptions/index.md | 2 +- .../edition-1/posts/08-kernel-heap/index.md | 2 +- blog/sass/css/edition-2/main.scss | 1 - blog/templates/edition-2/index.html | 18 +++--- blog/templates/edition-2/macros.html | 2 +- blog/templates/edition-2/page.html | 16 ++--- blog/templates/rss.xml | 2 +- 9 files changed, 65 insertions(+), 46 deletions(-) diff --git a/.github/workflows/blog.yml b/.github/workflows/blog.yml index 882b8fad..bce2bc34 100644 --- a/.github/workflows/blog.yml +++ b/.github/workflows/blog.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v1 - name: 'Download Zola' - run: curl -sL https://github.com/getzola/zola/releases/download/v0.12.1/zola-v0.12.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv + run: curl -sL https://github.com/getzola/zola/releases/download/v0.14.1/zola-v0.14.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" @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v1 - name: 'Download Zola' - run: curl -sL https://github.com/getzola/zola/releases/download/v0.12.1/zola-v0.12.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv + run: curl -sL https://github.com/getzola/zola/releases/download/v0.14.1/zola-v0.14.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv - name: "Run zola check" run: ../zola check diff --git a/blog/config.toml b/blog/config.toml index a7f45498..a67c2d01 100644 --- a/blog/config.toml +++ b/blog/config.toml @@ -1,24 +1,17 @@ -title = "Writing an OS in Rust" base_url = "https://os.phil-opp.com" -description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code." -highlight_code = true -highlight_theme = "visual-studio-dark" generate_feed = true feed_filename = "rss.xml" compile_sass = true - -languages = [ - { code = "zh-CN" }, # Chinese (simplified) - { code = "zh-TW" }, # Chinese (traditional) - { code = "ja" }, # Japanese - { code = "fa" }, # Persian - { code = "ru" }, # Russian - { code = "fr" }, # French -] +minify_html = true ignored_content = ["*/README.md"] +[markdown] +highlight_code = true +highlight_theme = "visual-studio-dark" +smart_punctuation = true + [link_checker] skip_prefixes = [ "https://crates.io/crates", # see https://github.com/rust-lang/crates.io/issues/788 @@ -36,9 +29,14 @@ skip_anchor_prefixes = [ [extra] subtitle = "Philipp Oppermann's blog" author = { name = "Philipp Oppermann" } +default_language = "en" +languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru"] -[translations.en] -lang_name = "English" +[languages.en] +title = "Writing an OS in Rust" +description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code." +[languages.en.translations] +lang_name = "English (original)" toc = "Table of Contents" all_posts = "« All Posts" comments = "Comments" @@ -50,7 +48,11 @@ translated_content_notice = "This is a community translation of the objdump -d build/kernel-x86_64.bin | grep -B1 "110970:" 11096f: cc int3 110970: 48 c7 01 2a 00 00 00 movq $0x2a,(%rcx) diff --git a/blog/content/edition-1/posts/08-kernel-heap/index.md b/blog/content/edition-1/posts/08-kernel-heap/index.md index 82686151..9013ba56 100644 --- a/blog/content/edition-1/posts/08-kernel-heap/index.md +++ b/blog/content/edition-1/posts/08-kernel-heap/index.md @@ -675,7 +675,7 @@ I created the [linked_list_allocator] crate to handle all of these cases. It con We need to add the extern crate to our `Cargo.toml` and our `lib.rs`: -``` shell +``` bash > cargo add linked_list_allocator ``` diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index d8b8867b..c398ac01 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -377,7 +377,6 @@ tbody tr:nth-child(odd) th { /* Blog post or page title */ .page-title, -.post-title, .post-title a { color: var(--post-title-color); } diff --git a/blog/templates/edition-2/index.html b/blog/templates/edition-2/index.html index bab56839..38264785 100644 --- a/blog/templates/edition-2/index.html +++ b/blog/templates/edition-2/index.html @@ -70,16 +70,14 @@

{% endblock after_main %} diff --git a/blog/templates/edition-2/page.html b/blog/templates/edition-2/page.html index 0175a2c2..f7000823 100644 --- a/blog/templates/edition-2/page.html +++ b/blog/templates/edition-2/page.html @@ -130,8 +130,6 @@ {%- endif -%}{% endfor %} {%- endif %} - - {{ snippets::dark_mode_note() }} {% endblock main %} diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index 96dbcf57..452fed9a 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -45,12 +45,3 @@ Instead of authenticating the giscus application, you can also comment directly on GitHub.

{% endmacro giscus %} - -{% macro dark_mode_note() %} -
-

Dark Mode is Experimental

-

- We're still working on adjusting text colors, fixing images, and removing inconsistencies. If you have any problems, please file an issue. -

- -{% endmacro dark_mode_note %} From 64c113090839899c03a6a9bd89ed8b222f11f442 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 23 Jan 2022 17:34:46 +0100 Subject: [PATCH 122/125] Fix HTML error: tags should go inside
  • tags --- blog/before_build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blog/before_build.py b/blog/before_build.py index 458fed76..56f7d4c1 100644 --- a/blog/before_build.py +++ b/blog/before_build.py @@ -65,7 +65,7 @@ while True: month_str = datetime.date(1900, month, 1).strftime('%B') link = 'This Month in Rust OSDev (' + month_str + " " + str(year) + ") " - lines.append(u"
  • " + link + "
  • \n") + lines.append(u"
  • " + link + "
  • \n") month = month + 1 if month > 12: @@ -78,7 +78,7 @@ with io.open("templates/auto/status-updates.html", 'w', encoding='utf8') as stat status_updates.truncate() for line in lines: - status_updates.write("" + line + "") + status_updates.write(line) with io.open("templates/auto/status-updates-truncated.html", 'w', encoding='utf8') as status_updates: status_updates.truncate() @@ -86,4 +86,4 @@ with io.open("templates/auto/status-updates-truncated.html", 'w', encoding='utf8 for index, line in enumerate(lines): if index == 5: break - status_updates.write("" + line + "") + status_updates.write(line) From 09e0c5915b06f0af72954e1d233b9a985c3c22cc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 23 Jan 2022 17:34:57 +0100 Subject: [PATCH 123/125] Link to license in footer --- blog/sass/css/edition-2/main.scss | 4 ++++ blog/templates/edition-2/base.html | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss index d90bb649..f2ca3806 100644 --- a/blog/sass/css/edition-2/main.scss +++ b/blog/sass/css/edition-2/main.scss @@ -636,6 +636,10 @@ main img { footer.footer { margin-top: 1rem; margin-bottom: 1rem; + + .spaced { + margin-left: 0.5rem; + } } .footnotes { diff --git a/blog/templates/edition-2/base.html b/blog/templates/edition-2/base.html index d2b9f81a..d638e665 100644 --- a/blog/templates/edition-2/base.html +++ b/blog/templates/edition-2/base.html @@ -56,8 +56,9 @@
    From 6b241de81b95c40a4027cdeb6bc92f94aa0ea914 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 23 Jan 2022 17:39:37 +0100 Subject: [PATCH 124/125] Fix HTML encoding of GitHub discussions link --- blog/templates/snippets.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index 452fed9a..4680e079 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -14,7 +14,7 @@ {% if search_term is number %} {% set discussion_url = "https://github.com/phil-opp/blog_os/discussions/" ~ search_term %} {% else %} - {% set discussion_url = `https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q="` ~ search_term ~ `"` %} + {% set discussion_url = `https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22` ~ search_term ~ `%22` %} {% endif %}

    From c7eced8b49f132cac16e06f7e79bb82ccd30de5f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 23 Jan 2022 17:53:12 +0100 Subject: [PATCH 125/125] Use proper HTML escaping for GitHub discussions link --- blog/templates/snippets.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/blog/templates/snippets.html b/blog/templates/snippets.html index 4680e079..9065977e 100644 --- a/blog/templates/snippets.html +++ b/blog/templates/snippets.html @@ -14,11 +14,12 @@ {% if search_term is number %} {% set discussion_url = "https://github.com/phil-opp/blog_os/discussions/" ~ search_term %} {% else %} - {% set discussion_url = `https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22` ~ search_term ~ `%22` %} + {% set search_term_encoded = `"` ~ search_term ~ `"` | urlencode %} + {% set discussion_url = `https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=` ~ search_term_encoded %} {% endif %}

    - 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 code of conduct. This comment thread directly maps to a discussion on GitHub, so you can also comment there if you prefer. + 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 code of conduct. This comment thread directly maps to a discussion on GitHub, so you can also comment there if you prefer.

    @@ -42,6 +43,6 @@

    - Instead of authenticating the giscus application, you can also comment directly on GitHub. + Instead of authenticating the giscus application, you can also comment directly on GitHub.

    {% endmacro giscus %}