Work (better mem tracking, mpfrt/mpcntphys syscalls, shared initrd, tasking internal locks)

This commit is contained in:
pjht 2024-08-12 13:19:06 -05:00
parent 5558fa427b
commit 899fde8218
Signed by: pjht
GPG Key ID: 7B5F6AFBEC7EE78E
9 changed files with 649 additions and 305 deletions

View File

@ -1,10 +1,15 @@
use crate::{ use crate::{
bootinfo::BOOTINFO, print, println, serial::SECOND_PORT, tasking::SleepReason, virtual_memory::{ASpaceMutex, AddressSpace, ACTIVE_SPACE, KERNEL_SPACE}, TASKING bootinfo::BOOTINFO,
print, println,
serial::SECOND_PORT,
tasking::SleepReason,
virtual_memory::{ASpaceMutex, AddressSpace, ACTIVE_SPACE, KERNEL_SPACE},
TASKING,
}; };
use alloc::{boxed::Box, vec::Vec}; use alloc::{boxed::Box, vec::Vec};
use az::WrappingCast; use az::WrappingCast;
use cast::{u64, usize}; use cast::{u64, usize};
use core::{arch::asm, ffi::CStr, ptr, slice, str}; use core::{arch::asm, ffi::CStr, ptr, slice};
use hashbrown::HashMap; use hashbrown::HashMap;
use pic8259::ChainedPics; use pic8259::ChainedPics;
use saturating_cast::SaturatingCast; use saturating_cast::SaturatingCast;
@ -15,46 +20,12 @@ use x86_64::{
set_general_handler, set_general_handler,
structures::{ structures::{
idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}, idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
paging::{mapper::TranslateResult, Page, PageTableFlags, Translate}, paging::{mapper::TranslateResult, Page, PageTableFlags, PhysFrame, Translate},
}, },
PrivilegeLevel, VirtAddr, PhysAddr, PrivilegeLevel, VirtAddr,
}; };
const IRQ_BASE: u8 = 32; const IRQ_BASE: u8 = 32;
const INTERRUPT_NAMES: [&str; 32] = [
"Divide By Zero",
"Debug",
"NMI",
"Breakpoint",
"Overflow",
"Bound Range Exceeded",
"Invalid Opcode",
"FPU Disabled",
"Double Fault",
"",
"Invalid TSS",
"Segment Not Present",
"Stack Segment Fault",
"General Protection Fault",
"Page Fault",
"",
"x87 FPU Exception",
"Alignment Check",
"Machine Check",
"SIMD FPU Exception",
"Virtualization Exception",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"Security Exception",
"",
];
static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| { static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
let mut idt = InterruptDescriptorTable::new(); let mut idt = InterruptDescriptorTable::new();
@ -87,10 +58,14 @@ extern "x86-interrupt" fn page_fault_handler(
#[warn(clippy::expect_used, reason = "FIXME")] #[warn(clippy::expect_used, reason = "FIXME")]
let faulting_addr = let faulting_addr =
Cr2::read().expect("Cannot handle page faults caused by non-canonical addresses"); Cr2::read().expect("Cannot handle page faults caused by non-canonical addresses");
#[warn(clippy::panic, reason = "FIXME: This should just abort the current process")] if let Some(current_pid) = TASKING.current_pid() {
print!("PID {current_pid} ");
} else {
print!("Kernel init ");
}
if error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION) { if error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION) {
panic!( println!(
"Got Page Fault {error_code:#?} at {:#x}\nEntry flags: {:#?}\n{stack_frame:#?}", "page faulted {error_code:#?} at {:#x}\nEntry flags: {:#?}\n{stack_frame:#?}",
faulting_addr, faulting_addr,
match ACTIVE_SPACE.lock().translate(faulting_addr) { match ACTIVE_SPACE.lock().translate(faulting_addr) {
TranslateResult::Mapped { flags, .. } => flags, TranslateResult::Mapped { flags, .. } => flags,
@ -105,8 +80,9 @@ extern "x86-interrupt" fn page_fault_handler(
}, },
); );
} else { } else {
panic!("Got Page Fault {error_code:#?} at {:#x}\n{stack_frame:#?}", faulting_addr); println!("page faulted {error_code:#?} at {:#x}\n{stack_frame:#?}", faulting_addr);
} }
TASKING.exit();
} }
#[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")] #[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")]
@ -115,20 +91,14 @@ fn general_handler(stack_frame: InterruptStackFrame, index: u8, _error_code: Opt
} }
#[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")] #[expect(clippy::needless_pass_by_value, reason = "Signature dictated by external crate")]
fn exc_handler(stack_frame: InterruptStackFrame, index: u8, error_code: Option<u64>) { fn exc_handler(_stack_frame: InterruptStackFrame, _index: u8, _error_code: Option<u64>) {
#[warn(clippy::panic, reason = "FIXME: This should just abort the current process")] if let Some(current_pid) = TASKING.current_pid() {
#[expect( print!("PID {current_pid} ");
clippy::indexing_slicing,
reason = "This function is only called for interrupt numbers 0-32, which all are valid indexes"
)]
if let Some(error_code) = error_code {
panic!(
"Got exception {} with error code {error_code}\n{stack_frame:#?}",
INTERRUPT_NAMES[usize(index)]
);
} else { } else {
panic!("Got exception {}\n{stack_frame:#?}", INTERRUPT_NAMES[usize(index)]); print!("Kernel init ");
}; }
println!("had exception, exiting");
TASKING.exit();
} }
pub struct EoiGuard(u8); pub struct EoiGuard(u8);
@ -226,15 +196,34 @@ extern "x86-interrupt" fn syscall_handler_header(stack_frame: InterruptStackFram
} }
fn get_buffer(id: u64) -> Option<Box<[u8], &'static ASpaceMutex>> { fn get_buffer(id: u64) -> Option<Box<[u8], &'static ASpaceMutex>> {
TASKING TASKING.data_buffers_mut(|x| {
.lock() x.try_remove(usize(id)).map(|buf| unsafe { Box::from_raw_in(buf, &*KERNEL_SPACE) })
.data_buffers_mut() })
.try_remove(usize(id))
.map(|buf| unsafe { Box::from_raw_in(buf, &*KERNEL_SPACE) })
} }
static REGISTERD_PIDS: Lazy<RwLock<HashMap<u64, u64>>> = Lazy::new(|| RwLock::new(HashMap::new())); static REGISTERD_PIDS: Lazy<RwLock<HashMap<u64, u64>>> = Lazy::new(|| RwLock::new(HashMap::new()));
static INITRD_BUF: Lazy<&'static [u8]> = Lazy::new(|| {
let initrd = unsafe {
#[warn(clippy::expect_used, reason = "FIXME")]
let ramdisk_start = BOOTINFO.ramdisk_addr.into_option().expect("initrd not present");
let ramdisk_len = BOOTINFO.ramdisk_len;
slice::from_raw_parts(
ptr::with_exposed_provenance::<u8>(usize(ramdisk_start)),
usize(ramdisk_len),
)
};
TASKING.record_heap_alloc(initrd.len().next_multiple_of(4096));
KERNEL_SPACE.lock().alloc_force_user = true;
let initrd = Box::leak(
Vec::with_capacity_in(initrd.len(), &*KERNEL_SPACE)
.tap_mut(|v| v.extend_from_slice(initrd))
.into_boxed_slice(),
);
KERNEL_SPACE.lock().alloc_force_user = false;
initrd
});
#[no_mangle] #[no_mangle]
extern "C" fn syscall_handler() { extern "C" fn syscall_handler() {
let regs = unsafe { SYSCALL_REGS }; let regs = unsafe { SYSCALL_REGS };
@ -255,47 +244,35 @@ extern "C" fn syscall_handler() {
}; };
retval = rval; retval = rval;
} }
1 => TASKING.lock().exit(), 1 => TASKING.exit(),
2 => { 2 => {
retval = if regs.rcx == 0 { retval = if regs.rcx == 0 {
ACTIVE_SPACE ACTIVE_SPACE
.lock() .lock()
.map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi)) .map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi))
.map_or(0, |x| u64(x.expose_provenance())) .map_or(0, |x| u64(x.expose_provenance()))
} else if let Some(space) = #[warn( } else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0." reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)] )]
TASKING if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
.lock()
.address_spaces_mut()
.get_mut(usize(regs.rcx - 1))
{
space space
.map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi)) .map_free(usize(regs.rdx), PageTableFlags::from_bits_truncate(regs.rsi))
.map_or(0, |x| u64(x.expose_provenance())) .map_or(0, |x| u64(x.expose_provenance()))
} else { } else {
0 0
} }
})
};
if retval != 0 {
TASKING.record_alloc(usize(regs.rdx) * 4096);
}
} }
3 => { 3 => {
let initrd = unsafe { retval = u64(INITRD_BUF.as_ptr().expose_provenance());
#[warn(clippy::expect_used, reason = "FIXME")] retval2 = u64(INITRD_BUF.len());
let ramdisk_start =
BOOTINFO.ramdisk_addr.into_option().expect("initrd not present");
let ramdisk_len = BOOTINFO.ramdisk_len;
slice::from_raw_parts(
ptr::with_exposed_provenance::<u8>(usize(ramdisk_start)),
usize(ramdisk_len),
)
};
let initrd = Box::leak(
Vec::with_capacity_in(initrd.len(), &*ACTIVE_SPACE)
.tap_mut(|v| v.extend_from_slice(initrd))
.into_boxed_slice(),
);
retval = u64(initrd.as_ptr().expose_provenance());
retval2 = u64(initrd.len());
} }
4 => { 4 => {
#[warn(clippy::expect_used, reason = "FIXME")] #[warn(clippy::expect_used, reason = "FIXME")]
@ -303,11 +280,9 @@ extern "C" fn syscall_handler() {
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "usize::MAX will never be returned as an index, and so incrementing can never overflow." reason = "usize::MAX will never be returned as an index, and so incrementing can never overflow."
)] )]
let address_space = u64(TASKING let address_space = u64(TASKING.address_spaces_mut(|x| {
.lock() x.insert(AddressSpace::new().expect("Failed to create address space")) + 1
.address_spaces_mut() }));
.insert(AddressSpace::new().expect("Failed to create address space"))
+ 1);
retval = address_space; retval = address_space;
} }
5 => { 5 => {
@ -315,7 +290,7 @@ extern "C" fn syscall_handler() {
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0." reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)] )]
TASKING.lock().address_spaces_mut().remove(usize(regs.rcx - 1)); TASKING.address_spaces_mut(|x| x.remove(usize(regs.rcx - 1)));
} }
6 => 'call6: { 6 => 'call6: {
let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else { let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else {
@ -325,34 +300,35 @@ extern "C" fn syscall_handler() {
let num_pages = usize(regs.rsi); let num_pages = usize(regs.rsi);
let flags = PageTableFlags::from_bits_truncate(regs.rdi); let flags = PageTableFlags::from_bits_truncate(regs.rdi);
let failed = if regs.rcx == 0 { let failed = if regs.rcx == 0 {
ACTIVE_SPACE.lock().map_assert_unused(page, num_pages, flags) ACTIVE_SPACE.lock().map_assert_unused(page, num_pages, flags).is_err()
} else { } else {
let mut tasking = TASKING.lock(); TASKING.address_spaces_mut(|x| {
let Some(address_space) =
#[warn(clippy::arithmetic_side_effects, reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0.")]
tasking.address_spaces_mut().get_mut(usize(regs.rcx - 1))
else {
retval = 1;
break 'call6;
};
address_space.map_assert_unused(page, num_pages, flags)
}
.is_err();
retval = failed.into();
}
7 => 'call7: {
if let Some(buffer) = get_buffer(regs.rdx) {
let len = usize(regs.rdi);
assert!(len <= buffer.len());
let mut tasking = TASKING.lock();
#[warn( #[warn(
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0." reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)] )]
let Some(space) = tasking.address_spaces_mut().get_mut(usize(regs.rcx - 1)) else { if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
retval = 1; space.map_assert_unused(page, num_pages, flags).is_err()
break 'call7; } else {
true
}
})
}; };
retval = failed.into();
if !failed {
TASKING.record_alloc(num_pages * 4096);
}
}
7 => {
if let Some(buffer) = get_buffer(regs.rdx) {
let len = usize(regs.rdi);
assert!(len <= buffer.len());
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
space.run(|| unsafe { space.run(|| unsafe {
(ptr::with_exposed_provenance_mut::<u8>(usize(regs.rsi))) (ptr::with_exposed_provenance_mut::<u8>(usize(regs.rsi)))
.copy_from(buffer.as_ptr(), len); .copy_from(buffer.as_ptr(), len);
@ -361,19 +337,25 @@ extern "C" fn syscall_handler() {
} else { } else {
retval = 1; retval = 1;
} }
});
TASKING.record_buf_dealloc(buffer.len());
} else {
retval = 1;
}
} }
8 => { 8 => {
let args = unsafe { let args = unsafe {
let argc = usize(regs.rsi); let argc = usize(regs.rsi);
let argv: &[&[u8]] = slice::from_raw_parts(ptr::with_exposed_provenance(usize(regs.rdi)), argc); let argv: &[&[u8]] =
slice::from_raw_parts(ptr::with_exposed_provenance(usize(regs.rdi)), argc);
argv.iter().map(|arg| CStr::from_bytes_with_nul_unchecked(arg)).collect::<Vec<_>>() argv.iter().map(|arg| CStr::from_bytes_with_nul_unchecked(arg)).collect::<Vec<_>>()
}; };
#[warn( #[warn(
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0." reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)] )]
let space = TASKING.lock().address_spaces_mut().remove(usize(regs.rdx - 1)); let space = TASKING.address_spaces_mut(|x| x.remove(usize(regs.rdx - 1)));
let res = TASKING.lock().new_process( let res = TASKING.new_process(
ptr::with_exposed_provenance(usize(regs.rcx)), ptr::with_exposed_provenance(usize(regs.rcx)),
space, space,
args.as_slice(), args.as_slice(),
@ -390,7 +372,7 @@ extern "C" fn syscall_handler() {
clippy::unwrap_used, clippy::unwrap_used,
reason = "Syscalls cannot be called during early boot, the only time when there is no current PID" reason = "Syscalls cannot be called during early boot, the only time when there is no current PID"
)] )]
REGISTERD_PIDS.write().insert(regs.rcx, u64(TASKING.lock().current_pid().unwrap())); REGISTERD_PIDS.write().insert(regs.rcx, u64(TASKING.current_pid().unwrap()));
} }
10 => { 10 => {
let id = REGISTERD_PIDS.read().get(&regs.rcx).copied(); let id = REGISTERD_PIDS.read().get(&regs.rcx).copied();
@ -406,8 +388,7 @@ extern "C" fn syscall_handler() {
if let Some(buffer) = get_buffer(regs.rdx) { if let Some(buffer) = get_buffer(regs.rdx) {
let len = usize(regs.rsi); let len = usize(regs.rsi);
assert!(len <= buffer.len()); assert!(len <= buffer.len());
let mut tasking = TASKING.lock(); if TASKING.message_queue_mut(pid, |_| ()).is_ok() {
if let Ok(_queue) = tasking.message_queue_mut(pid) {
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = "The min call guarantees that the value is in the range of a u32 before the cast" reason = "The min call guarantees that the value is in the range of a u32 before the cast"
@ -450,43 +431,43 @@ extern "C" fn syscall_handler() {
clippy::unwrap_used, clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition" reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)] )]
let data_bufs = tasking.proc_data_buffers_mut(pid).unwrap(); let new_buffer_key =
let new_buffer_key = data_bufs.insert(buffer); TASKING.proc_data_buffers_mut(pid, |x| x.insert(buffer)).unwrap();
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = "The option was already checked at the start of the if-let" reason = "The option was already checked at the start of the if-let"
)] )]
let queue = tasking.message_queue_mut(pid).unwrap(); TASKING.message_queue_mut(pid, |x| x.push((new_buffer_key, len))).unwrap();
queue.push((new_buffer_key, len));
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition" reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)] )]
let sleep_status = tasking.proc_sleeping(pid).unwrap(); let sleep_status = TASKING.proc_sleeping(pid).unwrap();
if sleep_status == Some(SleepReason::WaitingForIPC) { if sleep_status == Some(SleepReason::WaitingForIPC) {
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition" reason = "The PID is known valid due to using it in message_queue_mut in the if-let condition"
)] )]
tasking.wake(pid).unwrap(); TASKING.wake(pid).unwrap();
} }
retval = 0; retval = 0;
} else { } else {
println!("ipc_send: Bad PID ({})", pid);
retval = 1; retval = 1;
} }
} else { } else {
println!("ipc_send: Bad buffer ({})", regs.rdx);
retval = 1; retval = 1;
} }
} }
12 => { 12 => {
let mut tasking = TASKING.lock(); if let Some(msg) = TASKING.current_message_queue_mut(|x| x.pop()) {
if let Some(msg) = tasking.current_message_queue_mut().pop() {
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = "The message queue only contains valid buffer IDs" reason = "The message queue only contains valid buffer IDs"
)] )]
let buffer_addr = let buffer_addr =
u64((*tasking.data_buffers_mut().get(msg.0).unwrap()).expose_provenance()); u64(TASKING.data_buffers_mut(|x| *x.get(msg.0).unwrap()).expose_provenance());
retval2 = u64(msg.1); retval2 = u64(msg.1);
retval = buffer_addr; retval = buffer_addr;
retval3 = u64(msg.0); retval3 = u64(msg.0);
@ -499,7 +480,7 @@ extern "C" fn syscall_handler() {
clippy::unwrap_used, clippy::unwrap_used,
reason = "Syscalls cannot be called during early boot, the only time when there is no current PID" reason = "Syscalls cannot be called during early boot, the only time when there is no current PID"
)] )]
let pid = u64(TASKING.lock().current_pid().unwrap()); let pid = u64(TASKING.current_pid().unwrap());
retval = pid; retval = pid;
} }
14 => 'call14: { 14 => 'call14: {
@ -510,66 +491,161 @@ extern "C" fn syscall_handler() {
let num_pages = usize(regs.rsi); let num_pages = usize(regs.rsi);
let flags = PageTableFlags::from_bits_truncate(regs.rdi); let flags = PageTableFlags::from_bits_truncate(regs.rdi);
let failed = if regs.rcx == 0 { let failed = if regs.rcx == 0 {
ACTIVE_SPACE.lock().map_only_unused(page, num_pages, flags) ACTIVE_SPACE.lock().map_only_unused(page, num_pages, flags).is_err()
} else { } else {
let mut tasking = TASKING.lock(); TASKING.address_spaces_mut(|x| {
let Some(address_space) = #[warn(
#[warn(clippy::arithmetic_side_effects, reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0.")] clippy::arithmetic_side_effects,
tasking.address_spaces_mut().get_mut(usize(regs.rcx - 1)) reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
else { )]
retval = 1; if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
break 'call14; space.map_only_unused(page, num_pages, flags).is_err()
}; } else {
address_space.map_only_unused(page, num_pages, flags) true
} }
.is_err(); })
};
retval = failed.into(); retval = failed.into();
} }
15 => { 15 => {
get_buffer(regs.rcx); if let Some(buf) = get_buffer(regs.rcx) {
TASKING.record_buf_dealloc(buf.len());
}
} }
16 => { 16 => {
let size = usize(regs.rcx); let size = usize(regs.rcx);
let rounded_size = size.next_multiple_of(4096); let rounded_size = size.next_multiple_of(4096);
KERNEL_SPACE.lock().alloc_force_user = true; KERNEL_SPACE.lock().alloc_force_user = true;
let mut buffer = Vec::with_capacity_in(rounded_size, &*KERNEL_SPACE); let mut buffer = Vec::with_capacity_in(rounded_size, &*KERNEL_SPACE);
TASKING.record_buf_alloc(rounded_size);
buffer.resize(rounded_size, 0); buffer.resize(rounded_size, 0);
let buffer = buffer.into_boxed_slice(); let buffer = buffer.into_boxed_slice();
let buffer = Box::into_raw(buffer); let buffer = Box::into_raw(buffer);
KERNEL_SPACE.lock().alloc_force_user = false; KERNEL_SPACE.lock().alloc_force_user = false;
retval = u64(TASKING.lock().data_buffers_mut().insert(buffer)); retval = u64(TASKING.data_buffers_mut(|x| x.insert(buffer)));
retval2 = u64(buffer.cast::<u8>().expose_provenance()); retval2 = u64(buffer.cast::<u8>().expose_provenance());
retval3 = u64(rounded_size); retval3 = u64(rounded_size);
} }
17 => { 17 => {
let mut tasking = TASKING.lock();
#[warn(clippy::expect_used, reason = "FIXME")] #[warn(clippy::expect_used, reason = "FIXME")]
#[warn( #[warn(
clippy::arithmetic_side_effects, clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0." reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)] )]
let space = tasking TASKING.address_spaces_mut(|x| {
.address_spaces_mut() let space = x.get_mut(usize(regs.rcx - 1)).expect("Invalid address space");
.get_mut(usize(regs.rcx - 1))
.expect("Invalid address space");
let slice_start: *mut u8 = ptr::with_exposed_provenance_mut(usize(regs.rdx)); let slice_start: *mut u8 = ptr::with_exposed_provenance_mut(usize(regs.rdx));
space.run(|| unsafe { space.run(|| unsafe {
slice::from_raw_parts_mut(slice_start, usize(regs.rsi)).fill(0); slice::from_raw_parts_mut(slice_start, usize(regs.rsi)).fill(0);
}); });
});
retval = 0; retval = 0;
} }
18 => { 18 => {
let mut tasking = TASKING.lock(); if TASKING.current_message_queue_mut(|x| x.is_empty()) {
if tasking.current_message_queue_mut().is_empty() { TASKING.sleep(SleepReason::WaitingForIPC);
tasking.sleep(SleepReason::WaitingForIPC);
} }
} }
19 => { 19 => {
let tasking = TASKING.lock(); let args = TASKING.arguments();
let args = tasking.arguments();
retval = u64(args.0.expose_provenance()); retval = u64(args.0.expose_provenance());
retval2 = u64(args.1); retval2 = u64(args.1);
} }
20 => {
retval = if regs.rcx == 0 {
unsafe {
ACTIVE_SPACE
.lock()
.map_free_to(
PhysFrame::from_start_address(PhysAddr::new(regs.rdx)).unwrap(),
usize(regs.rsi),
PageTableFlags::from_bits_truncate(regs.rdi),
)
.map_or(0, |x| u64(x.expose_provenance()))
}
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
unsafe {
space
.map_free_to(
PhysFrame::from_start_address(PhysAddr::new(regs.rdx)).unwrap(),
usize(regs.rsi),
PageTableFlags::from_bits_truncate(regs.rdi),
)
.map_or(0, |x| u64(x.expose_provenance()))
}
} else {
0
}
})
}
}
21 => {
retval = if regs.rcx == 0 {
let (start_virt, start_phys) = ACTIVE_SPACE
.lock()
.map_free_cont_phys(
usize(regs.rdx),
PageTableFlags::from_bits_truncate(regs.rsi),
)
.map_or((0, 0), |(ptr, start_phys)| (u64(ptr.expose_provenance()), start_phys));
retval2 = start_phys;
start_virt
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
let (start_virt, start_phys) = space
.map_free_cont_phys(
usize(regs.rdx),
PageTableFlags::from_bits_truncate(regs.rsi),
)
.map_or((0, 0), |(ptr, start_phys)| {
(u64(ptr.expose_provenance()), start_phys)
});
retval2 = start_phys;
start_virt
} else {
0
}
})
};
if retval != 0 {
TASKING.record_alloc(usize(regs.rdx) * 4096);
}
}
22 => 'call22: {
let Ok(page) = Page::from_start_address(VirtAddr::new(regs.rdx)) else {
retval = 1;
break 'call22;
};
retval = if regs.rcx == 0 {
TASKING.record_dealloc(usize(regs.rsi * 4096));
u64::from(ACTIVE_SPACE.lock().unmap(page, usize(regs.rsi)).is_err())
} else {
TASKING.address_spaces_mut(|x| {
#[warn(
clippy::arithmetic_side_effects,
reason = "FIXME: The current address space should be usize::MAX as that is an invalid index, instead of 0."
)]
if let Some(space) = x.get_mut(usize(regs.rcx - 1)) {
TASKING.record_dealloc(usize(regs.rsi*4096));
u64::from(space.unmap(page, usize(regs.rsi)).is_err())
} else {
1
}
})
}
}
_ => (), _ => (),
}; };
unsafe { SYSCALL_REGS = regs }; unsafe { SYSCALL_REGS = regs };

