diff --git a/kernel/libk/src/task/process.rs b/kernel/libk/src/task/process.rs index 3169d8d0..8e355818 100644 --- a/kernel/libk/src/task/process.rs +++ b/kernel/libk/src/task/process.rs @@ -324,13 +324,32 @@ impl Process { } /// Raises a signal for the specified process - pub fn raise_signal(self: &Arc, signal: Signal) { - let thread = self.inner.read().threads[0].clone(); - thread.raise_signal(signal); + /// + /// If sender is Some(id) - signal was sent by some thread in some (possibly this) process. + /// If sender is None - signal was caused by some fault or some other non-deliberate cause. + pub fn raise_signal(self: &Arc, sender: Option, signal: Signal) { + // If signal was raised by a thread within the process, consider the signal a + // "synchronous-like" and deliver it to the same thread that issued it. + + let destination = { + let inner = self.inner.read(); + if let Some(thread) = sender.and_then(|id| inner.threads.iter().find(|t| t.id == id)) { + // Behave like a "synchronous" signal, because a thread within a process asked to + // do something with the process itself. Make the requesting thread handle the + // signal. + thread.clone() + } else { + // Asynchronous signal not caused by any thread within the process, basically, + // any thread can handle it, but for determinism's sake, let it be the main one + inner.threads[0].clone() + } + }; + + destination.raise_signal(signal); } /// Raises a signal for the specified process group - pub fn signal_group(group_id: ProcessGroupId, signal: Signal) { + pub fn signal_group(sender: Option, group_id: ProcessGroupId, signal: Signal) { MANAGER.for_each(|_, proc| { let inner = proc.inner.read(); if !proc.has_exited() && inner.group_id == group_id { @@ -342,7 +361,7 @@ impl Process { signal ); drop(inner); - proc.raise_signal(signal); + proc.raise_signal(sender, signal); } }); } @@ -398,6 +417,9 @@ impl Process { Ok(()) } + /// Terminates all threads with the exclusion of `except`. This may be useful when a thread + /// wants the whole process to terminate: the asking thread itself must be exempt from this + /// termination, as it's the one doing the cleanup. pub async fn terminate_others(&self, except: ThreadId) { let mut inner = self.inner.write(); @@ -407,7 +429,9 @@ impl Process { } log::info!("Terminate thread {}", thread.id); - thread.terminate().await; + thread.terminate(); + thread.exit.wait().await; + log::debug!("{} died", thread.id); } inner.retain_thread(except); diff --git a/kernel/libk/src/task/sched.rs b/kernel/libk/src/task/sched.rs index 9ccece2c..a9f9383f 100644 --- a/kernel/libk/src/task/sched.rs +++ b/kernel/libk/src/task/sched.rs @@ -82,6 +82,14 @@ impl CpuQueue { assert!(sched.in_queue); assert!(core::ptr::eq(self, sched.queue.unwrap())); + if thread.kill.is_signalled() { + sched.state = ThreadState::Terminated; + sched.in_queue = false; + sched.queue = None; + thread.set_terminated(); + continue; + } + match sched.state { ThreadState::Ready => { sched.state = ThreadState::Running; diff --git a/kernel/libk/src/task/thread.rs b/kernel/libk/src/task/thread.rs index 784b5348..ae7e0a9d 100644 --- a/kernel/libk/src/task/thread.rs +++ b/kernel/libk/src/task/thread.rs @@ -81,6 +81,7 @@ pub struct Thread { signal_queue: SegQueue, pub exit: Arc, + pub kill: BoolEvent, /// CPU scheduling affinity mask pub affinity: ThreadAffinity, @@ -135,6 +136,7 @@ impl Thread { inner: IrqSafeSpinlock::new(ThreadInner { signal_entry: None }), signal_queue: SegQueue::new(), exit: Arc::new(BoolEvent::new()), + kill: BoolEvent::new(), affinity: ThreadAffinity::any_cpu(), }); @@ -403,11 +405,16 @@ impl Thread { self.enqueue_to(queue); } - /// Requests thread termination and blocks until said thread finishes fully - pub async fn terminate(self: &Arc) { - // Will not abort the execution: called from another thread - self.dequeue(ThreadState::Terminated); - self.exit.wait().await; + /// Requests thread termination. This function only asks the thread to terminate, which will + /// happen the next time it's seen by the scheduler. + pub fn terminate(self: &Arc) { + // Will not abort the execution: called from another thread. + // + // Instead of dequeueing the thread, like done with a synchronous exit (when the thread + // exits on its own), instead, the thread needs to be **enqueued**, because we need to wake + // it up in order to make it die itself. + self.kill.signal_saturating(); + self.enqueue(); } /// Returns the current thread on the CPU. @@ -496,7 +503,6 @@ impl CurrentThread { unreachable!() } - // TODO: test multithreaded process exit /// Terminate the parent process of the thread, including all other threads and the current /// thread itself pub fn exit_process(&self, code: ExitCode) -> ! { diff --git a/kernel/libk/src/vfs/terminal.rs b/kernel/libk/src/vfs/terminal.rs index 8514472c..ac56e473 100644 --- a/kernel/libk/src/vfs/terminal.rs +++ b/kernel/libk/src/vfs/terminal.rs @@ -156,7 +156,7 @@ impl Terminal { self.output.notify_readers(); if let Some(group_id) = *self.input.signal_pgroup.read() { - Process::signal_group(group_id, Signal::Interrupted); + Process::signal_group(None, group_id, Signal::Interrupted); self.input.ready_ring.notify_all(); return; } diff --git a/kernel/src/syscall/imp/sys_process.rs b/kernel/src/syscall/imp/sys_process.rs index 8f9b3401..83698dd9 100644 --- a/kernel/src/syscall/imp/sys_process.rs +++ b/kernel/src/syscall/imp/sys_process.rs @@ -205,8 +205,9 @@ pub(crate) fn set_signal_entry(ip: usize, sp: usize) { } pub(crate) fn send_signal(pid: ProcessId, signal: Signal) -> Result<(), Error> { + let thread = Thread::current(); let target = Process::get(pid).ok_or(Error::DoesNotExist)?; - target.raise_signal(signal); + target.raise_signal(Some(thread.id), signal); Ok(()) } @@ -275,9 +276,11 @@ pub(crate) fn wait_thread(id: u32) -> Result<(), Error> { let this_thread = Thread::current(); let process = this_thread.process(); - block!(process.wait_for_thread(tid).await)??; + log::debug!("wait_thread({id})"); + let result = block!(process.wait_for_thread(tid).await)?; + log::debug!("www -> {result:?}"); - Ok(()) + result } pub(crate) fn get_thread_option(option: &mut ThreadOption) -> Result<(), Error> { diff --git a/lib/abi/src/process/mod.rs b/lib/abi/src/process/mod.rs index cfb7877f..1bcb4669 100644 --- a/lib/abi/src/process/mod.rs +++ b/lib/abi/src/process/mod.rs @@ -166,3 +166,13 @@ impl ProgramArgumentInner { } } } + +impl Signal { + pub fn is_synchronous(&self) -> bool { + matches!(self, Self::MemoryAccessViolation) + } + + pub fn is_handleable(&self) -> bool { + !matches!(self, Self::Killed) + } +} diff --git a/test.c b/test.c index f58ec6ab..e81ef8fa 100644 --- a/test.c +++ b/test.c @@ -2,31 +2,18 @@ #include #include #include +#include #include -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - static void *function(void *arg) { - pthread_barrier_t *barrier = (pthread_barrier_t *) arg; - printf("[child] waiting for parent\n"); - pthread_barrier_wait(barrier); - printf("[child] barrier signalled!!\n"); + abort(); return NULL; } int main(int argc, const char **argv) { pthread_t thread; - pthread_barrier_t barrier; - - pthread_barrier_init(&barrier, NULL, 2); - - printf("[main] will make the child wait on a barrier\n"); - assert(pthread_create(&thread, NULL, function, (void *) &barrier) == 0); - - sleep(3); - pthread_barrier_wait(&barrier); - printf("[main] barrier signalled!!\n"); + assert(pthread_create(&thread, NULL, function, (void *) NULL) == 0); pthread_join(thread, NULL); return 0;