/* ** GNU Pth - The GNU Portable Threads ** Copyright (c) 1999-2002 Ralf S. Engelschall ** ** This file is part of GNU Pth, a non-preemptive thread scheduling ** library which can be found at http://www.gnu.org/software/pth/. ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public ** License as published by the Free Software Foundation; either ** version 2.1 of the License, or (at your option) any later version. ** ** This library 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 ** Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser General Public ** License along with this library; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact Ralf S. Engelschall . ** ** pth_mctx.c: Pth machine context handling */ /* ``If you can't do it in ANSI C, it isn't worth doing.'' -- Unknown */ #include "pth_p.h" #if cpp /* * machine context state structure * * In `jb' the CPU registers, the program counter, the stack * pointer and (usually) the signals mask is stored. When the * signal mask cannot be implicitly stored in `jb', it's * alternatively stored explicitly in `sigs'. The `error' stores * the value of `errno'. */ #if PTH_MCTX_MTH(mcsc) #include #endif typedef struct pth_mctx_st pth_mctx_t; struct pth_mctx_st { #if PTH_MCTX_MTH(mcsc) ucontext_t uc; #elif PTH_MCTX_MTH(sjlj) pth_sigjmpbuf jb; #else #error "unknown mctx method" #endif sigset_t sigs; #if PTH_MCTX_DSP(sjlje) sigset_t block; #endif int error; }; /* ** ____ MACHINE STATE SWITCHING ______________________________________ */ /* * save the current machine context */ #if PTH_MCTX_MTH(mcsc) #define pth_mctx_save(mctx) \ ( (mctx)->error = errno, \ getcontext(&(mctx)->uc) ) #elif PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjlje) #define pth_mctx_save(mctx) \ ( (mctx)->error = errno, \ pth_sc(sigprocmask)(SIG_SETMASK, &((mctx)->block), NULL), \ pth_sigsetjmp((mctx)->jb) ) #elif PTH_MCTX_MTH(sjlj) #define pth_mctx_save(mctx) \ ( (mctx)->error = errno, \ pth_sigsetjmp((mctx)->jb) ) #else #error "unknown mctx method" #endif /* * restore the current machine context * (at the location of the old context) */ #if PTH_MCTX_MTH(mcsc) #define pth_mctx_restore(mctx) \ ( errno = (mctx)->error, \ (void)setcontext(&(mctx)->uc) ) #elif PTH_MCTX_MTH(sjlj) #define pth_mctx_restore(mctx) \ ( errno = (mctx)->error, \ (void)pth_siglongjmp((mctx)->jb, 1) ) #else #error "unknown mctx method" #endif /* * restore the current machine context * (at the location of the new context) */ #if PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjlje) #define pth_mctx_restored(mctx) \ pth_sc(sigprocmask)(SIG_SETMASK, &((mctx)->sigs), NULL) #else #define pth_mctx_restored(mctx) \ /*nop*/ #endif /* * switch from one machine context to another */ #define SWITCH_DEBUG_LINE \ "==== THREAD CONTEXT SWITCH ===========================================" #ifdef PTH_DEBUG #define _pth_mctx_switch_debug pth_debug(NULL, 0, 1, SWITCH_DEBUG_LINE); #else #define _pth_mctx_switch_debug /*NOP*/ #endif #if PTH_MCTX_MTH(mcsc) #define pth_mctx_switch(old,new) \ _pth_mctx_switch_debug \ swapcontext(&((old)->uc), &((new)->uc)); #elif PTH_MCTX_MTH(sjlj) #define pth_mctx_switch(old,new) \ _pth_mctx_switch_debug \ if (pth_mctx_save(old) == 0) \ pth_mctx_restore(new); \ pth_mctx_restored(old); #else #error "unknown mctx method" #endif #endif /* cpp */ /* ** ____ MACHINE STATE INITIALIZATION ________________________________ */ #if PTH_MCTX_MTH(mcsc) /* * VARIANT 1: THE STANDARDIZED SVR4/SUSv2 APPROACH * * This is the preferred variant, because it uses the standardized * SVR4/SUSv2 makecontext(2) and friends which is a facility intended * for user-space context switching. The thread creation therefore is * straight-foreward. */ intern int pth_mctx_set( pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { /* fetch current context */ if (getcontext(&(mctx->uc)) != 0) return FALSE; /* remove parent link */ mctx->uc.uc_link = NULL; /* configure new stack */ mctx->uc.uc_stack.ss_sp = pth_skaddr(makecontext, sk_addr_lo, sk_addr_hi-sk_addr_lo); mctx->uc.uc_stack.ss_size = pth_sksize(makecontext, sk_addr_lo, sk_addr_hi-sk_addr_lo); mctx->uc.uc_stack.ss_flags = 0; /* configure startup function (with no arguments) */ makecontext(&(mctx->uc), func, 0+1); return TRUE; } #elif PTH_MCTX_MTH(sjlj) &&\ !PTH_MCTX_DSP(sjljlx) &&\ !PTH_MCTX_DSP(sjljisc) &&\ !PTH_MCTX_DSP(sjljw32) /* * VARIANT 2: THE SIGNAL STACK TRICK * * This uses sigstack/sigaltstack() and friends and is really the * most tricky part of Pth. When you understand the following * stuff you're a good Unix hacker and then you've already * understood the gory ingredients of Pth. So, either welcome to * the club of hackers, or do yourself a favor and skip this ;) * * 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. A copy of the * draft of this paper you can find in the file rse-pmt.ps inside the * GNU Pth distribution. */ #if !defined(SA_ONSTACK) && defined(SV_ONSTACK) #define SA_ONSTACK SV_ONSTACK #endif #if !defined(SS_DISABLE) && defined(SA_DISABLE) #define SS_DISABLE SA_DISABLE #endif #if PTH_MCTX_STK(sas) && !defined(HAVE_SS_SP) && defined(HAVE_SS_BASE) #define ss_sp ss_base #endif static volatile jmp_buf mctx_trampoline; static volatile pth_mctx_t mctx_caller; static volatile sig_atomic_t mctx_called; static pth_mctx_t * volatile mctx_creating; static void (* volatile mctx_creating_func)(void); static volatile sigset_t mctx_creating_sigs; static void pth_mctx_set_trampoline(int); static void pth_mctx_set_bootstrap(void); /* initialize a machine state */ intern int pth_mctx_set( pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { struct sigaction sa; struct sigaction osa; #if PTH_MCTX_STK(sas) && defined(HAVE_STACK_T) stack_t ss; stack_t oss; #elif PTH_MCTX_STK(sas) struct sigaltstack ss; struct sigaltstack oss; #elif PTH_MCTX_STK(ss) struct sigstack ss; struct sigstack oss; #else #error "unknown mctx stack setup" #endif sigset_t osigs; sigset_t sigs; pth_debug1("pth_mctx_set: enter"); /* * 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); pth_sc(sigprocmask)(SIG_BLOCK, &sigs, &osigs); sa.sa_handler = pth_mctx_set_trampoline; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_ONSTACK; if (sigaction(SIGUSR1, &sa, &osa) != 0) return FALSE; /* * 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 PTH_MCTX_STK(sas) ss.ss_sp = pth_skaddr(sigaltstack, sk_addr_lo, sk_addr_hi-sk_addr_lo); ss.ss_size = pth_sksize(sigaltstack, sk_addr_lo, sk_addr_hi-sk_addr_lo); ss.ss_flags = 0; if (sigaltstack(&ss, &oss) < 0) return FALSE; #elif PTH_MCTX_STK(ss) ss.ss_sp = pth_skaddr(sigstack, sk_addr_lo, sk_addr_hi-sk_addr_lo); ss.ss_onstack = 0; if (sigstack(&ss, &oss) < 0) return FALSE; #else #error "unknown mctx stack setup" #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. */ mctx_called = FALSE; kill(getpid(), SIGUSR1); sigfillset(&sigs); sigdelset(&sigs, SIGUSR1); while (!mctx_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 PTH_MCTX_STK(sas) sigaltstack(NULL, &ss); ss.ss_flags = SS_DISABLE; if (sigaltstack(&ss, NULL) < 0) return FALSE; sigaltstack(NULL, &ss); if (!(ss.ss_flags & SS_DISABLE)) return_errno(FALSE, EIO); if (!(oss.ss_flags & SS_DISABLE)) sigaltstack(&oss, NULL); #elif PTH_MCTX_STK(ss) if (sigstack(&oss, NULL)) return FALSE; #endif /* * Restore the old SIGUSR1 signal handler and mask */ sigaction(SIGUSR1, &osa, NULL); pth_sc(sigprocmask)(SIG_SETMASK, &osigs, NULL); /* * Initialize additional ingredients of the machine * context structure. */ #if PTH_MCTX_DSP(sjlje) sigemptyset(&mctx->block); #endif sigemptyset(&mctx->sigs); mctx->error = 0; /* * Tell the trampoline and bootstrap function where to dump * the new machine context, and what to do afterwards... */ mctx_creating = mctx; mctx_creating_func = func; memcpy((void *)&mctx_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. The functionally * redundant ping-pong pointer arithmentic is neccessary to avoid * type-conversion warnings related to the `volatile' qualifier and * the fact that `jmp_buf' usually is an array type. */ if (pth_mctx_save((pth_mctx_t *)&mctx_caller) == 0) longjmp(*((jmp_buf *)&mctx_trampoline), 1); /* * Ok, we returned again, so now we're finished */ pth_debug1("pth_mctx_set: leave"); return TRUE; } /* trampoline signal handler */ static void pth_mctx_set_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.) * * The functionally redundant ping-pong pointer arithmentic is again * neccessary to avoid type-conversion warnings related to the * `volatile' qualifier and the fact that `jmp_buf' usually is an * array type. * * Additionally notice that we INTENTIONALLY DO NOT USE pth_mctx_save() * here. Instead we use a plain setjmp(3) call because we have to make * sure the alternate signal stack environment is _NOT_ saved into the * machine context (which can be the case for sigsetjmp(3) on some * platforms). */ if (setjmp(*((jmp_buf *)&mctx_trampoline)) == 0) { pth_debug1("pth_mctx_set_trampoline: return to caller"); mctx_called = TRUE; return; } pth_debug1("pth_mctx_set_trampoline: reentered from caller"); /* * 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). Else with * PIC (i.e. Position Independent Code which is used when PTH * is built as a shared library) most platforms would * horrible core dump as experience showed. */ pth_mctx_set_bootstrap(); } /* boot function */ static void pth_mctx_set_bootstrap(void) { pth_mctx_t * volatile mctx_starting; void (* volatile mctx_starting_func)(void); /* * Switch to the final signal mask (inherited from parent) */ pth_sc(sigprocmask)(SIG_SETMASK, (sigset_t *)&mctx_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. */ mctx_starting = mctx_creating; mctx_starting_func = mctx_creating_func; /* * Save current machine state (on new stack) and * go back to caller until we're scheduled for real... */ pth_debug1("pth_mctx_set_trampoline_jumpin: switch back to caller"); pth_mctx_switch((pth_mctx_t *)mctx_starting, (pth_mctx_t *)&mctx_caller); /* * The new thread is now running: GREAT! * Now we just invoke its init function.... */ pth_debug1("pth_mctx_set_trampoline_jumpin: reentered from scheduler"); mctx_starting_func(); abort(); } #elif PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjljlx) /* * VARIANT 3: LINUX SPECIFIC JMP_BUF FIDDLING * * Oh hell, I really love it when Linux guys talk about their "POSIX * compliant system". It's far away from POSIX compliant, IMHO. Autoconf * finds sigstack/sigaltstack() on Linux, yes. But it doesn't work. Why? * Because on Linux below version 2.2 and glibc versions below 2.1 these * two functions are nothing more than silly stub functions which always * return just -1. Very useful, yeah... */ #include intern int pth_mctx_set( pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { pth_mctx_save(mctx); #if defined(__GLIBC__) && defined(__GLIBC_MINOR__) \ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(JB_PC) && defined(JB_SP) mctx->jb[0].__jmpbuf[JB_PC] = (int)func; mctx->jb[0].__jmpbuf[JB_SP] = (int)sk_addr_hi; #elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(__mc68000__) mctx->jb[0].__jmpbuf[0].__aregs[0] = (long int)func; mctx->jb[0].__jmpbuf[0].__sp = (int *)sk_addr_hi; #elif defined(__GNU_LIBRARY__) && defined(__i386__) mctx->jb[0].__jmpbuf[0].__pc = (char *)func; mctx->jb[0].__jmpbuf[0].__sp = sk_addr_hi; #else #error "Unsupported Linux (g)libc version and/or platform" #endif sigemptyset(&mctx->sigs); mctx->error = 0; return TRUE; } /* * VARIANT 4: INTERACTIVE SPECIFIC JMP_BUF FIDDLING * * No wonder, Interactive Unix (ISC) 4.x contains Microsoft code, so * it's clear that this beast lacks both sigstack and sigaltstack (about * makecontext we not even have to talk). So our only chance is to * fiddle with it's jmp_buf ingredients, of course. We support only i386 * boxes. */ #elif PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjljisc) intern int pth_mctx_set(pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { pth_mctx_save(mctx); #if i386 mctx->jb[4] = (int)sk_addr_hi - sizeof(mctx->jb); mctx->jb[5] = (int)func; #else #error "Unsupported ISC architecture" #endif sigemptyset(&mctx->sigs); mctx->error = 0; return TRUE; } /* * VARIANT 5: WIN32 SPECIFIC JMP_BUF FIDDLING * * Oh hell, Win32 has setjmp(3), but no sigstack(2) or sigaltstack(2). * So we have to fiddle around with the jmp_buf here too... */ #elif PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjljw32) intern int pth_mctx_set(pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { pth_mctx_save(mctx); #if i386 mctx->jb[7] = (int)sk_addr_hi; mctx->jb[8] = (int)func; #else #error "Unsupported Win32 architecture" #endif sigemptyset(&mctx->sigs); mctx->error = 0; return TRUE; } /* * VARIANT X: JMP_BUF FIDDLING FOR ONE MORE ESOTERIC OS * Add the jmp_buf fiddling for your esoteric OS here... * #elif PTH_MCTX_MTH(sjlj) && PTH_MCTX_DSP(sjljeso) intern int pth_mctx_set(pth_mctx_t *mctx, void (*func)(void), char *sk_addr_lo, char *sk_addr_hi) { pth_mctx_save(mctx); sigemptyset(&mctx->sigs); mctx->error = 0; ...start hacking here... mctx->.... = func; mctx->.... = sk_addr_hi; mctx->.... = sk_addr_lo; return TRUE; } */ #else #error "unknown mctx method" #endif