View File

@ -1,4 +1,4 @@
use crate::{println, virtual_memory::KERNEL_SPACE}; use crate::{println, tasking::TASKING, virtual_memory::KERNEL_SPACE};
use core::{ use core::{
alloc::{GlobalAlloc, Layout}, alloc::{GlobalAlloc, Layout},
@ -45,6 +45,7 @@ unsafe impl GlobalAlloc for Heap {
let Ok(ptr) = KERNEL_SPACE.lock().map_free(num_pages, PageTableFlags::empty()) else { let Ok(ptr) = KERNEL_SPACE.lock().map_free(num_pages, PageTableFlags::empty()) else {
return (ptr::null_mut(), layout); return (ptr::null_mut(), layout);
}; };
TASKING.record_heap_alloc(num_pages * 4096);
#[expect(clippy::unwrap_used, reason = " #[expect(clippy::unwrap_used, reason = "
from_size_align requires align to be a nonzero power of two, which it is. from_size_align requires align to be a nonzero power of two, which it is.
Also, size must be less than isize when rounded up to a multiple of align. Also, size must be less than isize when rounded up to a multiple of align.

View File

@ -285,7 +285,6 @@ pub fn main() {
reason = "Argument does not contain a null byte, thus this cannot panic" reason = "Argument does not contain a null byte, thus this cannot panic"
)] )]
TASKING TASKING
.lock()
.new_process( .new_process(
ptr::with_exposed_provenance(usize(init.ehdr.e_entry)), ptr::with_exposed_provenance(usize(init.ehdr.e_entry)),
init_addr_space, init_addr_space,

View File

@ -15,15 +15,11 @@ use core::panic::PanicInfo;
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo<'_>) -> ! { fn panic(info: &PanicInfo<'_>) -> ! {
print!("Kernel panic in "); print!("Kernel panic in ");
if let Some(tasking) = TASKING.try_lock() { if let Some(pid) = TASKING.current_pid() {
if let Some(pid) = tasking.current_pid() {
print!("PID {}", pid); print!("PID {}", pid);
} else { } else {
print!("kernel init"); print!("kernel init");
} }
} else {
print!("PID Unknown");
}
println!(": {info}"); println!(": {info}");
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
print_backtrace(); print_backtrace();

View File

@ -11,7 +11,9 @@ use humansize::{SizeFormatter, BINARY};
use linked_list_allocator::hole::HoleList; use linked_list_allocator::hole::HoleList;
use spin::{Lazy, Mutex}; use spin::{Lazy, Mutex};
use x86_64::{ use x86_64::{
structures::paging::{FrameAllocator, FrameDeallocator, PhysFrame, Size4KiB}, structures::paging::{
frame::PhysFrameRange, FrameAllocator, FrameDeallocator, PhysFrame, Size4KiB,
},
PhysAddr, PhysAddr,
}; };
@ -39,6 +41,7 @@ enum EfiMemoryTypes {
pub struct PhysicalMemory { pub struct PhysicalMemory {
alloc: HoleList, alloc: HoleList,
bytes_used: usize, bytes_used: usize,
peak_used: usize,
} }
unsafe impl Send for PhysicalMemory {} unsafe impl Send for PhysicalMemory {}
@ -46,7 +49,16 @@ unsafe impl Sync for PhysicalMemory {}
impl PhysicalMemory { impl PhysicalMemory {
pub fn print_stats(&self) { pub fn print_stats(&self) {
println!("[PMM] {} currently allocated", SizeFormatter::new(self.bytes_used, BINARY)); println!(
"[PMM] {} currently allocated ({})",
SizeFormatter::new(self.bytes_used, BINARY),
self.bytes_used / 4096,
);
println!(
"[PMM] {} allocated at peak ({})",
SizeFormatter::new(self.peak_used, BINARY),
self.peak_used / 4096,
);
} }
} }
@ -55,6 +67,9 @@ const FRAME_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(4096, 40
unsafe impl FrameAllocator<Size4KiB> for PhysicalMemory { unsafe impl FrameAllocator<Size4KiB> for PhysicalMemory {
fn allocate_frame(&mut self) -> Option<PhysFrame> { fn allocate_frame(&mut self) -> Option<PhysFrame> {
self.bytes_used = self.bytes_used.saturating_add(4096); self.bytes_used = self.bytes_used.saturating_add(4096);
if self.bytes_used > self.peak_used {
self.peak_used = self.bytes_used;
}
self.alloc.allocate_first_fit(FRAME_LAYOUT).ok().map(|(ptr, _)| { self.alloc.allocate_first_fit(FRAME_LAYOUT).ok().map(|(ptr, _)| {
#[expect(clippy::unwrap_used, reason = "PhysFrame requires its argument to be 4k aligned, which is guaranteed by the allocator")] #[expect(clippy::unwrap_used, reason = "PhysFrame requires its argument to be 4k aligned, which is guaranteed by the allocator")]
#[expect(clippy::arithmetic_side_effects, reason = "All addresses passed to the allocator are > PHYS_OFFSET, so this cannot underflow")] #[expect(clippy::arithmetic_side_effects, reason = "All addresses passed to the allocator are > PHYS_OFFSET, so this cannot underflow")]
@ -79,6 +94,38 @@ impl FrameDeallocator<Size4KiB> for PhysicalMemory {
} }
} }
impl PhysicalMemory {
pub fn allocate_frame_range(&mut self, len: usize) -> Option<PhysFrameRange> {
self.bytes_used = self.bytes_used.saturating_add(4096 * len);
if self.bytes_used > self.peak_used {
self.peak_used = self.bytes_used;
}
if len >= 0x10_0000_0000_0000 {
return None;
}
#[expect(clippy::arithmetic_side_effects, reason = "The check above makes sure the multiplication cannot overflow.")]
#[expect(clippy::unwrap_used, reason = "4096 is a nonzero power of two, and len is already a multiple of 4096 so rounding cannot overflow.")]
self.alloc.allocate_first_fit(Layout::from_size_align(4096 * len, 4096).unwrap()).ok().map(|(ptr, _)| {
#[expect(clippy::unwrap_used, reason = "PhysFrame requires its argument to be 4k aligned, which is guaranteed by the allocator")]
#[expect(clippy::arithmetic_side_effects, reason = "All addresses passed to the allocator are > PHYS_OFFSET, so this cannot underflow")]
let start = PhysFrame::from_start_address(PhysAddr::new(
(u64(ptr.as_ptr().expose_provenance())) - PHYS_OFFSET.as_u64(),
))
.unwrap();
#[expect(clippy::unwrap_used, reason = "PhysFrame requires its argument to be 4k aligned, which is guaranteed by the allocator")]
#[expect(clippy::arithmetic_side_effects, reason = "All addresses passed to the allocator are > PHYS_OFFSET, so this cannot underflow")]
let end = PhysFrame::from_start_address(PhysAddr::new(
(u64(ptr.as_ptr().expose_provenance()) + (u64(len) * 4096)) - PHYS_OFFSET.as_u64(),
))
.unwrap();
PhysFrameRange {
start,
end
}
})
}
}
pub static PHYSICAL_MEMORY: Lazy<Mutex<PhysicalMemory>> = Lazy::new(|| { pub static PHYSICAL_MEMORY: Lazy<Mutex<PhysicalMemory>> = Lazy::new(|| {
println!("[PMM] Bootloader reports the following regions:"); println!("[PMM] Bootloader reports the following regions:");
let mut region_iter = BOOTINFO.memory_regions.iter().peekable(); let mut region_iter = BOOTINFO.memory_regions.iter().peekable();
@ -135,5 +182,5 @@ pub static PHYSICAL_MEMORY: Lazy<Mutex<PhysicalMemory>> = Lazy::new(|| {
SizeFormatter::new(usable_mem, BINARY), SizeFormatter::new(usable_mem, BINARY),
SizeFormatter::new(total_mem, BINARY), SizeFormatter::new(total_mem, BINARY),
); );
Mutex::new(PhysicalMemory { alloc, bytes_used: 0 }) Mutex::new(PhysicalMemory { alloc, bytes_used: 0, peak_used: 0 })
}); });

View File

@ -48,7 +48,7 @@ pub fn init(mut freq: u32) {
fn handler(_irq: u8, eoi_guard: EoiGuard) { fn handler(_irq: u8, eoi_guard: EoiGuard) {
drop(eoi_guard); drop(eoi_guard);
if let Some(mut tasking) = TASKING.try_lock() { if TASKING.ok_to_yield() {
tasking.task_yield(); TASKING.task_yield();
} }
} }

View File

@ -13,5 +13,5 @@ entry_point!(start, config = &BOOTLOADER_CONFIG);
fn start(bootinfo: &'static mut BootInfo) -> ! { fn start(bootinfo: &'static mut BootInfo) -> ! {
BOOTINFO.set(bootinfo); BOOTINFO.set(bootinfo);
main(); main();
TASKING.lock().exit(); TASKING.exit();
} }

View File

@ -1,12 +1,20 @@
use crate::{ use crate::{
gdt, println, qemu_exit, gdt,
println, qemu_exit,
virtual_memory::{ASpaceMutex, AddressSpace, PagingError, KERNEL_SPACE}, virtual_memory::{ASpaceMutex, AddressSpace, PagingError, KERNEL_SPACE},
}; };
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, ffi::CString, vec::Vec}; use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, ffi::CString, vec::Vec};
use core::{alloc::Layout, arch::asm, ffi::CStr, ptr::addr_of}; use core::{
alloc::Layout,
arch::asm,
ffi::CStr,
ptr::{addr_of, addr_of_mut},
sync::atomic::{AtomicUsize, Ordering},
};
use crossbeam_queue::SegQueue; use crossbeam_queue::SegQueue;
use humansize::{SizeFormatter, BINARY};
use slab::Slab; use slab::Slab;
use spin::{Lazy, Mutex}; use spin::{Lazy, Mutex, RwLock};
use x86_64::{ use x86_64::{
instructions::interrupts, instructions::interrupts,
structures::paging::{Page, PageTableFlags}, structures::paging::{Page, PageTableFlags},
@ -75,9 +83,6 @@ extern "C" fn task_init() {
} }
extern "C" fn task_force_unlock() { extern "C" fn task_force_unlock() {
// SAFETY: This is safe because this function is only ever used once for new tasks, to replace
// the end of the task_yield function that normally unlocks the mutex.
unsafe { TASKING.force_unlock() }
interrupts::enable(); interrupts::enable();
} }
@ -88,48 +93,63 @@ pub enum SleepReason {
#[derive(Debug)] #[derive(Debug)]
struct Process { struct Process {
address_space: Option<AddressSpace>,
kernel_stack: Box<[usize], &'static ASpaceMutex>, kernel_stack: Box<[usize], &'static ASpaceMutex>,
kernel_esp: *mut usize, kernel_esp: *mut usize,
kernel_esp_top: VirtAddr, kernel_esp_top: VirtAddr,
address_space: Option<AddressSpace>,
address_spaces: Slab<AddressSpace>,
data_buffers: Slab<*mut [u8]>,
message_queue: SegQueue<(usize, usize)>,
sleeping: Option<SleepReason>,
arguments: (*const *const u8, usize), arguments: (*const *const u8, usize),
address_spaces: Mutex<Slab<AddressSpace>>,
data_buffers: Mutex<Slab<*mut [u8]>>,
message_queue: Mutex<SegQueue<(usize, usize)>>,
sleeping: RwLock<Option<SleepReason>>,
bytes_allocated: AtomicUsize,
} }
unsafe impl Send for Process {} unsafe impl Send for Process {}
unsafe impl Sync for Process {}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct InvalidPid; pub struct InvalidPid;
pub static TASKING: Lazy<Mutex<Tasking>> = Lazy::new(|| { pub static TASKING: Lazy<Tasking> = Lazy::new(|| Tasking {
Mutex::new(Tasking { processes: RwLock::new(Slab::new()),
processes: Slab::new(), ready_to_run: Mutex::new(VecDeque::new()),
ready_to_run: VecDeque::new(), current_pid: RwLock::new(None),
current_process: None, freeable_kstacks: Mutex::new(Vec::new()),
freeable_kstacks: Vec::new(), alloc_to_account: AtomicUsize::new(0),
}) dealloc_to_account: AtomicUsize::new(0),
kinit_allocated: AtomicUsize::new(0),
buf_allocated: AtomicUsize::new(0),
kstacks_allocated: AtomicUsize::new(0),
heap_allocated: AtomicUsize::new(0),
}); });
#[derive(Debug)] #[derive(Debug)]
pub struct Tasking { pub struct Tasking {
processes: Slab<Process>, processes: RwLock<Slab<Process>>,
ready_to_run: VecDeque<usize>, ready_to_run: Mutex<VecDeque<usize>>,
current_process: Option<usize>, current_pid: RwLock<Option<usize>>,
freeable_kstacks: Vec<Box<[usize], &'static ASpaceMutex>>, freeable_kstacks: Mutex<Vec<Box<[usize], &'static ASpaceMutex>>>,
alloc_to_account: AtomicUsize,
dealloc_to_account: AtomicUsize,
kinit_allocated: AtomicUsize,
buf_allocated: AtomicUsize,
kstacks_allocated: AtomicUsize,
heap_allocated: AtomicUsize,
} }
pub const KSTACK_SIZE: usize = 0x1_0000 / 8;
impl Tasking { impl Tasking {
pub fn new_process( pub fn new_process(
&mut self, &self,
entry_point: *const extern "C" fn() -> !, entry_point: *const extern "C" fn() -> !,
mut address_space: AddressSpace, mut address_space: AddressSpace,
arguments: &[&CStr], arguments: &[&CStr],
) -> Result<usize, PagingError> { ) -> Result<usize, PagingError> {
let mut kernel_stack = Vec::new_in(&*KERNEL_SPACE); let mut kernel_stack = Vec::with_capacity_in(KSTACK_SIZE, &*KERNEL_SPACE);
kernel_stack.resize(0x1_0000 - 0x4, 0); kernel_stack.resize(KSTACK_SIZE - 0x4, 0);
#[expect(clippy::as_conversions, reason = "Needed to get address of function")] #[expect(clippy::as_conversions, reason = "Needed to get address of function")]
{ {
kernel_stack.push(task_force_unlock as usize); kernel_stack.push(task_force_unlock as usize);
@ -138,6 +158,7 @@ impl Tasking {
kernel_stack.push(0xFFF_FF80_0000 + (16 * 4096)); kernel_stack.push(0xFFF_FF80_0000 + (16 * 4096));
kernel_stack.push(entry_point.expose_provenance()); kernel_stack.push(entry_point.expose_provenance());
let mut kernel_stack = kernel_stack.into_boxed_slice(); let mut kernel_stack = kernel_stack.into_boxed_slice();
self.kstacks_allocated.fetch_add(KSTACK_SIZE * 8, Ordering::Relaxed);
address_space.map_assert_unused( address_space.map_assert_unused(
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
@ -147,6 +168,7 @@ impl Tasking {
16, 16,
PageTableFlags::USER_ACCESSIBLE, PageTableFlags::USER_ACCESSIBLE,
)?; )?;
self.kstacks_allocated.fetch_add(16 * 4096, Ordering::Relaxed);
let arguments = arguments.iter().map(|arg| (*arg).to_owned()).collect::<Vec<CString>>(); let arguments = arguments.iter().map(|arg| (*arg).to_owned()).collect::<Vec<CString>>();
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
@ -173,6 +195,7 @@ impl Tasking {
}; };
let user_arg_mem = let user_arg_mem =
address_space.map_free(args_layout.size() / 4096, PageTableFlags::USER_ACCESSIBLE)?; address_space.map_free(args_layout.size() / 4096, PageTableFlags::USER_ACCESSIBLE)?;
self.kstacks_allocated.fetch_add(args_layout.size(), Ordering::Relaxed);
address_space.run(|| unsafe { address_space.run(|| unsafe {
let mut ptr_ptr: *mut *const u8 = user_arg_mem.cast(); let mut ptr_ptr: *mut *const u8 = user_arg_mem.cast();
for (&offset, argument) in arg_offsets.iter().zip(arguments.iter()) { for (&offset, argument) in arg_offsets.iter().zip(arguments.iter()) {
@ -183,81 +206,132 @@ impl Tasking {
ptr_ptr = ptr_ptr.add(1); ptr_ptr = ptr_ptr.add(1);
} }
}); });
let pid = self.processes.insert(Process { let pid = self.processes.write().insert(Process {
#[expect( #[expect(
clippy::indexing_slicing, clippy::indexing_slicing,
reason = "Stack length is 0x1_0000, this cannot panic" reason = "Stack length is 0x1_0000, this cannot panic"
)] )]
kernel_esp: &mut kernel_stack[0xFFF6], kernel_esp: &mut kernel_stack[KSTACK_SIZE - 10],
#[expect( #[expect(
clippy::indexing_slicing, clippy::indexing_slicing,
reason = "Stack length is 0x1_0000, this cannot panic" reason = "Stack length is 0x1_0000, this cannot panic"
)] )]
kernel_esp_top: VirtAddr::from_ptr(addr_of!(kernel_stack[0xFFFF]).wrapping_add(1)), kernel_esp_top: VirtAddr::from_ptr(
addr_of!(kernel_stack[KSTACK_SIZE - 1]).wrapping_add(1),
),
kernel_stack, kernel_stack,
address_space: Some(address_space), address_space: Some(address_space),
address_spaces: Slab::new(), address_spaces: Mutex::new(Slab::new()),
data_buffers: Slab::new(), data_buffers: Mutex::new(Slab::new()),
message_queue: SegQueue::new(), message_queue: Mutex::new(SegQueue::new()),
sleeping: None, sleeping: RwLock::new(None),
arguments: (user_arg_mem.cast(), arguments.len()), arguments: (user_arg_mem.cast(), arguments.len()),
bytes_allocated: AtomicUsize::new(0),
}); });
self.ready_to_run.push_back(pid); self.ready_to_run.lock().push_back(pid);
Ok(pid) Ok(pid)
} }
pub fn task_yield(&mut self) { pub fn ok_to_yield(&self) -> bool {
self.freeable_kstacks.clear(); !(self.freeable_kstacks.is_locked()
let Some(current_process) = self.current_process else { || (self.current_pid.reader_count() > 0)
|| (self.current_pid.writer_count() > 0)
|| self.ready_to_run.is_locked()
|| (self.processes.reader_count() > 0)
|| (self.processes.writer_count() > 0)
|| (self.current_pid.writer_count() > 0))
}
pub fn task_yield(&self) {
self.kstacks_allocated
.fetch_sub(self.freeable_kstacks.lock().len() * KSTACK_SIZE * 8, Ordering::Relaxed);
self.freeable_kstacks.lock().clear();
let Some(current_pid) = *self.current_pid.read() else {
return; return;
}; };
if let Some(next_process_pid) = self.ready_to_run.pop_front() { let next_process_pid = self.ready_to_run.lock().pop_front();
if let Some(next_process_pid) = next_process_pid {
#[expect( #[expect(
clippy::expect_used, clippy::expect_used,
reason = "This expect checks a critical invariant. If this fails, the kernel MUST panic" reason = "This expect checks a critical invariant. If this fails, the kernel MUST panic"
)] )]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let current_address_space = self.processes[next_process_pid] let current_address_space = self.processes.write()[next_process_pid]
.address_space .address_space
.take() .take()
.expect("Non-current process has active page table") .expect("Non-current process has active page table")
.activate(); .activate();
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
self.processes[current_process].address_space = Some(current_address_space); self.processes.write()[current_pid].address_space = Some(current_address_space);
let processes = self.processes.read();
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let next_process = &self.processes[next_process_pid]; let current_process = &processes[current_pid];
current_process
.bytes_allocated
.fetch_add(self.alloc_to_account.load(Ordering::Relaxed), Ordering::Relaxed);
current_process
.bytes_allocated
.fetch_sub(self.dealloc_to_account.load(Ordering::Relaxed), Ordering::Relaxed);
self.alloc_to_account.store(0, Ordering::Relaxed);
self.dealloc_to_account.store(0, Ordering::Relaxed);
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let next_process = &processes[next_process_pid];
gdt::set_tss_stack(next_process.kernel_esp_top); gdt::set_tss_stack(next_process.kernel_esp_top);
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] if current_process.sleeping.read().is_none() {
if self.processes[current_process].sleeping.is_none() { self.ready_to_run.lock().push_back(current_pid);
self.ready_to_run.push_back(current_process);
} }
let kernel_esp = next_process.kernel_esp; let kernel_esp = next_process.kernel_esp;
let previous_process = current_process; let previous_process = current_pid;
*self.current_pid.write() = Some(next_process_pid);
core::mem::drop(processes);
let mut processes = self.processes.write();
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
self.current_process = Some(next_process_pid); let curr_stack = addr_of_mut!(processes[previous_process].kernel_esp);
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] core::mem::drop(processes);
switch_to_asm(&mut (self.processes[previous_process].kernel_esp), kernel_esp); switch_to_asm(curr_stack, kernel_esp);
} else if { } else if {
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let res = self.processes[current_process].sleeping.is_some(); let res = self.processes.read()[current_pid].sleeping.read().is_some();
res res
} { } {
println!("All processes sleeping, exiting QEMU"); println!("All processes sleeping, exiting QEMU");
self.print_stats();
qemu_exit::exit_qemu(); qemu_exit::exit_qemu();
} }
} }
pub fn current_pid(&self) -> Option<usize> { pub fn current_pid(&self) -> Option<usize> {
self.current_process *self.current_pid.read()
} }
pub fn exit(&mut self) -> ! { pub fn exit(&self) -> ! {
if let Some(next_process_pid) = self.ready_to_run.pop_front() { let next_process_pid = self.ready_to_run.lock().pop_front();
if let Some(current_process) = self.current_process { if let Some(next_process_pid) = next_process_pid {
self.freeable_kstacks.push(self.processes.remove(current_process).kernel_stack); #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let mut processes = self.processes.write();
if let Some(current_pid) = *self.current_pid.read() {
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let current_process = &processes[current_pid];
current_process
.bytes_allocated
.fetch_add(self.alloc_to_account.load(Ordering::Relaxed), Ordering::Relaxed);
current_process
.bytes_allocated
.fetch_sub(self.dealloc_to_account.load(Ordering::Relaxed), Ordering::Relaxed);
self.alloc_to_account.store(0, Ordering::Relaxed);
self.dealloc_to_account.store(0, Ordering::Relaxed);
println!(
"[TASKING] PID {current_pid} exiting, used {} ({}), this is being leaked.",
SizeFormatter::new(
current_process.bytes_allocated.load(Ordering::Relaxed),
BINARY
),
current_process.bytes_allocated.load(Ordering::Relaxed) / 4096
);
self.freeable_kstacks.lock().push(processes.remove(current_pid).kernel_stack);
} }
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
let next_process = &mut self.processes[next_process_pid]; let next_process = &mut processes[next_process_pid];
#[expect( #[expect(
clippy::expect_used, clippy::expect_used,
reason = "This expect checks a critical invariant. If this fails, the kernel MUST panic" reason = "This expect checks a critical invariant. If this fails, the kernel MUST panic"
@ -269,69 +343,158 @@ impl Tasking {
.activate(); .activate();
gdt::set_tss_stack(next_process.kernel_esp_top); gdt::set_tss_stack(next_process.kernel_esp_top);
let kernel_esp = next_process.kernel_esp; let kernel_esp = next_process.kernel_esp;
self.current_process = Some(next_process_pid); *self.current_pid.write() = Some(next_process_pid);
core::mem::drop(processes);
switch_to_asm_exit(kernel_esp); switch_to_asm_exit(kernel_esp);
unreachable!() unreachable!()
} else { } else {
println!("Last non-sleeping process exited, exiting QEMU"); println!("Last non-sleeping process exited, exiting QEMU");
self.print_stats();
qemu_exit::exit_qemu(); qemu_exit::exit_qemu();
} }
} }
pub fn address_spaces_mut(&mut self) -> &mut Slab<AddressSpace> { pub fn address_spaces_mut<F: FnOnce(&mut Slab<AddressSpace>) -> T, T>(&self, func: F) -> T {
let processes = self.processes.read();
#[warn(clippy::unwrap_used, reason = "FIXME")] #[warn(clippy::unwrap_used, reason = "FIXME")]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
&mut self.processes[self.current_process.unwrap()].address_spaces let mut aspaces = processes[self.current_pid.read().unwrap()].address_spaces.lock();
func(&mut aspaces)
} }
pub fn data_buffers_mut(&mut self) -> &mut Slab<*mut [u8]> { pub fn data_buffers_mut<F: FnOnce(&mut Slab<*mut [u8]>) -> T, T>(&self, func: F) -> T {
let processes = self.processes.read();
#[warn(clippy::unwrap_used, reason = "FIXME")] #[warn(clippy::unwrap_used, reason = "FIXME")]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
&mut self.processes[self.current_process.unwrap()].data_buffers let mut dbufs = processes[self.current_pid.read().unwrap()].data_buffers.lock();
func(&mut dbufs)
} }
pub fn proc_data_buffers_mut( pub fn proc_data_buffers_mut<F: FnOnce(&mut Slab<*mut [u8]>) -> T, T>(
&mut self, &self,
pid: usize, pid: usize,
) -> Result<&mut Slab<*mut [u8]>, InvalidPid> { func: F,
Ok(&mut self.processes.get_mut(pid).ok_or(InvalidPid)?.data_buffers) ) -> Result<T, InvalidPid> {
let processes = self.processes.read();
let mut dbufs = processes.get(pid).ok_or(InvalidPid)?.data_buffers.lock();
Ok(func(&mut dbufs))
} }
pub fn current_message_queue_mut(&mut self) -> &mut SegQueue<(usize, usize)> { pub fn current_message_queue_mut<F: FnOnce(&mut SegQueue<(usize, usize)>) -> T, T>(
&self,
func: F,
) -> T {
let processes = self.processes.read();
#[warn(clippy::unwrap_used, reason = "FIXME")] #[warn(clippy::unwrap_used, reason = "FIXME")]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
&mut self.processes[self.current_process.unwrap()].message_queue let mut queue = processes[self.current_pid.read().unwrap()].message_queue.lock();
func(&mut queue)
} }
pub fn message_queue_mut( pub fn message_queue_mut<F: FnOnce(&mut SegQueue<(usize, usize)>) -> T, T>(
&mut self, &self,
pid: usize, pid: usize,
) -> Result<&mut SegQueue<(usize, usize)>, InvalidPid> { func: F,
Ok(&mut self.processes.get_mut(pid).ok_or(InvalidPid)?.message_queue) ) -> Result<T, InvalidPid> {
let processes = self.processes.read();
let mut queue = processes.get(pid).ok_or(InvalidPid)?.message_queue.lock();
Ok(func(&mut queue))
} }
pub fn proc_sleeping(&self, pid: usize) -> Result<Option<SleepReason>, InvalidPid> { pub fn proc_sleeping(&self, pid: usize) -> Result<Option<SleepReason>, InvalidPid> {
Ok(self.processes.get(pid).ok_or(InvalidPid)?.sleeping) Ok(*(self.processes.read().get(pid).ok_or(InvalidPid)?.sleeping.read()))
} }
pub fn sleep(&mut self, reason: SleepReason) { pub fn sleep(&self, reason: SleepReason) {
#[warn(clippy::unwrap_used, reason = "FIXME")] #[warn(clippy::unwrap_used, reason = "FIXME")]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
self.processes[self.current_process.unwrap()].sleeping = Some(reason); *self.processes.read()[self.current_pid.read().unwrap()].sleeping.write() = Some(reason);
self.task_yield(); self.task_yield();
} }
pub fn wake(&mut self, pid: usize) -> Result<(), InvalidPid> { pub fn wake(&self, pid: usize) -> Result<(), InvalidPid> {
if self.processes.get(pid).ok_or(InvalidPid)?.sleeping.is_some() { *self.processes.read().get(pid).ok_or(InvalidPid)?.sleeping.write() = None;
self.processes.get_mut(pid).ok_or(InvalidPid)?.sleeping = None; self.ready_to_run.lock().push_back(pid);
self.ready_to_run.push_back(pid);
}
Ok(()) Ok(())
} }
pub fn arguments(&self) -> (*const *const u8, usize) { pub fn arguments(&self) -> (*const *const u8, usize) {
#[warn(clippy::unwrap_used, reason = "FIXME")] #[warn(clippy::unwrap_used, reason = "FIXME")]
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")] #[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
self.processes[self.current_process.unwrap()].arguments self.processes.read()[self.current_pid.read().unwrap()].arguments
}
pub fn record_alloc(&self, size: usize) {
if let Some(pid) = *self.current_pid.read() {
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
if self.processes.writer_count() == 0 {
self.processes.read()[pid].bytes_allocated.fetch_add(size, Ordering::Relaxed);
} else {
self.alloc_to_account.fetch_add(size, Ordering::Relaxed);
}
} else {
self.kinit_allocated.fetch_add(size, Ordering::Relaxed);
}
}
pub fn record_dealloc(&self, size: usize) {
if let Some(pid) = *self.current_pid.read() {
#[warn(clippy::indexing_slicing, reason = "FIXME(?)")]
if self.processes.writer_count() == 0 {
self.processes.read()[pid].bytes_allocated.fetch_sub(size, Ordering::Relaxed);
} else {
self.dealloc_to_account.fetch_add(size, Ordering::Relaxed);
}
} else {
self.kinit_allocated.fetch_sub(size, Ordering::Relaxed);
}
}
pub fn record_buf_alloc(&self, size: usize) {
self.buf_allocated.fetch_add(size, Ordering::Relaxed);
}
pub fn record_buf_dealloc(&self, size: usize) {
self.buf_allocated.fetch_sub(size, Ordering::Relaxed);
}
pub fn record_heap_alloc(&self, size: usize) {
self.heap_allocated.fetch_add(size, Ordering::Relaxed);
}
pub fn record_heap_dealloc(&self, size: usize) {
self.heap_allocated.fetch_sub(size, Ordering::Relaxed);
}
pub fn print_stats(&self) {
let mut total = self.kinit_allocated.load(Ordering::Relaxed)
+ self.buf_allocated.load(Ordering::Relaxed)
+ self.kstacks_allocated.load(Ordering::Relaxed)
+ self.heap_allocated.load(Ordering::Relaxed);
println!(
"[TASKING] Kernel init used {}",
SizeFormatter::new(self.kinit_allocated.load(Ordering::Relaxed), BINARY)
);
println!(
"[TASKING] Kernel buffers used {}",
SizeFormatter::new(self.buf_allocated.load(Ordering::Relaxed), BINARY)
);
println!(
"[TASKING] Kernel stacks used {}",
SizeFormatter::new(self.kstacks_allocated.load(Ordering::Relaxed), BINARY)
);
println!(
"[TASKING] Kernel heap used {}",
SizeFormatter::new(self.heap_allocated.load(Ordering::Relaxed), BINARY)
);
for (i, process) in self.processes.read().iter() {
total += process.bytes_allocated.load(Ordering::Relaxed);
println!(
"[TASKING] PID {} used {}",
i,
SizeFormatter::new(process.bytes_allocated.load(Ordering::Relaxed), BINARY)
);
}
println!("[TASKING] Total used {} ({})", SizeFormatter::new(total, BINARY), total / 4096);
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{bootinfo::BOOTINFO, physical_memory::PHYSICAL_MEMORY}; use crate::{bootinfo::BOOTINFO, physical_memory::PHYSICAL_MEMORY, tasking::TASKING};
use alloc::alloc::{AllocError, Allocator, Layout}; use alloc::alloc::{AllocError, Allocator, Layout};
use cast::{u64, usize}; use cast::{u64, usize};
use core::{ use core::{
@ -167,6 +167,7 @@ pub static KERNEL_SPACE: Lazy<ASpaceMutex> = Lazy::new(|| {
)] )]
let (new_child, new_child_phys) = let (new_child, new_child_phys) =
alloc_pt().expect("Could not allocate new kernel entry"); alloc_pt().expect("Could not allocate new kernel entry");
TASKING.record_alloc(4096);
new_child.write(PageTable::new()); new_child.write(PageTable::new());
new_child_phys new_child_phys
}, },
@ -452,25 +453,74 @@ impl AddressSpace {
.flush(); .flush();
} }
} }
// unsafe {
// self.mapper
// .map_range_with_table_flags(
// PageRange { start: page, end: page + num_pages as u64 },
// flags | PageTableFlags::PRESENT | PageTableFlags::WRITABLE,
// PageTableFlags::PRESENT
// | PageTableFlags::WRITABLE
// | PageTableFlags::USER_ACCESSIBLE,
// &mut *PHYSICAL_MEMORY.lock(),
// )
// .map_err(|(err, flush_range)| {
// flush_range.flush_range();
// err
// })?
// .flush_range();
// }
Ok(page.start_address().as_mut_ptr()) Ok(page.start_address().as_mut_ptr())
} }
/// Maps virtual pages to newly allocated contiguous physical memory
/// and returns the starting address and frame.
/// The newly allocated physical memory contains garbage data, so the mapping will always be writable,
/// as it doesn't make sense to map garbage data read only.
///
/// ## Safety
///
/// Creating page table mappings is a fundamentally unsafe operation because
/// there are various ways to break memory safety through it. For example,
/// re-mapping an in-use page to a different frame changes and invalidates
/// all values stored in that page, resulting in undefined behavior on the
/// next use.
///
/// The caller must ensure that no undefined behavior or memory safety
/// violations can occur through the new mapping by, among other things, preventing
/// creating uninitalized or invalid values. Rust requires that all values
/// have a correct memory layout. For example, a `bool` must be either a 0
/// or a 1 in memory, but not a 3 or 4. An exception is the `MaybeUninit`
/// wrapper type, which abstracts over possibly uninitialized memory.
/// Note: You only have to worry about this when re-mapping pages to
/// different physical frames. Mapping pages that are not in use yet is fine.
unsafe fn map_cont_phys(
&mut self,
page: Page,
num_pages: usize,
flags: PageTableFlags,
) -> Result<(*mut u8, u64), PagingError> {
self.check_request_valid(page, num_pages)?;
let frame_range = PHYSICAL_MEMORY
.lock()
.allocate_frame_range(num_pages)
.ok_or(PagingError::FrameAllocationFailed)?;
let phys_start = frame_range.start.start_address().as_u64();
#[expect(
clippy::arithmetic_side_effects,
reason = "check_request_valid guarentees this won't overflow"
)]
for (page, frame) in
(PageRange { start: page, end: page + u64(num_pages) }).zip(frame_range)
{
unsafe {
let mut phys_mem = PHYSICAL_MEMORY.lock();
self.mapper
.map_to_with_table_flags(
page,
frame,
flags
| PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| if self.alloc_force_user {
PageTableFlags::USER_ACCESSIBLE
} else {
PageTableFlags::empty()
},
PageTableFlags::PRESENT
| PageTableFlags::WRITABLE
| PageTableFlags::USER_ACCESSIBLE,
&mut *phys_mem,
)?
.flush();
}
}
Ok((page.start_address().as_mut_ptr(), phys_start))
}
/// Maps new virtual pages and returns the starting addresss /// Maps new virtual pages and returns the starting addresss
/// ///
/// ## Safety /// ## Safety
@ -502,6 +552,20 @@ impl AddressSpace {
unsafe { self.map(self.find_free_pages(num_pages)?, num_pages, flags) } unsafe { self.map(self.find_free_pages(num_pages)?, num_pages, flags) }
} }
/// Maps new virtual pages to new contigous physical memory
/// and returns the starting address and frame.
/// The newly allocated physical memory contains garbage data, so the mapping will always be writable,
/// as it doesn't make sense to map garbage data read only.
pub fn map_free_cont_phys(
&mut self,
num_pages: usize,
flags: PageTableFlags,
) -> Result<(*mut u8, u64), PagingError> {
// SAFETY: &mut aliasing is prevented by using free physical frames, and uninitialized
// values are prevented by using free virtual pages.
unsafe { self.map_cont_phys(self.find_free_pages(num_pages)?, num_pages, flags) }
}
/// Same behavior as `map`, but asserts that the requested virtual page range is unmapped, and /// Same behavior as `map`, but asserts that the requested virtual page range is unmapped, and
/// thus is safe. /// thus is safe.
pub fn map_assert_unused( pub fn map_assert_unused(
@ -532,6 +596,9 @@ impl AddressSpace {
if self.translate_addr(page.start_address()).is_some() { if self.translate_addr(page.start_address()).is_some() {
continue; continue;
} }
if !self.is_kernel {
TASKING.record_alloc(4096);
}
unsafe { unsafe {
let mut phys_mem = PHYSICAL_MEMORY.lock(); let mut phys_mem = PHYSICAL_MEMORY.lock();
let frame = phys_mem.allocate_frame().ok_or(PagingError::FrameAllocationFailed)?; let frame = phys_mem.allocate_frame().ok_or(PagingError::FrameAllocationFailed)?;
@ -551,6 +618,22 @@ impl AddressSpace {
Ok(page.start_address().as_mut_ptr()) Ok(page.start_address().as_mut_ptr())
} }
pub fn unmap(&mut self, page: Page, num_pages: usize) -> Result<(), PagingError> {
self.check_request_valid(page, num_pages)?;
#[expect(
clippy::arithmetic_side_effects,
reason = "check_request_valid guarentees this won't overflow"
)]
for page in (PageRange { start: page, end: page + u64(num_pages) }) {
unsafe {
let (frame, flush) = self.mapper.unmap(page)?;
PHYSICAL_MEMORY.lock().deallocate_frame(frame);
flush.flush();
}
}
Ok(())
}
/// Finds a range of free pages and returns the starting page /// Finds a range of free pages and returns the starting page
fn find_free_pages(&self, num_pages: usize) -> Result<Page, PagingError> { fn find_free_pages(&self, num_pages: usize) -> Result<Page, PagingError> {
if num_pages == 0 { if num_pages == 0 {
@ -652,11 +735,7 @@ unsafe impl Allocator for ASpaceMutex {
PageTableFlags::empty() PageTableFlags::empty()
}; };
let start = space.map_free(size / 4096, flags).map_err(|_| AllocError)?; let start = space.map_free(size / 4096, flags).map_err(|_| AllocError)?;
// if space.alloc_force_user { Ok(unsafe { slice::from_raw_parts_mut(start.cast::<u8>(), size) }.into())
// dbg!(start);
// }
let res = Ok(unsafe { slice::from_raw_parts_mut(start.cast::<u8>(), size) }.into());
res
} }
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) { unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
@ -670,12 +749,7 @@ unsafe impl Allocator for ASpaceMutex {
)] )]
let start_page = let start_page =
Page::from_start_address(VirtAddr::new(u64(ptr.as_ptr().expose_provenance()))).unwrap(); Page::from_start_address(VirtAddr::new(u64(ptr.as_ptr().expose_provenance()))).unwrap();
#[expect( let length = layout.size().div_ceil(4096);
clippy::arithmetic_side_effects,
reason = "div_ceil always returns at least one, and the call safety requirements means that start_page+<layout size in pages> cannot overflow."
)]
for page in Page::range(start_page, start_page + u64(layout.size().div_ceil(4096) - 1)) {
unsafe {
#[expect( #[expect(
clippy::unwrap_used, clippy::unwrap_used,
reason = " reason = "
@ -683,18 +757,6 @@ unsafe impl Allocator for ASpaceMutex {
The kernel doesn't use huge pages and the mapping must be valid to be returned from allocate, so unmap cannot fail. The kernel doesn't use huge pages and the mapping must be valid to be returned from allocate, so unmap cannot fail.
" "
)] )]
let (frame, flush) = self.0.lock().mapper.unmap(page).unwrap(); self.0.lock().unmap(start_page, length).unwrap();
PHYSICAL_MEMORY.lock().deallocate_frame(frame);
flush.flush();
}
}
// unsafe {
// self.0.lock().mapper.unmap_range(
// Page::range(start_page, start_page + (layout.size().div_ceil(4096) - 1) as u64),
// &mut *PHYSICAL_MEMORY.lock(),
// )
// }
// .unwrap()
// .flush_all();
} }
} }