From 48e2175bac11299d6f7dd134567d38e518bac17c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 Jun 2019 16:55:10 +0200 Subject: [PATCH 01/12] Add a dependency on the alloc crate --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 45cae2de..d27b7b49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,8 @@ #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] +extern crate alloc; + use core::panic::PanicInfo; pub mod gdt; From c0367074ac143c8952c7fdd7150b470ec9ad5739 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 Jun 2019 17:49:06 +0200 Subject: [PATCH 02/12] Create an allocator module with a dummy allocator --- src/allocator.rs | 14 ++++++++++++++ src/lib.rs | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/allocator.rs diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 00000000..ac452c10 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,14 @@ +use alloc::alloc::{GlobalAlloc, Layout}; +use core::ptr::null_mut; + +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") + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d27b7b49..320a3ca4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ extern crate alloc; use core::panic::PanicInfo; +pub mod allocator; pub mod gdt; pub mod interrupts; pub mod memory; From ebbc6d55d26b90a9ac0eceb8db6ba682b352553c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 Jun 2019 17:49:21 +0200 Subject: [PATCH 03/12] Use dummy allocator as global allocator --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 320a3ca4..94ee9290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,9 @@ pub mod memory; pub mod serial; pub mod vga_buffer; +#[global_allocator] +static ALLOCATOR: allocator::Dummy = allocator::Dummy; + pub fn init() { gdt::init(); interrupts::init_idt(); From 417c44159ea5de7d54335535c63288742f5912aa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 Jun 2019 17:50:35 +0200 Subject: [PATCH 04/12] Add a alloc_error_handler function --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 94ee9290..3e7673a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![cfg_attr(test, no_main)] #![feature(custom_test_frameworks)] #![feature(abi_x86_interrupt)] +#![feature(alloc_error_handler)] #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] @@ -82,3 +83,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) +} From d4623419b0be4d2dddad39b9d993a482e890c967 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 17 Jun 2019 17:51:12 +0200 Subject: [PATCH 05/12] Try to use Box type in main.rs This causes an allocation error because the Dummy::alloc function always returns a null pointer. --- src/main.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 10b38955..0d3e0df6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,12 @@ #![test_runner(blog_os::test_runner)] #![reexport_test_harness_main = "test_main"] +extern crate alloc; + use blog_os::println; use bootloader::{entry_point, BootInfo}; use core::panic::PanicInfo; +use alloc::boxed::Box; entry_point!(kernel_main); @@ -20,13 +23,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { let mut mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; - // map a previously unmapped page - let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); - memory::create_example_mapping(page, &mut mapper, &mut frame_allocator); - - // 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 x = Box::new(41); #[cfg(test)] test_main(); From 06fc63028ab3b4e111303b957356e937f625d437 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 13:14:56 +0200 Subject: [PATCH 06/12] Create a heap memory area --- src/allocator.rs | 34 +++++++++++++++++++++++++++++++++- src/main.rs | 5 ++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/allocator.rs b/src/allocator.rs index ac452c10..cf85bd6d 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -1,5 +1,37 @@ use alloc::alloc::{GlobalAlloc, Layout}; use core::ptr::null_mut; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + +pub const HEAP_START: usize = 0x_4444_4444_0000; +pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB + +pub fn init_heap( + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, +) -> Result<(), MapToError> { + 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; + unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; + } + + Ok(()) +} pub struct Dummy; @@ -11,4 +43,4 @@ unsafe impl GlobalAlloc for Dummy { unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { panic!("dealloc should be never called") } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 0d3e0df6..69e73633 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,8 @@ use alloc::boxed::Box; entry_point!(kernel_main); fn kernel_main(boot_info: &'static BootInfo) -> ! { + use blog_os::allocator; use blog_os::memory::{self, BootInfoFrameAllocator}; - use x86_64::{structures::paging::Page, VirtAddr}; println!("Hello World{}", "!"); blog_os::init(); @@ -23,6 +23,9 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { let mut mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; + allocator::init_heap(&mut mapper, &mut frame_allocator) + .expect("heap initialization failed"); + let x = Box::new(41); #[cfg(test)] From d7484ab48b740a5a9171b2ef622889aee9630e30 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 15:05:57 +0200 Subject: [PATCH 07/12] Use linked_list_allocator crate instead of dummy allocator --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/allocator.rs | 4 ++++ src/lib.rs | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index be6694de..c52c589a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ version = "0.1.0" dependencies = [ "bootloader 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "pc-keyboard 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "pic8259_simple 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -99,6 +100,14 @@ name = "libc" version = "0.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "linked_list_allocator" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "llvm-tools" version = "0.1.1" @@ -349,6 +358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" +"checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea" "checksum llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" diff --git a/Cargo.toml b/Cargo.toml index f7d9d20a..46e66f6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ x86_64 = "0.7.0" uart_16550 = "0.2.0" pic8259_simple = "0.1.1" pc-keyboard = "0.3.1" +linked_list_allocator = "0.6.4" [dependencies.lazy_static] version = "1.0" diff --git a/src/allocator.rs b/src/allocator.rs index cf85bd6d..0d1f3b73 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -30,6 +30,10 @@ pub fn init_heap( unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; } + unsafe { + super::ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE); + } + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 3e7673a5..f416c1e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ extern crate alloc; use core::panic::PanicInfo; +use linked_list_allocator::LockedHeap; pub mod allocator; pub mod gdt; @@ -18,7 +19,7 @@ pub mod serial; pub mod vga_buffer; #[global_allocator] -static ALLOCATOR: allocator::Dummy = allocator::Dummy; +static ALLOCATOR: LockedHeap = LockedHeap::empty(); pub fn init() { gdt::init(); From f429a8ab032647ed2a285dd7a536ef224a8adcd0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 15:06:40 +0200 Subject: [PATCH 08/12] Example use of Box, Vec, and Rc in kernel_main --- src/main.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 69e73633..3ce0b620 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ extern crate alloc; use blog_os::println; use bootloader::{entry_point, BootInfo}; use core::panic::PanicInfo; -use alloc::boxed::Box; +use alloc::{boxed::Box, vec, vec::Vec, rc::Rc}; entry_point!(kernel_main); @@ -26,7 +26,23 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { allocator::init_heap(&mut mapper, &mut frame_allocator) .expect("heap initialization failed"); - let x = Box::new(41); + // allocate a number on the heap + let heap_value = Box::new(41); + println!("heap_value at {:p}", heap_value); + + // create a dynamically sized vector + let mut vec = Vec::new(); + for i in 0..500 { + vec.push(i); + } + println!("vec at {:p}", vec.as_slice()); + + // create a reference counted vector -> will be deallocated when count reaches 0 + let reference_counted = Rc::new(vec![1, 2, 3]); + let cloned_reference = reference_counted.clone(); + println!("current reference count is {}", Rc::strong_count(&cloned_reference)); + core::mem::drop(reference_counted); + println!("reference count is {} now", Rc::strong_count(&cloned_reference)); #[cfg(test)] test_main(); From e5b6ba38ac74cb47ba6c1f639bdddff4b1861cb2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 16:33:20 +0200 Subject: [PATCH 09/12] Update Readme for new post --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33e5ea77..5d2fa573 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Blog OS (Paging Implementation) +# Blog OS (Heap Allocation) [![Azure Pipelines CI build](https://img.shields.io/azure-devops/build/phil-opp/blog_os/1/post-08.svg?label=Build&style=flat-square)](https://dev.azure.com/phil-opp/blog_os/_build?definitionId=1) -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.** From 5cf388439607c641a92611b98858a89b14fc87ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 16:59:38 +0200 Subject: [PATCH 10/12] Run cargo fmt --- src/main.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3ce0b620..5f2d1856 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,10 +6,10 @@ extern crate alloc; +use alloc::{boxed::Box, rc::Rc, vec, vec::Vec}; use blog_os::println; use bootloader::{entry_point, BootInfo}; use core::panic::PanicInfo; -use alloc::{boxed::Box, vec, vec::Vec, rc::Rc}; entry_point!(kernel_main); @@ -23,8 +23,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { let mut mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; - allocator::init_heap(&mut mapper, &mut frame_allocator) - .expect("heap initialization failed"); + allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); // allocate a number on the heap let heap_value = Box::new(41); @@ -40,9 +39,15 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { // create a reference counted vector -> will be deallocated when count reaches 0 let reference_counted = Rc::new(vec![1, 2, 3]); let cloned_reference = reference_counted.clone(); - println!("current reference count is {}", Rc::strong_count(&cloned_reference)); + println!( + "current reference count is {}", + Rc::strong_count(&cloned_reference) + ); core::mem::drop(reference_counted); - println!("reference count is {} now", Rc::strong_count(&cloned_reference)); + println!( + "reference count is {} now", + Rc::strong_count(&cloned_reference) + ); #[cfg(test)] test_main(); From df75f7f4e83d666ac5fa1b84066959635844922f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 16:59:56 +0200 Subject: [PATCH 11/12] Add an integration test for heap allocation --- tests/heap_allocation.rs | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/heap_allocation.rs diff --git a/tests/heap_allocation.rs b/tests/heap_allocation.rs new file mode 100644 index 00000000..a4548d9e --- /dev/null +++ b/tests/heap_allocation.rs @@ -0,0 +1,63 @@ +#![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::{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}; + + blog_os::init(); + let mut mapper = unsafe { memory::init(boot_info.physical_memory_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::(), (n - 1) * n / 2); + serial_println!("[ok]"); +} + +#[test_case] +fn many_boxes() { + serial_print!("many_boxes... "); + for i in 0..10_000 { + let x = Box::new(i); + assert_eq!(*x, i); + } + serial_println!("[ok]"); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + blog_os::test_panic_handler(info) +} From 4792ec41b13cb4e8df433a8adef3a12162ef5370 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 26 Jun 2019 21:08:08 +0200 Subject: [PATCH 12/12] Adjust comments to be equal with post --- src/main.rs | 2 +- tests/heap_allocation.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5f2d1856..de7cc0f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { } println!("vec at {:p}", vec.as_slice()); - // create a reference counted vector -> will be deallocated when count reaches 0 + // create a reference counted vector -> will be freed when count reaches 0 let reference_counted = Rc::new(vec![1, 2, 3]); let cloned_reference = reference_counted.clone(); println!( diff --git a/tests/heap_allocation.rs b/tests/heap_allocation.rs index a4548d9e..c803ba77 100644 --- a/tests/heap_allocation.rs +++ b/tests/heap_allocation.rs @@ -20,7 +20,6 @@ fn main(boot_info: &'static BootInfo) -> ! { blog_os::init(); let mut mapper = unsafe { memory::init(boot_info.physical_memory_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();