#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 rc; }