feat: create libkos crate with core modules (syscall, alloc, rt)

This commit is contained in:
2026-10-19 00:53:31 +02:00
parent 2792387348
commit dbec3842fa
9 changed files with 412 additions and 0 deletions

View File

@@ -53,6 +53,7 @@ members = [
"crates/drivers/input/uart", "crates/drivers/input/uart",
"crates/drivers/input/virtio", "crates/drivers/input/virtio",
"crates/hw-virtio", "crates/hw-virtio",
"crates/libkos",
] ]
resolver = "2" resolver = "2"
@@ -99,6 +100,9 @@ user-manager = { path = "crates/servers/user-manager" }
# hw # hw
hw-virtio = { path = "crates/hw-virtio" } hw-virtio = { path = "crates/hw-virtio" }
# libkos
libkos = { path = "crates/libkos" }
# ports # ports
port-framebuffer = { path = "crates/ports/framebuffer" } port-framebuffer = { path = "crates/ports/framebuffer" }
port-block-device = { path = "crates/ports/block-device" } port-block-device = { path = "crates/ports/block-device" }

7
crates/libkos/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "libkos"
version = "0.1.0"
edition = "2024"
[dependencies]
types = { workspace = true }

View File

@@ -0,0 +1,141 @@
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ptr;
const PAGE_SIZE: usize = 4096;
const MAX_PAGES: usize = 64;
struct FreeBlock {
size: usize,
next: *mut FreeBlock,
}
pub struct KosAllocator {
inner: UnsafeCell<AllocatorInner>,
}
struct AllocatorInner {
free_list: *mut FreeBlock,
pages: [usize; MAX_PAGES],
page_count: usize,
}
unsafe impl Sync for KosAllocator {}
impl KosAllocator {
pub const fn new() -> Self {
Self {
inner: UnsafeCell::new(AllocatorInner {
free_list: ptr::null_mut(),
pages: [0; MAX_PAGES],
page_count: 0,
}),
}
}
fn inner_mut(&self) -> &mut AllocatorInner {
unsafe { &mut *self.inner.get() }
}
}
impl AllocatorInner {
fn request_page(&mut self) -> *mut u8 {
if self.page_count >= MAX_PAGES {
return ptr::null_mut();
}
let count = match types::bounded::PageCount::new(1) {
Some(c) => c,
None => return ptr::null_mut(),
};
let va = match crate::syscall::request_pages(count) {
Some(va) => va,
None => return ptr::null_mut(),
};
self.pages[self.page_count] = va.raw();
self.page_count += 1;
va.raw() as *mut u8
}
fn alloc_from_free_list(&mut self, size: usize, align: usize) -> *mut u8 {
let mut prev: *mut FreeBlock = ptr::null_mut();
let mut current = self.free_list;
while !current.is_null() {
let block = unsafe { &mut *current };
let addr = current as usize;
let aligned = (addr + align - 1) & !(align - 1);
let padding = aligned - addr;
if block.size >= size + padding {
let remaining = block.size - size - padding;
if remaining >= core::mem::size_of::<FreeBlock>() {
let new_block = (aligned + size) as *mut FreeBlock;
unsafe {
(*new_block).size = remaining;
(*new_block).next = block.next;
}
if prev.is_null() {
self.free_list = new_block;
} else {
unsafe { (*prev).next = new_block; }
}
} else {
if prev.is_null() {
self.free_list = block.next;
} else {
unsafe { (*prev).next = block.next; }
}
}
return aligned as *mut u8;
}
prev = current;
current = block.next;
}
ptr::null_mut()
}
fn add_to_free_list(&mut self, ptr: *mut u8, size: usize) {
let block = ptr as *mut FreeBlock;
unsafe {
(*block).size = size;
(*block).next = self.free_list;
}
self.free_list = block;
}
}
unsafe impl GlobalAlloc for KosAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let inner = self.inner_mut();
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
let align = layout.align();
let ptr = inner.alloc_from_free_list(size, align);
if !ptr.is_null() {
return ptr;
}
let pages_needed = (size + PAGE_SIZE - 1) / PAGE_SIZE;
for _ in 0..pages_needed {
let page = inner.request_page();
if page.is_null() {
return ptr::null_mut();
}
inner.add_to_free_list(page, PAGE_SIZE);
}
inner.alloc_from_free_list(size, align)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let inner = self.inner_mut();
let size = layout.size().max(core::mem::size_of::<FreeBlock>());
inner.add_to_free_list(ptr, size);
}
}

View File

@@ -0,0 +1,27 @@
pub fn syscall0(_nr: usize) -> usize {
0
}
pub fn syscall1(_nr: usize, _a0: usize) -> usize {
0
}
pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize {
0
}
pub fn syscall3(_nr: usize, _a0: usize, _a1: usize, _a2: usize) -> usize {
0
}
pub fn syscall4(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize) -> usize {
0
}
pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize {
0
}
pub fn syscall_receive(_nr: usize, _a0: usize) -> (usize, usize, usize, usize) {
(0, 0, 0, 0)
}

View File

@@ -0,0 +1,9 @@
#[cfg(all(target_arch = "riscv64", target_os = "none"))]
mod riscv;
#[cfg(all(target_arch = "riscv64", target_os = "none"))]
pub use riscv::*;
#[cfg(not(target_os = "none"))]
mod mock;
#[cfg(not(target_os = "none"))]
pub use mock::*;

