Index: ossp-pkg/pth/.cvsignore RCS File: /v/ossp/cvs/ossp-pkg/pth/.cvsignore,v rcsdiff -q -kk '-r1.20' '-r1.21' -u '/v/ossp/cvs/ossp-pkg/pth/.cvsignore,v' 2>/dev/null --- .cvsignore 2002/01/30 13:05:22 1.20 +++ .cvsignore 2002/11/03 09:59:32 1.21 @@ -36,5 +36,6 @@ test_pthread test_select test_sfio +test_uctx test_sig test_std Index: ossp-pkg/pth/ChangeLog RCS File: /v/ossp/cvs/ossp-pkg/pth/ChangeLog,v rcsdiff -q -kk '-r1.582' '-r1.583' -u '/v/ossp/cvs/ossp-pkg/pth/ChangeLog,v' 2>/dev/null --- ChangeLog 2002/10/25 11:56:16 1.582 +++ ChangeLog 2002/11/03 09:59:32 1.583 @@ -21,6 +21,19 @@ Changes between 1.4.1 and 1.5.0 (27-Jan-2002 to xx-Oct-2002) + *) Added a stand-alone sub-API for manual user-space context + switching. It is somewhat modeled after the POSIX ucontext(3) + facility and consists of an opaque data type pth_uctx_t and + the management functions pth_uctx_create(), pth_uctx_make(), + pth_uctx_save(), pth_uctx_restore(), pth_uctx_switch() and + pth_uctx_destroy(). These functions are based on the same + underlying machine context switching facility (pth_mctx) + the threads in GNU Pth are using. This facility can be used + to implement co-routines without a full real multithreading + environment or even to implement an own multithreading + environment. + [Ralf S. Engelschall] + *) Add a Pth variant of the new POSIX pselect(2) function, including soft and hard syscall mapping support for it. [Ralf S. Engelschall] Index: ossp-pkg/pth/Makefile.in RCS File: /v/ossp/cvs/ossp-pkg/pth/Makefile.in,v rcsdiff -q -kk '-r1.147' '-r1.148' -u '/v/ossp/cvs/ossp-pkg/pth/Makefile.in,v' 2>/dev/null --- Makefile.in 2002/10/15 20:34:22 1.147 +++ Makefile.in 2002/11/03 09:59:32 1.148 @@ -88,19 +88,19 @@ TARGET_LIBS = libpth.la @LIBPTHREAD_LA@ TARGET_MANS = $(S)pth-config.1 $(S)pth.3 @PTHREAD_CONFIG_1@ @PTHREAD_3@ TARGET_TEST = test_std test_mp test_misc test_philo test_sig \ - test_select test_httpd test_sfio @TEST_PTHREAD@ + test_select test_httpd test_sfio test_uctx @TEST_PTHREAD@ # object files for library generation # (order is just aesthically important) -LOBJS = pth_debug.lo pth_ring.lo pth_pqueue.lo pth_time.lo pth_errno.lo \ - pth_mctx.lo pth_tcb.lo pth_sched.lo pth_attr.lo pth_lib.lo pth_event.lo \ +LOBJS = pth_debug.lo pth_ring.lo pth_pqueue.lo pth_time.lo pth_errno.lo pth_mctx.lo \ + pth_uctx.lo pth_tcb.lo pth_sched.lo pth_attr.lo pth_lib.lo pth_event.lo \ pth_data.lo pth_clean.lo pth_cancel.lo pth_msg.lo pth_sync.lo pth_fork.lo \ pth_util.lo pth_high.lo pth_syscall.lo pth_ext.lo pth_compat.lo pth_string.lo # source files for header generation # (order is important and has to follow dependencies in pth_p.h) HSRCS = $(S)pth_compat.c $(S)pth_debug.c $(S)pth_syscall.c $(S)pth_errno.c $(S)pth_ring.c $(S)pth_mctx.c \ - $(S)pth_clean.c $(S)pth_time.c $(S)pth_tcb.c $(S)pth_util.c $(S)pth_pqueue.c $(S)pth_event.c \ + $(S)pth_uctx.c $(S)pth_clean.c $(S)pth_time.c $(S)pth_tcb.c $(S)pth_util.c $(S)pth_pqueue.c $(S)pth_event.c \ $(S)pth_sched.c $(S)pth_data.c $(S)pth_msg.c $(S)pth_cancel.c $(S)pth_sync.c $(S)pth_attr.c $(S)pth_lib.c \ $(S)pth_fork.c $(S)pth_high.c $(S)pth_ext.c $(S)pth_string.c @@ -209,6 +209,8 @@ $(LIBTOOL) --mode=link --quiet $(CC) $(LDFLAGS) -o test_select test_select.o test_common.o libpth.la $(LIBS) test_sfio: test_sfio.o test_common.o libpth.la $(LIBTOOL) --mode=link --quiet $(CC) $(LDFLAGS) -o test_sfio test_sfio.o test_common.o libpth.la $(LIBS) +test_uctx: test_uctx.o test_common.o libpth.la + $(LIBTOOL) --mode=link --quiet $(CC) $(LDFLAGS) -o test_uctx test_uctx.o test_common.o libpth.la $(LIBS) test_pthread: test_pthread.o test_common.o libpthread.la $(LIBTOOL) --mode=link --quiet $(CC) $(LDFLAGS) -o test_pthread test_pthread.o test_common.o libpthread.la $(LIBS) @@ -371,6 +373,8 @@ ./test_select test-sfio: test_sfio ./test_sfio +test-uctx: test_uctx + ./test_uctx test-pthread: test_pthread ./test_pthread debug: debug-std @@ -390,6 +394,8 @@ TEST=test_select && $(_DEBUG) debug-sfio: test_sfio TEST=test_sfio && $(_DEBUG) +debug-uctx: test_uctx + TEST=test_uctx && $(_DEBUG) debug-pthread: test_pthread TEST=test_pthread && $(_DEBUG) @@ -445,5 +451,6 @@ test_pthread.o: test_pthread.c pthread.h test_select.o: test_select.c pth.h test_sfio.o: test_sfio.c pth.h +test_uctx.o: test_uctx.c pth.h test_sig.o: test_sig.c pth.h test_std.o: test_std.c pth.h Index: ossp-pkg/pth/pth.h.in RCS File: /v/ossp/cvs/ossp-pkg/pth/pth.h.in,v rcsdiff -q -kk '-r1.134' '-r1.135' -u '/v/ossp/cvs/ossp-pkg/pth/pth.h.in,v' 2>/dev/null --- pth.h.in 2002/10/25 11:56:16 1.134 +++ pth.h.in 2002/11/03 09:59:32 1.135 @@ -306,6 +306,10 @@ pth_mutex_t br_mutex; }; + /* the user-space context structure */ +typedef struct pth_uctx_st *pth_uctx_t; +struct pth_uctx_st; + /* filedescriptor blocking modes */ enum { PTH_FDMODE_ERROR = -1, @@ -475,6 +479,14 @@ extern int pth_barrier_init(pth_barrier_t *, int); extern int pth_barrier_reach(pth_barrier_t *); + /* user-space context functions */ +extern int pth_uctx_create(pth_uctx_t *); +extern int pth_uctx_make(pth_uctx_t, char *, size_t, const sigset_t *, void (*)(void *), void *, pth_uctx_t); +extern int pth_uctx_save(pth_uctx_t); +extern int pth_uctx_restore(pth_uctx_t); +extern int pth_uctx_switch(pth_uctx_t, pth_uctx_t); +extern int pth_uctx_destroy(pth_uctx_t); + /* extension functions */ extern Sfdisc_t *pth_sfiodisc(void); Index: ossp-pkg/pth/pth.pod RCS File: /v/ossp/cvs/ossp-pkg/pth/pth.pod,v rcsdiff -q -kk '-r1.157' '-r1.158' -u '/v/ossp/cvs/ossp-pkg/pth/pth.pod,v' 2>/dev/null --- pth.pod 2002/10/25 11:56:16 1.157 +++ pth.pod 2002/11/03 09:59:33 1.158 @@ -138,6 +138,15 @@ pth_barrier_init, pth_barrier_reach. +=item B + +pth_uctx_create, +pth_uctx_make, +pth_uctx_save, +pth_uctx_restore, +pth_uctx_switch, +pth_uctx_destroy. + =item B pth_sigwait_ev, @@ -1464,6 +1473,86 @@ =back +=head2 User-Space Context + +The following functions provide a stand-alone sub-API for user-space +context switching. It internally is based on the same underlying machine +context switching mechanism the threads in B are based on. +Hence these functions you can use for implementing your own simple +user-space threads. The C context is somewhat modeled after +POSIX ucontext(3). + +The time required to create (via pth_uctx_make(3)) a user-space context +can range from just a few microseconds up to a more dramatical time +(depending on the machine context switching method which is available on +the platform). On the other hand, the raw performance in switching the +user-space contexts is always very good (nearly independent of the used +machine context switching method). For instance, on an Intel Pentium-III +CPU with 800Mhz running under FreeBSD 4 one usually achieves about +260,000 user-space context switches (via pth_uctx_switch(3)) per second. + +=over 4 + +=item int B(pth_uctx_t *I); + +This function creates a user-space context and stores it into I. +There is still no underlying user-space context configured. You still +have to do this with pth_uctx_make(3) or pth_uctx_set(3). On success, +this function returns C, else C. + +=item int B(pth_uctx_t I, char *I, size_t I, const sigset_t *I, void (*I)(void *), void *I, pth_uctx_t I); + +This function makes a new user-space context in I which will +operate on the run-time stack I (which is of maximum +size I), with the signals in I blocked (if +I is not C) and starting to execute with the call +I(I). If I is C, a stack +is dynamically allocated. The stack size I has to be at +least 16384 (16KB). If the start function I returns and +I is not C, an implicit user-space context switch +to this context is performed. Else (if I is C) the +process is terminated with exit(3). This function is somewhat modeled +after POSIX makecontext(3). On success, this function returns C, +else C. + +=item int B(pth_uctx_t I); + +This function saves the current user-space context in I for later +restoring by either pth_uctx_restore(3) or pth_uctx_switch(3). This +function is somewhat modeled after POSIX getcontext(3). If I is +C, C is returned instead of C. This is the only error +possible. + +=item int B(pth_uctx_t I); + +This function restores the current user-space context from I, +which previously had to be set with either pth_uctx_make(3) or +pth_uctx_save(3). This function is somewhat modeled after POSIX +setcontext(3). If I is C or I contains no valid +user-space context, C is returned instead of C. These are +the only errors possible. + +=item int B(pth_uctx_t I, pth_uctx_t I); + +This function saves the current user-space context in I for +later restoring by either pth_uctx_restore(3) or pth_uctx_switch(3) and +restores the new user-space context from I, which previously +had to be set with either pth_uctx_make(3) or pth_uctx_save(3). This +function is somewhat modeled after POSIX swapcontext(3). If I +or I are C or if I contains no valid user-space +context, C is returned instead of C. These are the only +errors possible. + +=item int B(pth_uctx_t I); + +This function destroys the user-space context in I. The run-time +stack associated with the user-space context is deallocated only if it +was given by the application (see I of pth_uctx_create(3)). +If I is C, C is returned instead of C. This +is the only error possible. + +=back + =head2 Generalized POSIX Replacement API The following functions are generalized replacements functions for the POSIX Index: ossp-pkg/pth/pth_uctx.c RCS File: /v/ossp/cvs/ossp-pkg/pth/pth_uctx.c,v co -q -kk -p'1.1' '/v/ossp/cvs/ossp-pkg/pth/pth_uctx.c,v' | diff -u /dev/null - -L'ossp-pkg/pth/pth_uctx.c' 2>/dev/null --- ossp-pkg/pth/pth_uctx.c +++ - 2024-05-15 23:10:06.522294737 +0200 @@ -0,0 +1,235 @@ +/* +** 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_uctx.c: Pth user-space context handling (stand-alone sub-API) +*/ + /* ``It worries me however, to realize + how tough an ass-hole I have had to + be, in order to get to stick to the + principle of doing things right, + rather than "just hack it in".'' + -- Poul-Henning Kamp */ +#include "pth_p.h" + +/* user-space context structure */ +struct pth_uctx_st { + int uc_stack_own; /* whether stack were allocated by us */ + char *uc_stack_ptr; /* pointer to start address of stack area */ + size_t uc_stack_len; /* size of stack area */ + int uc_mctx_set; /* whether uc_mctx is set */ + pth_mctx_t uc_mctx; /* saved underlying machine context */ +}; + +/* create user-space context structure */ +int +pth_uctx_create( + pth_uctx_t *puctx) +{ + pth_uctx_t uctx; + + /* argument sanity checking */ + if (puctx == NULL) + return pth_error(FALSE, EINVAL); + + /* allocate the context structure */ + if ((uctx = (pth_uctx_t)malloc(sizeof(struct pth_uctx_st))) == NULL) + return pth_error(FALSE, errno); + + /* initialize the context structure */ + uctx->uc_stack_own = FALSE; + uctx->uc_stack_ptr = NULL; + uctx->uc_stack_len = 0; + uctx->uc_mctx_set = FALSE; + memset((void *)&uctx->uc_mctx, 0, sizeof(pth_mctx_t)); + + /* pass result to caller */ + *puctx = uctx; + + return TRUE; +} + +/* trampoline context */ +typedef struct { + pth_mctx_t *mctx_parent; + pth_uctx_t uctx_this; + pth_uctx_t uctx_after; + void (*start_func)(void *); + void *start_arg; +} pth_uctx_trampoline_t; +pth_uctx_trampoline_t pth_uctx_trampoline_ctx; + +/* trampoline function for pth_uctx_make() */ +static void pth_uctx_trampoline(void) +{ + volatile pth_uctx_trampoline_t ctx; + + /* move context information from global to local storage */ + ctx.mctx_parent = pth_uctx_trampoline_ctx.mctx_parent; + ctx.uctx_this = pth_uctx_trampoline_ctx.uctx_this; + ctx.uctx_after = pth_uctx_trampoline_ctx.uctx_after; + ctx.start_func = pth_uctx_trampoline_ctx.start_func; + ctx.start_arg = pth_uctx_trampoline_ctx.start_arg; + + /* switch back to parent */ + pth_mctx_switch(&(ctx.uctx_this->uc_mctx), ctx.mctx_parent); + + /* enter start function */ + (*ctx.start_func)(ctx.start_arg); + + /* switch to successor user-space context */ + if (ctx.uctx_after != NULL) + pth_uctx_restore(ctx.uctx_after); + + /* terminate process (the only reasonable thing to do here) */ + exit(0); + + /* NOTREACHED */ + return; +} + +/* make setup of user-space context structure */ +int +pth_uctx_make( + pth_uctx_t uctx, + char *sk_addr, size_t sk_size, + const sigset_t *sigmask, + void (*start_func)(void *), void *start_arg, + pth_uctx_t uctx_after) +{ + pth_mctx_t mctx_parent; + sigset_t ss; + + /* argument sanity checking */ + if (uctx == NULL || start_func == NULL || sk_size < 16*1024) + return pth_error(FALSE, EINVAL); + + /* configure run-time stack */ + if (sk_addr == NULL) { + if ((sk_addr = (char *)malloc(sk_size)) == NULL) + return pth_error(FALSE, errno); + uctx->uc_stack_own = TRUE; + } + else + uctx->uc_stack_own = FALSE; + uctx->uc_stack_ptr = sk_addr; + uctx->uc_stack_len = sk_size; + + /* configure the underlying machine context */ + if (!pth_mctx_set(&uctx->uc_mctx, pth_uctx_trampoline, + uctx->uc_stack_ptr, uctx->uc_stack_ptr+uctx->uc_stack_len)) + return pth_error(FALSE, errno); + + /* move context information into global storage for the trampoline jump */ + pth_uctx_trampoline_ctx.mctx_parent = &mctx_parent; + pth_uctx_trampoline_ctx.uctx_this = uctx; + pth_uctx_trampoline_ctx.uctx_after = uctx_after; + pth_uctx_trampoline_ctx.start_func = start_func; + pth_uctx_trampoline_ctx.start_arg = start_arg; + + /* optionally establish temporary signal mask */ + if (sigmask != NULL) + sigprocmask(SIG_SETMASK, sigmask, &ss); + + /* perform the trampoline step */ + pth_mctx_switch(&mctx_parent, &uctx->uc_mctx); + + /* optionally restore original signal mask */ + if (sigmask != NULL) + sigprocmask(SIG_SETMASK, &ss, NULL); + + /* finally flag that the context is now configured */ + uctx->uc_mctx_set = TRUE; + + return TRUE; +} + +/* save current user-space context */ +int +pth_uctx_save( + pth_uctx_t uctx) +{ + /* argument sanity checking */ + if (uctx == NULL) + return pth_error(FALSE, EINVAL); + + /* save underlying machine context */ + pth_mctx_save(&uctx->uc_mctx); + uctx->uc_mctx_set = TRUE; + + return TRUE; +} + +/* restore current user-space context */ +int +pth_uctx_restore( + pth_uctx_t uctx) +{ + /* argument sanity checking */ + if (uctx == NULL) + return pth_error(FALSE, EINVAL); + if (!(uctx->uc_mctx_set)) + return pth_error(FALSE, EPERM); + + /* restore underlying machine context */ + pth_mctx_restore(&uctx->uc_mctx); + + return TRUE; +} + +/* switch from current to other user-space context */ +int +pth_uctx_switch( + pth_uctx_t uctx_from, + pth_uctx_t uctx_to) +{ + /* argument sanity checking */ + if (uctx_from == NULL || uctx_to == NULL) + return pth_error(FALSE, EINVAL); + if (!(uctx_to->uc_mctx_set)) + return pth_error(FALSE, EPERM); + + /* switch underlying machine context */ + uctx_from->uc_mctx_set = TRUE; + pth_mctx_switch(&uctx_from->uc_mctx, &uctx_to->uc_mctx); + + return TRUE; +} + +/* destroy user-space context structure */ +int +pth_uctx_destroy( + pth_uctx_t uctx) +{ + /* argument sanity checking */ + if (uctx == NULL) + return pth_error(FALSE, EINVAL); + + /* deallocate dynamically allocated stack */ + if (uctx->uc_stack_own && uctx->uc_stack_ptr != NULL) + free(uctx->uc_stack_ptr); + + /* deallocate context structure */ + free(uctx); + + return TRUE; +} + Index: ossp-pkg/pth/test_uctx.c RCS File: /v/ossp/cvs/ossp-pkg/pth/test_uctx.c,v co -q -kk -p'1.1' '/v/ossp/cvs/ossp-pkg/pth/test_uctx.c,v' | diff -u /dev/null - -L'ossp-pkg/pth/test_uctx.c' 2>/dev/null --- ossp-pkg/pth/test_uctx.c +++ - 2024-05-15 23:10:06.525041349 +0200 @@ -0,0 +1,151 @@ +/* +** 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 . +** +** test_uctx.c: Pth test program (user-space context switching) +*/ + /* ``Engineering does not require science. + Science helps a lot, but people built + perfectly good brick walls long before + they knew why cement works.'' + -- Alan Cox */ +#include +#include + +#include "pth.h" + +volatile pth_uctx_t uctx[10]; + +/* + * Test 1: master and worker "threads" + */ + +volatile int worker_done[10]; + +static void worker(void *ctx) +{ + volatile int n = (int)ctx; + volatile int i = 0; + + fprintf(stderr, "worker #%d: enter\n", n); + for (i = 0; i < 100; i++) { + fprintf(stderr, "worker #%d: working (step %d)\n", n, i); + pth_uctx_switch(uctx[n], uctx[0]); + } + worker_done[n] = TRUE; + fprintf(stderr, "worker #%d: exit\n", n); + return; +} + +static void test_working(void) +{ + volatile int i; + volatile int todo; + + fprintf(stderr, "master: startup\n"); + + fprintf(stderr, "master: create contexts\n"); + pth_uctx_create((pth_uctx_t *)&uctx[0]); + worker_done[0] = FALSE; + for (i = 1; i < 10; i++) { + worker_done[i] = FALSE; + pth_uctx_create((pth_uctx_t *)&uctx[i]); + pth_uctx_make(uctx[i], NULL, 32*1024, NULL, worker, (void *)i, uctx[0]); + } + + do { + todo = 0; + for (i = 1; i < 10; i++) { + if (!worker_done[i]) { + fprintf(stderr, "master: switching to worker #%d\n", i); + pth_uctx_switch(uctx[0], uctx[i]); + fprintf(stderr, "master: came back from worker #%d\n", i); + todo = 1; + } + } + } while (todo); + + fprintf(stderr, "master: destroy contexts\n"); + for (i = 1; i < 10; i++) + pth_uctx_destroy(uctx[i]); + pth_uctx_destroy(uctx[0]); + + fprintf(stderr, "master: exit\n"); + return; +} + +/* + * Test 2: raw switching performance + */ + +#define DO_SWITCHES 10000000 + +time_t stat_start; +time_t stat_end; +volatile int stat_switched; + +static void dummy(void *ctx) +{ + while (1) { + stat_switched++; + pth_uctx_switch(uctx[1], uctx[0]); + } + return; +} + +static void test_performance(void) +{ + volatile int i; + + pth_uctx_create((pth_uctx_t *)&uctx[0]); + pth_uctx_create((pth_uctx_t *)&uctx[1]); + pth_uctx_make(uctx[1], NULL, 32*1024, NULL, dummy, NULL, uctx[0]); + + fprintf(stderr, "\n"); + fprintf(stderr, "Performing %d user-space context switches... " + "be patient!\n", DO_SWITCHES); + + stat_start = time(NULL); + stat_switched = 0; + for (i = 0; i < DO_SWITCHES; i++) { + stat_switched++; + pth_uctx_switch(uctx[0], uctx[1]); + } + stat_end = time(NULL); + + pth_uctx_destroy(uctx[0]); + pth_uctx_destroy(uctx[1]); + + fprintf(stderr, "We required %d seconds for performing the test, " + "so this means we can\n", (int)(stat_end-stat_start)); + fprintf(stderr, "perform %d user-space context switches per second " + "on this platform.\n", DO_SWITCHES/(int)(stat_end-stat_start)); + fprintf(stderr, "\n"); + return; +} + +int main(int argc, char *argv[]) +{ + test_working(); + test_performance(); + return 0; +} +