From ee4dd3e2f262340784c3816db0bac924b63e6499 Mon Sep 17 00:00:00 2001 From: Within Date: Wed, 23 Oct 2019 20:30:47 -0400 Subject: [PATCH] allocation :D --- Cargo.lock | 10 ++++ Cargo.toml | 3 +- src/allocator.rs | 62 ++++++++++++++++++++++++ src/gdt.rs | 14 ++++-- src/interrupts.rs | 52 ++++++++++++-------- src/lib.rs | 34 ++++++++++--- src/main.rs | 19 ++++++-- src/memory.rs | 101 +++++++++++++++++++++++++++++++++++++++ src/serial.rs | 9 ++-- tests/basic_boot.rs | 6 ++- tests/heap_allocation.rs | 53 ++++++++++++++++++++ tests/should_panic.rs | 2 +- tests/stack_overflow.rs | 2 +- 13 files changed, 325 insertions(+), 42 deletions(-) create mode 100644 src/allocator.rs create mode 100644 src/memory.rs create mode 100644 tests/heap_allocation.rs diff --git a/Cargo.lock b/Cargo.lock index 745f6c2..24b9c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,14 @@ dependencies = [ "spin 0.5.2 (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.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -101,6 +109,7 @@ version = "0.1.0" dependencies = [ "bootloader 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.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.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -117,6 +126,7 @@ dependencies = [ "checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" "checksum cpuio 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22b8e308ccfc5acf3b82f79c0eac444cf6114cb2ac67a230ca6c177210068daa" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum pc-keyboard 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fff50ab09ba31bcebc0669f4e64c0952fae1acdca9e6e0587e68e4e8443808ac" "checksum pic8259_simple 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc64b2fd10828da8521b6cdabe0679385d7d2a3a6d4c336b819d1fa31ba35c72" diff --git a/Cargo.toml b/Cargo.toml index ca9903c..af6bb9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,14 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader = "0.8.0" +bootloader = { version = "0.8.0", features = ["map_physical_memory"]} volatile = "0.2.6" spin = "0.5.2" x86_64 = "0.7.5" 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 new file mode 100644 index 0000000..03b494f --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,62 @@ +use alloc::alloc::{GlobalAlloc, Layout}; +use bootloader::BootInfo; +use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; +use core::ptr::null_mut; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + +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") + } +} + +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 fn init(boot_info: &'static BootInfo) { + use crate::memory::{self, BootInfoFrameAllocator}; + + 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) }; + + init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); +} diff --git a/src/gdt.rs b/src/gdt.rs index 0b3940f..ce1f55f 100644 --- a/src/gdt.rs +++ b/src/gdt.rs @@ -1,7 +1,7 @@ -use x86_64::VirtAddr; -use x86_64::structures::tss::TaskStateSegment; use lazy_static::lazy_static; -use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor, SegmentSelector}; +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; @@ -36,7 +36,13 @@ lazy_static! { let mut gdt = GlobalDescriptorTable::new(); let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); - (gdt, Selectors { code_selector, tss_selector }) + ( + gdt, + Selectors { + code_selector, + tss_selector, + }, + ) }; } diff --git a/src/interrupts.rs b/src/interrupts.rs index e292c66..9867425 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -1,19 +1,19 @@ -use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; -use crate::{gdt, print, println}; +use crate::{gdt, hlt_loop, print, println}; use lazy_static::lazy_static; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); idt.breakpoint.set_handler_fn(breakpoint_handler); + idt.page_fault.set_handler_fn(page_fault_handler); unsafe { - idt.double_fault.set_handler_fn(double_fault_handler) - .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + idt.double_fault + .set_handler_fn(double_fault_handler) + .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); } - idt[InterruptIndex::Timer.as_usize()] - .set_handler_fn(timer_interrupt_handler); - idt[InterruptIndex::Keyboard.as_usize()] - .set_handler_fn(keyboard_interrupt_handler); + idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer_interrupt_handler); + idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard_interrupt_handler); idt }; } @@ -22,15 +22,14 @@ pub fn init_idt() { IDT.load(); } -extern "x86-interrupt" fn breakpoint_handler( - stack_frame: &mut InterruptStackFrame) -{ +extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) { println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); } extern "x86-interrupt" fn double_fault_handler( - stack_frame: &mut InterruptStackFrame, _error_code: u64) -{ + stack_frame: &mut InterruptStackFrame, + _error_code: u64, +) { panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); } @@ -71,9 +70,7 @@ impl InterruptIndex { } } -extern "x86-interrupt" fn timer_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) -{ +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { //print!("."); unsafe { @@ -82,12 +79,10 @@ extern "x86-interrupt" fn timer_interrupt_handler( } } -extern "x86-interrupt" fn keyboard_interrupt_handler( - _stack_frame: &mut InterruptStackFrame) -{ - use x86_64::instructions::port::Port; - use pc_keyboard::{Keyboard, ScancodeSet1, DecodedKey, layouts}; +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { + use pc_keyboard::{layouts, DecodedKey, Keyboard, ScancodeSet1}; use spin::Mutex; + use x86_64::instructions::port::Port; lazy_static! { static ref KEYBOARD: Mutex> = @@ -112,3 +107,18 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); } } + +use x86_64::structures::idt::PageFaultErrorCode; + +extern "x86-interrupt" fn page_fault_handler( + stack_frame: &mut InterruptStackFrame, + error_code: PageFaultErrorCode, +) { + use x86_64::registers::control::Cr2; + + println!("EXCEPTION: PAGE FAULT"); + println!("Accessed Address: {:?}", Cr2::read()); + println!("Error Code: {:?}", error_code); + println!("{:#?}", stack_frame); + hlt_loop(); +} diff --git a/src/lib.rs b/src/lib.rs index a04059a..5f2e20c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,39 @@ #![no_std] #![cfg_attr(test, no_main)] #![feature(abi_x86_interrupt)] +#![feature(alloc_error_handler)] #![feature(custom_test_frameworks)] #![test_runner(crate::test_runner)] #![reexport_test_harness_main = "test_main"] +extern crate alloc; + +pub mod allocator; pub mod gdt; -pub mod serial; pub mod interrupts; +pub mod memory; +pub mod serial; pub mod vga_buffer; -use core::panic::PanicInfo; +use linked_list_allocator::LockedHeap; -pub fn init() { +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +#[alloc_error_handler] +fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { + panic!("allocation error: {:?}", layout) +} + +use core::panic::PanicInfo; +use bootloader::BootInfo; + +pub fn init(boot_info: &'static BootInfo) { gdt::init(); interrupts::init_idt(); unsafe { interrupts::PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); + allocator::init(&boot_info); } pub fn hlt_loop() -> ! { @@ -40,11 +57,16 @@ pub fn test_panic_handler(info: &PanicInfo) -> ! { hlt_loop(); } +#[cfg(test)] +use bootloader::entry_point; + +#[cfg(test)] +entry_point!(test_kernel_main); + /// Entry point for `cargo xtest` #[cfg(test)] -#[no_mangle] -pub extern "C" fn _start() -> ! { - init(); +fn test_kernel_main(boot_info: &'static BootInfo) -> ! { + init(boot_info); test_main(); hlt_loop(); } diff --git a/src/main.rs b/src/main.rs index e47cab0..998a957 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,27 @@ #![test_runner(xe_os::test_runner)] #![reexport_test_harness_main = "test_main"] +extern crate alloc; + +use alloc::boxed::Box; +use bootloader::{entry_point, BootInfo}; use core::panic::PanicInfo; use xe_os::println; -#[no_mangle] -pub extern "C" fn _start() -> ! { - xe_os::init(); +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static BootInfo) -> ! { + use x86_64::VirtAddr; + use xe_os::allocator; + use xe_os::memory::{self, BootInfoFrameAllocator}; + + xe_os::init(&boot_info); println!("Hello World{}", "!"); + // allocate a number on the heap + let heap_value = Box::new(41); + println!("heap_value at {:p}", heap_value); + #[cfg(test)] test_main(); diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..11c5044 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,101 @@ +use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; +use x86_64::{ + structures::paging::{ + FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB, + }, + PhysAddr, VirtAddr, +}; + +/// Initialize a new OffsetPageTable. +/// +/// This function is unsafe because the caller must guarantee that the +/// complete physical memory is mapped to virtual memory at the passed +/// `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> { + 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. +/// +/// This function is unsafe because the caller must guarantee that the +/// complete physical memory is mapped to virtual memory at the passed +/// `physical_memory_offset`. Also, this function must be only called once +/// to avoid aliasing `&mut` references (which is undefined behavior). +unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr // unsafe +} + +/// Creates an example mapping for the given page to frame `0xb8000`. +pub fn create_example_mapping( + page: Page, + mapper: &mut OffsetPageTable, + frame_allocator: &mut impl FrameAllocator, +) { + use x86_64::structures::paging::PageTableFlags as Flags; + + let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); + let flags = Flags::PRESENT | Flags::WRITABLE; + + let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) }; + map_to_result.expect("map_to failed").flush(); +} + +/// A FrameAllocator that always returns `None`. +pub struct EmptyFrameAllocator; + +unsafe impl FrameAllocator for EmptyFrameAllocator { + fn allocate_frame(&mut self) -> Option { + None + } +} + +/// A FrameAllocator that returns usable frames from the bootloader's memory map. +pub struct BootInfoFrameAllocator { + memory_map: &'static MemoryMap, + next: usize, +} + +impl BootInfoFrameAllocator { + /// Create a FrameAllocator from the passed memory map. + /// + /// This function is unsafe because the caller must guarantee that the passed + /// memory map is valid. The main requirement is that all frames that are marked + /// as `USABLE` in it are really unused. + pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { + BootInfoFrameAllocator { + memory_map, + next: 0, + } + } + + /// Returns an iterator over the usable frames specified in the memory map. + fn usable_frames(&self) -> impl Iterator { + // get usable regions from memory map + let regions = self.memory_map.iter(); + let usable_regions = regions.filter(|r| r.region_type == MemoryRegionType::Usable); + // map each region to its address range + let addr_ranges = usable_regions.map(|r| r.range.start_addr()..r.range.end_addr()); + // 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))) + } +} + +unsafe impl FrameAllocator for BootInfoFrameAllocator { + fn allocate_frame(&mut self) -> Option { + let frame = self.usable_frames().nth(self.next); + self.next += 1; + frame + } +} diff --git a/src/serial.rs b/src/serial.rs index 2a69296..6fb62c8 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,6 +1,6 @@ -use uart_16550::SerialPort; -use spin::Mutex; use lazy_static::lazy_static; +use spin::Mutex; +use uart_16550::SerialPort; lazy_static! { pub static ref SERIAL1: Mutex = { @@ -16,7 +16,10 @@ pub fn _print(args: ::core::fmt::Arguments) { use x86_64::instructions::interrupts; interrupts::without_interrupts(|| { - SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); }); } diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs index 726488e..a96f899 100644 --- a/tests/basic_boot.rs +++ b/tests/basic_boot.rs @@ -5,10 +5,12 @@ #![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; +use bootloader::{entry_point, BootInfo}; use xe_os::{println, serial_print, serial_println}; -#[no_mangle] // don't mangle the name of this function -pub extern "C" fn _start() -> ! { +entry_point!(kmain); + +fn kmain(_: &'static BootInfo) -> ! { test_main(); loop {} diff --git a/tests/heap_allocation.rs b/tests/heap_allocation.rs new file mode 100644 index 0000000..01b6fab --- /dev/null +++ b/tests/heap_allocation.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(xe_os::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate alloc; + +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; + +entry_point!(main); + +fn main(boot_info: &'static BootInfo) -> ! { + use xe_os::allocator; + use xe_os::memory::{self, BootInfoFrameAllocator}; + use x86_64::VirtAddr; + + xe_os::init(boot_info); + + test_main(); + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + xe_os::test_panic_handler(info) +} + +use xe_os::{serial_print, serial_println}; +use alloc::boxed::Box; + +#[test_case] +fn simple_allocation() { + serial_print!("simple_allocation... "); + let heap_value = Box::new(41); + assert_eq!(*heap_value, 41); + serial_println!("[ok]"); +} + +use alloc::vec::Vec; + +#[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]"); +} diff --git a/tests/should_panic.rs b/tests/should_panic.rs index 763316a..647190b 100644 --- a/tests/should_panic.rs +++ b/tests/should_panic.rs @@ -9,7 +9,7 @@ pub extern "C" fn _start() -> ! { should_fail(); serial_println!("[test did not panic]"); exit_qemu(QemuExitCode::Failed); - loop{} + loop {} } fn should_fail() { diff --git a/tests/stack_overflow.rs b/tests/stack_overflow.rs index 9f73f0f..056bae5 100644 --- a/tests/stack_overflow.rs +++ b/tests/stack_overflow.rs @@ -43,8 +43,8 @@ lazy_static! { }; } -use xe_os::{exit_qemu, QemuExitCode, serial_println}; use x86_64::structures::idt::InterruptStackFrame; +use xe_os::{exit_qemu, serial_println, QemuExitCode}; extern "x86-interrupt" fn test_double_fault_handler( _stack_frame: &mut InterruptStackFrame,