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/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.** diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 00000000..0d1f3b73 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,50 @@ +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() }; + } + + unsafe { + super::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") + } +} diff --git a/src/lib.rs b/src/lib.rs index 45cae2de..f416c1e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,17 +2,25 @@ #![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"] -use core::panic::PanicInfo; +extern crate alloc; +use core::panic::PanicInfo; +use linked_list_allocator::LockedHeap; + +pub mod allocator; pub mod gdt; pub mod interrupts; pub mod memory; pub mod serial; pub mod vga_buffer; +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + pub fn init() { gdt::init(); interrupts::init_idt(); @@ -76,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) +} diff --git a/src/main.rs b/src/main.rs index 10b38955..de7cc0f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,9 @@ #![test_runner(blog_os::test_runner)] #![reexport_test_harness_main = "test_main"] +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; @@ -11,8 +14,8 @@ 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::{structures::paging::Page, VirtAddr}; println!("Hello World{}", "!"); blog_os::init(); @@ -20,13 +23,31 @@ 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); + 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) }; + // 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 freed 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(); diff --git a/tests/heap_allocation.rs b/tests/heap_allocation.rs new file mode 100644 index 00000000..c803ba77 --- /dev/null +++ b/tests/heap_allocation.rs @@ -0,0 +1,62 @@ +#![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) +}