View File

@@ -0,0 +1,106 @@
#[inline(always)]
pub fn syscall0(nr: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
lateout("a0") ret,
);
}
ret
}
#[inline(always)]
pub fn syscall1(nr: usize, a0: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
);
}
ret
}
#[inline(always)]
pub fn syscall2(nr: usize, a0: usize, a1: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
);
}
ret
}
#[inline(always)]
pub fn syscall3(nr: usize, a0: usize, a1: usize, a2: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
);
}
ret
}
#[inline(always)]
pub fn syscall4(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
in("a3") a3,
);
}
ret
}
#[inline(always)]
pub fn syscall5(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> usize {
let ret: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => ret,
in("a1") a1,
in("a2") a2,
in("a3") a3,
in("a4") a4,
);
}
ret
}
#[inline(always)]
pub fn syscall_receive(nr: usize, a0: usize) -> (usize, usize, usize, usize) {
let r0: usize;
let r1: usize;
let r2: usize;
let r3: usize;
unsafe {
core::arch::asm!(
"ecall",
in("a7") nr,
inlateout("a0") a0 => r0,
lateout("a1") r1,
lateout("a2") r2,
lateout("a3") r3,
);
}
(r0, r1, r2, r3)
}

12
crates/libkos/src/lib.rs Normal file
View File

@@ -0,0 +1,12 @@
#![no_std]
extern crate alloc;
mod arch;
pub mod syscall;
pub mod alloc_impl;
mod rt;
#[cfg(target_os = "none")]
#[global_allocator]
static ALLOCATOR: alloc_impl::KosAllocator = alloc_impl::KosAllocator::new();

21
crates/libkos/src/rt.rs Normal file
View File

@@ -0,0 +1,21 @@
// Architecture-specific entry points.
// To add a new architecture: add a cfg block with the appropriate global_asm.
#[cfg(all(target_arch = "riscv64", target_os = "none"))]
core::arch::global_asm!(
r#"
.section .text.entry
.globl _start
_start:
call main
li a7, 16
ecall
j _start
"#
);
#[cfg(target_os = "none")]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
crate::syscall::exit();
}

View File

@@ -0,0 +1,85 @@
use crate::arch;
use types::syscall_abi::*;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::id::ProcessId;
use types::permission::PermissionSet;
use types::time::Ticks;
pub fn exit() -> ! {
arch::syscall0(SYS_EXIT);
loop {}
}
pub fn exit_thread() -> ! {
arch::syscall0(SYS_EXIT_THREAD);
loop {}
}
pub fn yield_now() {
arch::syscall0(SYS_YIELD);
}
pub fn halt() -> ! {
arch::syscall0(SYS_HALT);
loop {}
}
pub fn get_time() -> Ticks {
Ticks::from_raw(arch::syscall0(SYS_GET_TIME) as u64)
}
pub fn get_process_id() -> ProcessId {
ProcessId::from_raw(arch::syscall0(SYS_GET_PROCESS_ID) as u16)
}
pub fn create_process(create_cap: CapSlot) -> Option<CapSlot> {
let r = arch::syscall1(SYS_CREATE_PROCESS, create_cap.value() as usize);
if r == usize::MAX { None } else { CapSlot::new(r as u8) }
}
pub fn create_endpoint() -> Option<CapSlot> {
let r = arch::syscall0(SYS_CREATE_ENDPOINT);
if r == usize::MAX { None } else { CapSlot::new(r as u8) }
}
pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize {
arch::syscall2(SYS_SPAWN_THREAD, entry.raw(), stack.raw())
}
pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize {
arch::syscall3(SYS_SPAWN_THREAD_IN, process_cap.value() as usize, entry.raw(), stack.raw())
}
pub fn request_pages(count: PageCount) -> Option<VirtualAddress> {
let r = arch::syscall1(SYS_REQUEST_PAGES, count.value() as usize);
if r == usize::MAX { None } else { Some(VirtualAddress::from_raw(r)) }
}
pub fn map_page(process_cap: CapSlot, src_va: VirtualAddress, dst_va: VirtualAddress, executable: bool) -> bool {
arch::syscall4(SYS_MAP_PAGE, process_cap.value() as usize, src_va.raw(), dst_va.raw(), executable as usize) == 0
}
pub fn send(endpoint: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> usize {
arch::syscall5(SYS_SEND, endpoint.value() as usize, w0, w1, w2, w3)
}
pub fn receive(endpoint: CapSlot) -> (usize, usize, usize, usize) {
arch::syscall_receive(SYS_RECEIVE, endpoint.value() as usize)
}
pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool {
arch::syscall3(SYS_DELEGATE, slot.value() as usize, to.value() as usize, permissions.bits() as usize) == 0
}
pub fn kill(process_cap: CapSlot) -> bool {
arch::syscall1(SYS_KILL, process_cap.value() as usize) == 0
}
pub fn revoke(slot: CapSlot) -> bool {
arch::syscall1(SYS_REVOKE, slot.value() as usize) == 0
}
pub fn debug_putchar(byte: u8) {
arch::syscall1(SYS_DEBUG_PUTCHAR, byte as usize);
}