/* * PCL by Davide Libenzi (Portable Coroutine Library) * Copyright (C) 2003..2010 Davide Libenzi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Davide Libenzi * */ #include #include #include "pcl_config.h" #include "pcl.h" #include "pcl_private.h" #if defined(CO_USE_SIGCONTEXT) #include #endif #if defined(CO_USE_SIGCONTEXT) /* * Multi thread and CO_USE_SIGCONTEXT are a NO GO! */ #if defined(CO_MULTI_THREAD) #error "Sorry, it is not possible to use MT libpcl with CO_USE_SIGCONTEXT" #endif static volatile int ctx_called; static co_ctx_t *ctx_creating; static void *ctx_creating_func; static sigset_t ctx_creating_sigs; static co_ctx_t ctx_trampoline; static co_ctx_t ctx_caller; #endif /* #if defined(CO_USE_SIGCONTEXT) */ #if 0 static int co_ctx_sdir(unsigned long psp) { int nav = 0; unsigned long csp = (unsigned long) &nav; return psp > csp ? -1: +1; } static int co_ctx_stackdir(void) { int cav = 0; return co_ctx_sdir((unsigned long) &cav); } #endif #if defined(CO_USE_UCONEXT) static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) { if (getcontext(&ctx->cc)) return -1; ctx->cc.uc_link = NULL; ctx->cc.uc_stack.ss_sp = stkbase; ctx->cc.uc_stack.ss_size = stksiz - sizeof(long); ctx->cc.uc_stack.ss_flags = 0; makecontext(&ctx->cc, func, 1); return 0; } static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) { cothread_ctx *tctx = co_get_thread_ctx(); if (swapcontext(&octx->cc, &nctx->cc) < 0) { fprintf(stderr, "[PCL] Context switch failed: curr=%p\n", tctx->co_curr); exit(1); } } #else /* #if defined(CO_USE_UCONEXT) */ #if defined(CO_USE_SIGCONTEXT) /* * This code comes from the GNU Pth implementation and uses the * sigstack/sigaltstack() trick. * * The ingenious fact is that this variant runs really on _all_ POSIX * compliant systems without special platform kludges. But be _VERY_ * carefully when you change something in the following code. The slightest * change or reordering can lead to horribly broken code. Really every * function call in the following case is intended to be how it is, doubt * me... * * For more details we strongly recommend you to read the companion * paper ``Portable Multithreading -- The Signal Stack Trick for * User-Space Thread Creation'' from Ralf S. Engelschall. */ static void co_ctx_bootstrap(void) { cothread_ctx *tctx = co_get_thread_ctx(); co_ctx_t * volatile ctx_starting; void (* volatile ctx_starting_func)(void); /* * Switch to the final signal mask (inherited from parent) */ sigprocmask(SIG_SETMASK, &ctx_creating_sigs, NULL); /* * Move startup details from static storage to local auto * variables which is necessary because it has to survive in * a local context until the thread is scheduled for real. */ ctx_starting = ctx_creating; ctx_starting_func = (void (*)(void)) ctx_creating_func; /* * Save current machine state (on new stack) and * go back to caller until we're scheduled for real... */ if (!setjmp(ctx_starting->cc)) longjmp(ctx_caller.cc, 1); /* * The new thread is now running: GREAT! * Now we just invoke its init function.... */ ctx_starting_func(); fprintf(stderr, "[PCL] Hmm, you really shouldn't reach this point: curr=%p\n", tctx->co_curr); exit(1); } static void co_ctx_trampoline(int sig) { /* * Save current machine state and _immediately_ go back with * a standard "return" (to stop the signal handler situation) * to let him remove the stack again. Notice that we really * have do a normal "return" here, or the OS would consider * the thread to be running on a signal stack which isn't * good (for instance it wouldn't allow us to spawn a thread * from within a thread, etc.) */ if (setjmp(ctx_trampoline.cc) == 0) { ctx_called = 1; return; } /* * Ok, the caller has longjmp'ed back to us, so now prepare * us for the real machine state switching. We have to jump * into another function here to get a new stack context for * the auto variables (which have to be auto-variables * because the start of the thread happens later). */ co_ctx_bootstrap(); } static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) { struct sigaction sa; struct sigaction osa; sigset_t osigs; sigset_t sigs; #if defined(CO_HAS_SIGSTACK) struct sigstack ss; struct sigstack oss; #elif defined(CO_HAS_SIGALTSTACK) struct sigaltstack ss; struct sigaltstack oss; #else #error "PCL: Unknown context stack type" #endif /* * Preserve the SIGUSR1 signal state, block SIGUSR1, * and establish our signal handler. The signal will * later transfer control onto the signal stack. */ sigemptyset(&sigs); sigaddset(&sigs, SIGUSR1); sigprocmask(SIG_BLOCK, &sigs, &osigs); sa.sa_handler = co_ctx_trampoline; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_ONSTACK; if (sigaction(SIGUSR1, &sa, &osa) != 0) return -1; /* * Set the new stack. * * For sigaltstack we're lucky [from sigaltstack(2) on * FreeBSD 3.1]: ``Signal stacks are automatically adjusted * for the direction of stack growth and alignment * requirements'' * * For sigstack we have to decide ourself [from sigstack(2) * on Solaris 2.6]: ``The direction of stack growth is not * indicated in the historical definition of struct sigstack. * The only way to portably establish a stack pointer is for * the application to determine stack growth direction.'' */ #if defined(CO_HAS_SIGALTSTACK) ss.ss_sp = stkbase; ss.ss_size = stksiz - sizeof(long); ss.ss_flags = 0; if (sigaltstack(&ss, &oss) < 0) return -1; #elif defined(CO_HAS_SIGSTACK) if (co_ctx_stackdir() < 0) ss.ss_sp = (stkbase + stksiz - sizeof(long)); else ss.ss_sp = stkbase; ss.ss_onstack = 0; if (sigstack(&ss, &oss) < 0) return -1; #else #error "PCL: Unknown context stack type" #endif /* * Now transfer control onto the signal stack and set it up. * It will return immediately via "return" after the setjmp() * was performed. Be careful here with race conditions. The * signal can be delivered the first time sigsuspend() is * called. */ ctx_called = 0; kill(getpid(), SIGUSR1); sigfillset(&sigs); sigdelset(&sigs, SIGUSR1); while (!ctx_called) sigsuspend(&sigs); /* * Inform the system that we are back off the signal stack by * removing the alternative signal stack. Be careful here: It * first has to be disabled, before it can be removed. */ #if defined(CO_HAS_SIGALTSTACK) sigaltstack(NULL, &ss); ss.ss_flags = SS_DISABLE; if (sigaltstack(&ss, NULL) < 0) return -1; sigaltstack(NULL, &ss); if (!(ss.ss_flags & SS_DISABLE)) return -1; if (!(oss.ss_flags & SS_DISABLE)) sigaltstack(&oss, NULL); #elif defined(CO_HAS_SIGSTACK) if (sigstack(&oss, NULL)) return -1; #else #error "PCL: Unknown context stack type" #endif /* * Restore the old SIGUSR1 signal handler and mask */ sigaction(SIGUSR1, &osa, NULL); sigprocmask(SIG_SETMASK, &osigs, NULL); /* * Set creation information. */ ctx_creating = ctx; ctx_creating_func = func; memcpy(&ctx_creating_sigs, &osigs, sizeof(sigset_t)); /* * Now enter the trampoline again, but this time not as a signal * handler. Instead we jump into it directly. */ if (setjmp(ctx_caller.cc) == 0) longjmp(ctx_trampoline.cc, 1); return 0; } #else /* #if defined(CO_USE_SIGCONTEXT) */ static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) { char *stack; stack = stkbase + stksiz - sizeof(long); setjmp(ctx->cc); #if defined(__GLIBC__) && defined(__GLIBC_MINOR__) \ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(JB_PC) && defined(JB_SP) ctx->cc[0].__jmpbuf[JB_PC] = (int) func; ctx->cc[0].__jmpbuf[JB_SP] = (int) stack; #elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(__mc68000__) ctx->cc[0].__jmpbuf[0].__aregs[0] = (long) func; ctx->cc[0].__jmpbuf[0].__sp = (int *) stack; #elif defined(__GNU_LIBRARY__) && defined(__i386__) ctx->cc[0].__jmpbuf[0].__pc = func; ctx->cc[0].__jmpbuf[0].__sp = stack; #elif defined(__MINGW32__) ctx->cc[5] = (long) func; ctx->cc[4] = (long) stack; #elif defined(__APPLE__) /* START Apple */ #if defined(__x86_64__) *(long *) ((char *) &ctx->cc + 56) = (long) func; *(long *) ((char *) &ctx->cc + 16) = (long) stack; #elif defined(__i386__) *(long *) ((char *) &ctx->cc + 48) = (long) func; *(long *) ((char *) &ctx->cc + 36) = (long) stack; #elif defined(__arm__) *(long *) ((char *) &ctx->cc + 32) = (long) func; *(long *) ((char *) &ctx->cc + 28) = (long) stack; #else #error "PCL: Unsupported setjmp/longjmp OSX CPU. Please report to " #endif /* END Apple */ #elif defined(_WIN32) && defined(_MSC_VER) /* START Windows */ #if defined(_M_IX86) ((_JUMP_BUFFER *) &ctx->cc)->Eip = (long) func; ((_JUMP_BUFFER *) &ctx->cc)->Esp = (long) stack; #elif defined(_M_AMD64) ((_JUMP_BUFFER *) &ctx->cc)->Rip = (long) func; ((_JUMP_BUFFER *) &ctx->cc)->Rsp = (long) stack; #else #error "PCL: Unsupported setjmp/longjmp Windows CPU. Please report to " #endif /* END Windows */ #elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && (defined(__powerpc64__) || defined(__powerpc__)) ctx->cc[0].__jmpbuf[JB_LR] = (int) func; ctx->cc[0].__jmpbuf[JB_GPR1] = (int) stack; #else #error "PCL: Unsupported setjmp/longjmp platform. Please report to " #endif return 0; } #endif /* #if defined(CO_USE_SIGCONTEXT) */ static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) { if (setjmp(octx->cc) == 0) longjmp(nctx->cc, 1); } #endif /* #if defined(CO_USE_UCONEXT) */ static void co_runner(void) { cothread_ctx *tctx = co_get_thread_ctx(); coroutine *co = tctx->co_curr; co->restarget = co->caller; co->func(co->data); co_resume(); abort(); } coroutine_t co_create(void (*func)(void *), void *data, void *stack, int size) { int alloc = 0; coroutine *co; if ((size &= ~(sizeof(long) - 1)) < CO_MIN_SIZE) return NULL; if (stack == NULL) { size = (size + sizeof(coroutine) + CO_STK_ALIGN - 1) & ~(CO_STK_ALIGN - 1); stack = malloc(size); if (stack == NULL) return NULL; alloc = size; } co = stack; stack = (char *) stack + CO_STK_COROSIZE; co->alloc = alloc; co->func = func; co->data = data; if (co_set_context(&co->ctx, co_runner, stack, size - CO_STK_COROSIZE) < 0) { if (alloc) free(co); return NULL; } return (coroutine_t) co; } void co_delete(coroutine_t coro) { cothread_ctx *tctx = co_get_thread_ctx(); coroutine *co = (coroutine *) coro; if (co == tctx->co_curr) { fprintf(stderr, "[PCL] Cannot delete itself: curr=%p\n", tctx->co_curr); exit(1); } if (co->alloc) free(co); } void co_call(coroutine_t coro) { cothread_ctx *tctx = co_get_thread_ctx(); coroutine *co = (coroutine *) coro, *oldco = tctx->co_curr; co->caller = tctx->co_curr; tctx->co_curr = co; co_switch_context(&oldco->ctx, &co->ctx); } void co_resume(void) { cothread_ctx *tctx = co_get_thread_ctx(); co_call(tctx->co_curr->restarget); tctx->co_curr->restarget = tctx->co_curr->caller; } static void co_del_helper(void *data) { cothread_ctx *tctx; coroutine *cdh; for (;;) { tctx = co_get_thread_ctx(); cdh = tctx->co_dhelper; tctx->co_dhelper = NULL; co_delete(tctx->co_curr->caller); co_call((coroutine_t) cdh); if (tctx->co_dhelper == NULL) { fprintf(stderr, "[PCL] Resume to delete helper coroutine: curr=%p caller=%p\n", tctx->co_curr, tctx->co_curr->caller); exit(1); } } } void co_exit_to(coroutine_t coro) { cothread_ctx *tctx = co_get_thread_ctx(); coroutine *co = (coroutine *) coro; if (tctx->dchelper == NULL && (tctx->dchelper = co_create(co_del_helper, NULL, tctx->stk, sizeof(tctx->stk))) == NULL) { fprintf(stderr, "[PCL] Unable to create delete helper coroutine: curr=%p\n", tctx->co_curr); exit(1); } tctx->co_dhelper = co; co_call((coroutine_t) tctx->dchelper); fprintf(stderr, "[PCL] Stale coroutine called: curr=%p exitto=%p caller=%p\n", tctx->co_curr, co, tctx->co_curr->caller); exit(1); } void co_exit(void) { cothread_ctx *tctx = co_get_thread_ctx(); co_exit_to((coroutine_t) tctx->co_curr->restarget); } coroutine_t co_current(void) { cothread_ctx *tctx = co_get_thread_ctx(); return (coroutine_t) tctx->co_curr; } void *co_get_data(coroutine_t coro) { coroutine *co = (coroutine *) coro; return co->data; } void *co_set_data(coroutine_t coro, void *data) { coroutine *co = (coroutine *) coro; void *odata; odata = co->data; co->data = data; return odata; }