mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Merge #385
385: First bits of the second edition r=phil-opp a=phil-opp This PR adds the first two posts for the second edition, “A Freestanding Rust Binary” and “A Minimal Rust Kernel”. The largest changes in comparison to the first edition are: - Instead of GRUB, we use our own [bootloader](https://github.com/rust-osdev/bootloader) (written in Rust) and our [bootimage](https://github.com/rust-osdev/bootimage) tool. This removes the dependencies on GRUB and `nasm`. Note that both tools are still experimental and might contain bugs. - Support for Windows and Mac: Without GRUB, there's nothing preventing us from building on Windows or Mac OS anymore! We added additional CI jobs at travis and appveyor to ensure that the project always builds on all three platforms. (At the moment, users still need to install LLD, but with the [LLVM 6 update of rustc](https://github.com/rust-lang/rust/pull/47828) we should have a builtin LLD soon.) - No assembly in the main posts. Instead, we're creating a `no_std` _executable_ and relying on our custom bootloader to perform the initial page mapping and the switch to long mode. - No linker script: Our bootloader loads the kernel in a clean address space, so we can just use the default executable layout and don't need a linker script. (This also means that users that want a higher half kernel just need to update the mapping in their linker script. However, I'm not sure if it's a good idea to add this to the blog. Maybe later, when we begin to run user programs.) These changes only land in “beta mode” with this PR, which means that they're not linked from the front page yet. We will do that in a follow up PR.
This commit is contained in:
82
.appveyor.yml
Normal file
82
.appveyor.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
branches:
|
||||
except:
|
||||
# Used by bors
|
||||
- staging.tmp
|
||||
|
||||
# Appveyor configuration template for Rust using rustup for Rust installation
|
||||
# https://github.com/starkat99/appveyor-rust
|
||||
|
||||
## Operating System (VM environment) ##
|
||||
|
||||
# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
|
||||
os: Visual Studio 2015
|
||||
|
||||
## Build Matrix ##
|
||||
environment:
|
||||
matrix:
|
||||
|
||||
### MSVC Toolchains ###
|
||||
# Nightly 64-bit MSVC
|
||||
- channel: nightly
|
||||
target: x86_64-pc-windows-msvc
|
||||
MSYS_BITS: 64
|
||||
# Nightly 32-bit MSVC
|
||||
- channel: nightly
|
||||
target: i686-pc-windows-msvc
|
||||
MSYS_BITS: 32
|
||||
|
||||
### GNU Toolchains ###
|
||||
# Nightly 64-bit GNU
|
||||
- channel: nightly
|
||||
target: x86_64-pc-windows-gnu
|
||||
MSYS_BITS: 64
|
||||
# Nightly 32-bit GNU
|
||||
- channel: nightly
|
||||
target: i686-pc-windows-gnu
|
||||
MSYS_BITS: 32
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo\bin'
|
||||
- '%USERPROFILE%\.cargo\config'
|
||||
- '%USERPROFILE%\.cargo\env'
|
||||
- '%USERPROFILE%\.cargo\.crates.toml'
|
||||
- '%USERPROFILE%\.xargo'
|
||||
- target
|
||||
|
||||
## Install Script ##
|
||||
|
||||
# This is the most important part of the Appveyor configuration. This installs the version of Rust
|
||||
# specified by the 'channel' and 'target' environment variables from the build matrix. This uses
|
||||
# rustup to install Rust.
|
||||
#
|
||||
# For simple configurations, instead of using the build matrix, you can simply set the
|
||||
# default-toolchain and default-host manually here.
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
|
||||
## Build Script ##
|
||||
|
||||
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
|
||||
# the "directory does not contain a project or solution file" error.
|
||||
build: false
|
||||
|
||||
before_test:
|
||||
- mkdir bin
|
||||
- mklink "bin\ld.exe" "C:\Program Files\LLVM\bin\lld.exe"
|
||||
- set PATH=%CD%\bin;C:\msys64\mingw%MSYS_BITS%\bin;C:\msys64\usr\bin;%PATH%
|
||||
- rustup component add rust-src
|
||||
- set RUST_BACKTRACE=1
|
||||
- if not exist %USERPROFILE%\.cargo\bin\cargo-install-update.exe cargo install cargo-update
|
||||
- if not exist %USERPROFILE%\.cargo\bin\xargo.exe cargo install xargo
|
||||
- if not exist %USERPROFILE%\.cargo\bin\bootimage.exe cargo install bootimage
|
||||
- cargo install-update -a
|
||||
|
||||
# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs
|
||||
# directly or perform other testing commands. Rust will automatically be placed in the PATH
|
||||
# environment variable.
|
||||
test_script:
|
||||
- bootimage --target x86_64-blog_os
|
||||
56
.travis.yml
56
.travis.yml
@@ -1,20 +1,5 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cargo
|
||||
- $HOME/.xargo
|
||||
- $TRAVIS_BUILD_DIR/target
|
||||
|
||||
before_script:
|
||||
- rustup component add rust-src
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/xargo || cargo install xargo)
|
||||
- cargo install-update -a
|
||||
|
||||
sudo: false
|
||||
|
||||
notifications:
|
||||
@@ -22,10 +7,47 @@ notifications:
|
||||
on_success: never
|
||||
on_failure: change
|
||||
|
||||
branches:
|
||||
except:
|
||||
# Used by bors
|
||||
- staging.tmp
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- llvm-toolchain-trusty-5.0
|
||||
packages:
|
||||
- nasm
|
||||
- lld-5.0
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cargo
|
||||
- $HOME/.xargo
|
||||
- $TRAVIS_BUILD_DIR/target
|
||||
|
||||
before_install:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
set -e
|
||||
LLVM_URL="https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-x86_64-apple-darwin.tar.xz"
|
||||
travis_retry wget -O llvm.tar.xz -nv ${LLVM_URL}
|
||||
tar -xJ -f llvm.tar.xz
|
||||
export PATH="`pwd`/clang+llvm-5.0.1-final-x86_64-apple-darwin/bin:$PATH"
|
||||
fi
|
||||
|
||||
before_script:
|
||||
- rustup component add rust-src
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/xargo || cargo install xargo)
|
||||
- (test -x $HOME/.cargo/bin/bootimage || cargo install bootimage)
|
||||
- cargo install-update -a
|
||||
|
||||
script:
|
||||
- make
|
||||
- bootimage --target x86_64-blog_os
|
||||
|
||||
22
Cargo.toml
22
Cargo.toml
@@ -1,22 +1,14 @@
|
||||
[package]
|
||||
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
|
||||
name = "blog_os"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies]
|
||||
bit_field = "0.7.0"
|
||||
bitflags = "0.9.1"
|
||||
multiboot2 = "0.1.0"
|
||||
once = "0.3.2"
|
||||
rlibc = "1.0"
|
||||
spin = "0.4.5"
|
||||
volatile = "0.1.0"
|
||||
x86_64 = "0.1.2"
|
||||
linked_list_allocator = "0.4.2"
|
||||
|
||||
[dependencies.lazy_static]
|
||||
features = ["spin_no_std"]
|
||||
version = "0.2.1"
|
||||
# the profile used for `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
# the profile used for `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
82
Makefile
82
Makefile
@@ -1,82 +0,0 @@
|
||||
# Copyright 2016 Philipp Oppermann. See the README.md
|
||||
# file at the top-level directory of this distribution.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
arch ?= x86_64
|
||||
target ?= $(arch)-blog_os
|
||||
kernel := build/kernel-$(arch).bin
|
||||
iso := build/os-$(arch).iso
|
||||
|
||||
rust_os := target/$(target)/debug/libblog_os.a
|
||||
linker_script := src/arch/$(arch)/linker.ld
|
||||
grub_cfg := src/arch/$(arch)/grub.cfg
|
||||
assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
|
||||
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
|
||||
build/arch/$(arch)/%.o, $(assembly_source_files))
|
||||
|
||||
# used by docker_* targets
|
||||
docker_image ?= blog_os
|
||||
tag ?= 0.1
|
||||
docker_cargo_volume ?= blogos-$(shell id -u)-$(shell id -g)-cargo
|
||||
docker_rustup_volume ?= blogos-$(shell id -u)-$(shell id -g)-rustup
|
||||
docker_args ?= -e LOCAL_UID=$(shell id -u) -e LOCAL_GID=$(shell id -g) -v $(docker_cargo_volume):/usr/local/cargo -v $(docker_rustup_volume):/usr/local/rustup -v $(shell pwd):$(shell pwd) -w $(shell pwd)
|
||||
docker_clean_args ?= $(docker_cargo_volume) $(docker_rustup_volume)
|
||||
|
||||
.PHONY: all clean run debug iso cargo gdb
|
||||
|
||||
all: $(kernel)
|
||||
|
||||
clean:
|
||||
@cargo clean
|
||||
@rm -rf build
|
||||
|
||||
run: $(iso)
|
||||
@qemu-system-x86_64 -cdrom $(iso) -s
|
||||
|
||||
debug: $(iso)
|
||||
@qemu-system-x86_64 -cdrom $(iso) -s -S
|
||||
|
||||
# docker_* targets
|
||||
|
||||
docker_build:
|
||||
@docker build docker/ -t $(docker_image):$(tag)
|
||||
|
||||
docker_iso:
|
||||
@docker run --rm $(docker_args) $(docker_image):$(tag) make iso
|
||||
|
||||
docker_run: docker_iso
|
||||
@qemu-system-x86_64 -cdrom $(iso) -s
|
||||
|
||||
docker_interactive:
|
||||
@docker run -it --rm $(docker_args) $(docker_image):$(tag)
|
||||
|
||||
docker_clean:
|
||||
@docker volume rm $(docker_clean_args)
|
||||
|
||||
gdb:
|
||||
@rust-os-gdb/bin/rust-gdb "build/kernel-x86_64.bin" -ex "target remote :1234"
|
||||
|
||||
iso: $(iso)
|
||||
|
||||
$(iso): $(kernel) $(grub_cfg)
|
||||
@mkdir -p build/isofiles/boot/grub
|
||||
@cp $(kernel) build/isofiles/boot/kernel.bin
|
||||
@cp $(grub_cfg) build/isofiles/boot/grub
|
||||
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
|
||||
@rm -r build/isofiles
|
||||
|
||||
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
||||
@ld -n --gc-sections -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os)
|
||||
|
||||
cargo:
|
||||
@RUST_TARGET_PATH="$(shell pwd)" xargo build --target $(target)
|
||||
|
||||
# compile assembly files
|
||||
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm
|
||||
@mkdir -p $(shell dirname $@)
|
||||
@nasm -felf64 $< -o $@
|
||||
@@ -1,2 +0,0 @@
|
||||
[target.x86_64-blog_os.dependencies]
|
||||
alloc = {}
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Catching Exceptions"
|
||||
order = 1
|
||||
path = "catching-exceptions"
|
||||
date = "2016-05-28"
|
||||
date = 2016-05-28
|
||||
[extra]
|
||||
updated = "2016-06-25"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Better Exception Messages"
|
||||
order = 2
|
||||
path = "better-exception-messages"
|
||||
date = "2016-08-03"
|
||||
date = 2016-08-03
|
||||
[extra]
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Returning from Exceptions"
|
||||
order = 3
|
||||
path = "returning-from-exceptions"
|
||||
date = "2016-09-21"
|
||||
date = 2016-09-21
|
||||
[extra]
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
|
||||
4
blog/content/first-edition/_index.md
Normal file
4
blog/content/first-edition/_index.md
Normal file
@@ -0,0 +1,4 @@
|
||||
+++
|
||||
title = "First Edition"
|
||||
template = "index.html"
|
||||
+++
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "A minimal x86 kernel"
|
||||
order = 1
|
||||
path = "multiboot-kernel/"
|
||||
date = "2015-08-18"
|
||||
date = 2015-08-18
|
||||
+++
|
||||
|
||||
This post explains how to create a minimal x86 operating system kernel. In fact, it will just boot and print `OK` to the screen. In subsequent blog posts we will extend it using the [Rust] programming language.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Entering Long Mode"
|
||||
order = 2
|
||||
path = "entering-longmode"
|
||||
date = "2015-08-25"
|
||||
date = 2015-08-25
|
||||
[extra]
|
||||
updated = "2015-10-29"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Set Up Rust"
|
||||
order = 3
|
||||
path = "set-up-rust"
|
||||
date = "2015-09-02"
|
||||
date = 2015-09-02
|
||||
[extra]
|
||||
updated = "2017-04-12"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Printing to Screen"
|
||||
order = 4
|
||||
path = "printing-to-screen"
|
||||
date = "2015-10-23"
|
||||
date = 2015-10-23
|
||||
[extra]
|
||||
updated = "2016-10-31"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Allocating Frames"
|
||||
order = 5
|
||||
path = "allocating-frames"
|
||||
date = "2015-11-15"
|
||||
date = 2015-11-15
|
||||
+++
|
||||
|
||||
In this post we create an allocator that provides free physical frames for a future paging module. To get the required information about available and used memory we use the Multiboot information structure. Additionally, we improve the `panic` handler to print the corresponding message and source line.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Page Tables"
|
||||
order = 6
|
||||
path = "page-tables"
|
||||
date = "2015-12-09"
|
||||
date = 2015-12-09
|
||||
+++
|
||||
|
||||
In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will explore recursive page table mapping and use some Rust features to make it safe. Finally we will create functions to translate virtual addresses and to map and unmap pages.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Remap the Kernel"
|
||||
order = 7
|
||||
path = "remap-the-kernel"
|
||||
date = "2016-01-01"
|
||||
date = 2016-01-01
|
||||
[extra]
|
||||
updated = "2016-03-06"
|
||||
+++
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Kernel Heap"
|
||||
order = 8
|
||||
path = "kernel-heap"
|
||||
date = "2016-04-11"
|
||||
date = 2016-04-11
|
||||
updated = "2017-11-19"
|
||||
+++
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Handling Exceptions"
|
||||
order = 9
|
||||
path = "handling-exceptions"
|
||||
date = "2017-03-26"
|
||||
date = 2017-03-26
|
||||
+++
|
||||
|
||||
In this post, we start exploring CPU exceptions. Exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To catch 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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title = "Double Faults"
|
||||
order = 10
|
||||
path = "double-faults"
|
||||
date = "2017-01-02"
|
||||
date = 2017-01-02
|
||||
+++
|
||||
|
||||
In this post we explore double faults in detail. We also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack. This way, we can completely prevent triple faults, even on kernel stack overflow.
|
||||
|
||||
4
blog/content/second-edition/_index.md
Normal file
4
blog/content/second-edition/_index.md
Normal file
@@ -0,0 +1,4 @@
|
||||
+++
|
||||
title = "Second Edition"
|
||||
template = "second-edition/index.html"
|
||||
+++
|
||||
6
blog/content/second-edition/extra/_index.md
Normal file
6
blog/content/second-edition/extra/_index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "Extra Content"
|
||||
insert_anchor_links = "left"
|
||||
render = false
|
||||
sort_by = "order"
|
||||
+++
|
||||
27
blog/content/second-edition/extra/disable-red-zone/index.md
Normal file
27
blog/content/second-edition/extra/disable-red-zone/index.md
Normal file
@@ -0,0 +1,27 @@
|
||||
+++
|
||||
title = "Disable the Red Zone"
|
||||
order = 1
|
||||
path = "red-zone"
|
||||
template = "second-edition/extra.html"
|
||||
+++
|
||||
|
||||
The [red zone] is an optimization of the [System V ABI] that allows functions to temporary use the 128 bytes below its stack frame without adjusting the stack pointer:
|
||||
|
||||
[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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
The red zone is defined as the 128 bytes below the adjusted stack pointer. The function can use this area for temporary data that's not needed across function calls. Thus, the two instructions for adjusting the stack pointer can be avoided in some cases (e.g. in small leaf functions).
|
||||
|
||||
However, this optimization leads to huge problems with exceptions or hardware interrupts. Let's assume that an exception occurs while a function uses the red zone:
|
||||
|
||||

|
||||
|
||||
The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. This might lead to strange bugs that [take weeks to debug].
|
||||
|
||||
[take weeks to debug]: http://forum.osdev.org/viewtopic.php?t=21720
|
||||
|
||||
To avoid such bugs when we implement exception handling in the future, we disable the red zone right from the beginning. This is achieved by adding the `"disable-redzone": true` line to our target configuration file.
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="17cm" height="11cm" viewBox="-60 -21 340 212" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="90" y="65.9389">
|
||||
<tspan x="90" y="65.9389"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="204" y1="70" x2="176.236" y2="70"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="185.118,65 175.118,70 185.118,75 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="208" y="72.75">
|
||||
<tspan x="208" y="72.75">Old Stack Pointer</tspan>
|
||||
</text>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="75" y="230">
|
||||
<tspan x="75" y="230"></tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="70" x2="-4" y2="70"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="190" x2="-4" y2="190"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="-12" y1="72.2361" x2="-12" y2="187.764"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-7,81.118 -12,71.118 -17,81.118 "/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-17,178.882 -12,188.882 -7,178.882 "/>
|
||||
</g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="0" y1="-20" x2="0" y2="0"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="170" y1="-20" x2="170" y2="0"/>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="0" width="170" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="0" width="170" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="13.125">
|
||||
<tspan x="85" y="13.125">Return Address</tspan>
|
||||
</text>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="84" y="4">
|
||||
<tspan x="84" y="4"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="0" y="20" width="170" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="20" width="170" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="33.125">
|
||||
<tspan x="85" y="33.125">Local Variable 1</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #cccccc" x="0" y="40" width="170" height="32"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x="0" y="40" width="170" height="32"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="59.125">
|
||||
<tspan x="85" y="59.125">Local Variables 2..n</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="40" x2="170" y2="40"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="72" x2="120" y2="72"/>
|
||||
<g>
|
||||
<rect style="fill: #ff3333" x="0" y="70" width="170" height="120"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="70" width="170" height="120"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="40" y="179.125">
|
||||
<tspan x="40" y="179.125">Red Zone</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:end;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="132.75">
|
||||
<tspan x="-20" y="132.75">128 bytes</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ffc200" x="30" y="70" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="70" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="88.125">
|
||||
<tspan x="100" y="88.125">Exception Stack Frame</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ffe000" x="30" y="100" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="100" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="118.125">
|
||||
<tspan x="100" y="118.125">Register Backup</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #c6db97" x="30" y="130" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="130" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="8.46654" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="147.925">
|
||||
<tspan x="100" y="147.925">Handler Function Stack Frame</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #ff3333" x1="30" y1="70" x2="30" y2="160"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #ff3333" x1="30" y1="160" x2="170" y2="160"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="204" y1="160" x2="176.236" y2="160"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="185.118,155 175.118,160 185.118,165 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="208" y="162.635">
|
||||
<tspan x="208" y="162.635">New Stack Pointer</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="14cm" height="9cm" viewBox="-60 -21 270 172" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="90" y="65.9389">
|
||||
<tspan x="90" y="65.9389"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="154" y1="70" x2="126.236" y2="70"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="135.118,65 125.118,70 135.118,75 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="158" y="72.75">
|
||||
<tspan x="158" y="72.75">Stack Pointer</tspan>
|
||||
</text>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="75" y="230">
|
||||
<tspan x="75" y="230"></tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="70" x2="-4" y2="70"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="150" x2="-4" y2="150"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="-12" y1="72.2361" x2="-12" y2="147.764"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-7,81.118 -12,71.118 -17,81.118 "/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-17,138.882 -12,148.882 -7,138.882 "/>
|
||||
</g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="0" y1="-20" x2="0" y2="0"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="120" y1="-20" x2="120" y2="0"/>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="0" width="120" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="0" width="120" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="13.125">
|
||||
<tspan x="60" y="13.125">Return Address</tspan>
|
||||
</text>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="84" y="4">
|
||||
<tspan x="84" y="4"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="0" y="20" width="120" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="20" width="120" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="33.125">
|
||||
<tspan x="60" y="33.125">Local Variable 1</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #cccccc" x="0" y="40" width="120" height="32"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x="0" y="40" width="120" height="32"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="59.125">
|
||||
<tspan x="60" y="59.125">Local Variables 2..n</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="40" x2="120" y2="40"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="72" x2="120" y2="72"/>
|
||||
<g>
|
||||
<rect style="fill: #ff3333" x="0" y="70" width="120" height="80"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="70" width="120" height="80"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="113.125">
|
||||
<tspan x="60" y="113.125">Red Zone</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:end;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="112.75">
|
||||
<tspan x="-20" y="112.75">128 bytes</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
42
blog/content/second-edition/extra/disable-simd/index.md
Normal file
42
blog/content/second-edition/extra/disable-simd/index.md
Normal file
@@ -0,0 +1,42 @@
|
||||
+++
|
||||
title = "Disable SIMD"
|
||||
order = 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
|
||||
|
||||
- [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`.
|
||||
|
||||
[MMX]: https://en.wikipedia.org/wiki/MMX_(instruction_set)
|
||||
[x87 floating point unit]: https://en.wikipedia.org/wiki/X87
|
||||
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
|
||||
[AVX]: https://en.wikipedia.org/wiki/Advanced_Vector_Extensions
|
||||
|
||||
By using such SIMD standards, programs can often speed up significantly. Good compilers are able to transform normal loops into such SIMD code automatically through a process called [auto-vectorization].
|
||||
|
||||
[auto-vectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization
|
||||
|
||||
However, the large SIMD registers lead to problems in OS kernels. The reason is that the kernel has to backup all registers that it uses in hardware interrupt, because they need to have their original values when the interrupted program continues. So if the kernel uses SIMD registers, it has to backup a lot more data, which noticably decreases performance. To avoid this performance loss, we want to disable the `sse` and `mmx` features (the `avx` feature is disabled by default).
|
||||
|
||||
We can do that through the the `features` field in our target specification. To disable the `mmx` and `sse` features we add them prefixed with a minus:
|
||||
|
||||
```json
|
||||
"features": "-mmx,-sse"
|
||||
```
|
||||
|
||||
## Floating Point
|
||||
Unfortunately for us, the `x86_64` architecture uses SSE registers for floating point operations. Thus, every use of floating point with disabled SSE causes an error in LLVM. The problem is that Rust's core library already uses floats (e.g., it implements traits for `f32` and `f64`), so avoiding floats in our kernel does not suffice.
|
||||
|
||||
Fortunately, LLVM has support for a `soft-float` feature, emulates all floating point operations through software functions based on normal integers. This makes it possible to use floats in our kernel without SSE, it will just be a bit slower.
|
||||
|
||||
To turn on the `soft-float` feature for our kernel, we add it to the `features` line in our target specification, prefixed with a plus:
|
||||
|
||||
```json
|
||||
"features": "-mmx,-sse,+soft-float"
|
||||
```
|
||||
18
blog/content/second-edition/extra/installing-lld/index.md
Normal file
18
blog/content/second-edition/extra/installing-lld/index.md
Normal file
@@ -0,0 +1,18 @@
|
||||
+++
|
||||
title = "Installing LLD"
|
||||
order = 3
|
||||
path = "installing-lld"
|
||||
template = "second-edition/extra.html"
|
||||
+++
|
||||
|
||||
[LLD] is the linker by the LLVM project. It has the big advantage that it is a cross-linker by default. This means that you can link libraries and executables for all kinds of platforms with the same LLD installation.
|
||||
|
||||
[LLD]: https://lld.llvm.org/
|
||||
|
||||
There are plans to distribute LLD together with the Rust compiler, but is not quite there yet. So you have to install it manually. On this page, we try to describe the installation procedure for as many platforms as possible, so if you have additional information for any listed or unlisted platform, please send a pull request on the [Github repo](https://github.com/phil-opp/blog_os)!
|
||||
|
||||
## Linux
|
||||
On most Linux distributions LLD can be installed through the package manager. For example, for Debian and Ubuntu there is are official apt sources at <https://apt.llvm.org/>.
|
||||
|
||||
## Other Platforms
|
||||
For Windows and Mac you can download a pre-built LLVM release from <http://releases.llvm.org/download.html>, which contains LLD. If there are no pre-compiled versions for your platform (e.g. some other Linux distribution), you can download the source code and [build it yourself](https://lld.llvm.org/#build).
|
||||
@@ -0,0 +1,377 @@
|
||||
+++
|
||||
title = "A Freestanding Rust Binary"
|
||||
order = 3
|
||||
path = "freestanding-rust-binary"
|
||||
date = 0000-01-01
|
||||
template = "second-edition/page.html"
|
||||
+++
|
||||
|
||||
This post describes how 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.
|
||||
|
||||
[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## Introduction
|
||||
To write an operating system kernel, we need code that does not depend on any operating system features. This means that we can't use threads, files, heap memory, the network, random numbers, standard output, or any other features requiring OS abstractions or specific hardware. Which makes sense, since we're trying to write our own OS and our own drivers.
|
||||
|
||||
This means that we can't use most of the [Rust standard library], but there are a lot of Rust features that we _can_ use. For example, we can use [iterators], [closures], [pattern matching], [option] and [result], [string formatting], and of course the [ownership system]. These features make it possible to write a kernel in a very expressive, high level way without worrying about [undefined behavior] or [memory safety].
|
||||
|
||||
[option]: https://doc.rust-lang.org/core/option/
|
||||
[result]:https://doc.rust-lang.org/core/result/
|
||||
[Rust standard library]: https://doc.rust-lang.org/std/
|
||||
[iterators]: https://doc.rust-lang.org/book/second-edition/ch13-02-iterators.html
|
||||
[closures]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html
|
||||
[pattern matching]: https://doc.rust-lang.org/book/second-edition/ch06-00-enums.html
|
||||
[string formatting]: https://doc.rust-lang.org/core/macro.write.html
|
||||
[ownership system]: https://doc.rust-lang.org/book/second-edition/ch04-00-understanding-ownership.html
|
||||
[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
|
||||
[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
|
||||
|
||||
In order to create an OS kernel in Rust, we need to create an executable that can be run without an underlying operating system. Such an executable is often called a “freestanding” or “bare-metal” executable.
|
||||
|
||||
This post describes the necessary steps to get a freestanding Rust binary and explains why the steps are needed. If you're just interested in a minimal example, you can **[jump to the summary](#summary)**.
|
||||
|
||||
## Disabling the Standard Library
|
||||
By default, all Rust crates link the [standard library], which dependends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can not use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
||||
|
||||
[standard library]: https://doc.rust-lang.org/std/
|
||||
[`no_std` attribute]: https://doc.rust-lang.org/book/first-edition/using-rust-without-the-standard-library.html
|
||||
|
||||
We start by creating a new cargo application project. The easiest way to do this is through the command line:
|
||||
|
||||
```
|
||||
> cargo new blog_os --bin
|
||||
```
|
||||
|
||||
I named the project `blog_os`, but of course you can choose your own name. The `--bin` flag specifies that we want to create an executable binary (in contrast to a library). When we run the command, cargo creates the following directory structure for us:
|
||||
|
||||
```
|
||||
blog_os
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
The `Cargo.toml` contains the crate configuration, for example the crate name, the author, the [semantic version] number, and dependencies. The `src/main.rs` file contains the root module of our crate and our `main` function. You can compile your crate through `cargo build` and then run the compiled `blog_os` binary in the `target/debug` subfolder.
|
||||
|
||||
[semantic version]: http://semver.org/
|
||||
|
||||
### The `no_std` Attribute
|
||||
|
||||
Right now our crate implicitly links the standard library. Let's try to disable this by adding the [`no_std` attribute]:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
When we try to build it now (by running `cargo build`), the following error occurs:
|
||||
|
||||
```
|
||||
error: cannot find macro `println!` in this scope
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | println!("Hello, world!");
|
||||
| ^^^^^^^
|
||||
```
|
||||
|
||||
The reason for this error is that the [`println` macro] is part of the standard library, which we no longer include. So we can no longer print things. This makes sense, since `println` writes to [standard output], which is a special file descriptor provided by the operating system.
|
||||
|
||||
[`println` macro]: https://doc.rust-lang.org/std/macro.println.html
|
||||
[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
|
||||
|
||||
So let's remove the printing and try again with an empty main function:
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: language item required, but not found: `panic_fmt`
|
||||
error: language item required, but not found: `eh_personality`
|
||||
```
|
||||
|
||||
Now the compiler is missing some _language items_. Language items are special pluggable functions that the compiler invokes on certain conditions, for example when the application [panics]. Normally, these items are provided by the standard library, but we disabled it. So we need to provide our own implementations.
|
||||
|
||||
[panics]: https://doc.rust-lang.org/stable/book/second-edition/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
### Enabling Unstable Features
|
||||
|
||||
Implementing language items is unstable and protected by a so-called _feature gate_. A feature gate is a special attribute that you have to specify at the top of your `main.rs` in order to use the corresponding feature. By doing this you basically say: “I know that this feature is unstable and that it might stop working without warning. I want to use it anyway.”
|
||||
|
||||
To limit the use of unstable features, the feature gates are not available in the stable or beta Rust compilers, only [nightly Rust] makes it possible to opt-in. This means that you have to use a nightly compiler for OS development for the near future (since we need to implement unstable language items). To install a nightly compiler using [rustup], you just need to run `rustup default nightly` (for more information check out [rustup's documentation]).
|
||||
|
||||
[nightly Rust]: https://doc.rust-lang.org/book/first-edition/release-channels.html
|
||||
[rustup]: https://rustup.rs/
|
||||
[rustup's documentation]: https://github.com/rust-lang-nursery/rustup.rs#rustup-the-rust-toolchain-installer
|
||||
|
||||
After installing a nightly Rust compiler, you can enable the unstable `lang_items` feature by inserting `#![feature(lang_items)]` right at the top of `main.rs`.
|
||||
|
||||
### Implementing the Language Items
|
||||
|
||||
To create a `no_std` binary, we have to implement the `panic_fmt` and the `eh_personality` language items. The `panic_fmt` items specifies a function that should be invoked when a panic occurs. This function should format an error message (hence the `_fmt` suffix) and then invoke the panic routine. In our case, there is not much we can do, since we can neither print anything nor do we have a panic routine. So we just loop indefinitely:
|
||||
|
||||
```rust
|
||||
#![feature(lang_items)]
|
||||
#![no_std]
|
||||
|
||||
fn main() {}
|
||||
|
||||
#[lang = "panic_fmt"]
|
||||
#[no_mangle]
|
||||
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||
_file: &'static str, _line: u32, _column: u32) -> !
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
The function signature is taken from the [unstable Rust book]. The signature isn't verified by the compiler, so implement it carefully.
|
||||
|
||||
[unstable Rust book]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib
|
||||
|
||||
Note that there is already an accepted RFC for a stable panic mechnism, which is only waiting for an implementation. See the [tracking issue](https://github.com/rust-lang/rust/issues/44489) for more information.
|
||||
|
||||
Instead of implementing the second language item, `eh_personality`, we remove the need for it by disabling unwinding.
|
||||
|
||||
### Disabling Unwinding
|
||||
|
||||
The `eh_personality` language item is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of 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.
|
||||
|
||||
[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
|
||||
|
||||
There are other use cases as well for which unwinding is undesireable, so Rust provides an option to [abort on panic] instead. This disables the generation of unwinding symbol and thus considerably reduces binary size. There are multiple places where we can disable unwinding. The easiest way is to add the following lines to our `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
This sets the panic strategy to `abort` for both the `dev` profile (used for `cargo build`) and the `release` profile (used for `cargo build --release`). Now the `eh_personality` language item should no longer be required.
|
||||
|
||||
[abort on panic]: https://github.com/rust-lang/rust/pull/32900
|
||||
|
||||
However, if we try to compile it now, another language item is required:
|
||||
|
||||
```
|
||||
> cargo build
|
||||
error: requires `start` lang_item
|
||||
```
|
||||
|
||||
### The `start` attribute
|
||||
|
||||
One might think that the `main` function is the first function called when you run a program. However, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go). This runtime needs to be called before `main`, since it needs to initialize itself.
|
||||
|
||||
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
|
||||
|
||||
In a typical Rust binary that links the standard library, execution starts in a C runtime library called `crt0` (“C runtime zero”), which sets up the environment for a C application. This includes creating a stack and placing the arguments in the right registers. The C runtime then invokes the [entry point of the Rust runtime][rt::lang_start], which is marked by the `start` language item. Rust only has a very minimal runtime, which takes care of some small things such as setting up stack overflow guards or printing a backtrace on panic. The runtime then finally calls the `main` function.
|
||||
|
||||
[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
|
||||
|
||||
Our freestanding executable does not have access to the Rust runtime and `crt0`, so we need to define our own entry point. Implementing the `start` language item wouldn't help, since it would still require `crt0`. Instead, we need to overwrite the `crt0` entry point directly.
|
||||
|
||||
### Overwriting the Entry Point
|
||||
To tell the Rust compiler that we don't want to use the normal entry point chain, we add the `#![no_main]` attribute.
|
||||
|
||||
```rust
|
||||
#![feature(lang_items)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
#[lang = "panic_fmt"]
|
||||
#[no_mangle]
|
||||
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||
_file: &'static str, _line: u32, _column: u32) -> !
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
You might notice that we removed the `main` function. The reason is that a `main` doesn't make sense without an underlying runtime that calls it. Instead, we now to overwrite the operating system entry point.
|
||||
|
||||
The entry point convention depends on your operating system. I recommend you to read the Linux section even if you're on a different OS because it is the target we will derive to build our kernel in the next post.
|
||||
|
||||
#### Linux
|
||||
On Linux, the default entry point is called `_start`. The linker just looks for a function with that name and sets this function as entry point the executable. So to overwrite the entry point, we define our own `_start` function:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern fn _start() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
It's important that we disable the [name mangling][mangling] through the `no_mangle` attribute, otherwise the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol that the linker wouldn't recognize.
|
||||
|
||||
The `!` return type means that the function is diverging, i.e. not allowed to ever return. This is required because the entry point is not called by any function, but invoked directly by the operating system or bootloader. So instead of returning, the entry point should e.g. invoke the [`exit` system call] of the operating system. In our case, shutting down the machine could be a reasonable action, since there's nothing left to do if a freestanding binary returns. For now, we fulfil the requirement by looping endlessly.
|
||||
|
||||
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
|
||||
|
||||
If we try to build it now, an ugly linker error occurs:
|
||||
|
||||
```
|
||||
error: linking with `cc` failed: exit code: 1
|
||||
|
|
||||
= note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L"
|
||||
"/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
||||
"/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09.0.o" […]
|
||||
"-o" "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09"
|
||||
"-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
|
||||
"-L" "/…/blog_os/target/debug/deps"
|
||||
"-L" "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
||||
"-Wl,-Bstatic"
|
||||
"/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-dd5bba80e2402629.rlib"
|
||||
"-Wl,-Bdynamic"
|
||||
= note: /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x19): undefined reference to `__libc_csu_init'
|
||||
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||
(.text+0x25): undefined reference to `__libc_start_main'
|
||||
collect2: error: ld returned 1 exit status
|
||||
|
||||
```
|
||||
|
||||
The problem is that we still link the startup routine of the C runtime, which requires some symbols of the C standard library `libc`, which we don't link due to the `no_std` attribute. So we need to get rid of the C startup routine. We can do that by passing the `-nostartfiles` flag to the linker.
|
||||
|
||||
One way to pass linker attributes via cargo is the `cargo rustc` command. The command behaves exactly like `cargo build`, but allows to pass options to `rustc`, the underlying Rust compiler. `rustc` has the (unstable) `-Z pre-link-arg` flag, which passes an argument to the linker. Combined, our new build command looks like this:
|
||||
|
||||
```
|
||||
> cargo rustc -- -Z pre-link-arg=-nostartfiles
|
||||
```
|
||||
|
||||
With this command, our crate builds again as a freestanding executable!
|
||||
|
||||
#### Windows
|
||||
On Windows, the linker requires two entry points: `WinMain` and `WinMainCRTStartup`, [depending on the used subsystem](https://msdn.microsoft.com/en-us/library/f9t8842e.aspx). Like on Linux, we overwrite the entry points by defining `no_mangle` functions:
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern fn WinMainCRTStartup() -> ! {
|
||||
WinMain();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn WinMain() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
We just call `WinMain` from `WinMainCRTStartup` to avoid any ambiguity which function is called.
|
||||
|
||||
#### OS X
|
||||
Mac OS X [does not support statically linked binaries], so we have to link the `libSystem` library. The entry point is the called `main`:
|
||||
|
||||
[does not support statically linked binaries]: https://developer.apple.com/library/content/qa/qa1118/_index.html
|
||||
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern fn main() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
To build it and link `libSystem`, we execute:
|
||||
|
||||
```
|
||||
> cargo rustc -- -Z pre-link-arg=-lSystem
|
||||
```
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
A minimal freestanding Rust binary looks like this:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![feature(lang_items)] // required for defining the panic handler
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
|
||||
#[lang = "panic_fmt"] // define a function that should be called on panic
|
||||
#[no_mangle]
|
||||
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||
_file: &'static str, _line: u32, _column: u32) -> !
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
|
||||
// On Linux:
|
||||
#[no_mangle] // don't mangle the name of this function
|
||||
pub extern fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start_` by default
|
||||
loop {}
|
||||
}
|
||||
|
||||
// On Windows:
|
||||
#[no_mangle]
|
||||
pub extern fn WinMainCRTStartup() -> ! {
|
||||
WinMain();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn WinMain() -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
// On Mac:
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn main() -> ! {
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
`Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# the profile used for `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
|
||||
# the profile used for `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
```
|
||||
|
||||
It can be compiled with:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
> cargo rustc -- -Z pre-link-arg=-nostartfiles
|
||||
# Windows
|
||||
> cargo build
|
||||
# Mac
|
||||
> cargo rustc -- -Z pre-link-arg=-lSystem
|
||||
```
|
||||
|
||||
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?
|
||||
|
||||
The [next post] build upon our minimal freestanding binary by explaining the steps needed for creating a minimal operating system kernel. It explains how to configure the kernel for the target system, how to start it using a bootloader, and how to print something to the screen.
|
||||
|
||||
[next post]: ./second-edition/posts/02-minimal-rust-kernel/index.md
|
||||
@@ -0,0 +1,363 @@
|
||||
+++
|
||||
title = "A Minimal Rust Kernel"
|
||||
order = 4
|
||||
path = "minimal-rust-kernel"
|
||||
date = 0000-01-01
|
||||
template = "second-edition/page.html"
|
||||
+++
|
||||
|
||||
In this post we create a minimal 64-bit Rust kernel. We built upon the [freestanding Rust binary] from the previous post to create a bootable disk image, that prints something to the screen.
|
||||
|
||||
[freestanding Rust binary]: ./second-edition/posts/01-freestanding-rust-binary/index.md
|
||||
|
||||
<!-- more -->
|
||||
|
||||
This blog is openly developed on [Github]. If you have any problems or questions, please open an issue there. You can also leave comments [at the bottom].
|
||||
|
||||
[Github]: https://github.com/phil-opp/blog_os
|
||||
[at the bottom]: #comments
|
||||
|
||||
## The Boot Process
|
||||
When you turn on a computer, it begins executing firmware code that is stored in motherboard [ROM]. This code performs a [power-on self-test], detects available RAM, and pre-initializes the CPU and hardware. Afterwards it looks for a bootable disk and starts booting the operating system kernel.
|
||||
|
||||
[ROM]: https://en.wikipedia.org/wiki/Read-only_memory
|
||||
[power-on self-test]: https://en.wikipedia.org/wiki/Power-on_self-test
|
||||
|
||||
On x86, there are two firmware standards: the “Basic Input/Output System“ (**[BIOS]**) and the newer “Unified Extensible Firmware Interface” (**[UEFI]**). The BIOS standard is old and outdated, but simple and well-supported on any x86 machine since the 1980s. UEFI, in contrast, is more modern and has much more features, but is more complex to set up (at least in my opinion).
|
||||
|
||||
[BIOS]: https://en.wikipedia.org/wiki/BIOS
|
||||
[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
|
||||
|
||||
Currently, we only provide BIOS support, but support for UEFI is planned, too. If you'd like to help us with this, check out the [Github issue](https://github.com/phil-opp/blog_os/issues/349).
|
||||
|
||||
### BIOS Boot
|
||||
Almost all x86 systems have support for BIOS booting, even newer UEFI-based machines (they include an emulated BIOS). This is great, because you can use the same boot logic across all machines from the last centuries. But this wide compatibility is at the same time the biggest disadvantage of BIOS booting, because it means that the CPU is put into a 16-bit compability mode called [real mode] before booting so that that arcane bootloaders from the 1980s would still work.
|
||||
|
||||
But let's start from the beginning:
|
||||
|
||||
When you turn on a computer, it loads the BIOS from some special flash memory located on the motherboard. The BIOS runs self test and initialization routines of the hardware, then it looks for bootable disks. If it finds one, the control is transferred to its _bootloader_, which is a 512-byte portion of executable code stored at the disk's beginning. Most bootloaders are larger than 512 bytes, so bootloaders are commonly split into a small first stage, which fits into 512 bytes, and a second stage, which is subsequently loaded by the first stage.
|
||||
|
||||
The bootloader has to determine the location of the kernel image on the disk and load it into memory. It also needs to switch the CPU from the 16-bit [real mode] first to the 32-bit [protected mode], and then to the 64-bit [long mode], where 64-bit registers and the complete main memory are available. Its third job is to query certain information (such as a memory map) from the BIOS and pass it to the OS kernel.
|
||||
|
||||
[real mode]: https://en.wikipedia.org/wiki/Real_mode
|
||||
[protected mode]: https://en.wikipedia.org/wiki/Protected_mode
|
||||
[long mode]: https://en.wikipedia.org/wiki/Long_mode
|
||||
[memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation
|
||||
|
||||
Writing a bootloader is a bit cumbersome as it requires assembly language and a lot of non insightful steps like “write this magic value to this processor register”. Therefore we don't cover bootloader creation in this post and instead provide a tool named [bootimage] that automatically appends a bootloader to your kernel.
|
||||
|
||||
[bootimage]: https://github.com/phil-opp/bootimage
|
||||
|
||||
If you are interested in building your own bootloader: Stay tuned, a set of posts on this topic is already planned! <!-- , check out our “_[Writing a Bootloader]_” posts, where we explain in detail how a bootloader is built. -->
|
||||
|
||||
#### The Multiboot Standard
|
||||
To avoid that every operating system implements its own bootloader, which is only compatible with a single OS, the Free Software Foundation created an open bootloader standard called [Multiboot] in 1995. The standard defines an interface between the bootloader and operating system, so that any Multiboot compliant bootloader can load any Multiboot compliant operating system. The reference implementation is [GNU GRUB], which is the most popular bootloader for Linux systems.
|
||||
|
||||
[Multiboot]: https://wiki.osdev.org/Multiboot
|
||||
[GNU GRUB]: https://en.wikipedia.org/wiki/GNU_GRUB
|
||||
|
||||
To make a kernel Multiboot compliant, one just need to insert a so-called [Multiboot header] at the beginning of the kernel file. This makes it very easy to boot an OS in GRUB. However, GRUB and the the Multiboot standard have some problems too:
|
||||
|
||||
[Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format
|
||||
|
||||
- They support only the 32-bit protected mode. This means that you still have to do the CPU configuration to switch to the 64-bit long mode.
|
||||
- The newest version of the standard, [Multiboot 2.0], supports kernels in the 64-bit ELF format, but requires a [adjusting the default page size].
|
||||
- The development of the standard and GRUB is highly interleaved. For example, I'm not aware of an other bootloader that is Multiboot compliant. As a result, one often has to look into the GRUB source code when the standard is not clear enough.
|
||||
- The standard is written for the C programming language. That means lots of C-typical structures such as linked lists in the [boot information].
|
||||
- Both GRUB and the Multiboot standard are only sparsely documented.
|
||||
- GRUB needs to be installed on the host system to create a bootable disk image from the kernel file. This makes development on Windows or Mac more difficult.
|
||||
|
||||
[boot information]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
|
||||
[Multiboot 2.0]: https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html
|
||||
[adjusting the default page size]: https://wiki.osdev.org/Multiboot#Multiboot_2
|
||||
|
||||
Because of these drawbacks we decided to not use GRUB or the Multiboot standard. However, we plan to add Multiboot support to our [bootimage] tool, so that it's possible to load your kernel on a GRUB system too. If you're interested in writing a Multiboot compliant kernel, check out the [first edition] of this blog series.
|
||||
|
||||
[first edition]: ./first-edition/_index.md
|
||||
|
||||
### UEFI
|
||||
|
||||
(We don't provide UEFI support at the moment, but we would love to! If you'd like to help, please tell us in the [Github issue](https://github.com/phil-opp/blog_os/issues/349).)
|
||||
|
||||
## A Minimal Kernel
|
||||
Now that we roughly know how a computer boots, it's time to create our own minimal kernel. Our goal is to create a disk image that prints a green “Hello” to the screen when booted. For that we build upon the [freestanding Rust binary] from the previous post.
|
||||
|
||||
As you may remember, we built the freestanding binary through `cargo`, but depending on the operating system we needed different entry point names and compile flags. That's because `cargo` builds for the _host system_ by default, i.e. the system you're running on. This isn't something we want for our kernel, because a kernel that runs on top of e.g. Windows does not make much sense. Instead, we want to compile for a clearly defined _target system_.
|
||||
|
||||
### Target Specification
|
||||
Cargo supports different target systems through the `--target` parameter. The target is decribed by a so-called _[target triple]_, which describes the CPU architecture, the vendor, the operating system, and the [ABI]. For example, the `x86_64-unknown-linux-gnu` means a `x86_64` CPU, no clear vendor and a Linux operating system with the GNU ABI. Rust supports [many different target triples][platform-support], including `arm-linux-androideabi` for Android or [`wasm32-unknown-unknown` for WebAssembly](https://www.hellorust.com/setup/wasm-target/).
|
||||
|
||||
[target triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
|
||||
[ABI]: https://stackoverflow.com/a/2456882
|
||||
[platform-support]: https://forge.rust-lang.org/platform-support.html
|
||||
|
||||
For our target system, however, we require some special configuration parameters (e.g. no underlying OS), so none of the [existing target triples][platform-support] fits. Fortunately Rust allows us to define our own target through a JSON file. For example, a JSON file that describes the `x86_64-unknown-linux-gnu` target looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"arch": "x86_64",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
"os": "linux",
|
||||
"executables": true,
|
||||
"linker-flavor": "gcc",
|
||||
"pre-link-args": ["-m64"],
|
||||
"morestack": false
|
||||
}
|
||||
```
|
||||
|
||||
Most fields are required by LLVM to generate code for that platform. For example, the `data-layout` field defines the size of various integer, floating point, and pointer types. Then there are fields that Rust uses for conditional compilation, such as `target-pointer-width`. The third kind of fields define how the crate should be built. For example, the `pre-link-args` field specifies arguments passed to the [linker].
|
||||
|
||||
[linker]: https://en.wikipedia.org/wiki/Linker_(computing)
|
||||
|
||||
We also target `x86_64` systems with our kernel, so our target specification will look very similar to the above. Let's start by creating a `x86_64-blog_os.json` file (choose any name you like) with the common content:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-none",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"arch": "x86_64",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
"os": "none",
|
||||
"executables": true,
|
||||
}
|
||||
```
|
||||
|
||||
Note that we changed the OS in the `llvm-target` and the `os` field to `none`, because we will run on bare metal.
|
||||
|
||||
We add the following build-related entries:
|
||||
|
||||
|
||||
```json
|
||||
"linker-flavor": "ld",
|
||||
"linker": "ld.lld",
|
||||
```
|
||||
|
||||
Instead of using the platform's default linker (which might not support Linux targets), we use the cross platform [LLD] linker for linking our kernel.
|
||||
|
||||
[LLD]: https://lld.llvm.org/
|
||||
|
||||
```json
|
||||
"panic": "abort",
|
||||
```
|
||||
|
||||
This setting specifies that the target doesn't support [stack unwinding] on panic, so instead the program should abort directly. This has the same effect as the `panic = "abort"` option in our Cargo.toml, so we can remove it from there.
|
||||
|
||||
[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||
|
||||
```json
|
||||
"disable-redzone": true,
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```json
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
```
|
||||
|
||||
The `features` field enables/disables target features. We disable the `mmx` and `sse` features by prefixing them with a minus and enable the `soft-float` feature by prefixing it with a plus.
|
||||
|
||||
The `mmx` and `sse` features determine support for [Single Instruction Multiple Data (SIMD)] instructions, which can often speed up programs significantly. However, the large SIMD registers lead to performance problems in OS kernels, because the kernel has to back them up on each hardware interrupt. To avoid this, we disable SIMD for our kernel (not for applications running on top!).
|
||||
|
||||
[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
|
||||
|
||||
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).
|
||||
|
||||
#### Putting it Together
|
||||
Our target specification file now looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"arch": "x86_64",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
"os": "none",
|
||||
"linker-flavor": "ld",
|
||||
"linker": "ld.lld",
|
||||
"executables": true,
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
"disable-redzone": true,
|
||||
"panic": "abort"
|
||||
}
|
||||
```
|
||||
|
||||
### Building our Kernel
|
||||
Compiling for our new target will use Linux conventions. I'm not quite sure why, but I assume that it's just LLVM's default. This means that we need an entry point named `_start` as described in the [previous post]:
|
||||
|
||||
[previous post]: ./second-edition/posts/01-freestanding-rust-binary/index.md
|
||||
|
||||
```rust
|
||||
// src/main.rs
|
||||
|
||||
#![feature(lang_items)] // required for defining the panic handler
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
|
||||
#[lang = "panic_fmt"] // define a function that should be called on panic
|
||||
#[no_mangle]
|
||||
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||
_file: &'static str, _line: u32, _column: u32) -> !
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[no_mangle] // don't mangle the name of this function
|
||||
pub fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start_` by default
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
We can now build the kernel for our new target by passing the name of the JSON file (without the `.json` extension) as `--target`. There's is currently an [open bug][custom-target-bug] with custom target, so you also need to set the `RUST_TARGET_PATH` environment variable to the current directory, otherwise Rust might not be able to find your target. The full command is:
|
||||
|
||||
[custom-target-bug]: https://github.com/rust-lang/cargo/issues/4905
|
||||
|
||||
```
|
||||
> RUST_TARGET_PATH=(pwd) cargo build --target x86_64-unknown-blog_os
|
||||
|
||||
error[E0463]: can't find crate for `core`
|
||||
|
|
||||
= note: the `x86_64-blog_os` target may not be installed
|
||||
```
|
||||
|
||||
It failed! The error tells us that the Rust compiler no longer finds the core library. The [core library] is implicitly linked to all `no_std` crates and contains things such as `Result`, `Option`, and iterators.
|
||||
|
||||
[core library]: https://doc.rust-lang.org/nightly/core/index.html
|
||||
|
||||
The problem is that the core library is distributed together with the Rust compiler as a _precompiled_ library. So it is only valid for the host triple (e.g., `x86_64-unknown-linux-gnu`) but not for our custom target. If we want to compile code for other targets, we need to recompile `core` for these targets first.
|
||||
|
||||
#### Xargo
|
||||
That's where [xargo] comes in. It is a wrapper for cargo that eases cross compilation. We can install it by executing:
|
||||
|
||||
[xargo]: https://github.com/japaric/xargo
|
||||
|
||||
```
|
||||
cargo install xargo
|
||||
```
|
||||
|
||||
Xargo depends on the rust source code, which we can install with `rustup component add rust-src`.
|
||||
|
||||
Xargo is “a drop-in replacement for cargo”, so every cargo command also works with `xargo`. You can do e.g. `xargo --help`, `xargo clean`, or `xargo doc`. The only difference is that the build command has additional functionality: `xargo build` will automatically cross compile the `core` library when compiling for custom targets.
|
||||
|
||||
Let's try it:
|
||||
|
||||
```bash
|
||||
> RUST_TARGET_PATH=(pwd) xargo build --target x86_64-unknown-blog_os
|
||||
Compiling core v0.0.0 (file:///…/rust/src/libcore)
|
||||
Finished release [optimized] target(s) in 22.87 secs
|
||||
Compiling blog_os v0.1.0 (file:///…/blog_os)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
|
||||
```
|
||||
|
||||
(If you're getting a linking error because LLD could not be found, see our “[Installing LLD]” guide.)
|
||||
|
||||
[Installing LLD]: ./second-edition/extra/installing-lld/index.md
|
||||
|
||||
It worked! We see that `xargo` cross-compiled the `core` library for our new custom target and then continued to compile our `blog_os` crate.
|
||||
|
||||
Now we are able to build our kernel for a bare metal target. However, our `_start` entry point, which will be called by the boot loader, is still empty. So let's output something to screen from it.
|
||||
|
||||
### Printing to Screen
|
||||
The easiest way to print text to the screen at this stage is the [VGA text buffer]. It is a special memory area mapped to the VGA hardware that contains the contents displayed on screen. It normally consists of 50 lines that each contain 80 character cells. Each character cell displays an ASCII character with some foreground and background colors. The screen output looks like this:
|
||||
|
||||
[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
|
||||
|
||||

|
||||
|
||||
We will discuss the exact layout of the VGA buffer in the next post, where we write a first small driver for it. For printing “Hello”, we just need to know that the buffer is located at address `0xb8000` and that each character cell consists of an ASCII byte and a color byte.
|
||||
|
||||
The implementation looks like this:
|
||||
|
||||
```rust
|
||||
static HELLO: &[u8] = b"Hello World!";
|
||||
|
||||
#[no_mangle]
|
||||
pub fn _start(boot_info: &'static mut BootInfo) -> ! {
|
||||
let vga_buffer = 0xb8000 as *const u8 as *mut u8;
|
||||
|
||||
for (i, &byte) in HELLO.iter().enumerate() {
|
||||
unsafe {
|
||||
*vga_buffer.offset(i as isize * 2) = byte;
|
||||
*vga_buffer.offset(i as isize * 2 + 1) = 0xb;
|
||||
}
|
||||
}
|
||||
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
First, we cast the integer `0xb8000` into a [raw pointer]. Then we [iterate] over the bytes of the [static] `HELLO` [byte string]. We use the [`enumerate`] method to additionally get a running variable `i`. In the body of the for loop, we use the [`offset`] method to write the string byte and the corresponding color byte.
|
||||
|
||||
[iterate]: https://doc.rust-lang.org/book/second-edition/ch13-02-iterators.html
|
||||
[static]: https://doc.rust-lang.org/book/first-edition/const-and-static.html#static
|
||||
[`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate
|
||||
[byte string]: https://doc.rust-lang.org/reference/tokens.html#byte-string-literals
|
||||
[raw pointer]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
|
||||
[`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
|
||||
|
||||
Note that there's an [`unsafe`] block around all memory writes. The reason is that the Rust compiler can't prove that the raw pointers we create are valid. They could point anywhere and lead to data corruption. By putting them into an `unsafe` block we're basically telling the compiler that we are absolutely sure that the operations are valid. Note that an `unsafe` block does not turn off Rust's safety checks. It only allows you to do [four additional things].
|
||||
|
||||
[`unsafe`]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html
|
||||
[four additional things]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||
|
||||
I want to emphasize that **this is not the way we want to do things in Rust!** It's very easy to mess up when working with raw pointers inside unsafe blocks, for example, we could easily write behind the buffer's end if we're not careful.
|
||||
|
||||
So we want to minimize the use of `unsafe` as much as possible. Rust gives us the ability to do this by creating safe abstractions. For example, we could create a VGA buffer type that encapsulates all unsafety and ensures that it is _impossible_ to do anything wrong from the outside. This way, we would only need minimal amounts of `unsafe` and can be sure that we don't violate [memory safety]. We will create such a safe VGA buffer abstraction in the next post.
|
||||
|
||||
[memory safety]: https://en.wikipedia.org/wiki/Memory_safety
|
||||
|
||||
We now have a simple “Hello World!” kernel. It should be noted though that a more advanced kernel might still produce linker faults because the compiler tries to use some function normally provided by `libc`. For this case, there are two crates you should keep in mind: [`rlibc`] and [`compiler_builtins`]. The former provides implementations for `memcpy`, `memclear`, etc. and the latter provides various other builtin functions.
|
||||
|
||||
[`rlibc`]: https://docs.rs/crate/rlibc
|
||||
[`compiler_builtins`]: https://docs.rs/crate/compiler-builtins-snapshot
|
||||
|
||||
### Creating a Bootimage
|
||||
Now that we have an executable that does something perceptible, it is time to turn it into a bootable disk image. As we learned in the [section about booting], we need a bootloader for that, which initializes the CPU and loads our kernel.
|
||||
|
||||
[section about booting]: #the-boot-process
|
||||
|
||||
To make things easy, we created a tool named `bootimage` that automatically downloads a bootloader and combines it with the kernel executable to create a bootable disk image. To install it, execute `cargo install bootimage` in your terminal. After installing, creating a bootimage is as easy as executing `bootimage --target x86_64-unknown-blog_os`. The tool also recompiles your kernel using `xargo`, so it will automatically pick up any changes you make.
|
||||
|
||||
You should now see a file named `bootimage.bin` in your crate root directory. This file is a bootable disk image, so can boot it in a virtual machine or copy it to an USB drive to boot it on real hardware. (Note that this is not a CD image, which have a different format, so burning it to a CD doesn't work).
|
||||
|
||||
## Booting it!
|
||||
We can now boot our kernel in a virtual machine. To boot it in [QEMU], execute the following command:
|
||||
|
||||
[QEMU]: https://www.qemu.org/
|
||||
|
||||
```
|
||||
> qemu-system-x86_64 -drive format=raw,file=bootimage.bin
|
||||
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can also [convert the raw disk image to a VDI] to load it in [VirtualBox].
|
||||
|
||||
[convert the raw disk image to a VDI]: https://blog.sleeplessbeastie.eu/2012/04/29/virtualbox-convert-raw-image-to-vdi-and-otherwise/
|
||||
[VirtualBox]: https://www.virtualbox.org/
|
||||
|
||||
It is also possible to write it to an USB stick and boot it on a real machine:
|
||||
|
||||
```
|
||||
> dd if=bootimage.bin of=/dev/sdX && sync
|
||||
```
|
||||
|
||||
Where `sdX` is the device name of your USB stick. It overwrites everything on that device, so be careful to choose the correct device name.
|
||||
|
||||
## What's next?
|
||||
In the next post, we will explore the VGA text buffer in more detail and write a safe interface for it. We will also add support for the `println` macro.
|
||||
TODO
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
6
blog/content/second-edition/posts/_index.md
Normal file
6
blog/content/second-edition/posts/_index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "Posts"
|
||||
sort_by = "order"
|
||||
insert_anchor_links = "left"
|
||||
render = false
|
||||
+++
|
||||
@@ -1,7 +1,15 @@
|
||||
.masthead-title {
|
||||
font-size: 1.25rem;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.masthead-title small {
|
||||
.masthead p {
|
||||
font-size: 1.25rem;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
margin-left: 1rem;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.front-page-introduction {
|
||||
@@ -220,3 +228,11 @@ a.gutenberg-anchor {
|
||||
a.gutenberg-anchor:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
padding: .7rem 1rem;
|
||||
margin: 1rem .2rem;
|
||||
border: 2px solid orange;
|
||||
border-radius: 5px;
|
||||
background-color: #ffa50022;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ tbody tr:nth-child(odd) th {
|
||||
.masthead-title a {
|
||||
color: #505050;
|
||||
}
|
||||
.masthead-title small {
|
||||
.masthead small {
|
||||
font-size: 75%;
|
||||
font-weight: 400;
|
||||
color: #c0c0c0;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 45 KiB |
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
|
||||
<html lang="{{ config.language_code }}">
|
||||
<html lang="{{ config.default_language }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -20,10 +20,10 @@
|
||||
<body>
|
||||
<div class="container content">
|
||||
<header class="masthead">
|
||||
<h3 class="masthead-title">
|
||||
<h1 class="masthead-title">
|
||||
<a href="/" title="Home">{{ config.title }}</a>
|
||||
<small>{{ config.extra.subtitle }}</small>
|
||||
</h3>
|
||||
</h1>
|
||||
<p><small>{{ config.extra.subtitle }}</small></p>
|
||||
</header>
|
||||
|
||||
<main>{% block main %}{% endblock main %}</main>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</p>
|
||||
<p>Latest post:
|
||||
{% set latest_post = posts|last %}
|
||||
<strong><a href="{{ latest_post.permalink | safe }}">{{ latest_post.title }}</a></strong>
|
||||
<strong><a href="/{{ latest_post.path | safe }}">{{ latest_post.title }}</a></strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
<h1>{{ extra.title }}</h1>
|
||||
<ul>
|
||||
{% for subsection in extra.subsections|reverse %}
|
||||
<li><a href="{{ subsection.permalink | safe }}">{{ subsection.title }}</a></li>
|
||||
<li><a href="/{{ subsection.path | safe }}">{{ subsection.title }}</a></li>
|
||||
{% endfor %}
|
||||
{% for page in extra.pages|reverse %}
|
||||
<li><a href="{{ page.permalink | safe }}">{{ page.title }}</a></li>
|
||||
<li><a href="/{{ page.path | safe }}">{{ page.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% macro post_link(page) %}
|
||||
<article>
|
||||
<h2 class="post-title"><a href="{{ page.permalink | safe }}">{{ page.title }}</a></h2>
|
||||
<h2 class="post-title"><a href="/{{ page.path | safe }}">{{ page.title }}</a></h2>
|
||||
{{ page.summary | safe }}
|
||||
</article>
|
||||
{% endmacro post_link %}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
<h2>Table of Contents</h2>
|
||||
<ol>
|
||||
{% for h2 in page.toc %}<li>
|
||||
<a href="{{h2.permalink | safe}}">{{ h2.title | safe }}</a>
|
||||
<a href="#{{h2.id | safe}}">{{ h2.title | safe }}</a>
|
||||
{% if h2.children %}<ol>
|
||||
{% for h3 in h2.children %}<li>
|
||||
<a href="{{h3.permalink | safe}}">{{ h3.title | safe }}</a>
|
||||
<a href="#{{h3.id | safe}}">{{ h3.title | safe }}</a>
|
||||
</li>{% endfor %}
|
||||
</ol>{% endif %}
|
||||
</li>{% endfor %}
|
||||
@@ -28,10 +28,10 @@
|
||||
<hr>
|
||||
<div class="PageNavigation">
|
||||
{% if page.previous %}
|
||||
<a class="prev" href="{{ page.previous.permalink | safe }}">« {{ page.previous.title }}</a>
|
||||
<a class="prev" href="/{{ page.previous.path | safe }}">« {{ page.previous.title }}</a>
|
||||
{% endif %}
|
||||
{% if page.next %}
|
||||
<a class="next" href="{{ page.next.permalink | safe }}">{{ page.next.title }} »</a>
|
||||
<a class="next" href="/{{ page.next.path | safe }}">{{ page.next.title }} »</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
59
blog/templates/second-edition/base.html
Normal file
59
blog/templates/second-edition/base.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
|
||||
<html lang="{{ config.default_language }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ config.description }}">
|
||||
<meta name="author" content="{{ config.extra.author.name }}">
|
||||
|
||||
<link href="/css/poole.css" rel="stylesheet">
|
||||
<link href="/css/main.css" rel="stylesheet">
|
||||
|
||||
<script async src="/js/main.js"></script>
|
||||
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container content">
|
||||
<header class="masthead">
|
||||
<h1 class="masthead-title">
|
||||
<a href="{{ get_url(path="./second-edition/_index.md") }}" title="Home">{{ config.title }} (Second Edition)</a>
|
||||
</h1>
|
||||
<p><small>{{ config.extra.subtitle }}</small></p>
|
||||
</header>
|
||||
|
||||
<main>{% block main %}{% endblock main %}</main>
|
||||
|
||||
<div>{% block after_main %}{% endblock after_main %}</div>
|
||||
|
||||
<footer class="footer">
|
||||
<hr>
|
||||
<small>
|
||||
© <time datetime="2017">2017</time>. All rights reserved.
|
||||
<a href="{{ get_url(path="./pages/contact.md") }}">Contact</a>
|
||||
</small>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="//analytics.phil-opp.com/";
|
||||
_paq.push(['setTrackerUrl', u+'js/']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src='https://cdnjs.cloudflare.com/ajax/libs/piwik/3.0.4/piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Piwik Code -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
24
blog/templates/second-edition/extra.html
Normal file
24
blog/templates/second-edition/extra.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "second-edition/base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<div class="warning">
|
||||
<b>Note:</b> This post is part of the upcoming <a href="{{ get_url(path = "./second-edition/_index.md") }}">second edition</a> of “Writing an OS in Rust”, which is still under construction. To read the first edition, go <a href="{{ get_url(path = "/") }}">here</a>.
|
||||
</div>
|
||||
{{ page.content | safe }}
|
||||
{% endblock main %}
|
||||
|
||||
{% block after_main %}
|
||||
<hr>
|
||||
<section>
|
||||
<h2>Comments</h2>
|
||||
<script src="https://utteranc.es/client.js"
|
||||
repo="phil-opp/blog_os"
|
||||
branch="master"
|
||||
issue-term="pathname"
|
||||
async>
|
||||
</script>
|
||||
</section>
|
||||
{% endblock after_main %}
|
||||
37
blog/templates/second-edition/index.html
Normal file
37
blog/templates/second-edition/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "second-edition/base.html" %}
|
||||
|
||||
{% import "macros.html" as macros %}
|
||||
|
||||
{% block title %}{{ config.title }}{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
{% set posts_section = get_section(path = "second-edition/posts/_index.md") %}
|
||||
{% set posts = posts_section.pages | reverse %}
|
||||
<div class="warning">
|
||||
<b>Note:</b> You are viewing the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go <a href="{{ get_url(path = "/") }}">here</a>.
|
||||
</div>
|
||||
<div class="front-page-introduction">
|
||||
<p>
|
||||
This blog series creates a small operating system in the
|
||||
<a href="https://www.rust-lang.org/">Rust programming language</a>. Each post is a small tutorial and includes all
|
||||
needed code, so you can follow along if you like. The source code is also available in the corresponding
|
||||
<a href="https://github.com/phil-opp/blog_os">Github repository</a>.
|
||||
</p>
|
||||
<p>Latest post:
|
||||
{% set latest_post = posts|last %}
|
||||
<strong><a href="/{{ latest_post.path | safe }}">{{ latest_post.title }}</a></strong>
|
||||
</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) }}
|
||||
</div>
|
||||
|
||||
<aside id="recent-updates">
|
||||
<h2>Recent Updates</h2>
|
||||
{% include "recent-updates.html" %}
|
||||
</aside>
|
||||
|
||||
{% endblock main %}
|
||||
54
blog/templates/second-edition/page.html
Normal file
54
blog/templates/second-edition/page.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{% extends "second-edition/base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<time datetime="{{ page.date | date(format="%Y-%m-%d") }}" class="post-date">
|
||||
{{ page.date | date(format="%b %d, %Y") }}
|
||||
{% if page.extra.updated %} (updated on {{ page.extra.updated | date(format="%b %d, %Y") }}) {% endif %}
|
||||
</time>
|
||||
<aside id="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ol>
|
||||
{% for h2 in page.toc %}<li>
|
||||
<a href="#{{h2.id | safe}}">{{ h2.title | safe }}</a>
|
||||
{% if h2.children %}<ol>
|
||||
{% for h3 in h2.children %}<li>
|
||||
<a href="#{{h3.id | safe}}">{{ h3.title | safe }}</a>
|
||||
</li>{% endfor %}
|
||||
</ol>{% endif %}
|
||||
</li>{% endfor %}
|
||||
</ol>
|
||||
</aside>
|
||||
<div class="warning">
|
||||
<b>Note:</b> This post is part of the upcoming <a href="{{ get_url(path = "./second-edition/_index.md") }}">second edition</a> of “Writing an OS in Rust”, which is still under construction. To read the first edition, go <a href="{{ get_url(path = "/") }}">here</a>.
|
||||
</div>
|
||||
{{ page.content | safe }}
|
||||
{% endblock main %}
|
||||
|
||||
{% block after_main %}
|
||||
<hr>
|
||||
<div class="PageNavigation">
|
||||
{% if page.previous %}
|
||||
<a class="prev" href="/{{ page.previous.path | safe }}">« {{ page.previous.title }}</a>
|
||||
{% endif %}
|
||||
{% if page.next %}
|
||||
<a class="next" href="/{{ page.next.path | safe }}">{{ page.next.title }} »</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<section>
|
||||
<h2 id="comments">Comments</h2>
|
||||
<script src="https://utteranc.es/client.js"
|
||||
repo="phil-opp/blog_os"
|
||||
branch="master"
|
||||
issue-term="pathname"
|
||||
async>
|
||||
</script>
|
||||
</section>
|
||||
|
||||
{% endblock after_main %}
|
||||
|
||||
|
||||
4
bors.toml
Normal file
4
bors.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
status = [
|
||||
"continuous-integration/travis-ci/push",
|
||||
"continuous-integration/appveyor/branch"
|
||||
]
|
||||
2
libs/bump_allocator/.gitignore
vendored
2
libs/bump_allocator/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Generated by Cargo
|
||||
/target/
|
||||
@@ -1,202 +0,0 @@
|
||||
; Copyright 2016 Philipp Oppermann. See the README.md
|
||||
; file at the top-level directory of this distribution.
|
||||
;
|
||||
; Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
; http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
; <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
; option. This file may not be copied, modified, or distributed
|
||||
; except according to those terms.
|
||||
|
||||
global start
|
||||
extern long_mode_start
|
||||
|
||||
section .text
|
||||
bits 32
|
||||
start:
|
||||
mov esp, stack_top
|
||||
; Move Multiboot info pointer to edi to pass it to the kernel. We must not
|
||||
; modify the `edi` register until the kernel it called.
|
||||
mov edi, ebx
|
||||
|
||||
call check_multiboot
|
||||
call check_cpuid
|
||||
call check_long_mode
|
||||
|
||||
call set_up_page_tables
|
||||
call enable_paging
|
||||
call set_up_SSE
|
||||
|
||||
; load the 64-bit GDT
|
||||
lgdt [gdt64.pointer]
|
||||
|
||||
jmp gdt64.code:long_mode_start
|
||||
|
||||
set_up_page_tables:
|
||||
; recursive map P4
|
||||
mov eax, p4_table
|
||||
or eax, 0b11 ; present + writable
|
||||
mov [p4_table + 511 * 8], eax
|
||||
|
||||
; map first P4 entry to P3 table
|
||||
mov eax, p3_table
|
||||
or eax, 0b11 ; present + writable
|
||||
mov [p4_table], eax
|
||||
|
||||
; map first P3 entry to P2 table
|
||||
mov eax, p2_table
|
||||
or eax, 0b11 ; present + writable
|
||||
mov [p3_table], eax
|
||||
|
||||
; map each P2 entry to a huge 2MiB page
|
||||
mov ecx, 0 ; counter variable
|
||||
.map_p2_table:
|
||||
; map ecx-th P2 entry to a huge page that starts at address (2MiB * ecx)
|
||||
mov eax, 0x200000 ; 2MiB
|
||||
mul ecx ; start address of ecx-th page
|
||||
or eax, 0b10000011 ; present + writable + huge
|
||||
mov [p2_table + ecx * 8], eax ; map ecx-th entry
|
||||
|
||||
inc ecx ; increase counter
|
||||
cmp ecx, 512 ; if counter == 512, the whole P2 table is mapped
|
||||
jne .map_p2_table ; else map the next entry
|
||||
|
||||
ret
|
||||
|
||||
enable_paging:
|
||||
; load P4 to cr3 register (cpu uses this to access the P4 table)
|
||||
mov eax, p4_table
|
||||
mov cr3, eax
|
||||
|
||||
; enable PAE-flag in cr4 (Physical Address Extension)
|
||||
mov eax, cr4
|
||||
or eax, 1 << 5
|
||||
mov cr4, eax
|
||||
|
||||
; set the long mode bit in the EFER MSR (model specific register)
|
||||
mov ecx, 0xC0000080
|
||||
rdmsr
|
||||
or eax, 1 << 8
|
||||
wrmsr
|
||||
|
||||
; enable paging in the cr0 register
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31
|
||||
mov cr0, eax
|
||||
|
||||
ret
|
||||
|
||||
; Prints `ERR: ` and the given error code to screen and hangs.
|
||||
; parameter: error code (in ascii) in al
|
||||
error:
|
||||
mov dword [0xb8000], 0x4f524f45
|
||||
mov dword [0xb8004], 0x4f3a4f52
|
||||
mov dword [0xb8008], 0x4f204f20
|
||||
mov byte [0xb800a], al
|
||||
hlt
|
||||
|
||||
; Throw error 0 if eax doesn't contain the Multiboot 2 magic value (0x36d76289).
|
||||
check_multiboot:
|
||||
cmp eax, 0x36d76289
|
||||
jne .no_multiboot
|
||||
ret
|
||||
.no_multiboot:
|
||||
mov al, "0"
|
||||
jmp error
|
||||
|
||||
; Throw error 1 if the CPU doesn't support the CPUID command.
|
||||
check_cpuid:
|
||||
; Check if CPUID is supported by attempting to flip the ID bit (bit 21) in
|
||||
; the FLAGS register. If we can flip it, CPUID is available.
|
||||
|
||||
; Copy FLAGS in to EAX via stack
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Copy to ECX as well for comparing later on
|
||||
mov ecx, eax
|
||||
|
||||
; Flip the ID bit
|
||||
xor eax, 1 << 21
|
||||
|
||||
; Copy EAX to FLAGS via the stack
|
||||
push eax
|
||||
popfd
|
||||
|
||||
; Copy FLAGS back to EAX (with the flipped bit if CPUID is supported)
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Restore FLAGS from the old version stored in ECX (i.e. flipping the ID bit
|
||||
; back if it was ever flipped).
|
||||
push ecx
|
||||
popfd
|
||||
|
||||
; Compare EAX and ECX. If they are equal then that means the bit wasn't
|
||||
; flipped, and CPUID isn't supported.
|
||||
cmp eax, ecx
|
||||
je .no_cpuid
|
||||
ret
|
||||
.no_cpuid:
|
||||
mov al, "1"
|
||||
jmp error
|
||||
|
||||
; Throw error 2 if the CPU doesn't support Long Mode.
|
||||
check_long_mode:
|
||||
; test if extended processor info in available
|
||||
mov eax, 0x80000000 ; implicit argument for cpuid
|
||||
cpuid ; get highest supported argument
|
||||
cmp eax, 0x80000001 ; it needs to be at least 0x80000001
|
||||
jb .no_long_mode ; if it's less, the CPU is too old for long mode
|
||||
|
||||
; use extended info to test if long mode is available
|
||||
mov eax, 0x80000001 ; argument for extended processor info
|
||||
cpuid ; returns various feature bits in ecx and edx
|
||||
test edx, 1 << 29 ; test if the LM-bit is set in the D-register
|
||||
jz .no_long_mode ; If it's not set, there is no long mode
|
||||
ret
|
||||
.no_long_mode:
|
||||
mov al, "2"
|
||||
jmp error
|
||||
|
||||
; Check for SSE and enable it. If it's not supported throw error "a".
|
||||
set_up_SSE:
|
||||
; check for SSE
|
||||
mov eax, 0x1
|
||||
cpuid
|
||||
test edx, 1<<25
|
||||
jz .no_SSE
|
||||
|
||||
; enable SSE
|
||||
mov eax, cr0
|
||||
and ax, 0xFFFB ; clear coprocessor emulation CR0.EM
|
||||
or ax, 0x2 ; set coprocessor monitoring CR0.MP
|
||||
mov cr0, eax
|
||||
mov eax, cr4
|
||||
or ax, 3 << 9 ; set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
|
||||
mov cr4, eax
|
||||
|
||||
ret
|
||||
.no_SSE:
|
||||
mov al, "a"
|
||||
jmp error
|
||||
|
||||
section .bss
|
||||
align 4096
|
||||
p4_table:
|
||||
resb 4096
|
||||
p3_table:
|
||||
resb 4096
|
||||
p2_table:
|
||||
resb 4096
|
||||
stack_bottom:
|
||||
resb 4096 * 4
|
||||
stack_top:
|
||||
|
||||
section .rodata
|
||||
gdt64:
|
||||
dq 0 ; zero entry
|
||||
.code: equ $ - gdt64 ; new
|
||||
dq (1<<44) | (1<<47) | (1<<43) | (1<<53) ; code segment
|
||||
.pointer:
|
||||
dw $ - gdt64 - 1
|
||||
dq gdt64
|
||||
@@ -1,16 +0,0 @@
|
||||
# Copyright 2016 Philipp Oppermann. See the README.md
|
||||
# file at the top-level directory of this distribution.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
set timeout=0
|
||||
set default=0
|
||||
|
||||
menuentry "my os" {
|
||||
multiboot2 /boot/kernel.bin
|
||||
boot
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 Philipp Oppermann. See the README.md
|
||||
file at the top-level directory of this distribution.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
option. This file may not be copied, modified, or distributed
|
||||
except according to those terms.
|
||||
*/
|
||||
|
||||
ENTRY(start)
|
||||
|
||||
SECTIONS {
|
||||
. = 1M;
|
||||
|
||||
.rodata :
|
||||
{
|
||||
/* ensure that the multiboot header is at the beginning */
|
||||
KEEP(*(.multiboot_header))
|
||||
*(.rodata .rodata.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data .data.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.bss :
|
||||
{
|
||||
*(.bss .bss.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.got :
|
||||
{
|
||||
*(.got)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.got.plt :
|
||||
{
|
||||
*(.got.plt)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.data.rel.ro : ALIGN(4K) {
|
||||
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.gcc_except_table : ALIGN(4K) {
|
||||
*(.gcc_except_table)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
; Copyright 2016 Philipp Oppermann. See the README.md
|
||||
; file at the top-level directory of this distribution.
|
||||
;
|
||||
; Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
; http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
; <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
; option. This file may not be copied, modified, or distributed
|
||||
; except according to those terms.
|
||||
|
||||
global long_mode_start
|
||||
extern rust_main
|
||||
|
||||
section .text
|
||||
bits 64
|
||||
long_mode_start:
|
||||
; load 0 into all data segment registers
|
||||
mov ax, 0
|
||||
mov ss, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
; call rust main (with multiboot pointer in rdi)
|
||||
call rust_main
|
||||
.os_returned:
|
||||
; rust main returned, print `OS returned!`
|
||||
mov rax, 0x4f724f204f534f4f
|
||||
mov [0xb8000], rax
|
||||
mov rax, 0x4f724f754f744f65
|
||||
mov [0xb8008], rax
|
||||
mov rax, 0x4f214f644f654f6e
|
||||
mov [0xb8010], rax
|
||||
hlt
|
||||
@@ -1,24 +0,0 @@
|
||||
; Copyright 2016 Philipp Oppermann. See the README.md
|
||||
; file at the top-level directory of this distribution.
|
||||
;
|
||||
; Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
; http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
; <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
; option. This file may not be copied, modified, or distributed
|
||||
; except according to those terms.
|
||||
|
||||
section .multiboot_header
|
||||
header_start:
|
||||
dd 0xe85250d6 ; magic number (multiboot 2)
|
||||
dd 0 ; architecture 0 (protected mode i386)
|
||||
dd header_end - header_start ; header length
|
||||
; checksum
|
||||
dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))
|
||||
|
||||
; insert optional multiboot tags here
|
||||
|
||||
; required end tag
|
||||
dw 0 ; type
|
||||
dw 0 ; flags
|
||||
dd 8 ; size
|
||||
header_end:
|
||||
@@ -1,95 +0,0 @@
|
||||
use x86_64::structures::tss::TaskStateSegment;
|
||||
use x86_64::structures::gdt::SegmentSelector;
|
||||
use x86_64::PrivilegeLevel;
|
||||
|
||||
pub struct Gdt {
|
||||
table: [u64; 8],
|
||||
next_free: usize,
|
||||
}
|
||||
|
||||
impl Gdt {
|
||||
pub fn new() -> Gdt {
|
||||
Gdt {
|
||||
table: [0; 8],
|
||||
next_free: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector {
|
||||
let index = match entry {
|
||||
Descriptor::UserSegment(value) => self.push(value),
|
||||
Descriptor::SystemSegment(value_low, value_high) => {
|
||||
let index = self.push(value_low);
|
||||
self.push(value_high);
|
||||
index
|
||||
}
|
||||
};
|
||||
SegmentSelector::new(index as u16, PrivilegeLevel::Ring0)
|
||||
}
|
||||
|
||||
fn push(&mut self, value: u64) -> usize {
|
||||
if self.next_free < self.table.len() {
|
||||
let index = self.next_free;
|
||||
self.table[index] = value;
|
||||
self.next_free += 1;
|
||||
index
|
||||
} else {
|
||||
panic!("GDT full");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&'static self) {
|
||||
use x86_64::instructions::tables::{DescriptorTablePointer, lgdt};
|
||||
use core::mem::size_of;
|
||||
|
||||
let ptr = DescriptorTablePointer {
|
||||
base: self.table.as_ptr() as u64,
|
||||
limit: (self.table.len() * size_of::<u64>() - 1) as u16,
|
||||
};
|
||||
|
||||
unsafe { lgdt(&ptr) };
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Descriptor {
|
||||
UserSegment(u64),
|
||||
SystemSegment(u64, u64),
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub fn kernel_code_segment() -> Descriptor {
|
||||
let flags = USER_SEGMENT | PRESENT | EXECUTABLE | LONG_MODE;
|
||||
Descriptor::UserSegment(flags.bits())
|
||||
}
|
||||
|
||||
pub fn tss_segment(tss: &'static TaskStateSegment) -> Descriptor {
|
||||
use core::mem::size_of;
|
||||
use bit_field::BitField;
|
||||
|
||||
let ptr = tss as *const _ as u64;
|
||||
|
||||
let mut low = PRESENT.bits();
|
||||
// base
|
||||
low.set_bits(16..40, ptr.get_bits(0..24));
|
||||
low.set_bits(56..64, ptr.get_bits(24..32));
|
||||
// limit (the `-1` in needed since the bound is inclusive)
|
||||
low.set_bits(0..16, (size_of::<TaskStateSegment>() - 1) as u64);
|
||||
// type (0b1001 = available 64-bit tss)
|
||||
low.set_bits(40..44, 0b1001);
|
||||
|
||||
let mut high = 0;
|
||||
high.set_bits(0..32, ptr.get_bits(32..64));
|
||||
|
||||
Descriptor::SystemSegment(low, high)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct DescriptorFlags: u64 {
|
||||
const CONFORMING = 1 << 42;
|
||||
const EXECUTABLE = 1 << 43;
|
||||
const USER_SEGMENT = 1 << 44;
|
||||
const PRESENT = 1 << 47;
|
||||
const LONG_MODE = 1 << 53;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use memory::MemoryController;
|
||||
use x86_64::structures::tss::TaskStateSegment;
|
||||
use x86_64::structures::idt::{Idt, ExceptionStackFrame, PageFaultErrorCode};
|
||||
use spin::Once;
|
||||
|
||||
mod gdt;
|
||||
|
||||
const DOUBLE_FAULT_IST_INDEX: usize = 0;
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: Idt = {
|
||||
let mut idt = Idt::new();
|
||||
|
||||
idt.divide_by_zero.set_handler_fn(divide_by_zero_handler);
|
||||
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
||||
idt.invalid_opcode.set_handler_fn(invalid_opcode_handler);
|
||||
idt.page_fault.set_handler_fn(page_fault_handler);
|
||||
|
||||
unsafe {
|
||||
idt.double_fault.set_handler_fn(double_fault_handler)
|
||||
.set_stack_index(DOUBLE_FAULT_IST_INDEX as u16);
|
||||
}
|
||||
|
||||
idt
|
||||
};
|
||||
}
|
||||
|
||||
static TSS: Once<TaskStateSegment> = Once::new();
|
||||
static GDT: Once<gdt::Gdt> = Once::new();
|
||||
|
||||
pub fn init(memory_controller: &mut MemoryController) {
|
||||
use x86_64::structures::gdt::SegmentSelector;
|
||||
use x86_64::instructions::segmentation::set_cs;
|
||||
use x86_64::instructions::tables::load_tss;
|
||||
use x86_64::VirtualAddress;
|
||||
|
||||
let double_fault_stack = memory_controller
|
||||
.alloc_stack(1)
|
||||
.expect("could not allocate double fault stack");
|
||||
|
||||
let tss = TSS.call_once(|| {
|
||||
let mut tss = TaskStateSegment::new();
|
||||
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] =
|
||||
VirtualAddress(double_fault_stack.top());
|
||||
tss
|
||||
});
|
||||
|
||||
let mut code_selector = SegmentSelector(0);
|
||||
let mut tss_selector = SegmentSelector(0);
|
||||
let gdt = GDT.call_once(|| {
|
||||
let mut gdt = gdt::Gdt::new();
|
||||
code_selector = gdt.add_entry(gdt::Descriptor::kernel_code_segment());
|
||||
tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&tss));
|
||||
gdt
|
||||
});
|
||||
gdt.load();
|
||||
|
||||
unsafe {
|
||||
// reload code segment register
|
||||
set_cs(code_selector);
|
||||
// load TSS
|
||||
load_tss(tss_selector);
|
||||
}
|
||||
|
||||
IDT.load();
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn divide_by_zero_handler(stack_frame: &mut ExceptionStackFrame) {
|
||||
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame);
|
||||
loop {}
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
|
||||
println!(
|
||||
"\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
|
||||
stack_frame.instruction_pointer,
|
||||
stack_frame
|
||||
);
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: &mut ExceptionStackFrame) {
|
||||
println!(
|
||||
"\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}",
|
||||
stack_frame.instruction_pointer,
|
||||
stack_frame
|
||||
);
|
||||
loop {}
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn page_fault_handler(
|
||||
stack_frame: &mut ExceptionStackFrame,
|
||||
error_code: PageFaultErrorCode,
|
||||
) {
|
||||
use x86_64::registers::control_regs;
|
||||
println!(
|
||||
"\nEXCEPTION: PAGE FAULT while accessing {:#x}\nerror code: \
|
||||
{:?}\n{:#?}",
|
||||
control_regs::cr2(),
|
||||
error_code,
|
||||
stack_frame
|
||||
);
|
||||
loop {}
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn double_fault_handler(
|
||||
stack_frame: &mut ExceptionStackFrame,
|
||||
_error_code: u64,
|
||||
) {
|
||||
println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
|
||||
loop {}
|
||||
}
|
||||
112
src/lib.rs
112
src/lib.rs
@@ -1,112 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![feature(lang_items)]
|
||||
#![feature(const_fn, unique)]
|
||||
#![feature(alloc)]
|
||||
#![feature(asm)]
|
||||
#![feature(naked_functions)]
|
||||
#![feature(abi_x86_interrupt)]
|
||||
#![feature(const_unique_new, const_atomic_usize_new)]
|
||||
#![feature(allocator_api)]
|
||||
#![feature(global_allocator)]
|
||||
#![no_std]
|
||||
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
extern crate rlibc;
|
||||
extern crate volatile;
|
||||
extern crate spin;
|
||||
extern crate multiboot2;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate x86_64;
|
||||
#[macro_use]
|
||||
extern crate once;
|
||||
extern crate linked_list_allocator;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate bit_field;
|
||||
#[macro_use]
|
||||
mod vga_buffer;
|
||||
mod memory;
|
||||
mod interrupts;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
// ATTENTION: we have a very small stack and no guard page
|
||||
vga_buffer::clear_screen();
|
||||
println!("Hello World{}", "!");
|
||||
|
||||
let boot_info = unsafe { multiboot2::load(multiboot_information_address) };
|
||||
enable_nxe_bit();
|
||||
enable_write_protect_bit();
|
||||
|
||||
// set up guard page and map the heap pages
|
||||
let mut memory_controller = memory::init(boot_info);
|
||||
|
||||
unsafe {
|
||||
HEAP_ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
|
||||
}
|
||||
|
||||
// initialize our IDT
|
||||
interrupts::init(&mut memory_controller);
|
||||
|
||||
fn stack_overflow() {
|
||||
stack_overflow(); // for each recursion, the return address is pushed
|
||||
}
|
||||
|
||||
// trigger a stack overflow
|
||||
stack_overflow();
|
||||
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn enable_nxe_bit() {
|
||||
use x86_64::registers::msr::{IA32_EFER, rdmsr, wrmsr};
|
||||
|
||||
let nxe_bit = 1 << 11;
|
||||
unsafe {
|
||||
let efer = rdmsr(IA32_EFER);
|
||||
wrmsr(IA32_EFER, efer | nxe_bit);
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_write_protect_bit() {
|
||||
use x86_64::registers::control_regs::{cr0, cr0_write, Cr0};
|
||||
|
||||
unsafe { cr0_write(cr0() | Cr0::WRITE_PROTECT) };
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[lang = "eh_personality"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn eh_personality() {}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[lang = "panic_fmt"]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
|
||||
println!("\n\nPANIC in {} at line {}:", file, line);
|
||||
println!(" {}", fmt);
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _Unwind_Resume() -> ! { loop {} }
|
||||
|
||||
use linked_list_allocator::LockedHeap;
|
||||
|
||||
pub const HEAP_START: usize = 0o_000_001_000_000_0000;
|
||||
pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB
|
||||
|
||||
#[global_allocator]
|
||||
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
|
||||
32
src/main.rs
Normal file
32
src/main.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
#![feature(lang_items)] // required for defining the panic handler
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
|
||||
//extern crate rlibc;
|
||||
|
||||
static HELLO: &[u8] = b"Hello World!";
|
||||
|
||||
#[no_mangle] // don't mangle the name of this function
|
||||
pub fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start_` by default
|
||||
let vga_buffer = 0xb8000 as *const u8 as *mut u8;
|
||||
|
||||
for (i, &byte) in HELLO.iter().enumerate() {
|
||||
unsafe {
|
||||
*vga_buffer.offset(i as isize * 2) = byte;
|
||||
*vga_buffer.offset(i as isize * 2 + 1) = 0xb;
|
||||
}
|
||||
}
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[lang = "panic_fmt"] // define a function that should be called on panic
|
||||
#[no_mangle] // TODO required?
|
||||
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
|
||||
_file: &'static str,
|
||||
_line: u32,
|
||||
_column: u32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use memory::{Frame, FrameAllocator};
|
||||
use multiboot2::{MemoryAreaIter, MemoryArea};
|
||||
|
||||
/// A frame allocator that uses the memory areas from the multiboot information structure as
|
||||
/// source. The {kernel, multiboot}_{start, end} fields are used to avoid returning memory that is
|
||||
/// already in use.
|
||||
///
|
||||
/// `kernel_end` and `multiboot_end` are _inclusive_ bounds.
|
||||
pub struct AreaFrameAllocator {
|
||||
next_free_frame: Frame,
|
||||
current_area: Option<&'static MemoryArea>,
|
||||
areas: MemoryAreaIter,
|
||||
kernel_start: Frame,
|
||||
kernel_end: Frame,
|
||||
multiboot_start: Frame,
|
||||
multiboot_end: Frame,
|
||||
}
|
||||
|
||||
impl AreaFrameAllocator {
|
||||
pub fn new(
|
||||
kernel_start: usize,
|
||||
kernel_end: usize,
|
||||
multiboot_start: usize,
|
||||
multiboot_end: usize,
|
||||
memory_areas: MemoryAreaIter,
|
||||
) -> AreaFrameAllocator {
|
||||
let mut allocator = AreaFrameAllocator {
|
||||
next_free_frame: Frame::containing_address(0),
|
||||
current_area: None,
|
||||
areas: memory_areas,
|
||||
kernel_start: Frame::containing_address(kernel_start),
|
||||
kernel_end: Frame::containing_address(kernel_end),
|
||||
multiboot_start: Frame::containing_address(multiboot_start),
|
||||
multiboot_end: Frame::containing_address(multiboot_end),
|
||||
};
|
||||
allocator.choose_next_area();
|
||||
allocator
|
||||
}
|
||||
|
||||
fn choose_next_area(&mut self) {
|
||||
self.current_area = self.areas
|
||||
.clone()
|
||||
.filter(|area| {
|
||||
let address = area.base_addr + area.length - 1;
|
||||
Frame::containing_address(address as usize) >= self.next_free_frame
|
||||
})
|
||||
.min_by_key(|area| area.base_addr);
|
||||
|
||||
if let Some(area) = self.current_area {
|
||||
let start_frame = Frame::containing_address(area.base_addr as usize);
|
||||
if self.next_free_frame < start_frame {
|
||||
self.next_free_frame = start_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameAllocator for AreaFrameAllocator {
|
||||
fn allocate_frame(&mut self) -> Option<Frame> {
|
||||
if let Some(area) = self.current_area {
|
||||
// "clone" the frame to return it if it's free. Frame doesn't
|
||||
// implement Clone, but we can construct an identical frame.
|
||||
let frame = Frame { number: self.next_free_frame.number };
|
||||
|
||||
// the last frame of the current area
|
||||
let current_area_last_frame = {
|
||||
let address = area.base_addr + area.length - 1;
|
||||
Frame::containing_address(address as usize)
|
||||
};
|
||||
|
||||
if frame > current_area_last_frame {
|
||||
// all frames of current area are used, switch to next area
|
||||
self.choose_next_area();
|
||||
} else if frame >= self.kernel_start && frame <= self.kernel_end {
|
||||
// `frame` is used by the kernel
|
||||
self.next_free_frame = Frame { number: self.kernel_end.number + 1 };
|
||||
} else if frame >= self.multiboot_start && frame <= self.multiboot_end {
|
||||
// `frame` is used by the multiboot information structure
|
||||
self.next_free_frame = Frame { number: self.multiboot_end.number + 1 };
|
||||
} else {
|
||||
// frame is unused, increment `next_free_frame` and return it
|
||||
self.next_free_frame.number += 1;
|
||||
return Some(frame);
|
||||
}
|
||||
// `frame` was not valid, try it again with the updated `next_free_frame`
|
||||
self.allocate_frame()
|
||||
} else {
|
||||
None // no free frames left
|
||||
}
|
||||
}
|
||||
|
||||
fn deallocate_frame(&mut self, _frame: Frame) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
pub use self::area_frame_allocator::AreaFrameAllocator;
|
||||
pub use self::paging::remap_the_kernel;
|
||||
pub use self::stack_allocator::Stack;
|
||||
use self::paging::PhysicalAddress;
|
||||
use multiboot2::BootInformation;
|
||||
|
||||
mod area_frame_allocator;
|
||||
pub mod heap_allocator;
|
||||
mod paging;
|
||||
mod stack_allocator;
|
||||
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
pub fn init(boot_info: &BootInformation) -> MemoryController {
|
||||
assert_has_not_been_called!("memory::init must be called only once");
|
||||
|
||||
let memory_map_tag = boot_info.memory_map_tag().expect("Memory map tag required");
|
||||
let elf_sections_tag = boot_info
|
||||
.elf_sections_tag()
|
||||
.expect("Elf sections tag required");
|
||||
|
||||
let kernel_start = elf_sections_tag
|
||||
.sections()
|
||||
.filter(|s| s.is_allocated())
|
||||
.map(|s| s.addr)
|
||||
.min()
|
||||
.unwrap();
|
||||
let kernel_end = elf_sections_tag
|
||||
.sections()
|
||||
.filter(|s| s.is_allocated())
|
||||
.map(|s| s.addr + s.size)
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"kernel start: {:#x}, kernel end: {:#x}",
|
||||
kernel_start,
|
||||
kernel_end
|
||||
);
|
||||
println!(
|
||||
"multiboot start: {:#x}, multiboot end: {:#x}",
|
||||
boot_info.start_address(),
|
||||
boot_info.end_address()
|
||||
);
|
||||
|
||||
let mut frame_allocator = AreaFrameAllocator::new(
|
||||
kernel_start as usize,
|
||||
kernel_end as usize,
|
||||
boot_info.start_address(),
|
||||
boot_info.end_address(),
|
||||
memory_map_tag.memory_areas(),
|
||||
);
|
||||
|
||||
let mut active_table = paging::remap_the_kernel(&mut frame_allocator, boot_info);
|
||||
|
||||
use self::paging::Page;
|
||||
use super::{HEAP_START, HEAP_SIZE};
|
||||
|
||||
let heap_start_page = Page::containing_address(HEAP_START);
|
||||
let heap_end_page = Page::containing_address(HEAP_START + HEAP_SIZE - 1);
|
||||
|
||||
for page in Page::range_inclusive(heap_start_page, heap_end_page) {
|
||||
active_table.map(page, paging::WRITABLE, &mut frame_allocator);
|
||||
}
|
||||
|
||||
let stack_allocator = {
|
||||
let stack_alloc_start = heap_end_page + 1;
|
||||
let stack_alloc_end = stack_alloc_start + 100;
|
||||
let stack_alloc_range = Page::range_inclusive(stack_alloc_start, stack_alloc_end);
|
||||
stack_allocator::StackAllocator::new(stack_alloc_range)
|
||||
};
|
||||
|
||||
MemoryController {
|
||||
active_table: active_table,
|
||||
frame_allocator: frame_allocator,
|
||||
stack_allocator: stack_allocator,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryController {
|
||||
active_table: paging::ActivePageTable,
|
||||
frame_allocator: AreaFrameAllocator,
|
||||
stack_allocator: stack_allocator::StackAllocator,
|
||||
}
|
||||
|
||||
impl MemoryController {
|
||||
pub fn alloc_stack(&mut self, size_in_pages: usize) -> Option<Stack> {
|
||||
let &mut MemoryController {
|
||||
ref mut active_table,
|
||||
ref mut frame_allocator,
|
||||
ref mut stack_allocator,
|
||||
} = self;
|
||||
stack_allocator.alloc_stack(active_table, frame_allocator, size_in_pages)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Frame {
|
||||
number: usize,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
fn containing_address(address: usize) -> Frame {
|
||||
Frame { number: address / PAGE_SIZE }
|
||||
}
|
||||
|
||||
fn start_address(&self) -> PhysicalAddress {
|
||||
self.number * PAGE_SIZE
|
||||
}
|
||||
|
||||
fn clone(&self) -> Frame {
|
||||
Frame { number: self.number }
|
||||
}
|
||||
|
||||
fn range_inclusive(start: Frame, end: Frame) -> FrameIter {
|
||||
FrameIter {
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameIter {
|
||||
start: Frame,
|
||||
end: Frame,
|
||||
}
|
||||
|
||||
impl Iterator for FrameIter {
|
||||
type Item = Frame;
|
||||
|
||||
fn next(&mut self) -> Option<Frame> {
|
||||
if self.start <= self.end {
|
||||
let frame = self.start.clone();
|
||||
self.start.number += 1;
|
||||
Some(frame)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FrameAllocator {
|
||||
fn allocate_frame(&mut self) -> Option<Frame>;
|
||||
fn deallocate_frame(&mut self, frame: Frame);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use memory::Frame;
|
||||
use multiboot2::ElfSection;
|
||||
|
||||
pub struct Entry(u64);
|
||||
|
||||
impl Entry {
|
||||
pub fn is_unused(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
pub fn set_unused(&mut self) {
|
||||
self.0 = 0;
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> EntryFlags {
|
||||
EntryFlags::from_bits_truncate(self.0)
|
||||
}
|
||||
|
||||
pub fn pointed_frame(&self) -> Option<Frame> {
|
||||
if self.flags().contains(PRESENT) {
|
||||
Some(Frame::containing_address(
|
||||
self.0 as usize & 0x000fffff_fffff000,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, frame: Frame, flags: EntryFlags) {
|
||||
assert!(frame.start_address() & !0x000fffff_fffff000 == 0);
|
||||
self.0 = (frame.start_address() as u64) | flags.bits();
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct EntryFlags: u64 {
|
||||
const PRESENT = 1 << 0;
|
||||
const WRITABLE = 1 << 1;
|
||||
const USER_ACCESSIBLE = 1 << 2;
|
||||
const WRITE_THROUGH = 1 << 3;
|
||||
const NO_CACHE = 1 << 4;
|
||||
const ACCESSED = 1 << 5;
|
||||
const DIRTY = 1 << 6;
|
||||
const HUGE_PAGE = 1 << 7;
|
||||
const GLOBAL = 1 << 8;
|
||||
const NO_EXECUTE = 1 << 63;
|
||||
}
|
||||
}
|
||||
|
||||
impl EntryFlags {
|
||||
pub fn from_elf_section_flags(section: &ElfSection) -> EntryFlags {
|
||||
use multiboot2::{ELF_SECTION_ALLOCATED, ELF_SECTION_WRITABLE, ELF_SECTION_EXECUTABLE};
|
||||
|
||||
let mut flags = EntryFlags::empty();
|
||||
|
||||
if section.flags().contains(ELF_SECTION_ALLOCATED) {
|
||||
// section is loaded to memory
|
||||
flags = flags | PRESENT;
|
||||
}
|
||||
if section.flags().contains(ELF_SECTION_WRITABLE) {
|
||||
flags = flags | WRITABLE;
|
||||
}
|
||||
if !section.flags().contains(ELF_SECTION_EXECUTABLE) {
|
||||
flags = flags | NO_EXECUTE;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::{VirtualAddress, PhysicalAddress, Page, ENTRY_COUNT};
|
||||
use super::entry::*;
|
||||
use super::table::{self, Table, Level4};
|
||||
use memory::{PAGE_SIZE, Frame, FrameAllocator};
|
||||
use core::ptr::Unique;
|
||||
|
||||
pub struct Mapper {
|
||||
p4: Unique<Table<Level4>>,
|
||||
}
|
||||
|
||||
impl Mapper {
|
||||
pub unsafe fn new() -> Mapper {
|
||||
Mapper { p4: Unique::new_unchecked(table::P4) }
|
||||
}
|
||||
|
||||
pub fn p4(&self) -> &Table<Level4> {
|
||||
unsafe { self.p4.as_ref() }
|
||||
}
|
||||
|
||||
pub fn p4_mut(&mut self) -> &mut Table<Level4> {
|
||||
unsafe { self.p4.as_mut() }
|
||||
}
|
||||
|
||||
pub fn translate(&self, virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
let offset = virtual_address % PAGE_SIZE;
|
||||
self.translate_page(Page::containing_address(virtual_address))
|
||||
.map(|frame| frame.number * PAGE_SIZE + offset)
|
||||
}
|
||||
|
||||
pub fn translate_page(&self, page: Page) -> Option<Frame> {
|
||||
let p3 = self.p4().next_table(page.p4_index());
|
||||
|
||||
let huge_page = || {
|
||||
p3.and_then(|p3| {
|
||||
let p3_entry = &p3[page.p3_index()];
|
||||
// 1GiB page?
|
||||
if let Some(start_frame) = p3_entry.pointed_frame() {
|
||||
if p3_entry.flags().contains(HUGE_PAGE) {
|
||||
// address must be 1GiB aligned
|
||||
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
|
||||
return Some(Frame {
|
||||
number: start_frame.number + page.p2_index() * ENTRY_COUNT +
|
||||
page.p1_index(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(p2) = p3.next_table(page.p3_index()) {
|
||||
let p2_entry = &p2[page.p2_index()];
|
||||
// 2MiB page?
|
||||
if let Some(start_frame) = p2_entry.pointed_frame() {
|
||||
if p2_entry.flags().contains(HUGE_PAGE) {
|
||||
// address must be 2MiB aligned
|
||||
assert!(start_frame.number % ENTRY_COUNT == 0);
|
||||
return Some(Frame { number: start_frame.number + page.p1_index() });
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
};
|
||||
|
||||
p3.and_then(|p3| p3.next_table(page.p3_index()))
|
||||
.and_then(|p2| p2.next_table(page.p2_index()))
|
||||
.and_then(|p1| p1[page.p1_index()].pointed_frame())
|
||||
.or_else(huge_page)
|
||||
}
|
||||
|
||||
pub fn map_to<A>(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
let mut p3 = self.p4_mut().next_table_create(page.p4_index(), allocator);
|
||||
let mut p2 = p3.next_table_create(page.p3_index(), allocator);
|
||||
let mut p1 = p2.next_table_create(page.p2_index(), allocator);
|
||||
|
||||
assert!(p1[page.p1_index()].is_unused());
|
||||
p1[page.p1_index()].set(frame, flags | PRESENT);
|
||||
}
|
||||
|
||||
pub fn map<A>(&mut self, page: Page, flags: EntryFlags, allocator: &mut A)
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
let frame = allocator.allocate_frame().expect("out of memory");
|
||||
self.map_to(page, frame, flags, allocator)
|
||||
}
|
||||
|
||||
pub fn identity_map<A>(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
let page = Page::containing_address(frame.start_address());
|
||||
self.map_to(page, frame, flags, allocator)
|
||||
}
|
||||
|
||||
pub fn unmap<A>(&mut self, page: Page, allocator: &mut A)
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
use x86_64::VirtualAddress;
|
||||
use x86_64::instructions::tlb;
|
||||
|
||||
assert!(self.translate(page.start_address()).is_some());
|
||||
|
||||
let p1 = self.p4_mut()
|
||||
.next_table_mut(page.p4_index())
|
||||
.and_then(|p3| p3.next_table_mut(page.p3_index()))
|
||||
.and_then(|p2| p2.next_table_mut(page.p2_index()))
|
||||
.expect("mapping code does not support huge pages");
|
||||
let frame = p1[page.p1_index()].pointed_frame().unwrap();
|
||||
p1[page.p1_index()].set_unused();
|
||||
tlb::flush(VirtualAddress(page.start_address()));
|
||||
// TODO free p(1,2,3) table if empty
|
||||
// allocator.deallocate_frame(frame);
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
pub use self::entry::*;
|
||||
use memory::{PAGE_SIZE, Frame, FrameAllocator};
|
||||
use self::temporary_page::TemporaryPage;
|
||||
pub use self::mapper::Mapper;
|
||||
use core::ops::{Add, Deref, DerefMut};
|
||||
use multiboot2::BootInformation;
|
||||
|
||||
mod entry;
|
||||
mod table;
|
||||
mod temporary_page;
|
||||
mod mapper;
|
||||
|
||||
const ENTRY_COUNT: usize = 512;
|
||||
|
||||
pub type PhysicalAddress = usize;
|
||||
pub type VirtualAddress = usize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Page {
|
||||
number: usize,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn containing_address(address: VirtualAddress) -> Page {
|
||||
assert!(
|
||||
address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000,
|
||||
"invalid address: 0x{:x}",
|
||||
address
|
||||
);
|
||||
Page { number: address / PAGE_SIZE }
|
||||
}
|
||||
|
||||
pub fn start_address(&self) -> usize {
|
||||
self.number * PAGE_SIZE
|
||||
}
|
||||
|
||||
fn p4_index(&self) -> usize {
|
||||
(self.number >> 27) & 0o777
|
||||
}
|
||||
fn p3_index(&self) -> usize {
|
||||
(self.number >> 18) & 0o777
|
||||
}
|
||||
fn p2_index(&self) -> usize {
|
||||
(self.number >> 9) & 0o777
|
||||
}
|
||||
fn p1_index(&self) -> usize {
|
||||
(self.number >> 0) & 0o777
|
||||
}
|
||||
|
||||
pub fn range_inclusive(start: Page, end: Page) -> PageIter {
|
||||
PageIter {
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for Page {
|
||||
type Output = Page;
|
||||
|
||||
fn add(self, rhs: usize) -> Page {
|
||||
Page { number: self.number + rhs }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PageIter {
|
||||
start: Page,
|
||||
end: Page,
|
||||
}
|
||||
|
||||
impl Iterator for PageIter {
|
||||
type Item = Page;
|
||||
|
||||
fn next(&mut self) -> Option<Page> {
|
||||
if self.start <= self.end {
|
||||
let page = self.start;
|
||||
self.start.number += 1;
|
||||
Some(page)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActivePageTable {
|
||||
mapper: Mapper,
|
||||
}
|
||||
|
||||
impl Deref for ActivePageTable {
|
||||
type Target = Mapper;
|
||||
|
||||
fn deref(&self) -> &Mapper {
|
||||
&self.mapper
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ActivePageTable {
|
||||
fn deref_mut(&mut self) -> &mut Mapper {
|
||||
&mut self.mapper
|
||||
}
|
||||
}
|
||||
|
||||
impl ActivePageTable {
|
||||
unsafe fn new() -> ActivePageTable {
|
||||
ActivePageTable { mapper: Mapper::new() }
|
||||
}
|
||||
|
||||
pub fn with<F>(
|
||||
&mut self,
|
||||
table: &mut InactivePageTable,
|
||||
temporary_page: &mut temporary_page::TemporaryPage, // new
|
||||
f: F,
|
||||
) where
|
||||
F: FnOnce(&mut Mapper),
|
||||
{
|
||||
use x86_64::registers::control_regs;
|
||||
use x86_64::instructions::tlb;
|
||||
|
||||
{
|
||||
let backup = Frame::containing_address(control_regs::cr3().0 as usize);
|
||||
|
||||
// map temporary_page to current p4 table
|
||||
let p4_table = temporary_page.map_table_frame(backup.clone(), self);
|
||||
|
||||
// overwrite recursive mapping
|
||||
self.p4_mut()[511].set(table.p4_frame.clone(), PRESENT | WRITABLE);
|
||||
tlb::flush_all();
|
||||
|
||||
// execute f in the new context
|
||||
f(self);
|
||||
|
||||
// restore recursive mapping to original p4 table
|
||||
p4_table[511].set(backup, PRESENT | WRITABLE);
|
||||
tlb::flush_all();
|
||||
}
|
||||
|
||||
temporary_page.unmap(self);
|
||||
}
|
||||
|
||||
pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable {
|
||||
use x86_64::PhysicalAddress;
|
||||
use x86_64::registers::control_regs;
|
||||
|
||||
let old_table = InactivePageTable {
|
||||
p4_frame: Frame::containing_address(control_regs::cr3().0 as usize),
|
||||
};
|
||||
unsafe {
|
||||
control_regs::cr3_write(PhysicalAddress(new_table.p4_frame.start_address() as u64));
|
||||
}
|
||||
old_table
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InactivePageTable {
|
||||
p4_frame: Frame,
|
||||
}
|
||||
|
||||
impl InactivePageTable {
|
||||
pub fn new(
|
||||
frame: Frame,
|
||||
active_table: &mut ActivePageTable,
|
||||
temporary_page: &mut TemporaryPage,
|
||||
) -> InactivePageTable {
|
||||
{
|
||||
let table = temporary_page.map_table_frame(frame.clone(), active_table);
|
||||
table.zero();
|
||||
table[511].set(frame.clone(), PRESENT | WRITABLE);
|
||||
}
|
||||
temporary_page.unmap(active_table);
|
||||
|
||||
InactivePageTable { p4_frame: frame }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation) -> ActivePageTable
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
let mut temporary_page = TemporaryPage::new(Page { number: 0xcafebabe }, allocator);
|
||||
|
||||
let mut active_table = unsafe { ActivePageTable::new() };
|
||||
let mut new_table = {
|
||||
let frame = allocator.allocate_frame().expect("no more frames");
|
||||
InactivePageTable::new(frame, &mut active_table, &mut temporary_page)
|
||||
};
|
||||
|
||||
active_table.with(&mut new_table, &mut temporary_page, |mapper| {
|
||||
let elf_sections_tag = boot_info
|
||||
.elf_sections_tag()
|
||||
.expect("Memory map tag required");
|
||||
|
||||
// identity map the allocated kernel sections
|
||||
for section in elf_sections_tag.sections() {
|
||||
if !section.is_allocated() {
|
||||
// section is not loaded to memory
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(
|
||||
section.addr as usize % PAGE_SIZE == 0,
|
||||
"sections need to be page aligned"
|
||||
);
|
||||
println!(
|
||||
"mapping section at addr: {:#x}, size: {:#x}",
|
||||
section.addr,
|
||||
section.size
|
||||
);
|
||||
|
||||
let flags = EntryFlags::from_elf_section_flags(section);
|
||||
|
||||
let start_frame = Frame::containing_address(section.start_address());
|
||||
let end_frame = Frame::containing_address(section.end_address() - 1);
|
||||
for frame in Frame::range_inclusive(start_frame, end_frame) {
|
||||
mapper.identity_map(frame, flags, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
// identity map the VGA text buffer
|
||||
let vga_buffer_frame = Frame::containing_address(0xb8000);
|
||||
mapper.identity_map(vga_buffer_frame, WRITABLE, allocator);
|
||||
|
||||
// identity map the multiboot info structure
|
||||
let multiboot_start = Frame::containing_address(boot_info.start_address());
|
||||
let multiboot_end = Frame::containing_address(boot_info.end_address() - 1);
|
||||
for frame in Frame::range_inclusive(multiboot_start, multiboot_end) {
|
||||
mapper.identity_map(frame, PRESENT, allocator);
|
||||
}
|
||||
});
|
||||
|
||||
let old_table = active_table.switch(new_table);
|
||||
println!("NEW TABLE!!!");
|
||||
|
||||
let old_p4_page = Page::containing_address(old_table.p4_frame.start_address());
|
||||
active_table.unmap(old_p4_page, allocator);
|
||||
println!("guard page at {:#x}", old_p4_page.start_address());
|
||||
|
||||
active_table
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use memory::paging::entry::*;
|
||||
use memory::paging::ENTRY_COUNT;
|
||||
use memory::FrameAllocator;
|
||||
use core::ops::{Index, IndexMut};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub const P4: *mut Table<Level4> = 0xffffffff_fffff000 as *mut _;
|
||||
|
||||
pub struct Table<L: TableLevel> {
|
||||
entries: [Entry; ENTRY_COUNT],
|
||||
level: PhantomData<L>,
|
||||
}
|
||||
|
||||
impl<L> Table<L>
|
||||
where
|
||||
L: TableLevel,
|
||||
{
|
||||
pub fn zero(&mut self) {
|
||||
for entry in self.entries.iter_mut() {
|
||||
entry.set_unused();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses are expected to be canonical (bits 48-63 must be the same as bit 47), otherwise the
|
||||
* CPU will #GP when we ask it to translate it.
|
||||
*/
|
||||
fn make_address_canonical(address : usize) -> usize {
|
||||
let sign_extension = 0o177777_000_000_000_000_0000 * ((address >> 47) & 0b1);
|
||||
(address & ((1 << 48) - 1)) | sign_extension
|
||||
}
|
||||
|
||||
impl<L> Table<L>
|
||||
where
|
||||
L: HierarchicalLevel,
|
||||
{
|
||||
fn next_table_address(&self, index: usize) -> Option<usize> {
|
||||
let entry_flags = self[index].flags();
|
||||
if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) {
|
||||
let table_address = self as *const _ as usize;
|
||||
Some(make_address_canonical((table_address << 9) | (index << 12)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_table(&self, index: usize) -> Option<&Table<L::NextLevel>> {
|
||||
self.next_table_address(index)
|
||||
.map(|address| unsafe { &*(address as *const _) })
|
||||
}
|
||||
|
||||
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<L::NextLevel>> {
|
||||
self.next_table_address(index)
|
||||
.map(|address| unsafe { &mut *(address as *mut _) })
|
||||
}
|
||||
|
||||
pub fn next_table_create<A>(
|
||||
&mut self,
|
||||
index: usize,
|
||||
allocator: &mut A,
|
||||
) -> &mut Table<L::NextLevel>
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
if self.next_table(index).is_none() {
|
||||
assert!(
|
||||
!self.entries[index].flags().contains(HUGE_PAGE),
|
||||
"mapping code does not support huge pages"
|
||||
);
|
||||
let frame = allocator.allocate_frame().expect("no frames available");
|
||||
self.entries[index].set(frame, PRESENT | WRITABLE);
|
||||
self.next_table_mut(index).unwrap().zero();
|
||||
}
|
||||
self.next_table_mut(index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<L> Index<usize> for Table<L>
|
||||
where
|
||||
L: TableLevel,
|
||||
{
|
||||
type Output = Entry;
|
||||
|
||||
fn index(&self, index: usize) -> &Entry {
|
||||
&self.entries[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<L> IndexMut<usize> for Table<L>
|
||||
where
|
||||
L: TableLevel,
|
||||
{
|
||||
fn index_mut(&mut self, index: usize) -> &mut Entry {
|
||||
&mut self.entries[index]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TableLevel {}
|
||||
|
||||
pub enum Level4 {}
|
||||
#[allow(dead_code)]
|
||||
pub enum Level3 {}
|
||||
#[allow(dead_code)]
|
||||
pub enum Level2 {}
|
||||
pub enum Level1 {}
|
||||
|
||||
impl TableLevel for Level4 {}
|
||||
impl TableLevel for Level3 {}
|
||||
impl TableLevel for Level2 {}
|
||||
impl TableLevel for Level1 {}
|
||||
|
||||
pub trait HierarchicalLevel: TableLevel {
|
||||
type NextLevel: TableLevel;
|
||||
}
|
||||
|
||||
impl HierarchicalLevel for Level4 {
|
||||
type NextLevel = Level3;
|
||||
}
|
||||
|
||||
impl HierarchicalLevel for Level3 {
|
||||
type NextLevel = Level2;
|
||||
}
|
||||
|
||||
impl HierarchicalLevel for Level2 {
|
||||
type NextLevel = Level1;
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::{Page, ActivePageTable, VirtualAddress};
|
||||
use super::table::{Table, Level1};
|
||||
use memory::{Frame, FrameAllocator};
|
||||
|
||||
pub struct TemporaryPage {
|
||||
page: Page,
|
||||
allocator: TinyAllocator,
|
||||
}
|
||||
|
||||
impl TemporaryPage {
|
||||
pub fn new<A>(page: Page, allocator: &mut A) -> TemporaryPage
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
TemporaryPage {
|
||||
page: page,
|
||||
allocator: TinyAllocator::new(allocator),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the temporary page to the given frame in the active table.
|
||||
/// Returns the start address of the temporary page.
|
||||
pub fn map(&mut self, frame: Frame, active_table: &mut ActivePageTable) -> VirtualAddress {
|
||||
use super::entry::WRITABLE;
|
||||
|
||||
assert!(
|
||||
active_table.translate_page(self.page).is_none(),
|
||||
"temporary page is already mapped"
|
||||
);
|
||||
active_table.map_to(self.page, frame, WRITABLE, &mut self.allocator);
|
||||
self.page.start_address()
|
||||
}
|
||||
|
||||
/// Maps the temporary page to the given page table frame in the active table.
|
||||
/// Returns a reference to the now mapped table.
|
||||
pub fn map_table_frame(
|
||||
&mut self,
|
||||
frame: Frame,
|
||||
active_table: &mut ActivePageTable,
|
||||
) -> &mut Table<Level1> {
|
||||
unsafe { &mut *(self.map(frame, active_table) as *mut Table<Level1>) }
|
||||
}
|
||||
|
||||
/// Unmaps the temporary page in the active table.
|
||||
pub fn unmap(&mut self, active_table: &mut ActivePageTable) {
|
||||
active_table.unmap(self.page, &mut self.allocator)
|
||||
}
|
||||
}
|
||||
|
||||
struct TinyAllocator([Option<Frame>; 3]);
|
||||
|
||||
impl TinyAllocator {
|
||||
fn new<A>(allocator: &mut A) -> TinyAllocator
|
||||
where
|
||||
A: FrameAllocator,
|
||||
{
|
||||
let mut f = || allocator.allocate_frame();
|
||||
let frames = [f(), f(), f()];
|
||||
TinyAllocator(frames)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameAllocator for TinyAllocator {
|
||||
fn allocate_frame(&mut self) -> Option<Frame> {
|
||||
for frame_option in &mut self.0 {
|
||||
if frame_option.is_some() {
|
||||
return frame_option.take();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn deallocate_frame(&mut self, frame: Frame) {
|
||||
for frame_option in &mut self.0 {
|
||||
if frame_option.is_none() {
|
||||
*frame_option = Some(frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("Tiny allocator can hold only 3 frames.");
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use memory::paging::{self, Page, PageIter, ActivePageTable};
|
||||
use memory::{PAGE_SIZE, FrameAllocator};
|
||||
|
||||
pub struct StackAllocator {
|
||||
range: PageIter,
|
||||
}
|
||||
|
||||
impl StackAllocator {
|
||||
pub fn new(page_range: PageIter) -> StackAllocator {
|
||||
StackAllocator { range: page_range }
|
||||
}
|
||||
}
|
||||
|
||||
impl StackAllocator {
|
||||
pub fn alloc_stack<FA: FrameAllocator>(
|
||||
&mut self,
|
||||
active_table: &mut ActivePageTable,
|
||||
frame_allocator: &mut FA,
|
||||
size_in_pages: usize,
|
||||
) -> Option<Stack> {
|
||||
if size_in_pages == 0 {
|
||||
return None; /* a zero sized stack makes no sense */
|
||||
}
|
||||
|
||||
// clone the range, since we only want to change it on success
|
||||
let mut range = self.range.clone();
|
||||
|
||||
// try to allocate the stack pages and a guard page
|
||||
let guard_page = range.next();
|
||||
let stack_start = range.next();
|
||||
let stack_end = if size_in_pages == 1 {
|
||||
stack_start
|
||||
} else {
|
||||
// choose the (size_in_pages-2)th element, since index
|
||||
// starts at 0 and we already allocated the start page
|
||||
range.nth(size_in_pages - 2)
|
||||
};
|
||||
|
||||
match (guard_page, stack_start, stack_end) {
|
||||
(Some(_), Some(start), Some(end)) => {
|
||||
// success! write back updated range
|
||||
self.range = range;
|
||||
|
||||
// map stack pages to physical frames
|
||||
for page in Page::range_inclusive(start, end) {
|
||||
active_table.map(page, paging::WRITABLE, frame_allocator);
|
||||
}
|
||||
|
||||
// create a new stack
|
||||
let top_of_stack = end.start_address() + PAGE_SIZE;
|
||||
Some(Stack::new(top_of_stack, start.start_address()))
|
||||
}
|
||||
_ => None, /* not enough pages */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stack {
|
||||
top: usize,
|
||||
bottom: usize,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
fn new(top: usize, bottom: usize) -> Stack {
|
||||
assert!(top > bottom);
|
||||
Stack {
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn top(&self) -> usize {
|
||||
self.top
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn bottom(&self) -> usize {
|
||||
self.bottom
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// Copyright 2016 Philipp Oppermann. See the README.md
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use core::ptr::Unique;
|
||||
use core::fmt;
|
||||
use spin::Mutex;
|
||||
use volatile::Volatile;
|
||||
|
||||
const BUFFER_HEIGHT: usize = 25;
|
||||
const BUFFER_WIDTH: usize = 80;
|
||||
|
||||
pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
|
||||
column_position: 0,
|
||||
color_code: ColorCode::new(Color::LightGreen, Color::Black),
|
||||
buffer: unsafe { Unique::new_unchecked(0xb8000 as *mut _) },
|
||||
});
|
||||
|
||||
macro_rules! println {
|
||||
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
||||
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||
}
|
||||
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ({
|
||||
$crate::vga_buffer::print(format_args!($($arg)*));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn print(args: fmt::Arguments) {
|
||||
use core::fmt::Write;
|
||||
WRITER.lock().write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
pub fn clear_screen() {
|
||||
for _ in 0..BUFFER_HEIGHT {
|
||||
println!("");
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum Color {
|
||||
Black = 0,
|
||||
Blue = 1,
|
||||
Green = 2,
|
||||
Cyan = 3,
|
||||
Red = 4,
|
||||
Magenta = 5,
|
||||
Brown = 6,
|
||||
LightGray = 7,
|
||||
DarkGray = 8,
|
||||
LightBlue = 9,
|
||||
LightGreen = 10,
|
||||
LightCyan = 11,
|
||||
LightRed = 12,
|
||||
Pink = 13,
|
||||
Yellow = 14,
|
||||
White = 15,
|
||||
}
|
||||
|
||||
pub struct Writer {
|
||||
column_position: usize,
|
||||
color_code: ColorCode,
|
||||
buffer: Unique<Buffer>,
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
pub fn write_byte(&mut self, byte: u8) {
|
||||
match byte {
|
||||
b'\n' => self.new_line(),
|
||||
byte => {
|
||||
if self.column_position >= BUFFER_WIDTH {
|
||||
self.new_line();
|
||||
}
|
||||
let row = BUFFER_HEIGHT - 1;
|
||||
let col = self.column_position;
|
||||
|
||||
let color_code = self.color_code;
|
||||
|
||||
self.buffer().chars[row][col].write(ScreenChar {
|
||||
ascii_character: byte,
|
||||
color_code: color_code,
|
||||
});
|
||||
self.column_position += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer(&mut self) -> &mut Buffer {
|
||||
unsafe { self.buffer.as_mut() }
|
||||
}
|
||||
|
||||
fn new_line(&mut self) {
|
||||
for row in 1..BUFFER_HEIGHT {
|
||||
for col in 0..BUFFER_WIDTH {
|
||||
let buffer = self.buffer();
|
||||
let character = buffer.chars[row][col].read();
|
||||
buffer.chars[row - 1][col].write(character);
|
||||
}
|
||||
}
|
||||
self.clear_row(BUFFER_HEIGHT - 1);
|
||||
self.column_position = 0;
|
||||
}
|
||||
|
||||
fn clear_row(&mut self, row: usize) {
|
||||
let blank = ScreenChar {
|
||||
ascii_character: b' ',
|
||||
color_code: self.color_code,
|
||||
};
|
||||
for col in 0..BUFFER_WIDTH {
|
||||
self.buffer().chars[row][col].write(blank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
|
||||
for byte in s.bytes() {
|
||||
self.write_byte(byte)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct ColorCode(u8);
|
||||
|
||||
impl ColorCode {
|
||||
const fn new(foreground: Color, background: Color) -> ColorCode {
|
||||
ColorCode((background as u8) << 4 | (foreground as u8))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct ScreenChar {
|
||||
ascii_character: u8,
|
||||
color_code: ColorCode,
|
||||
}
|
||||
|
||||
struct Buffer {
|
||||
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"linker-flavor": "gcc",
|
||||
"arch": "x86_64",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"target-c-int-width": "32",
|
||||
"arch": "x86_64",
|
||||
"os": "none",
|
||||
"linker-flavor": "ld",
|
||||
"linker": "ld.lld",
|
||||
"executables": true,
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
"disable-redzone": true,
|
||||
"panic": "abort"
|
||||
|
||||
Reference in New Issue
Block a user