Compare commits

..

107 Commits

Author SHA1 Message Date
Philipp Oppermann
d21a78dc0b Switch back to official nightly 2020-03-22 17:30:00 +01:00
Philipp Oppermann
0186f65ece Use cooked-waker crate for creating wakers 2020-03-22 17:24:58 +01:00
Philipp Oppermann
2772abc8eb Implement proper wakeups using RawWaker 2020-03-22 16:54:47 +01:00
Philipp Oppermann
51f90236a5 Base dummy waker on RawWaker to avoid allocations
The Wake trait is based on Arc, which leads to deallocation on wakes. Since interrupts should not (de)allocate, this can lead to a deadlock.
2020-03-22 16:11:25 +01:00
Philipp Oppermann
90abd5c8c5 Add a keyboard task that prints keypresses 2020-03-22 12:45:24 +01:00
Philipp Oppermann
a6273614e4 Add waker support to ScancodeStream 2020-03-22 12:16:17 +01:00
Philipp Oppermann
3a2a468a0b Add a ScancodeStream based on the SCANCODE_QUEUE 2020-03-22 12:12:11 +01:00
Philipp Oppermann
05f6abd261 Fill the scancode queue on keyboard interrupts 2020-03-22 12:11:09 +01:00
Philipp Oppermann
255982a8b7 Implement scancode queue 2020-03-20 13:03:41 +01:00
Philipp Oppermann
f885f17b70 Implement a simple poll-loop executor 2020-03-19 16:46:37 +01:00
Philipp Oppermann
83b67df8af Merge branch 'post-10' into post-11 2020-03-08 14:39:38 +01:00
Philipp Oppermann
02c36bae29 Merge branch 'post-09' into post-10 2020-03-08 14:39:38 +01:00
Philipp Oppermann
714d9cef7d Merge branch 'post-10' into post-11 2020-03-08 14:30:38 +01:00
Philipp Oppermann
7b0d8f05ba Fix code for x86_64 v0.9.5 update 2020-03-08 14:30:33 +01:00
Philipp Oppermann
3735ae51c9 Merge branch 'post-10' into post-11 2020-03-08 14:29:03 +01:00
Philipp Oppermann
f230ee4258 Merge branch 'post-09' into post-10 2020-03-08 14:29:00 +01:00
Philipp Oppermann
a430d5ef06 Merge branch 'post-10' into post-11 2020-03-06 11:58:45 +01:00
Philipp Oppermann
39f14b6d0b Merge pull request #763 from phil-opp/post-10-update-linked-list-alloc
Update linked_list_allocator to v0.8.0
2020-03-06 11:58:23 +01:00
Philipp Oppermann
dd1daf2652 Update linked_list_allocator to v0.8.0 2020-03-06 11:47:49 +01:00
Philipp Oppermann
f4331daa25 Merge branch 'post-10' into post-11 2020-02-26 12:48:22 +01:00
Philipp Oppermann
9a415c85b9 Merge branch 'post-09' into post-10 2020-02-26 12:47:51 +01:00
Philipp Oppermann
921dd54207 Merge branch 'post-10' into post-11 2020-02-26 12:22:10 +01:00
Philipp Oppermann
c7cc718ae3 Merge branch 'post-09' into post-10 2020-02-26 12:22:06 +01:00
Philipp Oppermann
3a6d3153a4 Don't panic on overflow in allocator; return null pointer instead (#738) 2020-02-04 09:47:39 +01:00
Philipp Oppermann
9fb6c1d0bd Merge branch 'post-10' into post-11 2020-02-04 09:39:02 +01:00
Philipp Oppermann
5ed04baab0 Update linked_list_allocator dependency 2020-02-04 09:38:52 +01:00
Philipp Oppermann
00fedc801e Use LinkedListAllocator::lock instead of self.inner.lock() 2020-01-30 13:03:34 +01:00
Philipp Oppermann
0f74db4812 Implement align_up using align_offset from Rust's standard library (#723)
Improve `align_up` performance using a bitmask
2020-01-28 10:39:14 +01:00
Philipp Oppermann
93fd330ab9 Use bitmask instead of align_offset 2020-01-28 10:29:53 +01:00
Philipp Oppermann
3ad5f117c2 Use checked addition for allocator implementations (#726) 2020-01-27 13:25:08 +01:00
Philipp Oppermann
d1678f5a96 Implement align_up using align_offset from Rust's standard library 2020-01-22 11:35:29 +01:00
Philipp Oppermann
002d6f255f Set FixedSizeBlockAllocator as global allocator 2020-01-20 14:09:00 +01:00
Philipp Oppermann
6c3bf0b10f Implement GlobalAlloc::dealloc 2020-01-20 14:07:47 +01:00
Philipp Oppermann
7a792f5cb0 Implement GlobalAlloc::alloc 2020-01-20 14:07:02 +01:00
Philipp Oppermann
93b4dcf434 Add skeleton for GlobalAlloc implementation 2020-01-20 14:06:26 +01:00
Philipp Oppermann
821dd2adb4 Add function to calculate the list index 2020-01-20 14:05:24 +01:00
Philipp Oppermann
d636939b51 Add FixedSizeBlockAllocator::fallback_alloc method 2020-01-20 14:05:01 +01:00
Philipp Oppermann
9b7326541e Add FixedSizeBlockAllocator type 2020-01-20 14:04:13 +01:00
Philipp Oppermann
4f234b67ef Add ListNode type and BLOCK_SIZES constant 2020-01-20 14:02:57 +01:00
Philipp Oppermann
7381e11f3c Create a new fixed_size_block allocator submodule 2020-01-20 14:01:59 +01:00
Philipp Oppermann
a9fe65a0ce Use new LinkedListAllocator 2020-01-10 13:04:46 +01:00
Philipp Oppermann
2001814119 Implement LinkedListAllocator::size_align 2020-01-10 13:00:16 +01:00
Philipp Oppermann
a5c50e7408 Implement GlobalAlloc for LinkedListAllocator 2020-01-10 11:52:04 +01:00
Philipp Oppermann
70a52c291d Implement alloc_from_region 2020-01-10 11:48:56 +01:00
Philipp Oppermann
c56bfa27e4 Implement find_region 2020-01-10 11:46:10 +01:00
Philipp Oppermann
55aec9ebf3 Apply rustfmt to ListNode::new function 2020-01-10 11:44:38 +01:00
Philipp Oppermann
2e1d132a9a Implement add_free_region 2020-01-10 11:44:17 +01:00
Philipp Oppermann
63e8577d77 Create a basic LinkedListAllocator type 2020-01-10 11:42:04 +01:00
Philipp Oppermann
75d826bf69 Add a test that memory is reused with a long lived allocation
This test fails for the bump allocator because it can only free the complete heap at once, which is prevented by the single long-lived allocation.
2020-01-09 15:45:38 +01:00
Philipp Oppermann
45be3f0648 Use our BumpAllocator instead of linked_list_allocator crate 2020-01-09 15:37:43 +01:00
Philipp Oppermann
055c560a7a Add an align_up function 2020-01-09 15:36:06 +01:00
Philipp Oppermann
e87044a7ee Implement GlobalAlloc for BumpAllocator 2020-01-09 15:35:03 +01:00
Philipp Oppermann
08d2289dad Add a Locked wrapper type that can be used to implement GlobalAlloc 2020-01-09 15:34:04 +01:00
Philipp Oppermann
7c84dbaa1d Create a basic BumpAllocator type 2020-01-09 15:25:37 +01:00
Philipp Oppermann
882c83f9de Update many_boxes test to scale with heap size (#716)
Instead of using a hardcoded number of boxes, use the HEAP_SIZE constant. This ensures that we get a test failure because of an out-of-memory error when the allocator does not reuse freed memory.
2020-01-09 12:58:29 +01:00
Philipp Oppermann
869a69e531 Move #[global_allocator] into allocator module (#714)
The Rust issue that the #[global_allocator] cannot be defined in submodules was fixed.
2020-01-08 12:38:06 +01:00
Philipp Oppermann
817267e51c Update Github Actions badge for post-10 2019-12-12 09:32:59 +01:00
Philipp Oppermann
9e75078dab Merge branch 'post-09' into post-10 2019-12-12 09:32:46 +01:00
Philipp Oppermann
41f38d92f2 Merge branch 'post-09' into post-10 2019-12-12 09:23:31 +01:00
Philipp Oppermann
3ac5318c94 Remove now unneeded unsafe block
The `map_to` method is safe since x86_64 0.8.1.
2019-12-11 16:47:45 +01:00
Philipp Oppermann
e94a489a31 Merge branch 'post-09' into post-10 2019-12-11 16:38:08 +01:00
Philipp Oppermann
9da9ecb515 Merge branch 'post-09' into post-10 2019-12-10 17:31:24 +01:00
Philipp Oppermann
f226ba0c3f Merge branch 'post-09' into post-10 2019-12-10 17:17:53 +01:00
Philipp Oppermann
c1fc7171fb Merge branch 'post-09' into post-10 2019-12-10 16:55:41 +01:00
Philipp Oppermann
cf527e9ce7 Update post-10 to new lockfile format 2019-11-28 14:08:28 +01:00
Philipp Oppermann
c891acb234 Merge branch 'post-09' into post-10 2019-11-28 14:08:08 +01:00
Philipp Oppermann
b9a0702bc0 Merge branch 'post-09' into post-10 2019-11-25 13:35:20 +01:00
Philipp Oppermann
2919cbdefc Merge branch 'post-09' into post-10 2019-11-25 13:25:15 +01:00
Philipp Oppermann
059ea76848 Merge branch 'post-09' into post-10 2019-11-22 16:33:23 +01:00
Philipp Oppermann
2f085b7310 Merge branch 'post-09' into post-10 2019-11-22 16:05:13 +01:00
Philipp Oppermann
cc713e4570 Merge branch 'post-09' into post-10 2019-11-22 15:57:18 +01:00
Philipp Oppermann
b3066e9a78 Merge branch 'post-09' into post-10 2019-10-08 19:44:38 +02:00
Philipp Oppermann
66c3a0de76 Merge branch 'post-09' into post-10 2019-09-25 13:21:18 +02:00
Philipp Oppermann
96d9de76b0 Fix: memory::init expects a VirtAddr 2019-09-15 10:56:15 +02:00
Philipp Oppermann
f56e4d24e3 Add missing import 2019-09-14 19:08:53 +02:00
Philipp Oppermann
8d3d712cef Merge branch 'post-09' into post-10 2019-09-14 19:06:30 +02:00
Philipp Oppermann
5f66b437d5 Merge branch 'post-09' into post-10 2019-09-13 17:43:25 +02:00
Philipp Oppermann
0694f29946 Merge branch 'post-09' into post-10 2019-09-13 10:53:50 +02:00
Philipp Oppermann
889c0771d6 Merge branch 'post-09' into post-10 2019-09-11 13:33:07 +02:00
Philipp Oppermann
e74d9753f2 Merge branch 'post-09' into post-10 2019-09-11 11:21:15 +02:00
Philipp Oppermann
9fc71547d7 Run cargo update 2019-09-11 10:59:31 +02:00
Philipp Oppermann
caa9d8b0e4 Merge branch 'post-09' into post-10 2019-09-11 10:59:18 +02:00
Philipp Oppermann
29512ddd31 Merge branch 'post-09' into post-10 2019-09-11 10:32:57 +02:00
Philipp Oppermann
a7943e7e55 Merge branch 'post-09' into post-10 2019-09-11 10:12:46 +02:00
Philipp Oppermann
bcdadec5b6 Merge branch 'post-09' into post-10 2019-09-11 10:11:25 +02:00
Philipp Oppermann
17f8866264 Merge branch 'post-09' into post-10 2019-09-11 10:02:41 +02:00
Philipp Oppermann
a7ef4012d0 Merge branch 'post-09' into post-10 2019-09-10 11:12:54 +02:00
Philipp Oppermann
6f07c2b666 Merge branch 'post-09' into post-10 2019-09-10 10:21:14 +02:00
Philipp Oppermann
e89c77398d Merge branch 'post-09' into post-10 2019-08-07 12:40:04 +02:00
Philipp Oppermann
f3a7689f33 Merge branch 'post-09' into post-10 2019-07-22 10:46:35 +02:00
Philipp Oppermann
14d0e07b3e Merge branch 'post-09' into post-10 2019-07-22 10:36:58 +02:00
Philipp Oppermann
9651eb7a3b Merge branch 'post-09' into post-10 2019-07-18 10:17:50 +02:00
Philipp Oppermann
89e3e2b190 Use correct build badge in post-10 Readme 2019-07-07 11:26:15 +02:00
Philipp Oppermann
004282138c Merge branch 'post-09' into post-10 2019-07-07 11:25:48 +02:00
Philipp Oppermann
8c575388cf Merge pull request #626 from phil-opp/code-heap
Code for new heap allocation post
2019-06-26 21:30:42 +02:00
Philipp Oppermann
4792ec41b1 Adjust comments to be equal with post 2019-06-26 21:08:08 +02:00
Philipp Oppermann
df75f7f4e8 Add an integration test for heap allocation 2019-06-26 17:45:32 +02:00
Philipp Oppermann
5cf3884396 Run cargo fmt 2019-06-26 16:59:38 +02:00
Philipp Oppermann
e5b6ba38ac Update Readme for new post 2019-06-26 16:33:20 +02:00
Philipp Oppermann
f429a8ab03 Example use of Box, Vec, and Rc in kernel_main 2019-06-26 15:06:40 +02:00
Philipp Oppermann
d7484ab48b Use linked_list_allocator crate instead of dummy allocator 2019-06-26 15:05:57 +02:00
Philipp Oppermann
06fc63028a Create a heap memory area 2019-06-26 13:14:56 +02:00
Philipp Oppermann
d4623419b0 Try to use Box type in main.rs
This causes an allocation error because the Dummy::alloc function always returns a null pointer.
2019-06-26 12:34:57 +02:00
Philipp Oppermann
417c44159e Add a alloc_error_handler function 2019-06-26 12:34:57 +02:00
Philipp Oppermann
ebbc6d55d2 Use dummy allocator as global allocator 2019-06-26 12:34:57 +02:00
Philipp Oppermann
c0367074ac Create an allocator module with a dummy allocator 2019-06-26 12:34:57 +02:00
Philipp Oppermann
48e2175bac Add a dependency on the alloc crate 2019-06-26 12:34:57 +02:00
24 changed files with 1076 additions and 300 deletions

View File

@@ -1,7 +1,3 @@
[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
[build]
target = "x86_64-blog_os.json"

96
.github/workflows/build-code.yml vendored Normal file
View File

@@ -0,0 +1,96 @@
name: Build Code
on:
push:
branches:
- '*'
- '!staging.tmp'
tags:
- '*'
schedule:
- cron: '40 3 * * *' # every day at 3:40
pull_request:
jobs:
test:
name: "Test"
strategy:
matrix:
platform: [
ubuntu-latest,
macos-latest,
windows-latest
]
runs-on: ${{ matrix.platform }}
timeout-minutes: 15
steps:
- name: "Checkout Repository"
uses: actions/checkout@v1
- name: Install Rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly
echo ::add-path::$HOME/.cargo/bin
if: runner.os == 'macOS'
- name: "Print Rust Version"
run: |
rustc -Vv
cargo -Vv
- name: "Install Rustup Components"
run: rustup component add rust-src llvm-tools-preview
- name: "Install cargo-xbuild"
run: cargo install cargo-xbuild --debug
- name: "Install bootimage"
run: cargo install bootimage --debug
- name: "Run cargo xbuild"
run: cargo xbuild
- name: "Create Bootimage"
run: cargo bootimage
# install QEMU
- name: Install QEMU (Linux)
run: sudo apt update && sudo apt install qemu-system-x86
if: runner.os == 'Linux'
- name: Install QEMU (macOS)
run: brew install qemu
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
- name: Install Scoop (Windows)
run: |
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
echo ::add-path::$HOME\scoop\shims
if: runner.os == 'Windows'
shell: pwsh
- name: Install QEMU (Windows)
run: scoop install qemu
if: runner.os == 'Windows'
shell: pwsh
- name: "Print QEMU Version"
run: qemu-system-x86_64 --version
- name: "Run cargo xtest"
run: cargo xtest
check_formatting:
name: "Check Formatting"
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v1
- name: "Use the latest Rust nightly with rustfmt"
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
components: rustfmt
override: true
- run: cargo fmt -- --check

View File

@@ -1,130 +0,0 @@
name: Code
on:
push:
branches:
- '*'
- '!staging.tmp'
tags:
- '*'
schedule:
- cron: '40 3 * * *' # every day at 3:40
pull_request:
workflow_dispatch:
jobs:
check:
name: Check
strategy:
fail-fast: false
matrix:
platform: [
ubuntu-latest,
macos-latest,
windows-latest
]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
- name: Install `rust-src` Rustup Component
run: rustup component add rust-src
- name: Run `cargo check`
uses: actions-rs/cargo@v1
with:
command: check
test:
name: Test
strategy:
fail-fast: false
matrix:
platform: [
ubuntu-latest,
macos-latest,
windows-latest
]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
- name: Install bootimage
run: cargo install bootimage --debug
- name: Checkout Repository
uses: actions/checkout@v2
- name: Install Rustup Components
run: rustup component add rust-src llvm-tools-preview
- name: Run `cargo bootimage`
uses: actions-rs/cargo@v1
with:
command: bootimage
# install QEMU
- name: Install QEMU (Linux)
run: sudo apt update && sudo apt install qemu-system-x86
if: runner.os == 'Linux'
- name: Install QEMU (macOS)
run: brew install qemu
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
- name: Install QEMU (Windows)
run: |
choco install qemu --version 2021.5.5
echo "$Env:Programfiles\qemu" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
if: runner.os == 'Windows'
shell: pwsh
- name: "Print QEMU Version"
run: qemu-system-x86_64 --version
- name: Run `cargo test`
uses: actions-rs/cargo@v1
with:
command: test
check_formatting:
name: Check Formatting
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: rustfmt
override: true
- name: Run `cargo fmt`
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: clippy, rust-src
override: true
- name: Run `cargo clippy`
uses: actions-rs/cargo@v1
with:
command: clippy

243
Cargo.lock generated
View File

@@ -1,74 +1,210 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bit_field"
version = "0.10.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56"
[[package]]
name = "bitflags"
version = "1.3.2"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blog_os"
version = "0.1.0"
dependencies = [
"bootloader",
"conquer-once",
"cooked-waker",
"crossbeam-queue",
"futures-util",
"lazy_static",
"linked_list_allocator",
"pc-keyboard",
"pic8259",
"spin 0.5.2",
"pic8259_simple",
"spin",
"uart_16550",
"volatile 0.2.7",
"volatile",
"x86_64",
]
[[package]]
name = "bootloader"
version = "0.9.32"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea119c3ed05625c179e09d17d0914570a3753ca09c890a73d98f6b72aea00d2"
checksum = "152a28c753e229e037e910b4cd4cd16a90c53dd9a67fd751fa304b4b4a03970c"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "conquer-once"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7644600a548ecad74e4a918392af1798f7dd045be610be3203b9e129b4f98f"
dependencies = [
"conquer-util",
]
[[package]]
name = "conquer-util"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
[[package]]
name = "cooked-waker"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0032e8be0680b63daf14b0fd7fd57f85fd6897951e110b041d32e839a831914"
dependencies = [
"cooked-waker-derive",
"stowaway",
]
[[package]]
name = "cooked-waker-derive"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74ded02b04a8ff53ea7328cd71297cde598bee70d165ad92512bdb7e20be9d74"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cpuio"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22b8e308ccfc5acf3b82f79c0eac444cf6114cb2ac67a230ca6c177210068daa"
[[package]]
name = "crossbeam-queue"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
]
[[package]]
name = "futures-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
[[package]]
name = "futures-task"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
dependencies = [
"futures-core",
"futures-task",
"pin-utils",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
"spin 0.9.8",
"spin",
]
[[package]]
name = "linked_list_allocator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18c618c431dfe4419afbe22852f6aceffbc17bd82ba0a18b982def291000824c"
dependencies = [
"spinning_top",
]
[[package]]
name = "lock_api"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
dependencies = [
"scopeguard",
]
[[package]]
name = "pc-keyboard"
version = "0.7.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed089a1fbffe3337a1a345501c981f1eb1e47e69de5a40e852433e12953c3174"
checksum = "c48392db76c4e9a69e0b3be356c5f97ebb7b14413c5e4fd0af4755dbf86e2fce"
[[package]]
name = "pic8259"
version = "0.10.4"
name = "pic8259_simple"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb844b5b01db1e0b17938685738f113bfc903846f18932b378bc0eabfa40e194"
checksum = "dc64b2fd10828da8521b6cdabe0679385d7d2a3a6d4c336b819d1fa31ba35c72"
dependencies = [
"x86_64",
"cpuio",
]
[[package]]
name = "rustversion"
version = "1.0.22"
name = "pin-utils"
version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "spin"
@@ -77,42 +213,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
name = "spinning_top"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "32d801a3a53bcf5071f85fef8d5cab9e5f638fc5580a37e6eb7aba4b37438d24"
dependencies = [
"lock_api",
]
[[package]]
name = "stowaway"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b13d61e782863c0eaeeaf7e8e580dc956a625851fd2fb73e5e92db9601c891"
[[package]]
name = "syn"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "uart_16550"
version = "0.2.19"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614ff2a87880d4bd4374722268598a970bbad05ced8bf630439417347254ab2e"
checksum = "d44b0f30cb82b0fbc15b78ade1064226529ad52028bc8cb8accb98ff6f3d7131"
dependencies = [
"bitflags 1.3.2",
"rustversion",
"bitflags",
"x86_64",
]
[[package]]
name = "volatile"
version = "0.2.7"
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "volatile"
version = "0.4.6"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793"
checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29"
[[package]]
name = "x86_64"
version = "0.14.13"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491"
checksum = "4206b60c9f99766329b66962aa8ddc01df6c7edd02edc046b7a69d5df9fcdbcf"
dependencies = [
"bit_field",
"bitflags 2.9.2",
"rustversion",
"volatile 0.4.6",
"bitflags",
]

View File

@@ -2,7 +2,7 @@
name = "blog_os"
version = "0.1.0"
authors = ["Philipp Oppermann <dev@phil-opp.com>"]
edition = "2024"
edition = "2018"
[[test]]
name = "should_panic"
@@ -13,31 +13,37 @@ name = "stack_overflow"
harness = false
[dependencies]
bootloader = { version = "0.9", features = ["map_physical_memory"] }
bootloader = { version = "0.8.8", features = ["map_physical_memory"]}
volatile = "0.2.6"
spin = "0.5.2"
x86_64 = "0.14.2"
x86_64 = "0.9.6"
uart_16550 = "0.2.0"
pic8259 = "0.10.1"
pc-keyboard = "0.7.0"
pic8259_simple = "0.1.1"
pc-keyboard = "0.5.0"
linked_list_allocator = "0.8.0"
cooked-waker = "1.0.2"
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]
[[bin]]
name = "blog_os"
test = false
bench = false
[dependencies.crossbeam-queue]
version = "0.2.1"
default-features = false
features = ["alloc"]
[dependencies.conquer-once]
version = "0.2.0"
default-features = false
[dependencies.futures-util]
version = "0.3.4"
default-features = false
features = ["alloc"]
[package.metadata.bootimage]
test-args = [
"-device",
"isa-debug-exit,iobase=0xf4,iosize=0x04",
"-serial",
"stdio",
"-display",
"none",
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio",
"-display", "none"
]
test-success-exit-code = 33 # (0x10 << 1) | 1
test-success-exit-code = 33 # (0x10 << 1) | 1

View File

@@ -1,32 +1,28 @@
# Blog OS (Paging Implementation)
# Blog OS (Heap Allocation)
[![Build Status](https://github.com/phil-opp/blog_os/workflows/Code/badge.svg?branch=post-09)](https://github.com/phil-opp/blog_os/actions?query=workflow%3A%22Code%22+branch%3Apost-09)
[![Build Status](https://github.com/phil-opp/blog_os/workflows/Build%20Code/badge.svg?branch=post-10)](https://github.com/phil-opp/blog_os/actions?query=workflow%3A%22Build+Code%22+branch%3Apost-10)
This repository contains the source code for the [Paging Implementation][post] post of the [Writing an OS in Rust](https://os.phil-opp.com) series.
This repository contains the source code for the [Heap Allocation][post] post of the [Writing an OS in Rust](https://os.phil-opp.com) series.
[post]: https://os.phil-opp.com/paging-implementation/
[post]: https://os.phil-opp.com/heap-allocation/
**Check out the [master branch](https://github.com/phil-opp/blog_os) for more information.**
## Building
This project requires a nightly version of Rust because it uses some unstable features. At least nightly _2020-07-15_ is required for building. You might need to run `rustup update nightly --force` to update to the latest nightly even if some components such as `rustfmt` are missing it.
You can build the project by running:
You need a nightly Rust compiler. First you need to install the `cargo-xbuild` and `bootimage` tools:
```
cargo build
cargo install cargo-xbuild bootimage
```
To create a bootable disk image from the compiled kernel, you need to install the [`bootimage`] tool:
[`bootimage`]: https://github.com/rust-osdev/bootimage
Then you can build the project by running:
```
cargo install bootimage
cargo xbuild
```
After installing, you can create the bootable disk image by running:
To create a bootable disk image, run:
```
cargo bootimage
@@ -43,10 +39,10 @@ You can run the disk image in [QEMU] through:
[QEMU]: https://www.qemu.org/
```
cargo run
cargo xrun
```
[QEMU] and the [`bootimage`] tool need to be installed for this.
Of course [QEMU] needs to be installed for this.
You can also write the image to an USB stick for booting it on a real machine. On Linux, the command for this is:

82
src/allocator.rs Normal file
View File

@@ -0,0 +1,82 @@
use alloc::alloc::{GlobalAlloc, Layout};
use core::ptr::null_mut;
use fixed_size_block::FixedSizeBlockAllocator;
use x86_64::{
structures::paging::{
mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB,
},
VirtAddr,
};
pub mod bump;
pub mod fixed_size_block;
pub mod linked_list;
pub const HEAP_START: usize = 0x_4444_4444_0000;
pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB
#[global_allocator]
static ALLOCATOR: Locked<FixedSizeBlockAllocator> = Locked::new(FixedSizeBlockAllocator::new());
pub fn init_heap(
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<(), MapToError<Size4KiB>> {
let page_range = {
let heap_start = VirtAddr::new(HEAP_START as u64);
let heap_end = heap_start + HEAP_SIZE - 1u64;
let heap_start_page = Page::containing_address(heap_start);
let heap_end_page = Page::containing_address(heap_end);
Page::range_inclusive(heap_start_page, heap_end_page)
};
for page in page_range {
let frame = frame_allocator
.allocate_frame()
.ok_or(MapToError::FrameAllocationFailed)?;
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
mapper.map_to(page, frame, flags, frame_allocator)?.flush();
}
unsafe {
ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
}
Ok(())
}
pub struct Dummy;
unsafe impl GlobalAlloc for Dummy {
unsafe fn alloc(&self, _layout: Layout) -> *mut u8 {
null_mut()
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
panic!("dealloc should be never called")
}
}
/// A wrapper around spin::Mutex to permit trait implementations.
pub struct Locked<A> {
inner: spin::Mutex<A>,
}
impl<A> Locked<A> {
pub const fn new(inner: A) -> Self {
Locked {
inner: spin::Mutex::new(inner),
}
}
pub fn lock(&self) -> spin::MutexGuard<A> {
self.inner.lock()
}
}
/// Align the given address `addr` upwards to alignment `align`.
///
/// Requires that `align` is a power of two.
fn align_up(addr: usize, align: usize) -> usize {
(addr + align - 1) & !(align - 1)
}

61
src/allocator/bump.rs Normal file
View File

@@ -0,0 +1,61 @@
use super::{align_up, Locked};
use alloc::alloc::{GlobalAlloc, Layout};
use core::ptr;
pub struct BumpAllocator {
heap_start: usize,
heap_end: usize,
next: usize,
allocations: usize,
}
impl BumpAllocator {
/// Creates a new empty bump allocator.
pub const fn new() -> Self {
BumpAllocator {
heap_start: 0,
heap_end: 0,
next: 0,
allocations: 0,
}
}
/// Initializes the bump allocator with the given heap bounds.
///
/// This method is unsafe because the caller must ensure that the given
/// memory range is unused. Also, this method must be called only once.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
self.heap_start = heap_start;
self.heap_end = heap_start.saturating_add(heap_size);
self.next = heap_start;
}
}
unsafe impl GlobalAlloc for Locked<BumpAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut bump = self.lock(); // get a mutable reference
let alloc_start = align_up(bump.next, layout.align());
let alloc_end = match alloc_start.checked_add(layout.size()) {
Some(end) => end,
None => return ptr::null_mut(),
};
if alloc_end > bump.heap_end {
ptr::null_mut() // out of memory
} else {
bump.next = alloc_end;
bump.allocations += 1;
alloc_start as *mut u8
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
let mut bump = self.lock(); // get a mutable reference
bump.allocations -= 1;
if bump.allocations == 0 {
bump.next = bump.heap_start;
}
}
}

View File

@@ -0,0 +1,102 @@
use super::Locked;
use alloc::alloc::{GlobalAlloc, Layout};
use core::{
mem,
ptr::{self, NonNull},
};
/// The block sizes to use.
///
/// The sizes must each be power of 2 because they are also used as
/// the block alignment (alignments must be always powers of 2).
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
/// Choose an appropriate block size for the given layout.
///
/// Returns an index into the `BLOCK_SIZES` array.
fn list_index(layout: &Layout) -> Option<usize> {
let required_block_size = layout.size().max(layout.align());
BLOCK_SIZES.iter().position(|&s| s >= required_block_size)
}
struct ListNode {
next: Option<&'static mut ListNode>,
}
pub struct FixedSizeBlockAllocator {
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
fallback_allocator: linked_list_allocator::Heap,
}
impl FixedSizeBlockAllocator {
/// Creates an empty FixedSizeBlockAllocator.
pub const fn new() -> Self {
FixedSizeBlockAllocator {
list_heads: [None; BLOCK_SIZES.len()],
fallback_allocator: linked_list_allocator::Heap::empty(),
}
}
/// Initialize the allocator with the given heap bounds.
///
/// This function is unsafe because the caller must guarantee that the given
/// heap bounds are valid and that the heap is unused. This method must be
/// called only once.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
self.fallback_allocator.init(heap_start, heap_size);
}
/// Allocates using the fallback allocator.
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
match self.fallback_allocator.allocate_first_fit(layout) {
Ok(ptr) => ptr.as_ptr(),
Err(_) => ptr::null_mut(),
}
}
}
unsafe impl GlobalAlloc for Locked<FixedSizeBlockAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut allocator = self.lock();
match list_index(&layout) {
Some(index) => {
match allocator.list_heads[index].take() {
Some(node) => {
allocator.list_heads[index] = node.next.take();
node as *mut ListNode as *mut u8
}
None => {
// no block exists in list => allocate new block
let block_size = BLOCK_SIZES[index];
// only works if all block sizes are a power of 2
let block_align = block_size;
let layout = Layout::from_size_align(block_size, block_align).unwrap();
allocator.fallback_alloc(layout)
}
}
}
None => allocator.fallback_alloc(layout),
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let mut allocator = self.lock();
match list_index(&layout) {
Some(index) => {
let new_node = ListNode {
next: allocator.list_heads[index].take(),
};
// verify that block has size and alignment required for storing node
assert!(mem::size_of::<ListNode>() <= BLOCK_SIZES[index]);
assert!(mem::align_of::<ListNode>() <= BLOCK_SIZES[index]);
let new_node_ptr = ptr as *mut ListNode;
new_node_ptr.write(new_node);
allocator.list_heads[index] = Some(&mut *new_node_ptr);
}
None => {
let ptr = NonNull::new(ptr).unwrap();
allocator.fallback_allocator.deallocate(ptr, layout);
}
}
}
}

View File

@@ -0,0 +1,145 @@
use super::{align_up, Locked};
use alloc::alloc::{GlobalAlloc, Layout};
use core::{mem, ptr};
struct ListNode {
size: usize,
next: Option<&'static mut ListNode>,
}
impl ListNode {
const fn new(size: usize) -> Self {
ListNode { size, next: None }
}
fn start_addr(&self) -> usize {
self as *const Self as usize
}
fn end_addr(&self) -> usize {
self.start_addr() + self.size
}
}
pub struct LinkedListAllocator {
head: ListNode,
}
impl LinkedListAllocator {
/// Creates an empty LinkedListAllocator.
pub const fn new() -> Self {
Self {
head: ListNode::new(0),
}
}
/// Initialize the allocator with the given heap bounds.
///
/// This function is unsafe because the caller must guarantee that the given
/// heap bounds are valid and that the heap is unused. This method must be
/// called only once.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
self.add_free_region(heap_start, heap_size);
}
/// Adds the given memory region to the front of the list.
unsafe fn add_free_region(&mut self, addr: usize, size: usize) {
// ensure that the freed region is capable of holding ListNode
assert!(align_up(addr, mem::align_of::<ListNode>()) == addr);
assert!(size >= mem::size_of::<ListNode>());
// create a new list node and append it at the start of the list
let mut node = ListNode::new(size);
node.next = self.head.next.take();
let node_ptr = addr as *mut ListNode;
node_ptr.write(node);
self.head.next = Some(&mut *node_ptr)
}
/// Looks for a free region with the given size and alignment and removes
/// it from the list.
///
/// Returns a tuple of the list node and the start address of the allocation.
fn find_region(&mut self, size: usize, align: usize) -> Option<(&'static mut ListNode, usize)> {
// reference to current list node, updated for each iteration
let mut current = &mut self.head;
// look for a large enough memory region in linked list
while let Some(ref mut region) = current.next {
if let Ok(alloc_start) = Self::alloc_from_region(&region, size, align) {
// region suitable for allocation -> remove node from list
let next = region.next.take();
let ret = Some((current.next.take().unwrap(), alloc_start));
current.next = next;
return ret;
} else {
// region not suitable -> continue with next region
current = current.next.as_mut().unwrap();
}
}
// no suitable region found
None
}
/// Try to use the given region for an allocation with given size and alignment.
///
/// Returns the allocation start address on success.
fn alloc_from_region(region: &ListNode, size: usize, align: usize) -> Result<usize, ()> {
let alloc_start = align_up(region.start_addr(), align);
let alloc_end = alloc_start.checked_add(size).ok_or(())?;
if alloc_end > region.end_addr() {
// region too small
return Err(());
}
let excess_size = region.end_addr() - alloc_end;
if excess_size > 0 && excess_size < mem::size_of::<ListNode>() {
// rest of region too small to hold a ListNode (required because the
// allocation splits the region in a used and a free part)
return Err(());
}
// region suitable for allocation
Ok(alloc_start)
}
/// Adjust the given layout so that the resulting allocated memory
/// region is also capable of storing a `ListNode`.
///
/// Returns the adjusted size and alignment as a (size, align) tuple.
fn size_align(layout: Layout) -> (usize, usize) {
let layout = layout
.align_to(mem::align_of::<ListNode>())
.expect("adjusting alignment failed")
.pad_to_align();
let size = layout.size().max(mem::size_of::<ListNode>());
(size, layout.align())
}
}
unsafe impl GlobalAlloc for Locked<LinkedListAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// perform layout adjustments
let (size, align) = LinkedListAllocator::size_align(layout);
let mut allocator = self.lock();
if let Some((region, alloc_start)) = allocator.find_region(size, align) {
let alloc_end = alloc_start.checked_add(size).expect("overflow");
let excess_size = region.end_addr() - alloc_end;
if excess_size > 0 {
allocator.add_free_region(alloc_end, excess_size);
}
alloc_start as *mut u8
} else {
ptr::null_mut()
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// perform layout adjustments
let (size, _) = LinkedListAllocator::size_align(layout);
self.lock().add_free_region(ptr as usize, size)
}
}

View File

@@ -1,7 +1,7 @@
use lazy_static::lazy_static;
use x86_64::VirtAddr;
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
use x86_64::structures::tss::TaskStateSegment;
use x86_64::VirtAddr;
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
@@ -9,10 +9,10 @@ lazy_static! {
static ref TSS: TaskStateSegment = {
let mut tss = TaskStateSegment::new();
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
const STACK_SIZE: usize = 4096 * 5;
const STACK_SIZE: usize = 4096;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_start = VirtAddr::from_ptr(unsafe { &STACK });
let stack_end = stack_start + STACK_SIZE;
stack_end
};
@@ -41,12 +41,12 @@ struct Selectors {
}
pub fn init() {
use x86_64::instructions::segmentation::{CS, Segment};
use x86_64::instructions::segmentation::set_cs;
use x86_64::instructions::tables::load_tss;
GDT.0.load();
unsafe {
CS::set_reg(GDT.1.code_selector);
set_cs(GDT.1.code_selector);
load_tss(GDT.1.tss_selector);
}
}

View File

@@ -1,6 +1,6 @@
use crate::{gdt, hlt_loop, print, println};
use lazy_static::lazy_static;
use pic8259::ChainedPics;
use pic8259_simple::ChainedPics;
use spin;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
@@ -47,12 +47,12 @@ pub fn init_idt() {
IDT.load();
}
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn page_fault_handler(
stack_frame: InterruptStackFrame,
stack_frame: &mut InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
use x86_64::registers::control::Cr2;
@@ -65,13 +65,13 @@ extern "x86-interrupt" fn page_fault_handler(
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
stack_frame: &mut InterruptStackFrame,
_error_code: u64,
) -> ! {
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
print!(".");
unsafe {
PICS.lock()
@@ -79,32 +79,12 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFr
}
}
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
use pc_keyboard::{DecodedKey, HandleControl, Keyboard, ScancodeSet1, layouts};
use spin::Mutex;
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) {
use x86_64::instructions::port::Port;
lazy_static! {
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(
ScancodeSet1::new(),
layouts::Us104Key,
HandleControl::Ignore
));
}
let mut keyboard = KEYBOARD.lock();
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
crate::task::keyboard::add_scancode(scancode);
unsafe {
PICS.lock()
@@ -112,8 +92,13 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac
}
}
#[cfg(test)]
use crate::{serial_print, serial_println};
#[test_case]
fn test_breakpoint_exception() {
serial_print!("test_breakpoint_exception...");
// invoke a breakpoint exception
x86_64::instructions::interrupts::int3();
serial_println!("[ok]");
}

View File

@@ -2,15 +2,23 @@
#![cfg_attr(test, no_main)]
#![feature(custom_test_frameworks)]
#![feature(abi_x86_interrupt)]
#![feature(alloc_error_handler)]
#![feature(const_fn)]
#![feature(alloc_layout_extra)]
#![feature(const_in_array_repeat_expressions)]
#![test_runner(crate::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use core::panic::PanicInfo;
pub mod allocator;
pub mod gdt;
pub mod interrupts;
pub mod memory;
pub mod serial;
pub mod task;
pub mod vga_buffer;
pub fn init() {
@@ -19,25 +27,11 @@ pub fn init() {
unsafe { interrupts::PICS.lock().initialize() };
x86_64::instructions::interrupts::enable();
}
pub trait Testable {
fn run(&self) -> ();
}
impl<T> Testable for T
where
T: Fn(),
{
fn run(&self) {
serial_print!("{}...\t", core::any::type_name::<T>());
self();
serial_println!("[ok]");
}
}
pub fn test_runner(tests: &[&dyn Testable]) {
pub fn test_runner(tests: &[&dyn Fn()]) {
serial_println!("Running {} tests", tests.len());
for test in tests {
test.run();
test();
}
exit_qemu(QemuExitCode::Success);
}
@@ -72,7 +66,7 @@ pub fn hlt_loop() -> ! {
}
#[cfg(test)]
use bootloader::{BootInfo, entry_point};
use bootloader::{entry_point, BootInfo};
#[cfg(test)]
entry_point!(test_kernel_main);
@@ -90,3 +84,8 @@ fn test_kernel_main(_boot_info: &'static BootInfo) -> ! {
fn panic(info: &PanicInfo) -> ! {
test_panic_handler(info)
}
#[alloc_error_handler]
fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! {
panic!("allocation error: {:?}", layout)
}

View File

@@ -4,15 +4,19 @@
#![test_runner(blog_os::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use blog_os::println;
use bootloader::{BootInfo, entry_point};
use bootloader::{entry_point, BootInfo};
use core::panic::PanicInfo;
entry_point!(kernel_main);
fn kernel_main(boot_info: &'static BootInfo) -> ! {
use blog_os::allocator;
use blog_os::memory::{self, BootInfoFrameAllocator};
use x86_64::{VirtAddr, structures::paging::Page};
use blog_os::task::{keyboard, simple_executor::SimpleExecutor, Task};
use x86_64::VirtAddr;
println!("Hello World{}", "!");
blog_os::init();
@@ -21,13 +25,12 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
let mut mapper = unsafe { memory::init(phys_mem_offset) };
let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) };
// map an unused page
let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000));
memory::create_example_mapping(page, &mut mapper, &mut frame_allocator);
allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
// write the string `New!` to the screen through the new mapping
let page_ptr: *mut u64 = page.start_address().as_mut_ptr();
unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e) };
let mut executor = SimpleExecutor::new();
executor.spawn(Task::new(example_task()));
executor.spawn(Task::new(keyboard::print_keypresses()));
executor.run();
#[cfg(test)]
test_main();
@@ -36,6 +39,15 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
blog_os::hlt_loop();
}
async fn async_number() -> u32 {
42
}
async fn example_task() {
let number = async_number().await;
println!("async number: {}", number);
}
/// This function is called on panic.
#[cfg(not(test))]
#[panic_handler]
@@ -49,8 +61,3 @@ fn panic(info: &PanicInfo) -> ! {
fn panic(info: &PanicInfo) -> ! {
blog_os::test_panic_handler(info)
}
#[test_case]
fn trivial_assertion() {
assert_eq!(1, 1);
}

View File

@@ -1,9 +1,10 @@
use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
use x86_64::{
PhysAddr, VirtAddr,
structures::paging::{
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB,
UnusedPhysFrame,
},
PhysAddr, VirtAddr,
};
/// Initialize a new OffsetPageTable.
@@ -13,10 +14,8 @@ use x86_64::{
/// `physical_memory_offset`. Also, this function must be only called once
/// to avoid aliasing `&mut` references (which is undefined behavior).
pub unsafe fn init(physical_memory_offset: VirtAddr) -> OffsetPageTable<'static> {
unsafe {
let level_4_table = active_level_4_table(physical_memory_offset);
OffsetPageTable::new(level_4_table, physical_memory_offset)
}
let level_4_table = active_level_4_table(physical_memory_offset);
OffsetPageTable::new(level_4_table, physical_memory_offset)
}
/// Returns a mutable reference to the active level 4 table.
@@ -34,7 +33,7 @@ unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut
let virt = physical_memory_offset + phys.as_u64();
let page_table_ptr: *mut PageTable = virt.as_mut_ptr();
unsafe { &mut *page_table_ptr }
&mut *page_table_ptr // unsafe
}
/// Creates an example mapping for the given page to frame `0xb8000`.
@@ -46,12 +45,11 @@ pub fn create_example_mapping(
use x86_64::structures::paging::PageTableFlags as Flags;
let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000));
// FIXME: ONLY FOR TEMPORARY TESTING
let unused_frame = unsafe { UnusedPhysFrame::new(frame) };
let flags = Flags::PRESENT | Flags::WRITABLE;
let map_to_result = unsafe {
// FIXME: this is not safe, we do it only for testing
mapper.map_to(page, frame, flags, frame_allocator)
};
let map_to_result = mapper.map_to(page, unused_frame, flags, frame_allocator);
map_to_result.expect("map_to failed").flush();
}
@@ -59,7 +57,7 @@ pub fn create_example_mapping(
pub struct EmptyFrameAllocator;
unsafe impl FrameAllocator<Size4KiB> for EmptyFrameAllocator {
fn allocate_frame(&mut self) -> Option<PhysFrame> {
fn allocate_frame(&mut self) -> Option<UnusedPhysFrame> {
None
}
}
@@ -84,7 +82,7 @@ impl BootInfoFrameAllocator {
}
/// Returns an iterator over the usable frames specified in the memory map.
fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> {
fn usable_frames(&self) -> impl Iterator<Item = UnusedPhysFrame> {
// get usable regions from memory map
let regions = self.memory_map.iter();
let usable_regions = regions.filter(|r| r.region_type == MemoryRegionType::Usable);
@@ -93,12 +91,14 @@ impl BootInfoFrameAllocator {
// transform to an iterator of frame start addresses
let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096));
// create `PhysFrame` types from the start addresses
frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
let frames = frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)));
// we know that the frames are really unused
frames.map(|f| unsafe { UnusedPhysFrame::new(f) })
}
}
unsafe impl FrameAllocator<Size4KiB> for BootInfoFrameAllocator {
fn allocate_frame(&mut self) -> Option<PhysFrame> {
fn allocate_frame(&mut self) -> Option<UnusedPhysFrame> {
let frame = self.usable_frames().nth(self.next);
self.next += 1;
frame

79
src/task/keyboard.rs Normal file
View File

@@ -0,0 +1,79 @@
use crate::print;
use crate::println;
use conquer_once::spin::OnceCell;
use core::{
pin::Pin,
task::{Context, Poll},
};
use crossbeam_queue::ArrayQueue;
use futures_util::stream::StreamExt;
use futures_util::{stream::Stream, task::AtomicWaker};
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
static WAKER: AtomicWaker = AtomicWaker::new();
/// Called by the keyboard interrupt handler
///
/// Must not block or allocate.
pub(crate) fn add_scancode(scancode: u8) {
if let Ok(queue) = SCANCODE_QUEUE.try_get() {
if let Err(_) = queue.push(scancode) {
println!("WARNING: scancode queue full; dropping keyboard input");
} else {
WAKER.wake();
}
} else {
println!("WARNING: scancode queue uninitialized");
}
}
pub struct ScancodeStream {
_private: (),
}
impl ScancodeStream {
pub fn new() -> Self {
SCANCODE_QUEUE
.try_init_once(|| ArrayQueue::new(100))
.expect("ScancodeStream::new should only be called once");
ScancodeStream { _private: () }
}
}
impl Stream for ScancodeStream {
type Item = u8;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<u8>> {
let queue = SCANCODE_QUEUE
.try_get()
.expect("scancode queue not initialized");
// fast path
if let Ok(scancode) = queue.pop() {
return Poll::Ready(Some(scancode));
}
WAKER.register(&cx.waker());
match queue.pop() {
Ok(scancode) => Poll::Ready(Some(scancode)),
Err(crossbeam_queue::PopError) => Poll::Pending,
}
}
}
pub async fn print_keypresses() {
let mut scancodes = ScancodeStream::new();
let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore);
while let Some(scancode) = scancodes.next().await {
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
}
}

26
src/task/mod.rs Normal file
View File

@@ -0,0 +1,26 @@
use alloc::boxed::Box;
use core::task::{Context, Poll};
use core::{future::Future, pin::Pin};
pub mod keyboard;
pub mod simple_executor;
pub struct Task {
future: Pin<Box<dyn Future<Output = ()>>>,
}
impl Task {
pub fn new(future: impl Future<Output = ()> + 'static) -> Task {
Task {
future: Box::pin(future),
}
}
fn poll(&mut self, context: &mut Context) -> Poll<()> {
self.future.as_mut().poll(context)
}
fn id(&self) -> usize {
&*self.future as *const _ as *const () as usize
}
}

View File

@@ -0,0 +1,86 @@
use super::Task;
use alloc::{
collections::{BTreeMap, VecDeque},
sync::Arc,
};
use cooked_waker::IntoWaker;
use core::task::{Context, Poll};
use crossbeam_queue::ArrayQueue;
pub struct SimpleExecutor {
task_queue: VecDeque<Task>,
waiting_tasks: BTreeMap<usize, Task>,
wake_queue: Arc<ArrayQueue<usize>>,
}
impl SimpleExecutor {
pub fn new() -> SimpleExecutor {
SimpleExecutor {
task_queue: VecDeque::new(),
waiting_tasks: BTreeMap::new(),
wake_queue: Arc::new(ArrayQueue::new(100)),
}
}
pub fn spawn(&mut self, task: Task) {
self.task_queue.push_back(task)
}
pub fn run(&mut self) {
loop {
self.handle_wakeups();
self.run_ready_tasks();
}
}
fn handle_wakeups(&mut self) {
while let Ok(task_id) = self.wake_queue.pop() {
if let Some(task) = self.waiting_tasks.remove(&task_id) {
self.task_queue.push_back(task);
}
}
}
fn run_ready_tasks(&mut self) {
while let Some(mut task) = self.task_queue.pop_front() {
let waker = TaskWaker {
task_id: task.id(),
wake_queue: self.wake_queue.clone(),
}
.into_waker();
let mut context = Context::from_waker(&waker);
match task.poll(&mut context) {
Poll::Ready(()) => {} // task done
Poll::Pending => {
if self.waiting_tasks.insert(task.id(), task).is_some() {
panic!("Same task inserted into waiting_tasks twice");
}
}
}
}
}
}
#[derive(Debug, Clone, IntoWaker)]
struct TaskWaker {
task_id: usize,
wake_queue: Arc<ArrayQueue<usize>>,
}
impl TaskWaker {
fn wake_task(&self) {
self.wake_queue.push(self.task_id).expect("wake queue full");
}
}
impl cooked_waker::WakeRef for TaskWaker {
fn wake_by_ref(&self) {
self.wake_task();
}
}
impl cooked_waker::Wake for TaskWaker {
fn wake(self) {
self.wake_task();
}
}

View File

@@ -3,6 +3,9 @@ use lazy_static::lazy_static;
use spin::Mutex;
use volatile::Volatile;
#[cfg(test)]
use crate::{serial_print, serial_println};
lazy_static! {
/// A global `Writer` instance that can be used for printing to the VGA text buffer.
///
@@ -177,14 +180,18 @@ pub fn _print(args: fmt::Arguments) {
#[test_case]
fn test_println_simple() {
serial_print!("test_println... ");
println!("test_println_simple output");
serial_println!("[ok]");
}
#[test_case]
fn test_println_many() {
serial_print!("test_println_many... ");
for _ in 0..200 {
println!("test_println_many output");
}
serial_println!("[ok]");
}
#[test_case]
@@ -192,6 +199,8 @@ fn test_println_output() {
use core::fmt::Write;
use x86_64::instructions::interrupts;
serial_print!("test_println_output... ");
let s = "Some test string that fits on a single line";
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
@@ -201,4 +210,6 @@ fn test_println_output() {
assert_eq!(char::from(screen_char.ascii_character), c);
}
});
serial_println!("[ok]");
}

View File

@@ -4,10 +4,10 @@
#![test_runner(blog_os::test_runner)]
#![reexport_test_harness_main = "test_main"]
use blog_os::println;
use blog_os::{println, serial_print, serial_println};
use core::panic::PanicInfo;
#[unsafe(no_mangle)] // don't mangle the name of this function
#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
test_main();
@@ -21,5 +21,7 @@ fn panic(info: &PanicInfo) -> ! {
#[test_case]
fn test_println() {
serial_print!("test_println... ");
println!("test_println output");
serial_println!("[ok]");
}

76
tests/heap_allocation.rs Normal file
View File

@@ -0,0 +1,76 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(blog_os::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
use blog_os::{allocator::HEAP_SIZE, serial_print, serial_println};
use bootloader::{entry_point, BootInfo};
use core::panic::PanicInfo;
entry_point!(main);
fn main(boot_info: &'static BootInfo) -> ! {
use blog_os::allocator;
use blog_os::memory::{self, BootInfoFrameAllocator};
use x86_64::VirtAddr;
blog_os::init();
let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset);
let mut mapper = unsafe { memory::init(phys_mem_offset) };
let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) };
allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed");
test_main();
loop {}
}
#[test_case]
fn simple_allocation() {
serial_print!("simple_allocation... ");
let heap_value = Box::new(41);
assert_eq!(*heap_value, 41);
serial_println!("[ok]");
}
#[test_case]
fn large_vec() {
serial_print!("large_vec... ");
let n = 1000;
let mut vec = Vec::new();
for i in 0..n {
vec.push(i);
}
assert_eq!(vec.iter().sum::<u64>(), (n - 1) * n / 2);
serial_println!("[ok]");
}
#[test_case]
fn many_boxes() {
serial_print!("many_boxes... ");
for i in 0..HEAP_SIZE {
let x = Box::new(i);
assert_eq!(*x, i);
}
serial_println!("[ok]");
}
#[test_case]
fn many_boxes_long_lived() {
serial_print!("many_boxes_long_lived... ");
let long_lived = Box::new(1); // new
for i in 0..HEAP_SIZE {
let x = Box::new(i);
assert_eq!(*x, i);
}
assert_eq!(*long_lived, 1); // new
serial_println!("[ok]");
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
blog_os::test_panic_handler(info)
}

View File

@@ -1,10 +1,10 @@
#![no_std]
#![no_main]
use blog_os::{QemuExitCode, exit_qemu, serial_print, serial_println};
use blog_os::{exit_qemu, serial_print, serial_println, QemuExitCode};
use core::panic::PanicInfo;
#[unsafe(no_mangle)]
#[no_mangle]
pub extern "C" fn _start() -> ! {
should_fail();
serial_println!("[test did not panic]");
@@ -13,7 +13,7 @@ pub extern "C" fn _start() -> ! {
}
fn should_fail() {
serial_print!("should_panic::should_fail...\t");
serial_print!("should_fail... ");
assert_eq!(0, 1);
}

View File

@@ -2,14 +2,14 @@
#![no_main]
#![feature(abi_x86_interrupt)]
use blog_os::{QemuExitCode, exit_qemu, serial_print, serial_println};
use blog_os::{exit_qemu, serial_print, serial_println, QemuExitCode};
use core::panic::PanicInfo;
use lazy_static::lazy_static;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
#[unsafe(no_mangle)]
#[no_mangle]
pub extern "C" fn _start() -> ! {
serial_print!("stack_overflow::stack_overflow...\t");
serial_print!("stack_overflow... ");
blog_os::gdt::init();
init_test_idt();
@@ -23,7 +23,6 @@ pub extern "C" fn _start() -> ! {
#[allow(unconditional_recursion)]
fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
volatile::Volatile::new(0).read(); // prevent tail recursion optimizations
}
lazy_static! {
@@ -44,7 +43,7 @@ pub fn init_test_idt() {
}
extern "x86-interrupt" fn test_double_fault_handler(
_stack_frame: InterruptStackFrame,
_stack_frame: &mut InterruptStackFrame,
_error_code: u64,
) -> ! {
serial_println!("[ok]");

View File

@@ -1,16 +1,15 @@
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": 32,
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float",
"rustc-abi": "x86-softfloat"
"features": "-mmx,-sse,+soft-float"
}