diff --git a/Cargo.lock b/Cargo.lock index fa155f7..410bf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bit_field" version = "0.10.2" @@ -14,10 +20,21 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "mxrox_kernel" version = "0.1.0" dependencies = [ + "spin", "stable-vec", "volatile 0.6.1", "x86_64", @@ -35,6 +52,21 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable-vec" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index a2704c9..13f5985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] x86_64 = "0.15.2" volatile = "0.6.1" -stable-vec = "0.4.1" \ No newline at end of file +stable-vec = "0.4.1" +spin = "0.9" \ No newline at end of file diff --git a/Makefile b/Makefile index cac01d7..390cc75 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ build/boot.o: boot.s build_dir nasm -f elf32 '$<' -o '$@' build/kernel.elf: linker.ld build/boot.o build/kernel.o - i686-elf-ld -m elf_i386 -nostdlib -o '$@' -T $^ + i686-elf-ld -m elf_i386 -o '$@' -T $^ build/kernel.o: build_dir rustup override set nightly @@ -26,8 +26,8 @@ build: build/mxrox.iso clean: rm -rf build -# rm -rf target -# rm -rf Cargo.lock + rm -rf target +# rm -rf Cargo.lock mkdir build run-kernel: build/kernel.elf diff --git a/README.md b/README.md index add5d70..6f46f33 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Mixray's small x86_64 OS ## How to build ```bash -make clean # removes target/ +make clean # removes target/, build/ make build # builds iso image (build/mxrox.iso) make run # runs iso image in QEMU emulator make run-kernel # runs only kernel in QEMU emulator diff --git a/boot.s b/boot.s index 2c66ac8..73fb436 100644 --- a/boot.s +++ b/boot.s @@ -1,40 +1,90 @@ -global loader -global stack_ptr +; Declare constants for the multiboot header. +MBALIGN equ 1 << 0 ; align loaded modules on page boundaries +MEMINFO equ 1 << 1 ; provide memory map +MBFLAGS equ MBALIGN | MEMINFO ; this is the Multiboot 'flag' field +MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header +CHECKSUM equ -(MAGIC + MBFLAGS) ; checksum of above, to prove we are multiboot -extern main - -MODULEALIGN equ 1<<0 -MEMINFO equ 1<<1 -FLAGS equ MODULEALIGN | MEMINFO -MAGIC equ 0x1BADB002 -CHECKSUM equ -(MAGIC + FLAGS) - -section .mbheader +; Declare a multiboot header that marks the program as a kernel. These are magic +; values that are documented in the multiboot standard. The bootloader will +; search for this signature in the first 8 KiB of the kernel file, aligned at a +; 32-bit boundary. The signature is in its own section so the header can be +; forced to be within the first 8 KiB of the kernel file. +section .multiboot align 4 -MultiBootHeader: - dd MAGIC - dd FLAGS - dd CHECKSUM - -section .text - -STACKSIZE equ 0x4000 - -loader: - mov esp, stack+STACKSIZE - push eax - push ebx - - call main - - cli - -hang: - hlt - jmp hang + dd MAGIC + dd MBFLAGS + dd CHECKSUM +; The multiboot standard does not define the value of the stack pointer register +; (esp) and it is up to the kernel to provide a stack. This allocates room for a +; small stack by creating a symbol at the bottom of it, then allocating 16384 +; bytes for it, and finally creating a symbol at the top. The stack grows +; downwards on x86. The stack is in its own section so it can be marked nobits, +; which means the kernel file is smaller because it does not contain an +; uninitialized stack. The stack on x86 must be 16-byte aligned according to the +; System V ABI standard and de-facto extensions. The compiler will assume the +; stack is properly aligned and failure to align the stack will result in +; undefined behavior. section .bss -align 4 -stack: - resb STACKSIZE -stack_ptr: +align 16 +stack_bottom: +resb 16384 ; 16 KiB +stack_top: + +; The linker script specifies _start as the entry point to the kernel and the +; bootloader will jump to this position once the kernel has been loaded. It +; doesn't make sense to return from this function as the bootloader is gone. +; Declare _start as a function symbol with the given symbol size. +section .text +global _start:function (_start.end - _start) +_start: + ; The bootloader has loaded us into 32-bit protected mode on a x86 + ; machine. Interrupts are disabled. Paging is disabled. The processor + ; state is as defined in the multiboot standard. The kernel has full + ; control of the CPU. The kernel can only make use of hardware features + ; and any code it provides as part of itself. There's no printf + ; function, unless the kernel provides its own header and a + ; printf implementation. There are no security restrictions, no + ; safeguards, no debugging mechanisms, only what the kernel provides + ; itself. It has absolute and complete power over the + ; machine. + + ; To set up a stack, we set the esp register to point to the top of our + ; stack (as it grows downwards on x86 systems). This is necessarily done + ; in assembly as languages such as C cannot function without a stack. + mov esp, stack_top + + ; This is a good place to initialize crucial processor state before the + ; high-level kernel is entered. It's best to minimize the early + ; environment where crucial features are offline. Note that the + ; processor is not fully initialized yet: Features such as floating + ; point instructions and instruction set extensions are not initialized + ; yet. The GDT should be loaded here. Paging should be enabled here. + ; C++ features such as global constructors and exceptions will require + ; runtime support to work as well. + + ; Enter the high-level kernel. The ABI requires the stack is 16-byte + ; aligned at the time of the call instruction (which afterwards pushes + ; the return pointer of size 4 bytes). The stack was originally 16-byte + ; aligned above and we've since pushed a multiple of 16 bytes to the + ; stack since (pushed 0 bytes so far) and the alignment is thus + ; preserved and the call is well defined. + ; note, that if you are building on Windows, C functions may have "_" prefix in assembly: _kernel_main + extern main + call main + + ; If the system has nothing more to do, put the computer into an + ; infinite loop. To do that: + ; 1) Disable interrupts with cli (clear interrupt enable in eflags). + ; They are already disabled by the bootloader, so this is not needed. + ; Mind that you might later enable interrupts and return from + ; kernel_main (which is sort of nonsensical to do). + ; 2) Wait for the next interrupt to arrive with hlt (halt instruction). + ; Since they are disabled, this will lock up the computer. + ; 3) Jump to the hlt instruction if it ever wakes up due to a + ; non-maskable interrupt occurring or due to system management mode. + cli +.hang: hlt + jmp .hang +.end: \ No newline at end of file diff --git a/src/kernel/heap.rs b/src/kernel/heap.rs new file mode 100644 index 0000000..85df497 --- /dev/null +++ b/src/kernel/heap.rs @@ -0,0 +1,133 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::null_mut; +use core::mem; +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicUsize, Ordering}; + +struct FreeListNode { + size: usize, + next: *mut FreeListNode, +} + +pub struct FreeListAllocator { + head: UnsafeCell<*mut FreeListNode>, + heap_start: AtomicUsize, + heap_end: AtomicUsize, +} + +unsafe impl Send for FreeListAllocator {} +unsafe impl Sync for FreeListAllocator {} + +impl FreeListAllocator { + pub const fn new() -> Self { + FreeListAllocator { + head: UnsafeCell::new(null_mut()), + heap_start: AtomicUsize::new(0), + heap_end: AtomicUsize::new(0), + } + } + + pub unsafe fn init(&self, heap_start: usize, heap_size: usize) { + self.heap_start.store(heap_start, Ordering::Relaxed) ; + self.heap_end.store(heap_start + heap_size, Ordering::Relaxed) ; + + let first_node = heap_start as *mut FreeListNode; + first_node.write(FreeListNode { size: heap_size, next: null_mut() }); + + *self.head.get() = first_node; + } + + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mut prev: *mut FreeListNode = null_mut(); + let mut current = *self.head.get(); + + while !current.is_null() { + let node = &mut *current; + + let alloc_start = (current as usize + mem::size_of::() + layout.align() - 1) + & !(layout.align() - 1); + let alloc_end = alloc_start + layout.size(); + + if alloc_end > self.heap_end.load(Ordering::Relaxed) || alloc_end > (current as usize + node.size) { + prev = current; + current = node.next; + continue; + } + + let remaining_size = (current as usize + node.size) - alloc_end; + if remaining_size > mem::size_of::() { + let new_node = alloc_end as *mut FreeListNode; + new_node.write(FreeListNode { + size: remaining_size, + next: node.next, + }); + + if prev.is_null() { + *self.head.get() = new_node; + } else { + (*prev).next = new_node; + } + } else { + if prev.is_null() { + *self.head.get() = node.next; + } else { + (*prev).next = node.next; + } + } + + return alloc_start as *mut u8; + } + + null_mut() + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let free_node = ptr as *mut FreeListNode; + free_node.write(FreeListNode { + size: layout.size(), + next: *self.head.get(), + }); + + *self.head.get() = free_node; + } +} + +#[global_allocator] +static ALLOCATOR: FreeListAllocator = FreeListAllocator::new(); + +unsafe impl GlobalAlloc for FreeListAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dealloc(ptr, layout) + } +} + +#[no_mangle] +pub extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { + for i in 0..n { + unsafe { + *dest.add(i) = *src.add(i); + } + } + dest +} + +#[no_mangle] +pub extern "C" fn memset(dest: *mut u8, value: i32, n: usize) -> *mut u8 { + let byte = value as u8; + for i in 0..n { + unsafe { + *dest.add(i) = byte; + } + } + dest +} + +pub fn init_heap(heap_start: usize, heap_size: usize) { + unsafe { + ALLOCATOR.init(heap_start, heap_size); + } +} diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs index 1537dbf..ebf65d9 100644 --- a/src/kernel/mod.rs +++ b/src/kernel/mod.rs @@ -1,3 +1,4 @@ +use heap::init_heap; use stable_vec::StableVec; use vga::{ fill_with_color, @@ -10,6 +11,7 @@ mod vga; mod ps2; mod acpi; mod thread; +mod heap; type Vec = StableVec; @@ -18,6 +20,8 @@ pub fn show_error(message: &str) { put_string_by_index(0, message, VGA_COLOR_BLACK, VGA_COLOR_RED); } -pub fn start_kernel() { +pub fn init_kernel() { + init_heap(16400, 16384); + show_error("error test"); } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 62d0ef2..760e3bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ extern "C" fn eh_personality() {} use core::panic::PanicInfo; -use kernel::{start_kernel, show_error}; +use kernel::{init_kernel, show_error}; mod kernel; @@ -19,9 +19,8 @@ fn panic(info: &PanicInfo) -> ! { loop {} } - #[no_mangle] -extern "C" fn main() -> ! { - start_kernel(); +pub extern "C" fn main() -> ! { + init_kernel(); loop {} } \ No newline at end of file