Add unix signal integration with the iomux

This commit is contained in:
Nils O. Selåsdal
2015-11-28 15:38:21 +01:00
parent 9bb7cc335a
commit a8dffbc71e
2 changed files with 327 additions and 0 deletions
+48
View File
@@ -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
+279
View File
@@ -0,0 +1,279 @@
#include <pthread.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#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;
}