From a8dffbc71ef5d8f48309548fb095ffdfa1bb7b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Sat, 28 Nov 2015 15:38:21 +0100 Subject: [PATCH] Add unix signal integration with the iomux --- include/ucore/iomux_signal.h | 48 ++++++ src/iomux_signal.c | 279 +++++++++++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 include/ucore/iomux_signal.h create mode 100644 src/iomux_signal.c diff --git a/include/ucore/iomux_signal.h b/include/ucore/iomux_signal.h new file mode 100644 index 0000000..43e014b --- /dev/null +++ b/include/ucore/iomux_signal.h @@ -0,0 +1,48 @@ +#ifndef UC_IOMUX_SIGNAL_H_ +#define UC_IOMUX_SIGNAL_H_ + +#include "ucore/iomux.h" +/** Callback signature for handling unix signals. + * @param mux associated mux + * @param signo unix signal number + * @param cookie value passed to iomux_register_unixsignal + */ +typedef void (*IOMuxSignalHandler)(struct IOMux *mux, int signo, void *cookie); + + +/** Register a handler for a unix signal. + * The handler is invoked as normally within the iomux loop. + * Note, only one handler per signal can be used. + * + * The current implementation has the limitation that only + * one IOMux can globally be registered to handle signals. + * All signals must be unregistered prior to deleting the + * IOMux that is set up to handle signals. + * + * @param mux associated IOMux + * @param signo the signal (see man signal) to handle + * @param handler callback that will be called whrn the signal is caught + * @param cookie value passed back to the handler + * + * @return 0 on success, errno if registring the signal failed. + * (EINVAL if another mux is registered for signals, + * EEXISTS if a signal handler is already registered for the signo + * ENOMEM if memory allocation failed + * other errno values from syscalls) + * + */ +int iomux_register_unixsignal(struct IOMux *mux, + int signo, + IOMuxSignalHandler handler, + void *cookie); + +/** Unregister the current handler for a signal. + * + * @param mux associated mux + * @param signo signal number handler to unregister + * @return 0 on success, errno value on failure (ENOENT if + * no signal handler was registered for signo) + */ +int iomux_unregister_unixsignal(struct IOMux *mux, int signo); + +#endif diff --git a/src/iomux_signal.c b/src/iomux_signal.c new file mode 100644 index 0000000..ccaee4f --- /dev/null +++ b/src/iomux_signal.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ucore/tailq.h" +#include "ucore/fd_utils.h" +#include "ucore/iomux_signal.h" + +/* Integrating of unix signals (man 7 signal) with + * an IOMux event loop. + * This is done by setting up a pipe, registring a handler with + * the IOMux to handle the read end to that pipe, and writing + * the signal number to that pipe in the signal handler. + * + * Currently the signal number is treated as an uint8_t to avoid + * dealing with the extremely unlikely case of partial writes in + * a signal handler. Currently this is more than enough for the max value of + * signals on linux at least. + * + */ + +struct IOMuxSignalEntry { + struct TailQ entry; //within SignalData.head + int signo; + IOMuxSignalHandler callback; + void *cookie; + struct sigaction old_sa; +}; + +struct SignalData { + struct TailQ head; //of IOMuxSignalEntry + struct IOMuxFD fd; + int pipes[2]; + struct IOMux *mux; + int initialized; +}; + +static pthread_mutex_t g_signal_lock = PTHREAD_MUTEX_INITIALIZER; + +//protected by g_signal_lock +struct SignalData g_signal_global; + +static inline void uc_signal_lock(void) +{ + int rc = pthread_mutex_lock(&g_signal_lock); + assert(rc == 0); +} + +static inline void uc_signal_unlock(void) +{ + int rc = pthread_mutex_unlock(&g_signal_lock); + assert(rc == 0); +} + +//call with lock held +static struct IOMuxSignalEntry *uc_find_signal_entry(int signo) +{ + struct IOMuxSignalEntry *it; + + UC_TAILQ_FOREACH_CONTAINER(it, entry, &g_signal_global.head) { + if (it->signo == signo) { + return it; + } + } + + return NULL; +} + +//call with lock held +static void uc_deinitailize_iomux_signals(void) +{ + assert(g_signal_global.mux != NULL); + assert(g_signal_global.initialized == 1); + assert(uc_tailq_empty(&g_signal_global.head)); + + iomux_unregister_fd(g_signal_global.mux, &g_signal_global.fd); + + close(g_signal_global.pipes[0]); + close(g_signal_global.pipes[1]); + + g_signal_global.mux = NULL; + g_signal_global.initialized = 0; + g_signal_global.fd.callback = NULL; + g_signal_global.fd.fd = -1; +} + +//IOMux fd callback, which dispatches the signal to the registered handler +static void uc_iomux_signal_cb(struct IOMux *mux, struct IOMuxFD *fd, unsigned int events) +{ + + uint8_t signo; + ssize_t rc; + struct IOMuxSignalEntry *entry = NULL; + + uc_signal_lock(); + + rc = read(fd->fd, &signo, 1); + if (rc == 1) { + entry = uc_find_signal_entry(signo); + } + + assert(rc != -1 || errno == EWOULDBLOCK); + + //need to unlock here, otherwise we deadlock if the callback + //registers/unregisters a signal + uc_signal_unlock(); + + if (entry != NULL) { + entry->callback(mux, signo, entry->cookie); + //note don't touch entry after this, the callback might + //have unregistered itself + } +} + +//unix signal handler +static void uc_global_signal_handler(int signo) +{ + uint8_t data = signo; + int save_errno = errno; + + write(g_signal_global.pipes[1], &data, 1); + + errno = save_errno; +} + +//call with lock held +static int uc_initailize_iomux_signals(struct IOMux *mux) +{ + int rc = 0; + + assert(g_signal_global.mux == NULL); + assert(g_signal_global.initialized == 0); + + rc = pipe(g_signal_global.pipes); + if (rc != 0) { + return errno; + } + + uc_set_nonblocking(g_signal_global.pipes[0]); + uc_set_nonblocking(g_signal_global.pipes[1]); + + uc_tailq_init(&g_signal_global.head); + + g_signal_global.mux = mux; + g_signal_global.fd.fd = g_signal_global.pipes[0]; + g_signal_global.fd.what = MUX_EV_READ; + g_signal_global.fd.callback = uc_iomux_signal_cb; + + rc = iomux_register_fd(mux, &g_signal_global.fd); + if (rc == 0) { + g_signal_global.initialized = 1; + } else { + uc_deinitailize_iomux_signals(); + } + + return rc; +} + +static struct IOMuxSignalEntry *uc_signal_entry_new(int signo, + IOMuxSignalHandler handler, + void *cookie) +{ + struct IOMuxSignalEntry *entry; + + entry = calloc(1, sizeof *entry); + if (entry == NULL) { + return NULL; + } + + entry->signo = signo; + entry->callback = handler; + entry->cookie = cookie; + + return entry; +} + + +static int iomux_register_unixsignal_internal(struct IOMux *mux, + int signo, + IOMuxSignalHandler handler, + void *cookie) +{ + struct IOMuxSignalEntry *entry; + int rc; + struct sigaction sa; + + if (!g_signal_global.initialized) { + rc = uc_initailize_iomux_signals(mux); + if (rc != 0) { + return rc; + } + } + + //Only one mux can handle signals + if (g_signal_global.mux != mux) { + return EINVAL; + } + + entry = uc_find_signal_entry(signo); + if (entry != NULL) { + return EEXIST; + } + + entry = uc_signal_entry_new(signo, handler, cookie); + if (entry == NULL) { + return ENOMEM; + } + + memset(&sa, 0, sizeof sa); + sa.sa_handler = uc_global_signal_handler; + sa.sa_flags = SA_RESTART; + sigfillset(&sa.sa_mask); + + rc = sigaction(signo, &sa, &entry->old_sa); + if (rc == 0) { + uc_tailq_insert_tail(&g_signal_global.head, &entry->entry); + } else { + rc = errno; + free(entry); + } + + return rc; +} + +int iomux_register_unixsignal(struct IOMux *mux, + int signo, + IOMuxSignalHandler handler, + void *cookie) +{ + int rc; + + uc_signal_lock(); + + rc = iomux_register_unixsignal_internal(mux, signo, handler, cookie); + + if (g_signal_global.initialized && uc_tailq_empty(&g_signal_global.head)) { + //we might have initialized the iomux but otherwise failed + uc_deinitailize_iomux_signals(); + } + + uc_signal_unlock(); + + return rc; +} + +int iomux_unregister_unixsignal(struct IOMux *mux, int signo) +{ + struct IOMuxSignalEntry *entry; + int rc = ENOENT; + + uc_signal_lock(); + + if (!g_signal_global.initialized || g_signal_global.mux != mux) { + uc_signal_unlock(); + return EINVAL; + } + + + entry = uc_find_signal_entry(signo); + if (entry != NULL) { + uc_tailq_remove(&entry->entry); + rc = sigaction(entry->signo, &entry->old_sa, NULL); + assert(rc == 0); + free(entry); + } + + if (uc_tailq_empty(&g_signal_global.head)) { + uc_deinitailize_iomux_signals(); + } + + uc_signal_unlock(); + + return 0; +} +