mirror of https://github.com/dzavalishin/oskit/
1224 lines
30 KiB
C
Executable File
1224 lines
30 KiB
C
Executable File
/*
|
|
* Copyright (c) 1996, 1998, 1999, 2000, 2001 University of Utah and the Flux Group.
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of the Flux OSKit. The OSKit is free software, also known
|
|
* as "open source;" you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License (GPL), version 2, as published by the Free
|
|
* Software Foundation (FSF). To explore alternate licensing terms, contact
|
|
* the University of Utah at csl-dist@cs.utah.edu or +1-801-585-3271.
|
|
*
|
|
* The OSKit is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GPL for more details. You should have
|
|
* received a copy of the GPL along with the OSKit; see the file COPYING. If
|
|
* not, write to the FSF, 59 Temple Place #330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* Posix signals, or something approximating them. The goal is to get
|
|
* the obvious stuff nearly correct, but not go overboard.
|
|
*/
|
|
|
|
#include <threads/pthread_internal.h>
|
|
#include <strings.h>
|
|
#include <oskit/c/string.h>
|
|
#include <oskit/c/malloc.h>
|
|
#include "pthread_cond.h"
|
|
#include "pthread_signal.h"
|
|
|
|
/*
|
|
* The sigactions array. Protected by its own lock.
|
|
*/
|
|
static struct sigaction sigactions[NSIG];
|
|
static pthread_lock_t sigactions_lock = PTHREAD_LOCK_INITIALIZER;
|
|
|
|
/*
|
|
* There is a global (or "process") set of pending signals.
|
|
* kill() and sigqueue() affect the process pending set.
|
|
*/
|
|
static sigset_t sigpending;
|
|
|
|
/*
|
|
* A queue of all threads waiting in sigwait.
|
|
*/
|
|
static queue_head_t sigwaiters;
|
|
|
|
/*
|
|
* An array of queues of pending signals posted with sigqueue().
|
|
*/
|
|
static queue_head_t sigqueued[NSIG];
|
|
|
|
/*
|
|
* We avoid malloc in interrupt handlers by preallocating the queue
|
|
* entries for sigqueued above.
|
|
*/
|
|
static queue_head_t sigqueue_free;
|
|
|
|
/*
|
|
* Lock to protect the global signal stuff.
|
|
*/
|
|
static pthread_lock_t siglock = PTHREAD_LOCK_INITIALIZER;
|
|
|
|
/*
|
|
* Prototypes.
|
|
*/
|
|
int pthread_kill_locked(pthread_thread_t *pthread, int signo);
|
|
void really_deliver_signal(int sig, siginfo_t *code,struct sigcontext *scp);
|
|
void oskit_deliver_async_signal(int sig);
|
|
void oskit_deliver_process_signal(int sig);
|
|
|
|
int
|
|
pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
int err = 0;
|
|
|
|
assert(pthread);
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
pthread_lock(&pthread->siglock);
|
|
|
|
if (oset)
|
|
*oset = pthread->sigmask;
|
|
|
|
if (set) {
|
|
switch (how) {
|
|
case SIG_BLOCK:
|
|
pthread->sigmask |= *set;
|
|
break;
|
|
case SIG_UNBLOCK:
|
|
pthread->sigmask &= ~*set;
|
|
break;
|
|
case SIG_SETMASK:
|
|
pthread->sigmask = *set;
|
|
break;
|
|
default:
|
|
err = EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for process pending signals that are unblocked, and deliver.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
while (sigpending & ~pthread->sigmask) {
|
|
int sig = ffs(sigpending & ~pthread->sigmask);
|
|
|
|
/* Call with siglock and thread siglock locked */
|
|
oskit_deliver_process_signal(sig);
|
|
}
|
|
pthread_unlock(&siglock);
|
|
|
|
/*
|
|
* Look for pthread pending signals that are unblocked, and deliver.
|
|
*/
|
|
while (pthread->sigpending & ~pthread->sigmask) {
|
|
int sig = ffs(pthread->sigpending & ~pthread->sigmask);
|
|
|
|
/* Call with thread siglock locked */
|
|
oskit_deliver_async_signal(sig);
|
|
}
|
|
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* This can be called out of an interrupt handler, say from an alarm
|
|
* expiration. Therefore, must not take the pthread lock. We use a
|
|
* separate lock to protect the signal state.
|
|
*/
|
|
int
|
|
pthread_kill(pthread_t tid, int signo)
|
|
{
|
|
pthread_thread_t *pthread;
|
|
int enabled;
|
|
|
|
/* Error check? Sure! */
|
|
if (!signo)
|
|
return 0;
|
|
|
|
if (signo < 0 || signo >= NSIG)
|
|
return EINVAL;
|
|
|
|
if ((pthread = tidtothread(tid)) == NULL_THREADPTR)
|
|
return EINVAL;
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
enabled = save_disable_interrupts();
|
|
pthread_lock(&pthread->siglock);
|
|
|
|
/*
|
|
* pthread_kill_locked() will take care of unlocking.
|
|
*/
|
|
pthread_kill_locked(pthread, signo);
|
|
|
|
/*
|
|
* If not in an interrupt, use this opportunity to deliver
|
|
* pending unblocked signals to the current thread.
|
|
*/
|
|
if (! IN_AN_INTERRUPT()) {
|
|
pthread = CURPTHREAD();
|
|
SIGCHECK(pthread);
|
|
}
|
|
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This does all the work.
|
|
*
|
|
* Should be called with interrupts disabled, and siglock and pthread
|
|
* siglock are held.
|
|
* Returns with the locks released (caller must restore interrupts).
|
|
*/
|
|
int
|
|
pthread_kill_locked(pthread_thread_t *pthread, int signo)
|
|
{
|
|
/*
|
|
* Look at the process sigactions. If the "process" is ignoring
|
|
* the signal, then the signal is not placed in the pending list.
|
|
*/
|
|
pthread_lock(&sigactions_lock);
|
|
if (sigactions[signo].sa_handler == SIG_IGN) {
|
|
pthread_unlock(&pthread->siglock);
|
|
pthread_unlock(&sigactions_lock);
|
|
return 0;
|
|
}
|
|
/* Don't need the global siglock anymore. */
|
|
pthread_unlock(&sigactions_lock);
|
|
|
|
/*
|
|
* First have to check for sigwaiting, since that overrides sigmask.
|
|
*/
|
|
pthread_lock(&pthread->waitlock);
|
|
if ((pthread->waitflags & THREAD_WS_SIGWAIT) &&
|
|
sigismember(&pthread->sigwaiting, signo)) {
|
|
sigaddset(&pthread->sigpending, signo);
|
|
sigemptyset(&pthread->sigwaiting);
|
|
pthread_unlock(&pthread->siglock);
|
|
/* pthread waitlock will be released. */
|
|
pthread_wakeup_locked(pthread);
|
|
return 0;
|
|
}
|
|
pthread_unlock(&pthread->waitlock);
|
|
|
|
/*
|
|
* Add the signal to list of pending signals for the target thread.
|
|
*/
|
|
sigaddset(&pthread->sigpending, signo);
|
|
|
|
/*
|
|
* If the signal is currently blocked, then do nothing else.
|
|
* It will be noticed when the signal is unblocked.
|
|
*/
|
|
if (sigismember(&pthread->sigmask, signo)) {
|
|
pthread_unlock(&pthread->siglock);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Is the current thread sending itself a signal? This is okay, as
|
|
* long as its not from within an interrupt handler, say, from an
|
|
* alarm expiration. The caller is going to look for pending signals
|
|
* to the current thread.
|
|
*
|
|
* If this *is* from within an interrupt, must wait until later
|
|
* since delivering it now is really the wrong way to go. Lets
|
|
* schedule a soft interrupt, and get the thread switched out.
|
|
* The signal will be delivered when it comes back in.
|
|
*/
|
|
if (pthread == CURPTHREAD()) {
|
|
if (IN_AN_INTERRUPT()) {
|
|
softint_request(SOFTINT_ASYNCREQ);
|
|
}
|
|
pthread_unlock(&pthread->siglock);
|
|
return 0;
|
|
}
|
|
|
|
/* Don't need the pthread siglock anymore; waking up the thread */
|
|
pthread_unlock(&pthread->siglock);
|
|
|
|
/*
|
|
* Below here, we muck with the waitflags, which are accessed
|
|
* from interrupt handlers. Interrupts are already disabled,
|
|
* so take the thread waitlock.
|
|
*/
|
|
pthread_lock(&pthread->waitlock);
|
|
|
|
/*
|
|
* If the thread is in an osenv_sleep(), issue a wakeup.
|
|
* The thread will be allowed to return through the sleep, to
|
|
* be caught sometime later. This allows driver state to be
|
|
* cleaned up before the thread is actually signaled.
|
|
*/
|
|
if (pthread->waitflags & THREAD_WS_OSENVSLEEP) {
|
|
pthread_unlock(&pthread->waitlock);
|
|
osenv_wakeup(pthread->sleeprec, OSENV_SLEEP_CANCELED);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If the thread is THREAD_WS_SLEEPING, then restart it.
|
|
*/
|
|
if (pthread->waitflags & THREAD_WS_SLEEPING) {
|
|
/* pthread waitlock will be released. */
|
|
pthread_wakeup_locked(pthread);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If the thread is THREAD_WS_CONDWAIT, then restart it. The wrinkle
|
|
* is a race condition between the time the thread is taken off
|
|
* the condition queue and the time the thread state is changed.
|
|
*/
|
|
if (pthread->waitflags & THREAD_WS_CONDWAIT) {
|
|
struct pthread_cond_impl *pimpl = pthread->waitcond->impl;
|
|
|
|
pthread_lock(&(pimpl->lock));
|
|
|
|
/*
|
|
* The thread was still on the Q, so its safe to change
|
|
* its state to reflect that it is not longer waiting
|
|
* on the condition.
|
|
*
|
|
* If the thread was not on the Q, we caught the race,
|
|
* and do not have to do anything.
|
|
*/
|
|
if (pthread_remove_fromQ(&(pimpl->waiters), pthread)) {
|
|
pthread->waitflags &= ~THREAD_WS_CONDWAIT;
|
|
pthread->waitcond = 0;
|
|
pthread_unlock(&(pimpl->lock));
|
|
pthread_unlock(&pthread->waitlock);
|
|
pthread_sched_setrunnable(pthread);
|
|
return 0;
|
|
}
|
|
|
|
pthread_unlock(&(pimpl->lock));
|
|
pthread_unlock(&pthread->waitlock);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* IPC Wait.
|
|
*/
|
|
if (pthread->waitflags & THREAD_WS_IPCWAIT_FLAG) {
|
|
/* pthread waitlock will be released. */
|
|
pthread_ipc_cancel(pthread);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Done with waitflags.
|
|
*/
|
|
pthread_unlock(&pthread->waitlock);
|
|
|
|
/*
|
|
* Must be running on another CPU. Must wait for it to be noticed.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The rest of the Posix signal code. This stuff replaces the corresponding
|
|
* code in the Posix library, since I don't see how it can be done unless
|
|
* its all in one place.
|
|
*/
|
|
|
|
/*
|
|
* sigaction
|
|
*/
|
|
int
|
|
sigaction(int sig, const struct sigaction *act, struct sigaction *oact)
|
|
{
|
|
if (sig < 0 || sig >= NSIG)
|
|
return errno = EINVAL, -1;
|
|
|
|
assert_preemption_enabled();
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
pthread_lock(&sigactions_lock);
|
|
|
|
if (oact)
|
|
*oact = sigactions[sig];
|
|
if (act)
|
|
sigactions[sig] = *act;
|
|
|
|
pthread_unlock(&sigactions_lock);
|
|
|
|
/*
|
|
* If the action for this signal is being set to SIG_IGN or SIG_DFL,
|
|
* and that signal is process pending, then clear it.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
if (act && (act->sa_handler == SIG_IGN ||
|
|
act->sa_handler == SIG_DFL)) {
|
|
while (! queue_empty(&sigqueued[sig])) {
|
|
sigqueue_thingie_t *pthingie;
|
|
|
|
queue_remove_first(&sigqueued[sig], pthingie,
|
|
sigqueue_thingie_t *, chain);
|
|
queue_enter(&sigqueue_free, pthingie,
|
|
sigqueue_thingie_t *, chain);
|
|
}
|
|
sigdelset(&sigpending, sig);
|
|
}
|
|
pthread_unlock(&siglock);
|
|
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sigprocmask. In a multithreaded program, this is just pthread_sigmask
|
|
*/
|
|
int
|
|
sigprocmask(int how, const sigset_t *set, sigset_t *oset)
|
|
{
|
|
return pthread_sigmask(how, set, oset);
|
|
}
|
|
|
|
/*
|
|
* raise. In a multithreaded program, raise is pthread_kill on itself.
|
|
*/
|
|
int
|
|
raise(int sig)
|
|
{
|
|
return pthread_kill(pthread_self(), sig);
|
|
}
|
|
|
|
/*
|
|
* kill. What does it mean to kill() in a multithreaded program? The POSIX
|
|
* spec says that a signal sent to a "process" shall be delivered to only
|
|
* one thread. If no thread has that signal unblocked, then the first
|
|
* thread to unblock the signal is the lucky winner. Well, that means we
|
|
* need to have a global sigpending to record process pending signals.
|
|
*/
|
|
int
|
|
kill(pid_t pid, int signo)
|
|
{
|
|
pthread_thread_t *pthread;
|
|
int enabled, i;
|
|
struct sigaction act;
|
|
extern pthread_lock_t pthread_create_lock;
|
|
|
|
/* Error check? Sure! */
|
|
if (!signo)
|
|
return 0;
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
enabled = save_disable_interrupts();
|
|
|
|
/*
|
|
* Look at the process sigactions. If the "process" is ignoring
|
|
* the signal, then the signal is not placed in the pending list.
|
|
*/
|
|
pthread_lock(&sigactions_lock);
|
|
act = sigactions[signo];
|
|
pthread_unlock(&sigactions_lock);
|
|
|
|
if (act.sa_handler == SIG_IGN) {
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Take and hold the global siglock. This is needed to prevent
|
|
* a race with sigwait.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
|
|
/*
|
|
* Kill does not queue. If the signal is already pending, this
|
|
* one is tossed.
|
|
*/
|
|
if (sigismember(&sigpending, signo)) {
|
|
pthread_unlock(&siglock);
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Make the signal process pending.
|
|
*/
|
|
sigaddset(&sigpending, signo);
|
|
|
|
/*
|
|
* Look through the threads in sigwait to see if any of them
|
|
* is waiting for the signal. This is done as a separate pass
|
|
* since the value of the pthread sigmask is ignored (threads
|
|
* in sigwait will have blocked the signals being waited for).
|
|
*/
|
|
queue_iterate(&sigwaiters, pthread, pthread_thread_t *, chain) {
|
|
assert(pthread);
|
|
pthread_lock(&pthread->siglock);
|
|
pthread_lock(&pthread->waitlock);
|
|
if ((pthread->waitflags & THREAD_WS_SIGWAIT) &&
|
|
sigismember(&pthread->sigwaiting, signo)) {
|
|
/*
|
|
* Bingo. Make the signal thread pending and
|
|
* wake it up.
|
|
*/
|
|
sigaddset(&pthread->sigpending, signo);
|
|
sigemptyset(&pthread->sigwaiting);
|
|
pthread_unlock(&pthread->siglock);
|
|
/* pthread waitlock will be released. */
|
|
pthread_wakeup_locked(pthread);
|
|
pthread_unlock(&siglock);
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
pthread_unlock(&pthread->waitlock);
|
|
pthread_unlock(&pthread->siglock);
|
|
}
|
|
|
|
/*
|
|
* No threads in sigwait. Too bad. Must find another thread to
|
|
* deliver it to.
|
|
*/
|
|
pthread_lock(&pthread_create_lock);
|
|
for (i = 1; i < THREADS_MAX_THREAD; i++) {
|
|
if ((pthread = threads_tidtothread[i]) != NULL_THREADPTR) {
|
|
pthread_lock(&pthread->siglock);
|
|
if (! sigismember(&pthread->sigmask, signo)) {
|
|
pthread_unlock(&pthread_create_lock);
|
|
|
|
/* Thread siglock will be released. */
|
|
pthread_kill_locked(pthread, signo);
|
|
break;
|
|
}
|
|
pthread_unlock(&pthread->siglock);
|
|
}
|
|
}
|
|
pthread_unlock(&pthread_create_lock);
|
|
pthread_unlock(&siglock);
|
|
|
|
/*
|
|
* If not in an interrupt, use this opportunity to deliver
|
|
* pending unblocked signals to the current thread.
|
|
*/
|
|
if (! IN_AN_INTERRUPT()) {
|
|
pthread = CURPTHREAD();
|
|
SIGCHECK(pthread);
|
|
}
|
|
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sigqueue.
|
|
*/
|
|
int
|
|
sigqueue(pid_t pid, int signo, const union sigval value)
|
|
{
|
|
return sigqueue_internal(pid, signo, value, SI_QUEUE);
|
|
}
|
|
|
|
/*
|
|
* sigqueue internal
|
|
*/
|
|
int
|
|
sigqueue_internal(pid_t pid, int signo, const union sigval value, int code)
|
|
{
|
|
pthread_thread_t *pthread;
|
|
int enabled;
|
|
int i;
|
|
sigqueue_thingie_t *pthingie = NULL;
|
|
struct sigaction act;
|
|
extern pthread_lock_t pthread_create_lock;
|
|
|
|
/* Error check? Sure! */
|
|
if (!signo)
|
|
return 0;
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
enabled = save_disable_interrupts();
|
|
|
|
/*
|
|
* Look at the process sigactions. If the "process" is ignoring
|
|
* the signal, then the signal is not placed in the pending list.
|
|
*/
|
|
pthread_lock(&sigactions_lock);
|
|
act = sigactions[signo];
|
|
pthread_unlock(&sigactions_lock);
|
|
|
|
if (act.sa_handler == SIG_IGN) {
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Take and hold the global siglock. This is needed to prevent
|
|
* a race with sigwait.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
|
|
/*
|
|
* If the flags does not include SA_SIGINFO, and there is already
|
|
* a signal pending, this new one is dropped.
|
|
*/
|
|
if ((! (act.sa_flags & SA_SIGINFO)) &&
|
|
sigismember(&sigpending, signo)) {
|
|
pthread_unlock(&siglock);
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Gotta have space for the new signal.
|
|
*/
|
|
if (queue_empty(&sigqueue_free)) {
|
|
pthread_unlock(&siglock);
|
|
restore_interrupt_enable(enabled);
|
|
return EAGAIN;
|
|
}
|
|
|
|
/*
|
|
* Create a queue entry.
|
|
*/
|
|
queue_remove_first(&sigqueue_free,
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
pthingie->info.si_signo = signo;
|
|
pthingie->info.si_code = code;
|
|
pthingie->info.si_value = value;
|
|
|
|
/*
|
|
* Queue the signal on the process.
|
|
*/
|
|
queue_enter(&sigqueued[signo], pthingie, sigqueue_thingie_t *, chain);
|
|
sigaddset(&sigpending, signo);
|
|
|
|
/*
|
|
* Look through the threads in sigwait to see if any of them
|
|
* is waiting for the signal. This is done as a separate pass
|
|
* since the value of the pthread sigmask is ignored (threads
|
|
* in sigwait will have blocked the signals being waited for).
|
|
* If we find one, wakeup that thread. Note that POSIX says that
|
|
* if multiple threads are sigwaiting for the same signal number,
|
|
* exactly one thread is woken up. The problem is how to maintain
|
|
* the FIFO order, and how to prevent lost signals in the case that
|
|
* a thread calls sigwait before the woken thread runs and gets it.
|
|
*/
|
|
queue_iterate(&sigwaiters, pthread, pthread_thread_t *, chain) {
|
|
pthread_lock(&pthread->siglock);
|
|
pthread_lock(&pthread->waitlock);
|
|
if ((pthread->waitflags & THREAD_WS_SIGWAIT) &&
|
|
sigismember(&pthread->sigwaiting, signo)) {
|
|
/*
|
|
* Bingo. Wake it up.
|
|
*/
|
|
sigemptyset(&pthread->sigwaiting);
|
|
pthread_unlock(&pthread->siglock);
|
|
/* pthread waitlock will be released. */
|
|
pthread_wakeup_locked(pthread);
|
|
pthread_unlock(&siglock);
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
pthread_unlock(&pthread->waitlock);
|
|
pthread_unlock(&pthread->siglock);
|
|
}
|
|
|
|
/*
|
|
* Need to find a thread to deliver the signal to. Look for the
|
|
* first thread that is not blocking the signal, and send it the
|
|
* signal. It is my opinion that any program that is using sigwait,
|
|
* and has not blocked signals in all of its threads, is bogus. The
|
|
* same is true if the program is not using sigwait, and has the
|
|
* signal unblocked in more than one thread.
|
|
* Why? You might wake up a thread, but not have an actual queue
|
|
* entry left by the time it runs again and looks, since another
|
|
* thread could call sigwait and get that queue entry, or if there
|
|
* are multiple threads that can take the signal, one thread could
|
|
* get all the entries. This could result in an interrupted thread,
|
|
* but with no signal to deliver. Well, not much to do about it.
|
|
* Lets just queue the signal for the process, and let the chips
|
|
* fall where they may.
|
|
*/
|
|
pthread_lock(&pthread_create_lock);
|
|
for (i = 1; i < THREADS_MAX_THREAD; i++) {
|
|
if ((pthread = threads_tidtothread[i]) != NULL_THREADPTR) {
|
|
pthread_lock(&pthread->siglock);
|
|
if (! sigismember(&pthread->sigmask, signo)) {
|
|
pthread_unlock(&pthread_create_lock);
|
|
|
|
/* Thread siglock will be released. */
|
|
pthread_kill_locked(pthread, signo);
|
|
break;
|
|
}
|
|
pthread_unlock(&pthread->siglock);
|
|
}
|
|
}
|
|
pthread_unlock(&pthread_create_lock);
|
|
pthread_unlock(&siglock);
|
|
|
|
/*
|
|
* If not in an interrupt, use this opportunity to deliver
|
|
* pending unblocked signals to the current thread.
|
|
*/
|
|
if (! IN_AN_INTERRUPT()) {
|
|
pthread = CURPTHREAD();
|
|
SIGCHECK(pthread);
|
|
}
|
|
|
|
restore_interrupt_enable(enabled);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sigwait. Sigwait overrides the state of the pthread sigmask and the global
|
|
* sigactions. The caller *must* block the set of signals in "set", before
|
|
* calling sigwait, otherwise the behaviour is undefined (which means that
|
|
* the caller will take an async signal anyway, and sigwait will return EINTR.
|
|
*/
|
|
static oskit_error_t
|
|
oskit_sigwait_internal(const sigset_t *set,
|
|
siginfo_t *info, const oskit_timespec_t *timeout)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
int thissig, rval;
|
|
|
|
pthread_testcancel();
|
|
|
|
assert_preemption_enabled();
|
|
|
|
/* siglock and pthread siglock are taken from an interrupt handler */
|
|
assert_interrupts_enabled();
|
|
disable_interrupts();
|
|
|
|
/*
|
|
* First check for process pending signals. Must take and hold
|
|
* the global siglock to prevent races with kill() and sigqueue().
|
|
*/
|
|
pthread_lock(&pthread->siglock);
|
|
pthread_lock(&siglock);
|
|
if (sigpending & *set) {
|
|
sigqueue_thingie_t *pthingie;
|
|
|
|
thissig = ffs(sigpending & *set);
|
|
|
|
/*
|
|
* Sent with kill(). Using sigwait and kill is Bogus!
|
|
*/
|
|
if (queue_empty(&sigqueued[thissig])) {
|
|
info->si_signo = thissig;
|
|
info->si_code = SI_USER;
|
|
info->si_value.sival_int = 0;
|
|
|
|
sigdelset(&pthread->sigpending, thissig);
|
|
sigdelset(&sigpending, thissig);
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Grab the first queue entry.
|
|
*/
|
|
queue_remove_first(&sigqueued[thissig],
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
/*
|
|
* If that was the last one, reset the process sigpending.
|
|
*/
|
|
if (queue_empty(&sigqueued[thissig]))
|
|
sigdelset(&sigpending, thissig);
|
|
sigdelset(&pthread->sigpending, thissig);
|
|
|
|
/*
|
|
* Copy the information and free the queue entry.
|
|
*/
|
|
info->si_signo = pthingie->info.si_signo;
|
|
info->si_code = pthingie->info.si_code;
|
|
info->si_value.sival_int = pthingie->info.si_value.sival_int;
|
|
queue_enter(&sigqueue_free,
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now check for pthread pending signals.
|
|
*/
|
|
if (pthread->sigpending & *set) {
|
|
thissig = ffs(pthread->sigpending & *set);
|
|
info->si_signo = thissig;
|
|
info->si_code = SI_USER;
|
|
info->si_value.sival_int = 0;
|
|
sigdelset(&pthread->sigpending, thissig);
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* For timed wait, if nothing is available and the timeout value
|
|
* is zero, its an error.
|
|
*/
|
|
if (timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0) {
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return EAGAIN;
|
|
}
|
|
|
|
/*
|
|
* Grab the wait lock and set the sigwaiting mask. Once that is done,
|
|
* release the thread siglock; Another thread can try and wake this
|
|
* thread up as a result of seeing it in sigwait, but the actual
|
|
* wakeup will be delayed until the waitlock is released in the switch
|
|
* code.
|
|
*/
|
|
pthread_lock(&pthread->waitlock);
|
|
pthread->sigwaiting = *set;
|
|
pthread_unlock(&pthread->siglock);
|
|
|
|
/*
|
|
* Add this thread to the list of threads in sigwait. Once that is
|
|
* done, it is safe to release the global siglock, which will allow
|
|
* another thread to scan the sigwaiters list. As above, it might
|
|
* find a thread in sigwait, but it will not be able to wake it up
|
|
* until the waitlock is released in the switch code.
|
|
*/
|
|
queue_enter(&sigwaiters, pthread, pthread_thread_t *, chain);
|
|
pthread_unlock(&siglock);
|
|
|
|
/* and block */
|
|
rval = oskit_pthread_sleep_withflags(THREAD_WS_SIGWAIT, timeout);
|
|
|
|
pthread_lock(&pthread->siglock);
|
|
pthread->sigwaiting = 0;
|
|
|
|
/*
|
|
* Remove from the list of threads in sigwait.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
queue_remove(&sigwaiters, pthread, pthread_thread_t *, chain);
|
|
|
|
/*
|
|
* Check for cancelation.
|
|
*
|
|
* XXX hacky, need to drop all our locks in case the cancelation
|
|
* happens.
|
|
*/
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
pthread_testcancel();
|
|
|
|
/*
|
|
* While the locks are down, check for the timed-out case.
|
|
*/
|
|
if (rval == ETIMEDOUT) {
|
|
enable_interrupts();
|
|
return EAGAIN;
|
|
}
|
|
|
|
pthread_lock(&pthread->siglock);
|
|
pthread_lock(&siglock);
|
|
|
|
/*
|
|
* Look for a wakeup to deliver a queued signal. This would come
|
|
* either from kill() or from sigqueue().
|
|
*/
|
|
if (sigpending & *set) {
|
|
sigqueue_thingie_t *pthingie;
|
|
|
|
thissig = ffs(sigpending & *set);
|
|
|
|
/*
|
|
* Sent with kill(). Using sigwait and kill is Bogus!
|
|
*/
|
|
if (queue_empty(&sigqueued[thissig])) {
|
|
info->si_signo = thissig;
|
|
info->si_code = SI_USER;
|
|
info->si_value.sival_int = 0;
|
|
|
|
sigdelset(&sigpending, thissig);
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Grab the first queue entry.
|
|
*/
|
|
queue_remove_first(&sigqueued[thissig],
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
/*
|
|
* If that was the last one, reset the process sigpending.
|
|
*/
|
|
if (queue_empty(&sigqueued[thissig]))
|
|
sigdelset(&sigpending, thissig);
|
|
|
|
/*
|
|
* Copy the information and free the queue entry.
|
|
*/
|
|
info->si_signo = pthingie->info.si_signo;
|
|
info->si_code = pthingie->info.si_code;
|
|
info->si_value.sival_int = pthingie->info.si_value.sival_int;
|
|
queue_enter(&sigqueue_free,
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Well, at the moment I am going to assume that if this thread
|
|
* wakes up, and there is no signal pending in the waitset, the
|
|
* thread wait was interrupted for some other reason. Return EINTR.
|
|
*/
|
|
if (! (pthread->sigpending & *set)) {
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
return EINTR;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, get the first signal and return it.
|
|
*/
|
|
thissig = ffs(pthread->sigpending & *set);
|
|
info->si_signo = thissig;
|
|
info->si_code = SI_USER;
|
|
info->si_value.sival_int = 0;
|
|
sigdelset(&pthread->sigpending, thissig);
|
|
pthread_unlock(&siglock);
|
|
pthread_unlock(&pthread->siglock);
|
|
|
|
enable_interrupts();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sigwait.
|
|
*/
|
|
int
|
|
sigwait(const sigset_t *set, int *sig)
|
|
{
|
|
siginfo_t info;
|
|
oskit_error_t rc;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
rc = oskit_sigwait_internal(set, &info, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
*sig = info.si_signo;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sigwaitinfo.
|
|
*/
|
|
int
|
|
sigwaitinfo(const sigset_t *set, siginfo_t *info)
|
|
{
|
|
oskit_error_t rc;
|
|
|
|
rc = oskit_sigwait_internal(set, info, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sigtimedwait.
|
|
*/
|
|
int
|
|
sigtimedwait(const sigset_t *set,
|
|
siginfo_t *info, const struct oskit_timespec *timeout)
|
|
{
|
|
oskit_error_t rc;
|
|
|
|
if (! timeout)
|
|
return EINVAL;
|
|
|
|
rc = oskit_sigwait_internal(set, info, timeout);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* XXX we could probably implement this
|
|
*/
|
|
int
|
|
sigsuspend(const sigset_t *sigmask)
|
|
{
|
|
return ENOSYS;
|
|
}
|
|
|
|
/*
|
|
* Internal stuff.
|
|
*/
|
|
|
|
/*
|
|
* Deliver a signal generated from a trap. This is the upcall from the
|
|
* machine dependent code that created the sigcontext structure from
|
|
* the trap state.
|
|
*/
|
|
void
|
|
oskit_libc_sendsig(int sig, int code, struct sigcontext *scp)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
siginfo_t siginfo;
|
|
int enabled;
|
|
|
|
siginfo.si_signo = sig;
|
|
siginfo.si_code = SI_EXCEP;
|
|
siginfo.si_value.sival_int = code;
|
|
|
|
/*
|
|
* Need to call really_deliver_signal() with interrupts blocked
|
|
* and the pthread siglock held.
|
|
*/
|
|
enabled = save_disable_interrupts();
|
|
pthread_lock(&pthread->siglock);
|
|
|
|
really_deliver_signal(sig, &siginfo, scp);
|
|
|
|
pthread_unlock(&pthread->siglock);
|
|
restore_interrupt_enable(enabled);
|
|
}
|
|
|
|
/*
|
|
* Deliver an asynchronous signal. This must be called with interrupts
|
|
* blocked and the pthread siglock held.
|
|
*/
|
|
void
|
|
oskit_deliver_async_signal(int sig)
|
|
{
|
|
struct sigcontext sc;
|
|
siginfo_t siginfo;
|
|
|
|
/* create a stub sigcontext_t. */
|
|
bzero(&sc, sizeof(sc));
|
|
|
|
siginfo.si_signo = sig;
|
|
siginfo.si_code = SI_USER;
|
|
siginfo.si_value.sival_int = 0;
|
|
|
|
really_deliver_signal(sig, &siginfo, &sc);
|
|
}
|
|
|
|
/*
|
|
* Deliver a process signals. This must be called with interrupts
|
|
* blocked and the siglock and pthread siglock held.
|
|
*/
|
|
void
|
|
oskit_deliver_process_signal(int sig)
|
|
{
|
|
struct sigcontext sc;
|
|
siginfo_t siginfo;
|
|
sigqueue_thingie_t *pthingie;
|
|
|
|
/* create a stub sigcontext_t. */
|
|
bzero(&sc, sizeof(sc));
|
|
|
|
/*
|
|
* Sent with kill(). Using sigwait and kill is Bogus!
|
|
*/
|
|
if (queue_empty(&sigqueued[sig])) {
|
|
siginfo.si_signo = sig;
|
|
siginfo.si_code = SI_USER;
|
|
siginfo.si_value.sival_int = 0;
|
|
|
|
sigdelset(&sigpending, sig);
|
|
goto deliver;
|
|
}
|
|
|
|
/*
|
|
* Grab the first queue entry.
|
|
*/
|
|
queue_remove_first(&sigqueued[sig],
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
/*
|
|
* If that was the last one, reset the process sigpending.
|
|
*/
|
|
if (queue_empty(&sigqueued[sig]))
|
|
sigdelset(&sigpending, sig);
|
|
|
|
/*
|
|
* Copy the information and free the queue entry.
|
|
*/
|
|
siginfo.si_signo = pthingie->info.si_signo;
|
|
siginfo.si_code = pthingie->info.si_code;
|
|
siginfo.si_value.sival_int = pthingie->info.si_value.sival_int;
|
|
queue_enter(&sigqueue_free, pthingie, sigqueue_thingie_t *, chain);
|
|
|
|
/*
|
|
* Release the global siglock for the delivery.
|
|
*/
|
|
deliver:
|
|
pthread_unlock(&siglock);
|
|
|
|
really_deliver_signal(sig, &siginfo, &sc);
|
|
|
|
/*
|
|
* Reacquire since the caller expects it.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
}
|
|
|
|
/*
|
|
* Deliver any pending signals. Called out of the context switch code
|
|
* when a thread switches in, and there are pending signals.
|
|
*
|
|
* Interrupts are blocked and the thread siglock is locked.
|
|
*/
|
|
void
|
|
oskit_deliver_pending_signals(void)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
|
|
/*
|
|
* Look for process pending signals that are unblocked, and deliver.
|
|
*/
|
|
pthread_lock(&siglock);
|
|
while (sigpending & ~pthread->sigmask) {
|
|
int sig = ffs(sigpending & ~pthread->sigmask);
|
|
|
|
/* Call with siglock and thread siglock locked */
|
|
oskit_deliver_process_signal(sig);
|
|
}
|
|
pthread_unlock(&siglock);
|
|
|
|
/*
|
|
* Now deliver any pthread pending signals that are left.
|
|
*/
|
|
while (pthread->sigpending & ~pthread->sigmask) {
|
|
int sig = ffs(pthread->sigpending & ~pthread->sigmask);
|
|
|
|
/* Call at splhigh and thread locked */
|
|
oskit_deliver_async_signal(sig);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Actually deliver the signal to the thread. At this point the signal
|
|
* is going to be delivered, so it no longer matters if it is blocked.
|
|
*/
|
|
void
|
|
really_deliver_signal(int sig, siginfo_t *info, struct sigcontext *scp)
|
|
{
|
|
pthread_thread_t *pthread = CURPTHREAD();
|
|
sigset_t sigmask, oldmask;
|
|
struct sigaction act;
|
|
int enabled;
|
|
|
|
assert_interrupts_disabled();
|
|
save_preemption_enable(enabled);
|
|
|
|
pthread_lock(&sigactions_lock);
|
|
act = sigactions[sig];
|
|
pthread_unlock(&sigactions_lock);
|
|
|
|
/*
|
|
* Ignored?
|
|
*/
|
|
if (act.sa_handler == SIG_IGN || act.sa_handler == SIG_ERR)
|
|
return;
|
|
|
|
if (act.sa_handler == SIG_DFL) {
|
|
/* Default action for all signals is termination */
|
|
if (info->si_code == SI_EXCEP) {
|
|
printf("TID %p: exception, code=0x%x\n",
|
|
pthread->tid, info->si_value.sival_int);
|
|
sigcontext_dump(scp);
|
|
}
|
|
panic("Sendsig: Signal %d caught but no handler", sig);
|
|
}
|
|
|
|
/*
|
|
* Set the signal mask for calling the handler.
|
|
*/
|
|
oldmask = sigmask = pthread->sigmask;
|
|
sigaddset(&sigmask, sig);
|
|
sigmask |= act.sa_mask;
|
|
sigdelset(&pthread->sigpending, sig);
|
|
pthread->sigmask = sigmask;
|
|
pthread_unlock(&pthread->siglock);
|
|
enable_interrupts();
|
|
enable_preemption();
|
|
|
|
/*
|
|
* and call the handler ...
|
|
*/
|
|
if (act.sa_flags & SA_SIGINFO)
|
|
act.sa_sigaction(sig, info, (void *) scp);
|
|
else
|
|
((void (*)(int, int, struct sigcontext *))act.sa_handler)
|
|
(sig, info->si_value.sival_int, scp);
|
|
|
|
disable_interrupts();
|
|
restore_preemption_enable(enabled);
|
|
pthread_lock(&pthread->siglock);
|
|
pthread->sigmask = oldmask;
|
|
|
|
/*
|
|
* and return with thread siglock locked at splhigh.
|
|
*/
|
|
}
|
|
|
|
void
|
|
pthread_init_signals()
|
|
{
|
|
int i;
|
|
sigqueue_thingie_t *pthingie;
|
|
extern void libc_sendsig_init(void);
|
|
|
|
queue_init(&sigwaiters);
|
|
queue_init(&sigqueue_free);
|
|
|
|
/* Initialize the default signal actions. */
|
|
for (i = 0; i < NSIG; i++)
|
|
sigactions[i].sa_handler = SIG_DFL;
|
|
|
|
/* Initialize the signal queue headers. */
|
|
for (i = 0; i < NSIG; i++)
|
|
queue_init(&sigqueued[i]);
|
|
|
|
/* Create a free list of queue thingies. */
|
|
if ((pthingie = (sigqueue_thingie_t *)
|
|
smalloc(sizeof(sigqueue_thingie_t) * SIGQUEUE_MAX))
|
|
== NULL)
|
|
panic("pthread_init_signals: Out of memory");
|
|
|
|
for (i = 0; i < SIGQUEUE_MAX; i++) {
|
|
queue_enter(&sigqueue_free,
|
|
pthingie, sigqueue_thingie_t *, chain);
|
|
pthingie++;
|
|
}
|
|
|
|
libc_sendsig_init();
|
|
}
|
|
|
|
void
|
|
signals_init(void)
|
|
{
|
|
/* Called by libc. Initialization was already done above. */
|
|
}
|