diff --git a/Makefile b/Makefile index 6d49cee..21c78f9 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar STRIP = $(CROSS_COMPILE)strip -CFLAGS += -fPIC -g -Os +CFLAGS += -fPIC -Os CPPFLAGS += -W -Wall -Iinclude ARFLAGS = crus JUNK = *~ *.bak *.map .*.d DEADJOE *.gdb *.elf core core.* diff --git a/include/libuev/uev.h b/include/libuev/uev.h index 14688d1..35723ef 100644 --- a/include/libuev/uev.h +++ b/include/libuev/uev.h @@ -28,6 +28,9 @@ #include "queue.h" +/* Max. number of simulateneous events */ +#define UEV_MAX_EVENTS 10 + /* Elapsed time between two times() tick measurements in msec */ #define TIME_DIFF_MSEC(now, then) \ ( (now - then) / clock_tick * 1000 + \ @@ -42,64 +45,56 @@ typedef enum { UEV_DIR_OUTBOUND = 1 } uev_dir_t; +/* File, Timer, ... */ +typedef enum { + UEV_FILE_TYPE = 1, + UEV_TIMER_TYPE, +} uev_type_t; + /* I/O event watcher */ typedef struct uev_io { TAILQ_ENTRY(uev_io) link; TAILQ_ENTRY(uev_io) gc; + uev_type_t type; + int fd; ///< File descriptor uev_dir_t dir; ///< Direction: in or out - int index; ///< Index in poll array + + int timeout; ///< Timeout in milliseconds + int period; ///< Period time, in milliseconds void (*handler)(struct uev *, struct uev_io *, void *); void *data; } uev_io_t; -/* Timer event watcher */ -typedef struct uev_timer { - TAILQ_ENTRY(uev_timer) link; - - int timeout; ///< Timeout in milliseconds - int period; ///< Period time, in milliseconds - - void (*handler)(struct uev *, struct uev_timer *, void *); - void *data; -} uev_timer_t; - /* Main libuev context type */ typedef struct { - TAILQ_HEAD(, uev_io) io_list; ///< File handlers - TAILQ_HEAD(, uev_timer) timer_list; ///< Timer handlers + int running; - TAILQ_HEAD(, uev_io) io_delist; ///< List of file handles to be garbage collected - TAILQ_HEAD(, uev_timer) timer_delist; ///< List of timer handles to be garbage collected + int efd; /* For epoll() */ + struct epoll_event *events; - clock_t base_time; ///< Time at last timer tick - clock_t next_time; ///< Next timer tick epoch - int use_next; ///< True if next timer epoch is set - - int exiting; ///< True if the application is terminating + TAILQ_HEAD(, uev_io) active_list; + TAILQ_HEAD(, uev_io) inactive_list; } uev_t; -/* Callbacks for different watchers */ -typedef void (*uev_io_cb_t) (uev_t *ctx, uev_io_t *w, int fd, void *data); -typedef void (*uev_timer_cb_t)(uev_t *ctx, uev_timer_t *w, void *data); +/* Generic callback for watchers */ +typedef void (uev_cb_t) (uev_t *ctx, uev_io_t *w, void *data); /* Public interface */ -uev_io_t *uev_io_create (uev_t *ctx, uev_io_cb_t cb, void *data, int fd, uev_dir_t dir); -int uev_io_delete (uev_t *ctx, uev_io_t *w); +uev_io_t *uev_io_create (uev_t *ctx, uev_cb_t *cb, void *data, int fd, uev_dir_t dir); +int uev_io_delete (uev_t *ctx, uev_io_t *w); -int uev_timer_set (uev_t *ctx, uev_timer_t *w, int timeout, int period); -uev_timer_t *uev_timer_create (uev_t *ctx, uev_timer_cb_t cb, void *data, int timeout, int period); -int uev_timer_delete (uev_t *ctx, uev_timer_t *w); +int uev_timer_set (uev_t *ctx, uev_io_t *w, int timeout, int period); +uev_io_t *uev_timer_create(uev_t *ctx, uev_cb_t *cb, void *data, int timeout, int period); +int uev_timer_delete(uev_t *ctx, uev_io_t *w); -void uev_run (uev_t *ctx); -void uev_run_tick (uev_t *ctx, int msec); -int uev_run_pending (uev_t *ctx); -void uev_exit (uev_t *ctx); +uev_t *uev_ctx_create (void); +void uev_ctx_delete (uev_t *uev); -uev_t *uev_ctx_create (void); -void uev_ctx_delete (uev_t *uev); +int uev_run (uev_t *ctx); +void uev_exit (uev_t *ctx); #endif /* LIBUEV_UEV_H_ */ diff --git a/libuev.c b/libuev.c index 7eda59f..07730dc 100644 --- a/libuev.c +++ b/libuev.c @@ -26,29 +26,27 @@ #include #include #include -#include #include +#include #include #include +#include +#include #include #include #include #include "libuev/uev.h" -static clock_t clock_tick = 0; - -static clock_t uev_time_now(uev_t *ctx) -{ - if (!ctx->use_next) - return times(NULL); - - return ctx->next_time; -} - -uev_io_t *uev_io_create(uev_t *ctx, uev_io_cb_t handler, void *data, int fd, uev_dir_t dir) +static uev_io_t *new_watcher(uev_t *ctx, uev_type_t type, int fd, uev_dir_t dir, uev_cb_t *handler, void *data) { uev_io_t *w; + struct epoll_event ev; + + if (!ctx || !handler) { + errno = EINVAL; + return NULL; + } w = (uev_io_t *)calloc(1, sizeof(*w)); if (!w) @@ -56,16 +54,23 @@ uev_io_t *uev_io_create(uev_t *ctx, uev_io_cb_t handler, void *data, int fd, uev w->fd = fd; w->dir = dir; + w->type = type; w->handler = (void *)handler; w->data = data; - w->index = -1; - TAILQ_INSERT_TAIL(&ctx->io_list, w, link); + ev.events = dir == UEV_DIR_OUTBOUND ? EPOLLOUT : EPOLLIN; + ev.data.ptr = w; + if (epoll_ctl(ctx->efd, EPOLL_CTL_ADD, fd, &ev) < 0) { + free(w); + return NULL; + } + + TAILQ_INSERT_TAIL(&ctx->active_list, w, link); return w; } -int uev_io_delete(uev_t *ctx, uev_io_t *w) +static int delete_watcher(uev_t *ctx, uev_io_t *w) { uev_io_t *tmp; @@ -75,29 +80,31 @@ int uev_io_delete(uev_t *ctx, uev_io_t *w) } /* Check if already removed */ - TAILQ_FOREACH(tmp, &ctx->io_delist, gc) { + TAILQ_FOREACH(tmp, &ctx->inactive_list, gc) { if (tmp == w) { errno = EALREADY; return -1; } } + /* Remove from kernel */ + epoll_ctl(ctx->efd, EPOLL_CTL_DEL, w->fd, NULL); + + /* Mark as inactive */ w->handler = NULL; - TAILQ_INSERT_TAIL(&ctx->io_delist, w, gc); + TAILQ_INSERT_TAIL(&ctx->inactive_list, w, gc); return 0; } -static int timer_active(uev_t *ctx, uev_timer_t *w) +uev_io_t *uev_io_create(uev_t *ctx, uev_cb_t *handler, void *data, int fd, uev_dir_t dir) { - uev_timer_t *tmp; + return new_watcher(ctx, UEV_FILE_TYPE, fd, dir, handler, data); +} - TAILQ_FOREACH(tmp, &ctx->timer_list, link) { - if (tmp == w) - return 1; - } - - return 0; +int uev_io_delete(uev_t *ctx, uev_io_t *w) +{ + return delete_watcher(ctx, w); } /** @@ -118,41 +125,45 @@ static int timer_active(uev_t *ctx, uev_timer_t *w) * * @return The new timer, or %NULL if invalid pointers or out or memory. */ -uev_timer_t *uev_timer_create(uev_t *ctx, uev_timer_cb_t handler, void *data, int timeout, int period) +uev_io_t *uev_timer_create(uev_t *ctx, uev_cb_t *handler, void *data, int timeout, int period) { - uev_timer_t *w; + int fd; + uev_io_t *w; - if (!ctx || !handler) { - errno = EINVAL; + fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (fd < 0) + return NULL; + + w = new_watcher(ctx, UEV_TIMER_TYPE, fd, UEV_DIR_INBOUND, handler, data); + if (!w) { + close(fd); return NULL; } - w = (uev_timer_t *)calloc(1, sizeof(*w)); - if (!w) + if (uev_timer_set(ctx, w, timeout, period)) { + delete_watcher(ctx, w); + close(fd); return NULL; - - w->handler = (void *)handler; - w->data = data; - - /* Zero delay is a special case: A oneshot workproc: Must go first */ - if (0 == timeout) { - TAILQ_INSERT_HEAD(&ctx->timer_list, w, link); - return w; } - uev_timer_set(ctx, w, timeout, period); - return w; } /** * Reset or reschedule a timer */ -int uev_timer_set(uev_t *ctx, uev_timer_t *w, int timeout, int period) +int uev_timer_set(uev_t *ctx, uev_io_t *w, int timeout, int period) { - int diff; - clock_t now; - uev_timer_t *tmp; + struct itimerspec time = { + .it_value = { + .tv_sec = timeout / 1000, + .tv_nsec = (timeout % 1000) * 1000000 + }, + .it_interval = { + .tv_sec = period / 1000, + .tv_nsec = (period % 1000) * 1000000 + } + }; if (!ctx || !w) { errno = EINVAL; @@ -162,248 +173,50 @@ int uev_timer_set(uev_t *ctx, uev_timer_t *w, int timeout, int period) w->timeout = timeout; w->period = period; - /* Adjust timers. - * Jiffie wraparound is OK since we only care about diff time */ - now = uev_time_now(ctx); - diff = TIME_DIFF_MSEC(now, ctx->base_time); + if (!ctx->running) + return 0; - /* When rescheduling we need to remove the entry first */ - if (timer_active(ctx, w)) - TAILQ_REMOVE(&ctx->timer_list, w, link); - - /* Update all timers and add this entry (ordered) to the list */ - TAILQ_FOREACH(tmp, &ctx->timer_list, link) { - if (tmp->timeout > diff) - tmp->timeout -= diff; - else - tmp->timeout = 0; - - if (w && tmp->timeout > timeout) { - TAILQ_INSERT_BEFORE(tmp, w, link); - w = NULL; - } - } - - if (w) - TAILQ_INSERT_TAIL(&ctx->timer_list, w, link); - - /* Update time base */ - ctx->base_time = now; - - return 0; + return timerfd_settime(w->fd, 0, &time, NULL); } /** * Remove a timer event */ -int uev_timer_delete(uev_t *ctx, uev_timer_t *w) +int uev_timer_delete(uev_t *ctx, uev_io_t *w) { - uev_timer_t *tmp; - - if (!ctx || !w) { - errno = EINVAL; - return -1; - } - - /* Check if already removed */ - TAILQ_FOREACH(tmp, &ctx->timer_delist, link) { - if (tmp == w) { - errno = EALREADY; - return -1; - } - } - - TAILQ_REMOVE(&ctx->timer_list, w, link); - TAILQ_INSERT_TAIL(&ctx->timer_delist, w, link); - - return 0; + uev_timer_set(ctx, w, 0, 0); + return delete_watcher(ctx, w); } -/** - * Process pending events - */ -static int do_run_pending(uev_t *ctx, int immediate) -{ - int i, result, diff = 0; - size_t num = 0; - clock_t now = 0; - uev_io_t *w; - uev_timer_t *timer, *tmp; - - /* Do due timers */ - if (!TAILQ_EMPTY(&ctx->timer_list)) { - now = uev_time_now(ctx); - diff = TIME_DIFF_MSEC(now, ctx->base_time); - - TAILQ_FOREACH(timer, &ctx->timer_list, link) { - if (timer->timeout > diff) - continue; - - timer->handler((struct uev *)ctx, timer, timer->data); - if (!timer->period) - uev_timer_delete(ctx, timer); - else - uev_timer_set(ctx, timer, timer->period, timer->period); - - /* base_time changes for every uev_timer_create(), which - * can be called in every timer->handler() above. */ - diff = TIME_DIFF_MSEC(now, ctx->base_time); - } - - /* Garbage collect */ - TAILQ_FOREACH_SAFE(timer, &ctx->timer_delist, link, tmp) { - TAILQ_REMOVE(&ctx->timer_delist, timer, link); - free(timer); - } - } - - /* Calculate waiting time */ - if (ctx->exiting) - diff = 0; - else if ((timer = TAILQ_FIRST(&ctx->timer_list)) && timer->timeout > diff) - diff = timer->timeout - diff; - else if (NULL != timer) - diff = 0; - else - diff = -1; - - /* Do pipe handlers */ - TAILQ_FOREACH(w, &ctx->io_list, link) - num++; - - if (!TAILQ_EMPTY(&ctx->io_list) && !ctx->exiting) { - struct pollfd polls[num]; - - /* Prepare all I/O watchers for poll() */ - i = 0; - TAILQ_FOREACH(w, &ctx->io_list, link) { - w->index = i; - polls[i].fd = w->fd; - polls[i].events = UEV_DIR_INBOUND == w->dir ? POLLIN : POLLOUT; - polls[i].revents = 0; - i++; - } - - while ((result = poll(polls, i, immediate ? 0 : diff)) < 0) { - if (EINTR == errno) - continue; /* Signalled, try again */ - - if (EBADF == errno) { - TAILQ_FOREACH(w, &ctx->io_list, link) { - if (fcntl(w->fd, F_GETFD) == -1 && EBADF == errno) - w->handler((struct uev *)ctx, w, w->data); - } - } else { - /* Error in poll. Cannot continue */ - assert(false); - return -2; - } - } - - if (result > 0) { - TAILQ_FOREACH(w, &ctx->io_list, link) { - if (w->handler && w->index >= 0 && polls[w->index].revents) - w->handler((struct uev *)ctx, w, w->data); - } - } - - /* Garbage collect */ - if (!TAILQ_EMPTY(&ctx->io_delist)) { - TAILQ_FOREACH(w, &ctx->io_delist, gc) { - TAILQ_REMOVE(&ctx->io_delist, w, gc); - TAILQ_REMOVE(&ctx->io_list, w, link); - free(w); - } - } - } else if (diff > 0) { - if (!immediate) - poll(NULL, 0, diff); - } else if (diff == -1) { - diff = -2; /* Nothing more to do */ - } - - return diff; -} - -/** - * Run the application - */ -void uev_run(uev_t *ctx) -{ - int ret = 0; - - if (!ctx) - return; - - while (!ctx->exiting && ret != -2) - ret = do_run_pending(ctx, 0); - - ctx->exiting = false; -} - -/** - * Drive the timeouts ourself (from now on until an increment of -1) - * This is intended for use in testing frameworks - * - * @param ctx libuev context object - * @param msec Milliseconds to advance the timer. Use -1 to return to realtime - */ -void uev_run_tick(uev_t *ctx, int msec) -{ - /* Obviously we are executing a callback right now (arent we always) - * The time leap is taken into account before falling back into poll() below */ - if (msec >= 0) { - if (!ctx->use_next) { - ctx->use_next = true; - ctx->next_time = times(NULL) + msec; - } else { - ctx->next_time += msec; - } - } else if (msec == -1) { - /* Go back to realtime */ - ctx->use_next = false; - ctx->base_time += times(NULL) - ctx->next_time; - ctx->next_time = 0; - } -} - -/** - * Process pending events - */ -int uev_run_pending(uev_t *ctx) -{ - return do_run_pending(ctx, 1); -} - -/** - * Terminate the application - */ -void uev_exit(uev_t *ctx) -{ - ctx->exiting = true; -} - - /** * Create an application context */ uev_t *uev_ctx_create(void) { + int fd; uev_t *ctx; - ctx = (uev_t *)calloc(1, sizeof(*ctx)); - if (!ctx) + fd = epoll_create(1); + if (fd < 0) return NULL; - TAILQ_INIT(&ctx->io_list); - TAILQ_INIT(&ctx->timer_list); - TAILQ_INIT(&ctx->io_delist); - TAILQ_INIT(&ctx->timer_delist); + ctx = (uev_t *)calloc(1, sizeof(*ctx)); + if (!ctx) { + close(fd); + return NULL; + } - /* Get system time and clock resolution, in ticks/second */ - ctx->base_time = times(NULL); - if (0 == clock_tick) - clock_tick = sysconf(_SC_CLK_TCK); + ctx->events = (struct epoll_event *)calloc(UEV_MAX_EVENTS, sizeof(struct epoll_event)); + if (!ctx->events) { + close(fd); + free(ctx); + return NULL; + } + + ctx->efd = fd; + + TAILQ_INIT(&ctx->active_list); + TAILQ_INIT(&ctx->inactive_list); return ctx; } @@ -413,22 +226,98 @@ uev_t *uev_ctx_create(void) */ void uev_ctx_delete(uev_t *ctx) { - uev_io_t *io, *tmp_io; - uev_timer_t *timer, *tmp_timer; + uev_io_t *w, *tmp; - TAILQ_FOREACH_SAFE(io, &ctx->io_list, link, tmp_io) { - TAILQ_REMOVE(&ctx->io_list, io, link); - free(io); - } - - TAILQ_FOREACH_SAFE(timer, &ctx->timer_list, link, tmp_timer) { - TAILQ_REMOVE(&ctx->timer_list, timer, link); - free(timer); + TAILQ_FOREACH_SAFE(w, &ctx->active_list, link, tmp) { + TAILQ_REMOVE(&ctx->active_list, w, link); + free(w); } + close(ctx->efd); + free(ctx->events); free(ctx); } +/** + * Run the application + */ +int uev_run(uev_t *ctx) +{ + int result = 0; + uev_io_t *w, *tmp; + + if (!ctx) { + errno = EINVAL; + return 1; + } + + /* Start the event loop */ + ctx->running = 1; + + /* Start all dormant timers */ + TAILQ_FOREACH(w, &ctx->active_list, link) { + if (UEV_TIMER_TYPE == w->type) + uev_timer_set(ctx, w, w->timeout, w->period); + } + + while (ctx->running) { + int i, nfds; + + while ((nfds = epoll_wait(ctx->efd, ctx->events, UEV_MAX_EVENTS, -1)) < 0) { + if (EINTR == errno) + continue; /* Signalled, try again */ + + /* Error in poll. Cannot continue */ + result = 2; + break; + } + + for (i = 0; i < nfds; i++) { + uev_io_t *w = (uev_io_t *)ctx->events[i].data.ptr; + + if (w->handler) + w->handler((struct uev *)ctx, w, w->data); + + if (UEV_TIMER_TYPE == w->type) { + int result; + uint64_t exp; + + result = read(w->fd, &exp, sizeof(exp)); + if (result != sizeof(exp)) { + /* Error in timerfd. Cannot continue */ + result = 3; + ctx->running = 0; + } + + if (!w->period) + uev_timer_delete(ctx, w); + } + } + + /* Garbage collect */ + if (!TAILQ_EMPTY(&ctx->inactive_list)) { + TAILQ_FOREACH_SAFE(w, &ctx->inactive_list, gc, tmp) { + TAILQ_REMOVE(&ctx->inactive_list, w, gc); + TAILQ_REMOVE(&ctx->active_list, w, link); + free(w); + } + } + } + + ctx->running = 0; + + return result; +} + +/** + * Terminate the application + */ +void uev_exit(uev_t *ctx) +{ + ctx->running = 0; +} + + /** * Local Variables: * version-control: t diff --git a/test.c b/test.c index 23f5847..18395b6 100644 --- a/test.c +++ b/test.c @@ -31,16 +31,16 @@ static int in, out; static int period = 0; -static uev_timer_t *timer = NULL; +static uev_io_t *timer = NULL; -static void lifetime_cb(uev_t *ctx, uev_timer_t *w __attribute__ ((unused)), void *data) +static void lifetime_cb(uev_t *ctx, uev_io_t *w __attribute__ ((unused)), void *data) { fprintf(stderr, "\nLifetime exceeded %p\n", data); uev_exit(ctx); } /* The pipe watchdog, if it triggers we haven't received data in time. */ -static void timeout_cb(uev_t *ctx, uev_timer_t *w, void *data) +static void timeout_cb(uev_t *ctx, uev_io_t *w, void *data) { timer = NULL; fprintf(stderr, "\nTimeout exceeded %p\n", data); @@ -49,7 +49,7 @@ static void timeout_cb(uev_t *ctx, uev_timer_t *w, void *data) uev_exit(ctx); } -static void periodic_task(uev_t *ctx __attribute__ ((unused)), uev_timer_t *w __attribute__ ((unused)), void *data __attribute__ ((unused))) +static void periodic_task(uev_t *ctx __attribute__ ((unused)), uev_io_t *w __attribute__ ((unused)), void *data __attribute__ ((unused))) { fprintf(stderr, "|"); } @@ -68,7 +68,7 @@ static void pipe_read_cb(uev_t *ctx, uev_io_t *w __attribute__ ((unused)), void fprintf(stderr, "%.*s.%d ", cnt, msg, cnt); } -static void pipe_write_cb(uev_t *ctx, uev_timer_t *w __attribute__ ((unused)), void *data) +static void pipe_write_cb(uev_t *ctx, uev_io_t *w __attribute__ ((unused)), void *data) { int cnt = (int)(intptr_t)data; char *msg = "TESTING"; @@ -91,7 +91,7 @@ int main(void) uev_t *ctx = uev_ctx_create(); /* Total program execution time */ - uev_timer_create(ctx, (uev_timer_cb_t)lifetime_cb, (void *)(intptr_t)2, 4000, 0); + uev_timer_create(ctx, lifetime_cb, (void *)(intptr_t)2, 4000, 0); /* Work load, one timer callback writes to a pipe periodically, * and one I/O watcher that is called every time the pipe has @@ -104,15 +104,15 @@ int main(void) in = fd[0]; out = fd[1]; - uev_timer_create(ctx, (uev_timer_cb_t)pipe_write_cb, (void *)(intptr_t)1, 400, 0); - uev_io_create(ctx, (uev_io_cb_t)pipe_read_cb, NULL, in, UEV_DIR_INBOUND); + uev_timer_create(ctx, pipe_write_cb, (void *)(intptr_t)1, 400, 0); + uev_io_create(ctx, pipe_read_cb, NULL, in, UEV_DIR_INBOUND); /* Watchdog for the above timer callback, if it doesn't wake up * and write to the pipe within a given deadline it will bark. */ - timer = uev_timer_create(ctx, (uev_timer_cb_t)timeout_cb, (void *)(intptr_t)1, 950, 0); + timer = uev_timer_create(ctx, timeout_cb, (void *)(intptr_t)1, 950, 0); /* Periodic background task */ - uev_timer_create(ctx, (uev_timer_cb_t)periodic_task, NULL, 200, 200); + uev_timer_create(ctx, periodic_task, NULL, 200, 200); /* Start event loop */ uev_run(ctx);