Merge branch 'master' into translations
12
.github/workflows/build-site.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: 'Clone Zola'
|
||||
run: git clone https://github.com/getzola/zola.git --branch next
|
||||
- name: 'Download Zola'
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.10.0/zola-v0.10.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
- name: "Install Python Tools"
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: 'Install Python Libraries'
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run: python before_build.py
|
||||
working-directory: "blog"
|
||||
- name: "Build Site"
|
||||
run: cargo run --manifest-path=../zola/Cargo.toml -- build
|
||||
run: ../zola build
|
||||
working-directory: "blog"
|
||||
|
||||
- name: Upload Generated Site
|
||||
@@ -47,11 +47,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: 'Clone Zola'
|
||||
run: git clone https://github.com/getzola/zola.git --branch next
|
||||
- name: 'Download Zola'
|
||||
run: curl -sL https://github.com/getzola/zola/releases/download/v0.10.0/zola-v0.10.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
|
||||
- name: "Run zola check"
|
||||
run: cargo run --manifest-path=../zola/Cargo.toml -- check
|
||||
run: ../zola check
|
||||
working-directory: "blog"
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
13
.travis.yml
@@ -1,13 +0,0 @@
|
||||
language: minimal
|
||||
|
||||
branches:
|
||||
only:
|
||||
# This is where pull requests from "bors r+" are built.
|
||||
- staging
|
||||
# This is where pull requests from "bors try" are built.
|
||||
- trying
|
||||
# Build post braches
|
||||
- /^post-.*$/
|
||||
|
||||
script:
|
||||
- echo "Nothing to do for master branch"
|
||||
@@ -10,7 +10,7 @@ The code for each post lives in a separate git branch. This makes it possible to
|
||||
|
||||
**The code for the latest post is available [here][latest-post].**
|
||||
|
||||
[latest-post]: https://github.com/phil-opp/blog_os/tree/post-10
|
||||
[latest-post]: https://github.com/phil-opp/blog_os/tree/post-11
|
||||
|
||||
You can find the branch for each post by following the `(source code)` link in the [post list](#posts) below. The branches are named `post-XX` where `XX` is the post number, for example `post-03` for the _VGA Text Mode_ post or `post-07` for the _Hardware Interrupts_ post. For build instructions, see the Readme of the respective branch.
|
||||
|
||||
@@ -56,6 +56,8 @@ The goal of this project is to provide step-by-step tutorials in individual blog
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/post-09))
|
||||
- [Heap Allocation](https://os.phil-opp.com/heap-allocation/)
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/post-10))
|
||||
- [Allocator Designs](https://os.phil-opp.com/allocator-designs/)
|
||||
([source code](https://github.com/phil-opp/blog_os/tree/post-11))
|
||||
|
||||
## First Edition Posts
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Documentation: https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
exclude:
|
||||
- 'staging.tmp'
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
echo "Hello world from $AGENT_NAME running on $AGENT_OS"
|
||||
echo "Reason: $BUILD_REASON"
|
||||
echo "Requested for: $BUILD_REQUESTEDFOR"
|
||||
displayName: 'Build Info'
|
||||
continueOnError: true
|
||||
|
||||
- bash: curl -sL https://github.com/getzola/zola/releases/download/v0.9.0/zola-v0.9.0-x86_64-unknown-linux-gnu.tar.gz | tar zxv
|
||||
displayName: "Download Zola"
|
||||
|
||||
- script: python -m pip install --upgrade pip setuptools wheel
|
||||
displayName: 'Install Python Tools'
|
||||
|
||||
- script: python -m pip install --user -r requirements.txt
|
||||
displayName: 'Install Python Libraries'
|
||||
workingDirectory: "blog"
|
||||
|
||||
- script: python before_build.py
|
||||
displayName: "Run before_build.py script"
|
||||
workingDirectory: "blog"
|
||||
|
||||
- script: ../zola build
|
||||
displayName: "Build Site"
|
||||
workingDirectory: "blog"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'generated_site'
|
||||
targetPath: 'blog/public'
|
||||
|
||||
- script: curl -L https://git.io/misspell | bash
|
||||
displayName: "Install misspell"
|
||||
|
||||
- script: bin/misspell -error blog/content
|
||||
displayName: "Check for common typos"
|
||||
@@ -1,5 +1,6 @@
|
||||
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"
|
||||
|
||||
@@ -8,4 +8,4 @@ Philipp Oppermann
|
||||
|
||||
<big>contact@phil-opp.com</big>
|
||||
|
||||
<small>Dr.Gustav-Knodel-Str. 7b, 76344 Eggenstein, Germany</small>
|
||||
<small>Gerwigstraße 17, 76131 Karlsruhe, Germany</small>
|
||||
|
||||
@@ -6,6 +6,8 @@ weight = 3
|
||||
|
||||
I finally managed to get `blog_os` building on my Android phone using [termux](https://termux.com/). This post explains the necessary steps to set it up.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
<img src="building-on-android.png" alt="Screenshot of the compilation output from android" style="height: 50rem;" >
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 1
|
||||
path = "freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
+++
|
||||
|
||||
The first step in creating our own operating system kernel is to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system.
|
||||
@@ -145,10 +147,11 @@ Language items are special functions and types that are required internally by t
|
||||
[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||
[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
|
||||
|
||||
Providing own implementations of language items would be possible, but this should only be done as a last resort. The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types). Fortunately, there is a more stable way to fix the above language item error.
|
||||
While providing custom implementations of language items is possible, it should only be done as a last resort. The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types). Fortunately, there is a more stable way to fix the above language item error.
|
||||
|
||||
The `eh_personality` language item marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
|
||||
The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic]. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
|
||||
|
||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
||||
[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
[libunwind]: http://www.nongnu.org/libunwind/
|
||||
[structured exception handling]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx
|
||||
@@ -507,7 +510,7 @@ cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||
```
|
||||
|
||||
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So it probably for any real use of such a binary, more steps are required**.
|
||||
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
|
||||
|
||||
## What's next?
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Disable the Red Zone"
|
||||
weight = 1
|
||||
path = "red-zone"
|
||||
|
||||
template = "second-edition/extra.html"
|
||||
+++
|
||||
|
||||
The [red zone] is an optimization of the [System V ABI] that allows functions to temporarily use the 128 bytes below its stack frame without adjusting the stack pointer:
|
||||
@@ -10,6 +10,8 @@ The [red zone] is an optimization of the [System V ABI] that allows functions to
|
||||
[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
[System V ABI]: http://wiki.osdev.org/System_V_ABI
|
||||
|
||||
<!-- more -->
|
||||
|
||||

|
||||
|
||||
The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the return address and the local variables.
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -2,13 +2,15 @@
|
||||
title = "Disable SIMD"
|
||||
weight = 2
|
||||
path = "disable-simd"
|
||||
|
||||
template = "second-edition/extra.html"
|
||||
+++
|
||||
|
||||
[Single Instruction Multiple Data (SIMD)] instructions are able to perform an operation (e.g. addition) simultaneously on multiple data words, which can speed up programs significantly. The `x86_64` architecture supports various SIMD standards:
|
||||
|
||||
[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
|
||||
|
||||
<!-- more -->
|
||||
|
||||
- [MMX]: The _Multi Media Extension_ instruction set was introduced in 1997 and defines eight 64 bit registers called `mm0` through `mm7`. These registers are just aliases for the registers of the [x87 floating point unit].
|
||||
- [SSE]: The _Streaming SIMD Extensions_ instruction set was introduced in 1999. Instead of re-using the floating point registers, it adds a completely new register set. The sixteen new registers are called `xmm0` through `xmm15` and are 128 bits each.
|
||||
- [AVX]: The _Advanced Vector Extensions_ are extensions that further increase the size of the multimedia registers. The new registers are called `ymm0` through `ymm15` and are 256 bits each. They extend the `xmm` registers, so e.g. `xmm0` is the lower half of `ymm0`.
|
||||
@@ -4,6 +4,8 @@ weight = 2
|
||||
path = "minimal-rust-kernel"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
+++
|
||||
|
||||
In this post we create a minimal 64-bit Rust kernel for the x86 architecture. We build upon the [freestanding Rust binary] from the previous post to create a bootable disk image, that prints something to the screen.
|
||||
@@ -169,7 +171,7 @@ This setting specifies that the target doesn't support [stack unwinding] on pani
|
||||
|
||||
We're writing a kernel, so we'll need to handle interrupts at some point. To do that safely, we have to disable a certain stack pointer optimization called the _“red zone”_, because it would cause stack corruptions otherwise. For more information, see our separate post about [disabling the red zone].
|
||||
|
||||
[disabling the red zone]: @/second-edition/extra/disable-red-zone/index.md
|
||||
[disabling the red zone]: @/second-edition/posts/02-minimal-rust-kernel/disable-red-zone/index.md
|
||||
|
||||
```json
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
@@ -183,7 +185,7 @@ The `mmx` and `sse` features determine support for [Single Instruction Multiple
|
||||
|
||||
A problem with disabling SIMD is that floating point operations on `x86_64` require SIMD registers by default. To solve this problem, we add the `soft-float` feature, which emulates all floating point operations through software functions based on normal integers.
|
||||
|
||||
For more information, see our post on [disabling SIMD](@/second-edition/extra/disable-simd/index.md).
|
||||
For more information, see our post on [disabling SIMD](@/second-edition/posts/02-minimal-rust-kernel/disable-simd/index.md).
|
||||
|
||||
#### Putting it Together
|
||||
Our target specification file now looks like this:
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 3
|
||||
path = "vga-text-mode"
|
||||
date = 2018-02-26
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
+++
|
||||
|
||||
The [VGA text mode] is a simple way to print text to the screen. In this post, we create an interface that makes its usage safe and simple, by encapsulating all unsafety in a separate module. We also implement support for Rust's [formatting macros].
|
||||
@@ -590,7 +592,7 @@ macro_rules! println {
|
||||
|
||||
Macros are defined through one or more rules, which are similar to `match` arms. The `println` macro has two rules: The first rule for is invocations without arguments (e.g `println!()`), which is expanded to `print!("\n")` and thus just prints a newline. the second rule is for invocations with parameters such as `println!("Hello")` or `println!("Number: {}", 4)`. It is also expanded to an invocation of the `print!` macro, passing all arguments and an additional newline `\n` at the end.
|
||||
|
||||
The `#[macro_export]` attribute makes the available to the whole crate (not just the module it is defined) and external crates. It also places the macro at the crate root, which means that we have to import the macro through `use std::println` instead of `std::macros::println`.
|
||||
The `#[macro_export]` attribute makes the macro available to the whole crate (not just the module it is defined) and external crates. It also places the macro at the crate root, which means that we have to import the macro through `use std::println` instead of `std::macros::println`.
|
||||
|
||||
The [`print!` macro] is defined as:
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 4
|
||||
path = "testing"
|
||||
date = 2019-04-27
|
||||
|
||||
[extra]
|
||||
chapter = "Bare Bones"
|
||||
+++
|
||||
|
||||
This post explores unit and integration testing in `no_std` executables. We will use Rust's support for custom test frameworks to execute test functions inside our kernel. To report the results out of QEMU, we will use different features of QEMU and the `bootimage` tool.
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 5
|
||||
path = "cpu-exceptions"
|
||||
date = 2018-06-17
|
||||
|
||||
[extra]
|
||||
chapter = "Interrupts"
|
||||
+++
|
||||
|
||||
CPU exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To react to them we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards.
|
||||
@@ -168,6 +170,8 @@ In contrast to function calls, exceptions can occur on _any_ instruction. In mos
|
||||
|
||||
Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for exception handlers. Instead, we need a calling convention means that preserves _all registers_. The `x86-interrupt` calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return.
|
||||
|
||||
Note that this does not mean that all registers are saved to the stack at function entry. Instead, the compiler only backs up the registers that are overwritten by the function. This way, very efficient code can be generated for short functions that only use a few registers.
|
||||
|
||||
### The Interrupt Stack Frame
|
||||
On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this:
|
||||
|
||||
@@ -193,12 +197,6 @@ In the `x86_64` crate, the interrupt stack frame is represented by the [`Interru
|
||||
|
||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.8.1/x86_64/structures/idt/struct.InterruptStackFrame.html
|
||||
|
||||
Note that there is currently [a bug in LLVM] that leads to wrong error code arguments. The cause of the issue is already known and a solution is [being worked on].
|
||||
|
||||
[a bug in LLVM]: https://github.com/rust-lang/rust/issues/57270
|
||||
[being worked on]: https://reviews.llvm.org/D56275
|
||||
|
||||
|
||||
### Behind the Scenes
|
||||
The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of:
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 6
|
||||
path = "double-fault-exceptions"
|
||||
date = 2018-06-18
|
||||
|
||||
[extra]
|
||||
chapter = "Interrupts"
|
||||
+++
|
||||
|
||||
This post explores the double fault exception in detail, which occurs when the CPU fails to invoke an exception handler. By handling this exception we avoid fatal _triple faults_ that cause a system reset. To prevent triple faults in all cases we also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack.
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 7
|
||||
path = "hardware-interrupts"
|
||||
date = 2018-10-22
|
||||
|
||||
[extra]
|
||||
chapter = "Interrupts"
|
||||
+++
|
||||
|
||||
In this post we set up the programmable interrupt controller to correctly forward hardware interrupts to the CPU. To handle these interrupts we add new entries to our interrupt descriptor table, just like we did for our exception handlers. We will learn how to get periodic timer interrupts and how to get input from the keyboard.
|
||||
|
||||
@@ -4,6 +4,8 @@ weight = 8
|
||||
path = "paging-introduction"
|
||||
date = 2019-01-14
|
||||
|
||||
[extra]
|
||||
chapter = "Memory Management"
|
||||
+++
|
||||
|
||||
This post introduces _paging_, a very common memory management scheme that we will also use for our operating system. It explains why memory isolation is needed, how _segmentation_ works, what _virtual memory_ is, and how paging solves memory fragmentation issues. It also explores the layout of multilevel page tables on the x86_64 architecture.
|
||||
|
||||
@@ -3,6 +3,9 @@ title = "Paging Implementation"
|
||||
weight = 9
|
||||
path = "paging-implementation"
|
||||
date = 2019-03-14
|
||||
|
||||
[extra]
|
||||
chapter = "Memory Management"
|
||||
+++
|
||||
|
||||
This post shows how to implement paging support in our kernel. It first explores different techniques to make the physical page table frames accessible to the kernel and discusses their respective advantages and drawbacks. It then implements an address translation function and a function to create a new mapping.
|
||||
|
||||
@@ -3,6 +3,9 @@ title = "Heap Allocation"
|
||||
weight = 10
|
||||
path = "heap-allocation"
|
||||
date = 2019-06-26
|
||||
|
||||
[extra]
|
||||
chapter = "Memory Management"
|
||||
+++
|
||||
|
||||
This post adds support for heap allocation to our kernel. First, it gives an introduction to dynamic memory and shows how the borrow checker prevents common allocation errors. It then implements the basic allocation interface of Rust, creates a heap memory region, and sets up an allocator crate. At the end of this post all the allocation and collection types of the built-in `alloc` crate will be available to our kernel.
|
||||
@@ -251,7 +254,7 @@ It defines the two required methods [`alloc`] and [`dealloc`], which correspond
|
||||
|
||||
The trait additionally defines the two methods [`alloc_zeroed`] and [`realloc`] with default implementations:
|
||||
|
||||
- The [`alloc_zeroed`] method is equivalent to calling `alloc` and then setting the allocated memory block to zero, which is exactly what the provided default implementation does. An allocator implementations can override the default implementations with a more efficient custom implementation if possible.
|
||||
- The [`alloc_zeroed`] method is equivalent to calling `alloc` and then setting the allocated memory block to zero, which is exactly what the provided default implementation does. An allocator implementation can override the default implementations with a more efficient custom implementation if possible.
|
||||
- The [`realloc`] method allows to grow or shrink an allocation. The default implementation allocates a new memory block with the desired size and copies over all the content from the previous allocation. Again, an allocator implementation can probably provide a more efficient implementation of this method, for example by growing/shrinking the allocation in-place if possible.
|
||||
|
||||
[`alloc_zeroed`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#method.alloc_zeroed
|
||||
@@ -306,15 +309,13 @@ We now have a simple allocator, but we still have to tell the Rust compiler that
|
||||
The `#[global_allocator]` attribute tells the Rust compiler which allocator instance it should use as the global heap allocator. The attribute is only applicable to a `static` that implements the `GlobalAlloc` trait. Let's register an instance of our `Dummy` allocator as the global allocator:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
// in src/allocator.rs
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: allocator::Dummy = allocator::Dummy;
|
||||
static ALLOCATOR: Dummy = Dummy;
|
||||
```
|
||||
|
||||
Since the `Dummy` allocator is a [zero sized type], we don't need to specify any fields in the initialization expression. Note that the `#[global_allocator]` module [cannot be used in submodules][pr51335], so we need to put it into the `lib.rs`.
|
||||
|
||||
[pr51335]: https://github.com/rust-lang/rust/pull/51335
|
||||
Since the `Dummy` allocator is a [zero sized type], we don't need to specify any fields in the initialization expression.
|
||||
|
||||
When we now try to compile it, the first error should be gone. Let's fix the remaining second error:
|
||||
|
||||
@@ -520,7 +521,7 @@ linked_list_allocator = "0.6.4"
|
||||
Then we can replace our dummy allocator with the allocator provided by the crate:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
// in src/allocator.rs
|
||||
|
||||
use linked_list_allocator::LockedHeap;
|
||||
|
||||
@@ -547,7 +548,7 @@ pub fn init_heap(
|
||||
|
||||
// new
|
||||
unsafe {
|
||||
super::ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
|
||||
ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -745,10 +746,12 @@ As a third test, we create ten thousand allocations after each other:
|
||||
```rust
|
||||
// in tests/heap_allocation.rs
|
||||
|
||||
use blog_os::allocator::HEAP_SIZE;
|
||||
|
||||
#[test_case]
|
||||
fn many_boxes() {
|
||||
serial_print!("many_boxes... ");
|
||||
for i in 0..10_000 {
|
||||
for i in 0..HEAP_SIZE {
|
||||
let x = Box::new(i);
|
||||
assert_eq!(*x, i);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
1245
blog/content/second-edition/posts/11-allocator-designs/index.md
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 47 KiB |
58
blog/content/status-update/2020-01-07.md
Normal file
@@ -0,0 +1,58 @@
|
||||
+++
|
||||
title = "Updates in December 2019"
|
||||
date = 2020-01-07
|
||||
+++
|
||||
|
||||
Happy New Year!
|
||||
|
||||
This post gives an overview of the recent updates to the _Writing an OS in Rust_ blog and the corresponding libraries and tools.
|
||||
|
||||
## `blog_os`
|
||||
|
||||
The repository of the _Writing an OS in Rust_ blog received the following updates:
|
||||
|
||||
- Update `x86_64` dependency to version 0.8.1. This included the [dependency update](https://github.com/phil-opp/blog_os/pull/701) itself, an [update of the frame allocation code](https://github.com/phil-opp/blog_os/pull/703), and an [update of the blog](https://github.com/phil-opp/blog_os/pull/704).
|
||||
- [License the `blog/content` folder under CC BY-NC](https://github.com/phil-opp/blog_os/pull/705)
|
||||
- [Reword sentence in first post](https://github.com/phil-opp/blog_os/pull/709) by [@pamolloy](https://github.com/pamolloy)
|
||||
|
||||
Further, we're still working on adding [Experimental Support for Community Translations](https://github.com/phil-opp/blog_os/pull/692) to the blog, starting with [Simplified Chinese](https://github.com/phil-opp/blog_os/pull/694) and [Traditional Chinese](https://github.com/phil-opp/blog_os/pull/699). Any help is appreciated!
|
||||
|
||||
## `bootloader`
|
||||
|
||||
There were no updates to the bootloader this month.
|
||||
|
||||
I'm currently working on rewriting the 16-bit/32-bit stages in Rust and making the bootloader more modular in the process. This should make it much easier to add support for UEFI and GRUB booting later.
|
||||
|
||||
## `bootimage`
|
||||
|
||||
There were no updates to the `bootimage` tool this month.
|
||||
|
||||
## `x86_64`
|
||||
|
||||
We landed a number of breaking changes this month:
|
||||
|
||||
- [Replace `ux` dependency with custom wrapper structs](https://github.com/rust-osdev/x86_64/pull/91)
|
||||
- [Add new UnusedPhysFrame type and use it in Mapper::map_to](https://github.com/rust-osdev/x86_64/pull/89)
|
||||
- [Make Mapper trait object safe by adding `Self: Sized` bounds on generic functions](https://github.com/rust-osdev/x86_64/pull/84)
|
||||
- [Rename divide_by_zero field of IDT to divide_error](https://github.com/rust-osdev/x86_64/pull/108)
|
||||
- [Introduce new diverging handler functions for exceptions classified as "abort"](https://github.com/rust-osdev/x86_64/pull/109)
|
||||
|
||||
These changes were released an version 0.8.0. Unfortunately, there was a missing re-export for the new `UnusedPhysFrame` type. We fixed it in [#110](https://github.com/rust-osdev/x86_64/pull/110) and released the fix as version 0.8.1.
|
||||
|
||||
There was one more addition to the `x86_64` crate afterwards:
|
||||
|
||||
- [Add support for cr4 control register (with complete documentation)](https://github.com/rust-osdev/x86_64/pull/111) by [@KarimAllah](https://github.com/KarimAllah) (released as version 0.8.2).
|
||||
|
||||
There were also a few changes related to continuous integration:
|
||||
|
||||
- [Remove bors from this repo](https://github.com/rust-osdev/x86_64/pull/103)
|
||||
- [Run 'push' builds only for master branch](https://github.com/rust-osdev/x86_64/pull/104)
|
||||
- [Remove Travis CI and Azure Pipelines scripts](https://github.com/rust-osdev/x86_64/pull/105)
|
||||
- [Add caching of cargo crates to GitHub Actions CI](https://github.com/rust-osdev/x86_64/pull/100)
|
||||
|
||||
## `cargo-xbuild`
|
||||
|
||||
The `cargo-xbuild` crate, which cross-compiles the sysroot, received the following updates this month:
|
||||
|
||||
- [Add `--quiet` flag that suppresses "waiting for file lock" message](https://github.com/rust-osdev/cargo-xbuild/pull/43) by [@Nils-TUD](https://github.com/Nils-TUD) (published as version 0.5.19)
|
||||
- [Fix wrong feature name for memcpy=false](https://github.com/rust-osdev/cargo-xbuild/pull/50) (released as version 0.5.20)
|
||||
69
blog/content/status-update/2020-02-01.md
Normal file
@@ -0,0 +1,69 @@
|
||||
+++
|
||||
title = "Updates in January 2020"
|
||||
date = 2020-02-01
|
||||
+++
|
||||
|
||||
This post gives an overview of the recent updates to the _Writing an OS in Rust_ blog and the corresponding libraries and tools.
|
||||
|
||||
## `blog_os`
|
||||
|
||||
The repository of the _Writing an OS in Rust_ blog received the following updates:
|
||||
|
||||
- [Move #[global_allocator] into allocator module](https://github.com/phil-opp/blog_os/pull/714)
|
||||
- [Update many_boxes test to scale with heap size](https://github.com/phil-opp/blog_os/pull/716)
|
||||
- [New post about allocator designs](https://github.com/phil-opp/blog_os/pull/719) 🎉
|
||||
- [Provide multiple implementations of align_up and mention performance](https://github.com/phil-opp/blog_os/pull/721)
|
||||
- [Refactor Simplified Chinese translation of post 3](https://github.com/phil-opp/blog_os/pull/725) by [@Rustin-Liu](https://github.com/Rustin-Liu)
|
||||
- [Use checked addition for allocator implementations](https://github.com/phil-opp/blog_os/pull/726)
|
||||
- [Fix dummy allocator code example](https://github.com/phil-opp/blog_os/pull/728)
|
||||
- [Some style updates to the front page](https://github.com/phil-opp/blog_os/pull/729)
|
||||
- [Mark active item in table of contents](https://github.com/phil-opp/blog_os/pull/733)
|
||||
- [Make active section link more discreet](https://github.com/phil-opp/blog_os/pull/734) by [@Menschenkindlein](https://github.com/Menschenkindlein)
|
||||
|
||||
I also started working on the upcoming post about threads.
|
||||
|
||||
## `bootloader`
|
||||
|
||||
The bootloader crate received two minor updates this month:
|
||||
|
||||
- [Move architecture checks from build script into lib.rs](https://github.com/rust-osdev/bootloader/pull/91)
|
||||
- [Update x86_64 dependency to version 0.8.3](https://github.com/rust-osdev/bootloader/pull/92) by [@vinaychandra](https://github.com/vinaychandra)
|
||||
|
||||
Since I focused my time on the new _Allocator Designs_ post, I did not have the time to make more progress on my plan to rewrite the 16-bit/32-bit stages of the bootloader in Rust. I hope to get back to it soon.
|
||||
|
||||
## `bootimage`
|
||||
|
||||
There were no updates to the `bootimage` tool this month.
|
||||
|
||||
## `x86_64`
|
||||
|
||||
The following changes were merged this month:
|
||||
|
||||
- [Allow immediate port version of in/out instructions](https://github.com/rust-osdev/x86_64/pull/115) by [@m-ou-se](https://github.com/m-ou-se)
|
||||
- [Make more functions const](https://github.com/rust-osdev/x86_64/pull/116) by [@m-ou-se](https://github.com/m-ou-se)
|
||||
- Released as version 0.8.3
|
||||
- [Return the UnusedPhysFrame on MapToError::PageAlreadyMapped](https://github.com/rust-osdev/x86_64/pull/118) by [@haraldh](https://github.com/haraldh)
|
||||
- This is a **breaking change** since it changes the signature of a type.
|
||||
- No new release was published yet to give us the option to bundle it with other breaking changes.
|
||||
|
||||
There are also some pull requests that have some open design questions and are still being discussed:
|
||||
|
||||
- [Add p23_insert_flag_mask argument to mapper.map_to()](https://github.com/rust-osdev/x86_64/pull/114) by [@haraldh](https://github.com/haraldh)
|
||||
- Related proposal: [Page Table Visitors](https://github.com/rust-osdev/x86_64/issues/121) by [@mark-i-m](https://github.com/mark-i-m)
|
||||
- [Add User Mode registers](https://github.com/rust-osdev/x86_64/pull/119) by [@vinaychandra](https://github.com/vinaychandra)
|
||||
|
||||
Please feel free to join these discussions if you have opinions on the matter.
|
||||
|
||||
## `cargo-xbuild`
|
||||
|
||||
The `cargo-xbuild` crate, which cross-compiles the sysroot, received the following updates this month:
|
||||
|
||||
- [Override target path for building sysroot](https://github.com/rust-osdev/cargo-xbuild/pull/52) by [@upsuper](https://github.com/upsuper)
|
||||
- Published as version 0.5.21
|
||||
|
||||
## `uart_16550`
|
||||
|
||||
The `uart_16550` crate, which provides basic support for uart_16550 serial output, received a small dependency update:
|
||||
|
||||
- [Update dependency for x86_64](https://github.com/rust-osdev/uart_16550/pull/4) by [@haraldh](https://github.com/haraldh)
|
||||
- Published as version 0.2.2
|
||||
@@ -82,10 +82,14 @@ main img {
|
||||
border: 2px solid #fc0
|
||||
}
|
||||
|
||||
.posts.exceptions {
|
||||
.posts.interrupts {
|
||||
border: 2px solid #f66;
|
||||
}
|
||||
|
||||
.posts.multitasking {
|
||||
border: 2px solid #556b2f;
|
||||
}
|
||||
|
||||
.posts hr {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
@@ -122,10 +126,14 @@ main img {
|
||||
color: #990;
|
||||
}
|
||||
|
||||
.post-category.exceptions {
|
||||
.post-category.interrupts {
|
||||
color: #f33;
|
||||
}
|
||||
|
||||
.post-category.multitasking {
|
||||
color: #556b2f;
|
||||
}
|
||||
|
||||
.post-footer-support {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -193,19 +201,26 @@ aside#all-posts-link {
|
||||
width: 12rem;
|
||||
position: sticky;
|
||||
float: left;
|
||||
top: 1rem;
|
||||
top: 3.5rem;
|
||||
margin-top: -4rem;
|
||||
margin-left: -15rem;
|
||||
font-size: 90%;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#toc-aside li > a, #toc-aside h2 {
|
||||
opacity: .5;
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
#toc-aside:hover {
|
||||
#toc-aside:hover li > a, #toc-aside:hover h2 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#toc-aside li.active > a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#toc-aside h2 {
|
||||
font-size: 110%;
|
||||
margin-bottom: .2rem;
|
||||
@@ -217,7 +232,7 @@ aside#all-posts-link {
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
#toc-aside ol li:before {
|
||||
#toc-aside ol li a:before {
|
||||
content: "";
|
||||
border-color: transparent #008eef;
|
||||
border-style: solid;
|
||||
@@ -263,11 +278,10 @@ aside#all-posts-link {
|
||||
}
|
||||
|
||||
aside#all-posts-link {
|
||||
float: left;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -15rem;
|
||||
position: fixed;
|
||||
top: 1.25rem;
|
||||
margin-top: 0;
|
||||
margin-left: -15rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ window.onload = function() {
|
||||
|
||||
if (container != null) {
|
||||
resize_toc(container);
|
||||
toc_scroll_position(container);
|
||||
window.onscroll = function() { toc_scroll_position(container) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +26,38 @@ function resize_toc(container) {
|
||||
resizeId = setTimeout(resize, 300);
|
||||
};
|
||||
}
|
||||
|
||||
function toc_scroll_position(container) {
|
||||
if (container.offsetParent === null) {
|
||||
// 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")) {
|
||||
item.classList.remove("active");
|
||||
}
|
||||
|
||||
// look for active item
|
||||
var site_offset = document.documentElement.scrollTop;
|
||||
var 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);
|
||||
if (heading.offsetTop <= (site_offset + document.documentElement.clientHeight / 3)) {
|
||||
current_toc_item = item;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// set active class for current ToC item
|
||||
if (current_toc_item != null) {
|
||||
current_toc_item.classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h2 class="post-title"><a href="/{{ post.path | safe }}">{{ post.title }}</a></h2>
|
||||
<div class="post-summary">
|
||||
{{ post.summary | safe}}
|
||||
<a class="read-more" href="/{{ post.path | safe }}">read more…</a>
|
||||
<a class="read-more" href="/{{ post.path | safe }}"><em>read more »</em></a>
|
||||
|
||||
{% if not_translated %}
|
||||
<aside class="no-translation">
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
{% macro utterances() %}
|
||||
<script src="https://utteranc.es/client.js"
|
||||
repo="phil-opp/blog_os"
|
||||
issue-term="url"
|
||||
label="comments"
|
||||
data-repo="phil-opp/blog_os"
|
||||
data-issue-term="url"
|
||||
data-label="comments"
|
||||
crossorigin="anonymous"
|
||||
async>
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ config.description }}">
|
||||
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}">
|
||||
<meta name="author" content="{{ config.extra.author.name }}">
|
||||
|
||||
{% if current_url %}
|
||||
@@ -25,7 +25,7 @@
|
||||
<header class="masthead">
|
||||
<div style="position:relative">
|
||||
<h1 class="masthead-title">
|
||||
<a href="{{ config.base_url | safe }}" title="Home">{{ config.title | safe }} (Second Edition)</a>
|
||||
<a href="{{ config.base_url | safe }}" title="Home">{{ config.title | safe }}</a>
|
||||
</h1>
|
||||
<p><small>{{ config.extra.subtitle | replace(from=" ", to=" ") | safe }}</small></p>
|
||||
{% block header %}{% endblock header %}
|
||||
@@ -39,7 +39,7 @@
|
||||
<footer class="footer">
|
||||
<hr>
|
||||
<small>
|
||||
© <time datetime="2019">2019</time>. All rights reserved.
|
||||
© <time datetime="2020">2020</time>. All rights reserved.
|
||||
<a href="{{ get_url(path="@/pages/contact.md") | safe }}">Contact</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||
|
||||
{% block description -%}
|
||||
{{ page.summary | safe | striptags }}
|
||||
{%- endblock description %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
{{ page.content | safe }}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
{% set posts_section = get_section(path = "second-edition/posts/_index.md") %}
|
||||
{% set posts = posts_section.pages %}
|
||||
|
||||
<h1 style="visibility: hidden; height: 0px; margin: 0px; padding: 0px;">Posts</h1>
|
||||
|
||||
<div class="front-page-introduction">
|
||||
<p>
|
||||
This blog series creates a small operating system in the
|
||||
@@ -21,59 +23,37 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="bare-bones" class="post-category bare-bones">Bare Bones</div>
|
||||
<div class="posts bare-bones">
|
||||
{{ macros::post_link(page=posts.0) }}
|
||||
{{ macros::post_link(page=posts.1) }}
|
||||
{{ macros::post_link(page=posts.2) }}
|
||||
{{ macros::post_link(page=posts.3) }}
|
||||
<div>
|
||||
{% set chapter = "none" %}
|
||||
{% for post in posts %}
|
||||
{% if post.extra["chapter"] %}
|
||||
{% if post.extra["chapter"] != chapter %}
|
||||
{# Begin new chapter #}
|
||||
{% set_global chapter = post.extra["chapter"] %}
|
||||
</div>
|
||||
|
||||
<div id="exceptions" class="post-category exceptions">Exceptions</div>
|
||||
<div class="posts exceptions">
|
||||
{{ macros::post_link(page=posts.4) }}
|
||||
{{ macros::post_link(page=posts.5) }}
|
||||
{{ macros::post_link(page=posts.6) }}
|
||||
</div>
|
||||
<div id="{{chapter | slugify}}" class="post-category {{chapter | slugify}}">{{ chapter }}</div>
|
||||
<div class="posts {{chapter | slugify}}">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div id="memory-management" class="post-category memory-management">Memory Management</div>
|
||||
<div class="posts memory-management">
|
||||
{{ macros::post_link(page=posts.7) }}
|
||||
{{ macros::post_link(page=posts.8) }}
|
||||
{{ macros::post_link(page=posts.9) }}
|
||||
{{ macros::post_link(page=post) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="posts subscribe">
|
||||
<h2 class="post-title">Subscribe</h2>
|
||||
<p>Receive notifications about new posts and other major changes! You can either:</p>
|
||||
|
||||
<p><ul>
|
||||
<ul>
|
||||
<li>Subscribe to our <a href="/rss.xml">RSS/Atom Feed</a>,</li>
|
||||
<li>Subscribe to <a href="https://github.com/phil-opp/blog_os/issues/479">this GitHub issue</a>, or</li>
|
||||
<li>Subscribe to our <a href="https://tinyletter.com/phil-opp/">email newsletter</a>.</li>
|
||||
</ul></p>
|
||||
</div>
|
||||
|
||||
<div class="posts first-edition">
|
||||
<h2 class="post-title">First Edition</h2>
|
||||
You are viewing the second edition of “Writing an OS in Rust”, which is still in progress. The <a href="{{ get_url(path = "/first-edition") | safe }}">first edition</a> has more content, but is no longer updated. We try our best to incorporate the missing content soon.
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="frontpage-section">
|
||||
{% set extra = get_section(path = "second-edition/extra/_index.md") %}
|
||||
<h1>{{ extra.title }}</h1>
|
||||
<ul>
|
||||
{% for subsection in extra.subsections %}
|
||||
<li><a href="/{{ subsection.path | safe }}">{{ subsection.title }}</a></li>
|
||||
{% endfor %}
|
||||
{% for page in extra.pages %}
|
||||
<li><a href="/{{ page.path | safe }}">{{ page.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="frontpage-section">
|
||||
<h1>Status Updates</h1>
|
||||
{% set status_updates = get_section(path = "status-update/_index.md") %}
|
||||
@@ -87,10 +67,17 @@
|
||||
</div>
|
||||
|
||||
<div class="frontpage-section">
|
||||
<h1 class="post-title">First Edition</h1>
|
||||
<p>You are currently viewing the second edition of “Writing an OS in Rust”. The first edition is very different in many aspects, for example it builds upon the GRUB bootloader instead of using the `bootloader` crate. In case you're interested in it, it is still available. Note that the first edition is no longer updated and might contain outdated information. <a class="read-more" href="{{ get_url(path = "/first-edition") | safe }}"><em>read the first edition »</em></a></p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<h1>Support Me</h1>
|
||||
{% include "support.html" %}
|
||||
</div>
|
||||
{% endblock main %}
|
||||
|
||||
{% block after_main %}
|
||||
<aside id="recent-updates">
|
||||
<div class="block">
|
||||
<h1>Recent Updates</h1>
|
||||
@@ -121,7 +108,7 @@
|
||||
{% include "auto/forks.html" %}
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/sponsors/phil-opp" class="sponsor" href="#support-me">
|
||||
<a href="https://github.com/sponsors/phil-opp" class="sponsor">
|
||||
<svg viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M9 2c-.97 0-1.69.42-2.2 1-.51.58-.78.92-.8 1-.02-.08-.28-.42-.8-1-.52-.58-1.17-1-2.2-1-1.632.086-2.954 1.333-3 3 0 .52.09 1.52.67 2.67C1.25 8.82 3.01 10.61 6 13c2.98-2.39 4.77-4.17 5.34-5.33C11.91 6.51 12 5.5 12 5c-.047-1.69-1.342-2.913-3-3z"></path></svg>
|
||||
Sponsor
|
||||
</a>
|
||||
@@ -131,4 +118,4 @@
|
||||
|
||||
</aside>
|
||||
|
||||
{% endblock main %}
|
||||
{% endblock after_main %}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
{% endif %}
|
||||
{% endblock header %}
|
||||
|
||||
{% block description -%}
|
||||
{{ page.summary | safe | striptags }}
|
||||
{%- endblock description %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<time datetime="{{ page.date | date(format="%Y-%m-%d") }}" class="post-date">
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
<div>
|
||||
<h2>Thank You!</h2>
|
||||
<p>I want to thank all the people who support me on <a href="https://github.com/sponsors/phil-opp">GitHub</a>, <a href="https://www.patreon.com/phil_opp">Patreon</a>, and <a href="https://donorbox.org/phil-opp">Donorbox</a>. It means a lot to me!</p>
|
||||
<p>Thanks to all the contributors this month!</p>
|
||||
<p>I also want to thank all the people who support me on <a href="https://github.com/sponsors/phil-opp">GitHub</a>, <a href="https://www.patreon.com/phil_opp">Patreon</a>, and <a href="https://donorbox.org/phil-opp">Donorbox</a>. It means a lot to me!</p>
|
||||
</div>
|
||||
{% endblock main %}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p>
|
||||
While creating this blog and the libraries and tools behind the scenes takes a lot of time, I really enjoy it and I'm committed to keep working on it. By supporting me, you allow me to invest more time into new content, new features, and continuous maintainance.
|
||||
Creating and <a href="{{ get_url(path="@/status-update/_index.md") }}">maintaining</a> this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance.
|
||||
</p>
|
||||
<p>
|
||||
The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. GitHub will even match sponsorships until October 2020! If you prefer other platforms, I also have <a href="https://www.patreon.com/phil_opp"><em>Patreon</em></a> and <a href="https://donorbox.org/phil-opp"><em>Donorbox</em></a> accounts. The latter is the most flexible as it supports multiple currencies and one-time contributions.
|
||||
|
||||