proc: more signal determinism, proper process exit in mt mode
This commit is contained in:
parent
d7111e8d99
commit
cbd823e17b
@ -324,13 +324,32 @@ impl 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();
|
||||
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<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
|
||||
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| {
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -81,6 +81,7 @@ pub struct Thread {
|
||||
signal_queue: SegQueue<Signal>,
|
||||
|
||||
pub exit: Arc<BoolEvent>,
|
||||
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<Self>) {
|
||||
// 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<Self>) {
|
||||
// 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) -> ! {
|
||||
|
@ -156,7 +156,7 @@ impl<O: TerminalOutput> Terminal<O> {
|
||||
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;
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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
19
test.c
@ -2,31 +2,18 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/yggdrasil.h>
|
||||
|
||||
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user