diff --git a/crates/init/build.sh b/crates/init/build.sh index 352645fc..2c1498dc 100755 --- a/crates/init/build.sh +++ b/crates/init/build.sh @@ -13,6 +13,9 @@ mkdir -p fs # ./ls.rs # cp ls.elf fs/ +./signals_test.rs +cp signals_test.elf fs/ + cargo run -q -p initfs --bin util \ -- create --compress --out fs.arc --root fs fs --verbose diff --git a/crates/init/signals_test.rs b/crates/init/signals_test.rs new file mode 100755 index 00000000..b7c90017 --- /dev/null +++ b/crates/init/signals_test.rs @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +#![doc = r##""##] +#![no_std] +#![no_main] + +#[macro_use] +extern crate ulib; + +use ulib::sys::{exit, register_signal_handler, mmap, sys_sigreturn, sys_kill, sys_kill_unblockable, SignalCode}; + +fn page_fault_handler(_signal_number: u32, fault_addr: *mut ()) { + + println!("Inside of the user page fault handler, page fault at address {:x}", fault_addr as usize); + let _mmap_addr: *mut u8 = + unsafe { mmap(0, 4096, 0, 1 << 1, 0, 0).unwrap() } as *mut u8; + println!("Memory range is mmaped!"); + + unsafe { sys_sigreturn(); } + unreachable!(); +} + +#[no_mangle] +fn main() { + + let _root_fd = 3; + let page_fault_handler_ptr: fn() = unsafe { core::mem::transmute(page_fault_handler) }; + register_signal_handler(SignalCode::PageFault, page_fault_handler_ptr); + + static HELLO_CHARS: [u8; 5] = *b"hello"; + const VIRTUAL_ADDR: usize = 0x1E00000; + let hello_addr = VIRTUAL_ADDR as *mut u8; + + //Writing to unmappred region, user page fault handler should trigger + unsafe { + core::ptr::copy_nonoverlapping( + &raw const HELLO_CHARS[0], + hello_addr, + HELLO_CHARS.len(), + ); + } + + for i in 0..5 { + let curr_char: u8 = unsafe { *(hello_addr.wrapping_add(i)) }; + if curr_char != HELLO_CHARS[i] { + panic!( + "incorrect character at index {}! Expected {} got {}", + i, HELLO_CHARS[i] as char, curr_char as char + ); + } + } + + println!("signal handler test succeeded!"); + + //TODO: add kill and kill unblockable tests + + exit(15); +} diff --git a/crates/kernel/src/event/context.rs b/crates/kernel/src/event/context.rs index 22d89af9..f8df7947 100644 --- a/crates/kernel/src/event/context.rs +++ b/crates/kernel/src/event/context.rs @@ -74,6 +74,7 @@ unsafe impl Sync for AllCores {} /// The register context of a thread. #[repr(C)] +#[derive(Copy, Clone)] pub struct Context { pub regs: [usize; 31], pub kernel_sp: usize, diff --git a/crates/kernel/src/event/mod.rs b/crates/kernel/src/event/mod.rs index 1b5d5daa..c2256ad6 100644 --- a/crates/kernel/src/event/mod.rs +++ b/crates/kernel/src/event/mod.rs @@ -10,6 +10,9 @@ use alloc::boxed::Box; use context::{deschedule_thread, Context, DescheduleAction, CORES}; use scheduler::{Priority, Scheduler}; +use crate::process::signal::{SignalCode, SignalFlagOptions}; +use crate::syscall::proc::exit_user_thread; + pub static SCHEDULER: Scheduler = Scheduler::new(); pub struct Event { @@ -68,7 +71,68 @@ pub unsafe extern "C" fn run_event_loop() -> ! { EventKind::Function(func) => { func(); } - EventKind::ScheduleThread(thread) => { + EventKind::ScheduleThread(mut thread) => { + + if thread.is_user_thread() { + let proc = thread.process.as_ref().unwrap(); + + //Cleanup, if not in handler then invalidate backup + if !proc.signal_flags.contains(SignalFlagOptions::IN_HANDLER) && thread.backup_context.is_some() { + thread.backup_context = None; + } + + if proc.signal_flags.contains(SignalFlagOptions::IS_DEAD) { + unsafe { exit_user_thread(thread, SignalCode::KillUnblockable.into()) }; + } else if proc.signal_flags.contains(SignalFlagOptions::IS_KILL) { + + if proc.signal_flags.contains(SignalFlagOptions::IN_HANDLER) { + //Received kill while in another signal handler + unsafe { exit_user_thread(thread, SignalCode::InHandler.into()) }; + } + + if let Some(kill_block_handler) = proc.signal_handlers.lock().kill_block_handler { + //enter_thread will now use the backup context + proc.signal_flags.set(SignalFlagOptions::IN_HANDLER, true); + + thread.backup_context = Some(thread.context.unwrap()); + //replacing link register with the address of the handler and the + //the first two registers with the signal number and stack pointer + thread.backup_context.unwrap().regs[0] = SignalCode::KillBlockable as usize; + thread.backup_context.unwrap().regs[30] = unsafe { core::mem::transmute::(kill_block_handler) }; + + //It is up to sigreturn to remove handler status which will then + //invalidate the secondary context + + } else { + //No kill block handler registered + unsafe { exit_user_thread(thread, SignalCode::KillBlockable.into()) }; + } + } else if proc.signal_flags.contains(SignalFlagOptions::IS_PAGE_FAULT) { + //There should be a nicer way to write this with less code duplication + + if proc.signal_flags.contains(SignalFlagOptions::IN_HANDLER) { + //Received kill while in another signal handler + unsafe { exit_user_thread(thread, SignalCode::InHandler.into()) }; + } + + if let Some(page_fault_handler) = proc.signal_handlers.lock().user_page_fault_handler { + //enter_thread will now use the backup context + proc.signal_flags.set(SignalFlagOptions::IN_HANDLER, true); + + thread.backup_context = Some(thread.context.unwrap()); + //replacing link register with the address of the handler and the + //the first two registers with the signal number and stack pointer + thread.backup_context.unwrap().regs[0] = SignalCode::PageFault as usize; + //TODO: save the fault address into register 1 + thread.backup_context.unwrap().regs[30] = unsafe { core::mem::transmute::(page_fault_handler) }; + + } else { + //No page fault handler registed + unsafe { exit_user_thread(thread, SignalCode::PageFault.into()) }; + } + } + } + unsafe { thread.enter_thread() }; } EventKind::AsyncTask(task_id) => { diff --git a/crates/kernel/src/event/thread.rs b/crates/kernel/src/event/thread.rs index a0591488..564a2961 100644 --- a/crates/kernel/src/event/thread.rs +++ b/crates/kernel/src/event/thread.rs @@ -12,12 +12,16 @@ use super::{Event, SCHEDULER}; /// while the thread isn't running, stores the saved register state of /// the thread. pub struct Thread { + //Pointer to the last context run on this thread. More documentation is offered below pub last_context: NonNull, pub stack: NonNull<[u128]>, // Stored on the thread's stack func: Option>, pub context: Option, + //Backup user context which stores the old context prior to the run of the handler + //This is necessary because we do not want to context to be modified inside of the handler + pub backup_context: Option, pub user_regs: Option, pub process: Option, pub priority: Priority, @@ -74,6 +78,7 @@ impl Thread { last_context: NonNull::dangling(), func: None, context: Some(data), + backup_context: None, user_regs: Some(UserRegs { ttbr0_el1: process.get_ttbr0(), usermode: true, @@ -118,6 +123,7 @@ impl Thread { last_context: context, func, context: None, + backup_context: None, user_regs: None, process: None, priority, @@ -176,7 +182,14 @@ impl Thread { unsafe { crate::sync::disable_interrupts() }; if let Some(user) = &self.user_regs { - let ctx = unsafe { &mut *next_ctx }; + //If in handler, use backup context as to not modify regular context + let proc = self.process.as_ref().unwrap(); + let ctx: &mut Context = if proc.signal_flags.contains(crate::process::signal::SignalFlagOptions::IN_HANDLER) { + &mut self.backup_context.unwrap() + } else { + unsafe { &mut *next_ctx } + }; + unsafe { Self::restore_user_regs(user, ctx) }; } diff --git a/crates/kernel/src/process.rs b/crates/kernel/src/process.rs index 890f0191..005b8a12 100644 --- a/crates/kernel/src/process.rs +++ b/crates/kernel/src/process.rs @@ -6,6 +6,7 @@ use crate::sync::SpinLock; pub mod fd; pub mod mem; +pub mod signal; pub type ProcessRef = Arc; @@ -22,6 +23,8 @@ pub struct Process { pub root: Option, pub file_descriptors: SpinLock, pub exit_code: Arc>, + pub signal_handlers: SpinLock, + pub signal_flags: signal::SignalFlags, // TODO: replace this with signal queue } impl Process { @@ -33,6 +36,8 @@ impl Process { root: None, file_descriptors: SpinLock::new(FileDescriptorList { desc: Vec::new() }), exit_code: Arc::new(BlockingOnceCell::new()), + signal_handlers: SpinLock::new(signal::SignalHandlers::new()), + signal_flags: signal::SignalFlags::empty(), } } @@ -61,6 +66,8 @@ impl Process { root: self.root.clone(), file_descriptors: SpinLock::new(new_fds), exit_code: Arc::new(BlockingOnceCell::new()), + signal_handlers: SpinLock::new(signal::SignalHandlers::new()), + signal_flags: signal::SignalFlags::empty(), }; new_process diff --git a/crates/kernel/src/process/mem.rs b/crates/kernel/src/process/mem.rs index 313821c4..a44c6b7b 100644 --- a/crates/kernel/src/process/mem.rs +++ b/crates/kernel/src/process/mem.rs @@ -11,9 +11,11 @@ use crate::arch::memory::vmm::{ use crate::event::async_handler::{run_async_handler, HandlerContext}; use crate::event::context::Context; use crate::event::exceptions::DataAbortISS; +use crate::process::signal::{SignalFlagOptions, SignalFlags}; use crate::syscall::proc::exit_user_thread; use super::fd::ArcFd; +use super::signal; #[derive(Debug)] pub enum MmapError { @@ -310,6 +312,10 @@ unsafe impl Sync for UserAddrSpace {} pub fn page_fault_handler(ctx: &mut Context, far: usize, _iss: DataAbortISS) -> *mut Context { run_async_handler(ctx, async move |mut context: HandlerContext<'_>| { let proc = context.cur_process().unwrap(); + + if proc.signal_flags.contains(signal::SignalFlagOptions::IN_HANDLER) { + println!("Page fault at addr {far:#10x} while in a signal handler"); + } // TODO: make sure misaligned loads don't loop here? let page_addr = (far / PAGE_SIZE) * PAGE_SIZE; @@ -318,17 +324,29 @@ pub fn page_fault_handler(ctx: &mut Context, far: usize, _iss: DataAbortISS) -> let vme = mem.get_vme(page_addr); match vme { None => { - let exit_code = &proc.exit_code; - exit_code.set(crate::process::ExitStatus { - status: -1i32 as u32, - }); - drop(mem); - - println!("Invalid user access at addr {far:#10x}"); - println!("{:#?}", &*context.regs()); - - let thread = context.detach_thread(); - unsafe { exit_user_thread(thread, -4i32 as u32) } + + if let Some(_user_page_fault_handler) = proc.signal_handlers.lock().user_page_fault_handler { + //The event loop handle the rest + proc.signal_flags.set(SignalFlagOptions::IN_HANDLER, true); + + //It is up to sigreturn to remove handler status and invalidate the secondary + //context + drop(mem); + context.resume_final() + } else { + + let exit_code = &proc.exit_code; + exit_code.set(crate::process::ExitStatus { + status: signal::SignalCode::PageFault.into(), + }); + drop(mem); + + println!("Invalid user access at addr {far:#10x}"); + println!("{:#?}", &*context.regs()); + + let thread = context.detach_thread(); + unsafe { exit_user_thread(thread, -4i32 as u32) } + } } Some(vme) => { mem.populate_page(vme, page_addr).await.unwrap(); // TODO: errors? diff --git a/crates/kernel/src/process/signal.rs b/crates/kernel/src/process/signal.rs new file mode 100644 index 00000000..6ba64717 --- /dev/null +++ b/crates/kernel/src/process/signal.rs @@ -0,0 +1,83 @@ +use core::sync::atomic::{Ordering, AtomicU8}; + +#[derive(Debug)] +pub struct SignalHandlers { + pub user_page_fault_handler: Option, + pub kill_block_handler: Option, +} + +//These numbers are somewhat based on the Linux signal numbers +pub enum SignalCode { + InHandler = u32::MAX as isize, + Interrupt = 2, + KillUnblockable = 9, + PageFault = 11, + KillBlockable = 15, +} + +impl From for SignalCode { + fn from(value: u32) -> SignalCode { + match value { + u32::MAX => SignalCode::InHandler, + 2 => SignalCode::Interrupt, + 9 => SignalCode::KillUnblockable, + 11 => SignalCode::PageFault, + 15 => SignalCode::KillBlockable, + _ => panic!("Failed to convert u32 {} into signal code", value), + } + } +} + +impl From for u32 { + fn from(value: SignalCode) -> u32 { + match value { + SignalCode::InHandler => u32::MAX, + SignalCode::Interrupt => 2, + SignalCode::KillUnblockable => 9, + SignalCode::PageFault => 11, + SignalCode::KillBlockable => 15, + } + } +} + +//TODO: replace this with a queue +bitflags::bitflags! { + + #[derive(Clone, Copy, Debug)] + pub struct SignalFlagOptions: u8 { + const IS_KILL = 1 << 0; //can block this + const IS_DEAD = 1 << 1; //can't block this + const IS_PAGE_FAULT = 1 << 2; + //currently not supporting nested signal handlers + //WARN: this may break with multiple threads in a process + const IN_HANDLER = 1 << 3; + } +} + +impl SignalHandlers { + pub fn new() -> SignalHandlers { + SignalHandlers { user_page_fault_handler: None, kill_block_handler: None } + } + +} + +pub struct SignalFlags(AtomicU8); + +impl SignalFlags { + pub fn empty() -> Self { + return SignalFlags(AtomicU8::new(0)); + } + + pub fn set(&self, flag: SignalFlagOptions, val: bool) { + if val { + self.0.fetch_or(flag.bits(), Ordering::SeqCst); + } else { + self.0.fetch_and(!flag.bits(), Ordering::SeqCst); + } + } + + pub fn contains(&self, flag: SignalFlagOptions) -> bool { + return (self.0.load(Ordering::SeqCst) & flag.bits()) != 0; + } +} + diff --git a/crates/kernel/src/syscall/mod.rs b/crates/kernel/src/syscall/mod.rs index 311e4ac8..5cf9c73c 100644 --- a/crates/kernel/src/syscall/mod.rs +++ b/crates/kernel/src/syscall/mod.rs @@ -10,6 +10,7 @@ pub mod proc; pub mod semaphore; pub mod sync; pub mod time; +pub mod signal; pub unsafe fn register_syscalls() { unsafe { @@ -43,5 +44,10 @@ pub unsafe fn register_syscalls() { register_syscall_handler(26, semaphore::sys_sem_create); register_syscall_handler(27, semaphore::sys_sem_up); register_syscall_handler(28, semaphore::sys_sem_down); + + register_syscall_handler(33, signal::sys_register_signal_handler); + register_syscall_handler(34, signal::sys_sigreturn); + register_syscall_handler(35, signal::sys_kill); + register_syscall_handler(36, signal::sys_kill_unblockable); } } diff --git a/crates/kernel/src/syscall/signal.rs b/crates/kernel/src/syscall/signal.rs new file mode 100644 index 00000000..b60650b9 --- /dev/null +++ b/crates/kernel/src/syscall/signal.rs @@ -0,0 +1,51 @@ +use crate::event::async_handler::{run_async_handler, HandlerContext}; +use crate::event::context::{deschedule_thread, Context, DescheduleAction, CORES}; +use crate::process::signal::SignalCode; +use crate::process::{signal, ExitStatus}; + +//TODO: register these syscalls + +//Users should be able to redefine these? + +///Register a function to run when the user process encounters a page fault +pub unsafe fn sys_register_signal_handler(ctx: &mut Context) -> *mut Context { + let signal_number= ctx.regs[0] as u32; + let user_handler: fn() = unsafe { core::mem::transmute::(ctx.regs[1]) }; + //TODO: add safety checks on this pointer + let mut ret_val = 0; + run_async_handler(ctx, async move |mut context: HandlerContext<'_>| { + let proc = context.cur_process().unwrap(); + let signal_code = SignalCode::from(signal_number); + match signal_code { + signal::SignalCode::PageFault => proc.signal_handlers.lock().user_page_fault_handler = Some(user_handler), + signal::SignalCode::KillBlockable => proc.signal_handlers.lock().kill_block_handler = Some(user_handler), + _ => ret_val = -1, //User is trying to register a handler for an unsupported signal + } + + context.resume_return(ret_val as usize) + }) +} + +///Return from a signal handler +pub unsafe fn sys_sigreturn(ctx: &mut Context) -> *mut Context { + + run_async_handler(ctx, async move |mut context: HandlerContext<'_>| { + let proc = context.cur_process().unwrap(); + + proc.signal_flags.set(signal::SignalFlagOptions::IN_HANDLER, false); + //replacing context with backup context is done in event loop + + context.resume_return(0) + }) +} + +///Kill which can be blocked if the user registers a handler +pub unsafe fn sys_kill(ctx: &mut Context) -> *mut Context { + todo!(); +} + +///Kill which cannot be blocked +pub unsafe fn sys_kill_unblockable(ctx: &mut Context) -> *mut Context { + todo!(); +} + diff --git a/crates/ulib/src/sys.rs b/crates/ulib/src/sys.rs index 05b302f5..b29fb4ea 100644 --- a/crates/ulib/src/sys.rs +++ b/crates/ulib/src/sys.rs @@ -122,6 +122,13 @@ syscall!(26 => pub fn sys_sem_create(value: usize) -> isize); syscall!(27 => pub fn sys_sem_up(fd: usize) -> isize); syscall!(28 => pub fn sys_sem_down(fd: usize) -> isize); +syscall!(33 => pub fn sys_register_signal_handler(signal_number: usize, signal_handler: usize) -> isize); +//Shouldn't return +syscall!(34 => pub fn sys_sigreturn() -> isize); +syscall!(35 => pub fn sys_kill(pid: usize) -> isize); +syscall!(36 => pub fn sys_kill_unblockable(pid: usize) -> isize); + + /* * * * * * * * * * * * * * * * * * * */ /* Syscall wrappers */ /* * * * * * * * * * * * * * * * * * * */ @@ -341,3 +348,40 @@ extern "C" fn exec_child(spawn_args: *const SpawnArgs) -> ! { // TODO: notify parent of spawn failure exit(1); } + +pub enum SignalCode { + Interrupt = 2, + KillUnblockable = 9, + PageFault = 11, + KillBlockable = 15, +} + +impl From for SignalCode { + fn from(value: u32) -> SignalCode { + match value { + 2 => SignalCode::Interrupt, + 9 => SignalCode::KillUnblockable, + 11 => SignalCode::PageFault, + 15 => SignalCode::KillBlockable, + _ => panic!("Failed to convert u32 {} into signal code", value), + } + } +} + +impl From for u32 { + fn from(value: SignalCode) -> u32 { + match value { + SignalCode::Interrupt => 2, + SignalCode::KillUnblockable => 9, + SignalCode::PageFault => 11, + SignalCode::KillBlockable => 15, + } + } +} + +//Currently the kernel only supports registering handlers for PageFault and KillBlockable +pub unsafe fn register_signal_handler(code: SignalCode, handler: fn()) -> Result { + let code_as_int: u32 = code.into(); + let res = unsafe { sys_register_signal_handler(code_as_int as usize, core::mem::transmute::(handler)) }; + int_to_error(res) +}