proc: more signal determinism, proper process exit in mt mode

This commit is contained in:
Mark Poliakov 2024-11-19 12:15:34 +02:00
parent d7111e8d99
commit cbd823e17b
7 changed files with 70 additions and 32 deletions

View File

@ -324,13 +324,32 @@ impl Process {
} }
/// Raises a signal for the specified process /// Raises a signal for the specified process
pub fn raise_signal(self: &Arc<Self>, signal: Signal) { ///
let thread = self.inner.read().threads[0].clone(); /// If sender is Some(id) - signal was sent by some thread in some (possibly this) process.
thread.raise_signal(signal); /// If sender is None - signal was caused by some fault or some other non-deliberate cause.
pub fn raise_signal(self: &Arc<Self>, sender: Option<ThreadId>, 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 /// Raises a signal for the specified process group
pub fn signal_group(group_id: ProcessGroupId, signal: Signal) { pub fn signal_group(sender: Option<ThreadId>, group_id: ProcessGroupId, signal: Signal) {
MANAGER.for_each(|_, proc| { MANAGER.for_each(|_, proc| {
let inner = proc.inner.read(); let inner = proc.inner.read();
if !proc.has_exited() && inner.group_id == group_id { if !proc.has_exited() && inner.group_id == group_id {
@ -342,7 +361,7 @@ impl Process {
signal signal
); );
drop(inner); drop(inner);
proc.raise_signal(signal); proc.raise_signal(sender, signal);
} }
}); });
} }
@ -398,6 +417,9 @@ impl Process {
Ok(()) 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) { pub async fn terminate_others(&self, except: ThreadId) {
let mut inner = self.inner.write(); let mut inner = self.inner.write();
@ -407,7 +429,9 @@ impl Process {
} }
log::info!("Terminate thread {}", thread.id); log::info!("Terminate thread {}", thread.id);
thread.terminate().await; thread.terminate();
thread.exit.wait().await;
log::debug!("{} died", thread.id);
} }
inner.retain_thread(except); inner.retain_thread(except);

View File

@ -82,6 +82,14 @@ impl CpuQueue {
assert!(sched.in_queue); assert!(sched.in_queue);
assert!(core::ptr::eq(self, sched.queue.unwrap())); 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 { match sched.state {
ThreadState::Ready => { ThreadState::Ready => {
sched.state = ThreadState::Running; sched.state = ThreadState::Running;

View File

@ -81,6 +81,7 @@ pub struct Thread {
signal_queue: SegQueue<Signal>, signal_queue: SegQueue<Signal>,
pub exit: Arc<BoolEvent>, pub exit: Arc<BoolEvent>,
pub kill: BoolEvent,
/// CPU scheduling affinity mask /// CPU scheduling affinity mask
pub affinity: ThreadAffinity, pub affinity: ThreadAffinity,
@ -135,6 +136,7 @@ impl Thread {
inner: IrqSafeSpinlock::new(ThreadInner { signal_entry: None }), inner: IrqSafeSpinlock::new(ThreadInner { signal_entry: None }),
signal_queue: SegQueue::new(), signal_queue: SegQueue::new(),
exit: Arc::new(BoolEvent::new()), exit: Arc::new(BoolEvent::new()),
kill: BoolEvent::new(),
affinity: ThreadAffinity::any_cpu(), affinity: ThreadAffinity::any_cpu(),
}); });
@ -403,11 +405,16 @@ impl Thread {
self.enqueue_to(queue); self.enqueue_to(queue);
} }
/// Requests thread termination and blocks until said thread finishes fully /// Requests thread termination. This function only asks the thread to terminate, which will
pub async fn terminate(self: &Arc<Self>) { /// happen the next time it's seen by the scheduler.
// Will not abort the execution: called from another thread pub fn terminate(self: &Arc<Self>) {
self.dequeue(ThreadState::Terminated); // Will not abort the execution: called from another thread.
self.exit.wait().await; //
// 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. /// Returns the current thread on the CPU.
@ -496,7 +503,6 @@ impl CurrentThread {
unreachable!() unreachable!()
} }
// TODO: test multithreaded process exit
/// Terminate the parent process of the thread, including all other threads and the current /// Terminate the parent process of the thread, including all other threads and the current
/// thread itself /// thread itself
pub fn exit_process(&self, code: ExitCode) -> ! { pub fn exit_process(&self, code: ExitCode) -> ! {

View File

@ -156,7 +156,7 @@ impl<O: TerminalOutput> Terminal<O> {
self.output.notify_readers(); self.output.notify_readers();
if let Some(group_id) = *self.input.signal_pgroup.read() { 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(); self.input.ready_ring.notify_all();
return; return;
} }

View File

@ -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> { pub(crate) fn send_signal(pid: ProcessId, signal: Signal) -> Result<(), Error> {
let thread = Thread::current();
let target = Process::get(pid).ok_or(Error::DoesNotExist)?; let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
target.raise_signal(signal); target.raise_signal(Some(thread.id), signal);
Ok(()) Ok(())
} }
@ -275,9 +276,11 @@ pub(crate) fn wait_thread(id: u32) -> Result<(), Error> {
let this_thread = Thread::current(); let this_thread = Thread::current();
let process = this_thread.process(); 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> { pub(crate) fn get_thread_option(option: &mut ThreadOption) -> Result<(), Error> {

View File

@ -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)
}
}

19
test.c
View File

@ -2,31 +2,18 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h>
#include <sys/yggdrasil.h> #include <sys/yggdrasil.h>
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void *function(void *arg) { static void *function(void *arg) {
pthread_barrier_t *barrier = (pthread_barrier_t *) arg; abort();
printf("[child] waiting for parent\n");
pthread_barrier_wait(barrier);
printf("[child] barrier signalled!!\n");
return NULL; return NULL;
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
pthread_t thread; 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); pthread_join(thread, NULL);
return 0; return 0;