/* fiber.c - fiber management and scheduling * * Copyright (C) 2009 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 or later as * published by the Free Software Foundation. * * See http://www.gnu.org/ for details. */ #include #include #include #include #include struct tf_fiber { unsigned int ref_count; struct tf_list_node queue_node; struct tf_heap_node heap_node; char data[TF_EMPTY_ARRAY]; }; #include TF_UCTX_H /* FIXME: should be in thread local storage */ struct tf_scheduler *__tf_scheduler; void *tf_fiber_create(tf_fiber_proc fiber_main, int private_size) { struct tf_scheduler *sched = tf_get_scheduler(); struct tf_fiber *fiber; if (tf_heap_prealloc(&sched->heap, sched->num_fibers + 1) < 0) return NULL; fiber = tf_uctx_create(fiber_main, private_size); if (fiber == NULL) return NULL; /* The initial references for caller and scheduler */ *fiber = (struct tf_fiber) { .ref_count = 2, .queue_node = TF_LIST_INITIALIZER(fiber->queue_node), }; tf_list_add_tail(&fiber->queue_node, &sched->run_q); sched->num_fibers++; return fiber->data; } static void __tf_fiber_destroy(struct tf_fiber *fiber) { tf_heap_delete(&fiber->heap_node, &tf_get_scheduler()->heap); tf_uctx_destroy(fiber); } void *tf_fiber_get(void *data) { struct tf_fiber *fiber = container_of(data, struct tf_fiber, data); tf_atomic_inc(fiber->ref_count); return data; } void tf_fiber_put(void *data) { struct tf_fiber *fiber = container_of(data, struct tf_fiber, data); if (tf_atomic_dec(fiber->ref_count) == 0) __tf_fiber_destroy(fiber); } static void update_time(struct tf_scheduler *sched) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); sched->scheduler_time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } static void run_fiber(struct tf_scheduler *sched, struct tf_fiber *f) { struct tf_fiber *schedf = container_of((void*) tf_get_scheduler(), struct tf_fiber, data); sched->active_fiber = f; switch (tf_uctx_transfer(schedf, f, 1)) { case EFAULT: /* Fiber is dead */ tf_fiber_put(f->data); sched->num_fibers--; break; case EAGAIN: /* Yielded, reshedule */ tf_list_add_tail(&f->queue_node, &sched->run_q); break; case EIO: /* Blocked, in sleep */ tf_list_add_tail(&f->queue_node, &sched->sleep_q); break; default: TF_BUG_ON("bad scheduler call from fiber"); } } static void process_heap(struct tf_scheduler *sched) { struct tf_heap_node *node; struct tf_fiber *f; tf_mtime_t now = tf_mtime(); while (!tf_heap_empty(&sched->heap) && tf_mtime_diff(now, tf_heap_get_value(&sched->heap)) > 0) { node = tf_heap_get_node(&sched->heap); f = container_of(node, struct tf_fiber, heap_node); run_fiber(sched, f); } } static void process_runq(struct tf_scheduler *sched) { struct tf_fiber *f; while (!tf_list_empty(&sched->run_q)) { f = tf_list_first(&sched->run_q, struct tf_fiber, queue_node); tf_list_del(&f->queue_node); run_fiber(sched, f); } } int tf_main(tf_fiber_proc main_fiber) { struct tf_uctx *ctx = alloca(sizeof(struct tf_uctx) + sizeof(struct tf_scheduler)); struct tf_scheduler *sched = (struct tf_scheduler*) ctx->fiber.data; int stack_guard = STACK_GUARD; ctx->stack_guard = &stack_guard; *sched = (struct tf_scheduler){ .run_q = TF_LIST_HEAD_INITIALIZER(sched->run_q), .sleep_q = TF_LIST_HEAD_INITIALIZER(sched->sleep_q), }; __tf_scheduler = sched; update_time(sched); tf_fiber_put(tf_fiber_create(main_fiber, 0)); do { tf_mtime_diff_t timeout; update_time(sched); if (!tf_list_empty(&sched->run_q)) { timeout = 0; } else if (!tf_heap_empty(&sched->heap)) { timeout = tf_mtime_diff(tf_heap_get_value(&sched->heap), sched->scheduler_time); if (timeout < 0) timeout = 0; } else timeout = -1; if (timeout > 0) usleep(timeout * 1000); process_heap(sched); process_runq(sched); } while (likely(sched->num_fibers)); __tf_scheduler = NULL; return 0; } int tf_schedule(int err) { struct tf_scheduler *sched = tf_get_scheduler(); struct tf_fiber *schedf = container_of((void*) sched, struct tf_fiber, data); struct tf_fiber *f = sched->active_fiber; int r; r = tf_uctx_transfer(f, schedf, err); if (r == 1) return 0; return r; } int tf_msleep(int milliseconds) { struct tf_scheduler *sched = tf_get_scheduler(); struct tf_fiber *f = sched->active_fiber; tf_heap_change(&f->heap_node, &sched->heap, tf_mtime() + milliseconds); return tf_schedule(EIO); } void tf_kill(void *fiber) { }