New version of the optimistic semaphores. The process gets/puts a ticket in the semaphore without going to kernel space when possible. It goes to kernel space when a down() has to block, or an up() has to awake another process. This version also includes the alt operation for semaphores. The userspace part of the semaphore is not an int, now it is a struct (Sem), protected by a "pure" spin lock (no backoff). The critical region is as small as possible. The kernel space part of the semaphore holds the spinlock to update the struct. It does that carefully (a process can't loop forever in kernel space, if the lock is corrupted, it would get a suicide note). The man page has been updated. The tube library has been also updated. Reference: /n/patches.lsub.org/patch/sems Date: Wed Jun 6 20:40:34 CES 2012 Signed-off-by: esoriano@lsub.org --- /sys/src/nix/port/lib.h Thu Apr 26 11:22:24 2012 +++ /sys/src/nix/port/lib.h Wed Jun 6 10:12:28 2012 @@ -290,3 +290,26 @@ extern char etext[]; extern char edata[]; extern char end[]; + +/* + * Nix optimistic semaphores + */ + +/* + * Userspace spin lock (libc's Lock). + */ +typedef struct Ulock Ulock; + +struct Ulock{ + int val; +}; + +typedef struct Sem Sem; + +struct Sem +{ + int tickets; + int waiting; /* procs that may be waiting in the sem */ + int going; /* procs calling down, transiting to the kernel */ + Ulock userlock; /* userspace spin lock */ +}; --- /sys/src/nix/port/portdat.h Thu Apr 12 12:26:29 2012 +++ /sys/src/nix/port/portdat.h Wed Jun 6 19:09:41 2012 @@ -46,9 +46,9 @@ typedef struct Sched Sched; typedef struct Schedq Schedq; typedef struct Segment Segment; -typedef struct Sem Sem; +typedef struct Ksem Ksem; typedef struct Sema Sema; -typedef struct Sems Sems; +typedef struct Ksems Ksems; typedef struct Timer Timer; typedef struct Timers Timers; typedef struct Uart Uart; @@ -461,21 +461,27 @@ Sema* prev; }; +enum{ + Semok, + Semdead, +}; + /* NIX semaphores */ -struct Sem +struct Ksem { Lock; - int* np; /* user-level semaphore */ - Proc** q; + Sem * sem; /* user-level semaphore */ + int state; + Proc **q; int nq; - Sem* next; /* in list of semaphores for this Segment */ + RWlock *semaltlock; /* located in the Segment */ + Ksem *next; /* in list of semaphores for this Segment */ }; /* NIX semaphores */ -struct Sems +struct Ksems { - Sem** s; - int ns; + Ksem** s; }; /* Zero copy per-segment information (locked using Segment.lk) */ @@ -513,7 +519,8 @@ Pte *ssegmap[SSEGMAPSIZE]; Lock semalock; Sema sema; - Sems sems; + Ksems sems; + RWlock semaltlock; /* For nix sems, alt */ Zseg zseg; }; @@ -698,6 +705,7 @@ Waitrelease, Exotic, /* NIX */ Semdown, + Semalt, Proc_stopme = 1, /* devproc requests */ Proc_exitme, @@ -896,7 +904,7 @@ /* NIX */ Mach *ac; Page *acpml4; - Sem *waitsem; + Ksem *waitsem; int prepagemem; Nixpctl *nixpctl; /* NIX queue based system calls */ @@ -912,6 +920,7 @@ int nqtrap; /* # of traps in last quantum */ int nqsyscall; /* # of syscalls in the last quantum */ int nfullq; + int semawaken; /* nix sems */ /* * machine specific fpu, mmu and notify @@ -1146,8 +1155,6 @@ int n; vlong offset; }; - - #define DEVDOTDOT -1 --- /sys/src/nix/port/portfns.h Sun Apr 15 19:46:44 2012 +++ /sys/src/nix/port/portfns.h Wed Jun 6 10:17:46 2012 @@ -331,7 +331,7 @@ long seconds(void); Segment* seg(Proc*, uintptr, int); void segclock(uintptr); -Sem* segmksem(Segment*, int*); +Ksem* segmksem(Segment*, Sem*); void segpage(Segment*, Page*); uintmem segppn(Segment*, uintmem); int semadec(int*); --- /sys/src/nix/port/segment.c Thu Apr 12 12:26:29 2012 +++ /sys/src/nix/port/segment.c Wed Jun 6 10:18:40 2012 @@ -69,26 +69,50 @@ #define NHASH 101 #define SHASH(np) (PTR2UINT(np)%NHASH) -Sem* -segmksem(Segment *sg, int *np) +Ksem* +segmksem(Segment *sg, Sem *us) { - Sem *s, **l; + Ksem *s, **l; qlock(&sg->lk); if(sg->sems.s == nil) - sg->sems.s = mallocz(NHASH * sizeof(Sem*), 1); - for(l = &sg->sems.s[SHASH(np)]; (s = *l) != nil; l = &s->next) - if(s->np == np){ + sg->sems.s = mallocz(NHASH * sizeof(Ksem*), 1); + for(l = &sg->sems.s[SHASH(us)]; (s = *l) != nil; l = &s->next) + if(s->sem == us){ qunlock(&sg->lk); return s; } s = mallocz(sizeof *s, 1); - s->np = np; + s->sem = us; + s->state = Semok; + s->semaltlock = &sg->semaltlock; *l = s; qunlock(&sg->lk); return s; } +static void +freesems(Segment *sg) +{ + int i; + Ksem *s, *aux; + + if(sg->sems.s == nil) + return; + + for(i=0; isems.s[i]; + while(s != nil){ + aux = s; + s = s->next; + free(aux->q); + free(aux); + } + } + free(sg->sems.s); + sg->sems.s = nil; +} + void putseg(Segment *s) { @@ -132,7 +156,7 @@ if(s->profile != 0) free(s->profile); if(s->sems.s != nil) - free(s->sems.s); + freesems(s); if(s->type&SG_ZIO) freezseg(s); free(s); --- /sys/src/nix/port/syssem.c Sun Apr 15 19:46:44 2012 +++ /sys/src/nix/port/syssem.c Wed Jun 6 19:30:51 2012 @@ -5,222 +5,398 @@ #include "fns.h" #include "../port/error.h" -static int semtrytimes = 100; - - /* - * Support for user-level optimistic semaphores. - * - * A semaphore is an integer n. - * If n > 0, there are tickets - * If n == 0, there are no tickets - * If n < 0, there are |n| processes waiting for tickets - * or preparing to wait for tickets. + * Sems are represented by two structures, Sem (user space) and + * Ksem (kernel space). A Sem includes a spin lock (userlock) to + * protect its three counters: * - * CAUTION: Do not use adec/ainc for semaphores, they would - * trap if the integer is negative, but that's ok for semaphores. + * tickets: natural, number of tokens in the semaphore. + * going: natural, number of processes that are in transit + * from the libc's dowsem to the kernel's semsleep. + * waiting: natural, estimation of the number of processes sleeping + * in the ksem, always >= than the real number of processes + * in the ksem's queue (Ksem.nq). */ +enum { + Semtrytimes = 100, + Locktrytimes = 10, + Maxaltsems = 300, +}; + +/* + * If the userland spinlock is corrupted, + */ +static void +_userlock(Ksem *s) +{ + int n; + int try; + n = 0; + + + try = Locktrytimes; + while(tas32(&s->sem->userlock.val)){ + if(s->state == Semdead) + error(Esemdead); + if(++n % 100000 == 0){ + iprint("syssem: warning, userlock busy\n"); + if(--try == 0){ + iprint("syssem: the userlock of ksem %p is dead\n", s); + s->state = Semdead; + error(Esemtimeout); + } + } + } + return; +} static void -semwakeup(Sem *s, int didwake, int dolock) +_userunlock(Ksem *s) +{ + s->sem->userlock.val = 0; +} + +/* + * called with Ksem's lock held + */ +static Proc* +semdequeue(Ksem *s) { Proc *p; - DBG("semwakeup up %#p sem %#p\n", up, s->np); - if(dolock) - lock(s); + assert(s->nq >= 0); + if(s->nq == 0) + return nil; + p = s->q[0]; + s->nq--; + if(s->nq < 0) + panic("semdequeue"); + if(s->nq != 0) + memmove(s->q, s->q + 1, s->nq * sizeof s->q[0]); + return p; +} + +/* + * called with Ksem's lock held + */ +static int +semdequeueme(Ksem *s) +{ + int i; + + assert(s->nq >= 0); + if(s->nq == 0) + return -1; + for(i = 0; i < s->nq; i++) + if(s->q[i] == up) + break; + if(i == s->nq) + return -1; + s->nq--; + if(s->nq < 0) + panic("semdequeueme"); + if(s->nq == 0) /* the queue is empty now */ + return 0; + if(s->nq == i) /* it was the last element in the queue */ + return 0; + memmove(s->q + i, s->q + i + 1, (s->nq - i) * sizeof s->q[0]); + return 0; +} + +/* + * called with Ksem's lock held + */ +static void +semqueue(Ksem *s, Proc *p) +{ + assert(s->nq >= 0); + s->q = realloc(s->q, (s->nq+1) * sizeof s->q[0]); + if(s->q == nil) + panic("semqueue: no memory"); + + s->q[s->nq] = p; + s->nq++; +} + +static void +semwakeup(Ksem *s) +{ + Proc* p; + int retries = 0; + + DBG("semwakeup up %#p sem %#p\n", up, s->sem); +retry: + if(retries != 0 && retries % 10 == 0) + print("semwakeup: %d retries\n", retries); + + rlock(s->semaltlock); + lock(s); + + if(waserror()){ + runlock(s->semaltlock); + unlock(s); + pprint("suicide: semwakeup, userlock is dead\n"); + pexit("Suicide", 0); + } + /* - * If there are more processes sleeping than |*s->np| then - * there are ups not yet seen by sleepers, wake up those that - * have tickets. + * There can be awaken procs in the queue: + * procs performing an alt that have been already + * awaken in other sems but they have not been + * dequeued yet (by semalt). We can dequeue them. + * Note that the queue can also be empty. */ - while(s->nq > 0 && s->nq > - *s->np){ - p = s->q[0]; - s->nq--; - s->q[0] = s->q[s->nq]; - if(didwake){ - DBG("semwakeup up %#p waking up %#p\n", up, p); + while(s->nq > 0){ + p = semdequeue(s); + if(p == nil) + panic("semwakeup"); + _userlock(s); + s->sem->waiting--; + _userunlock(s); + if(tas32(&p->semawaken) == 0){ + poperror(); p->waitsem = s; - /* - * p can be up if it's being killed, in which - * case it might be consuming a ticket being - * put while dying. In that case, another - * process might wait because we would get the - * ticket. Up must call semwakeup to be sure - * that other process no longer sleeps. - * But we can't be put in the scheduler queue. - */ - if(p != up) - ready(p); + unlock(s); + runlock(s->semaltlock); + ready(p); + return; } } - if(dolock) + + /* + * Is there any proc to be awaken coming from user space? + * This can happen: an upsem reaches here before the + * preceding downsem reaches semsleep. + */ + _userlock(s); + if(s->sem->going > 0){ + _userunlock(s); + poperror(); unlock(s); + runlock(s->semaltlock); + retries++; + /* + * take a break? + */ + if(retries % 3 == 0) + sched(); + goto retry; + } + + /* + * The queue is empty and no one is coming: this upsem() + * was fooled by a semalt. + * It has to generate a real ticket to compensate. + */ + s->sem->tickets++; + _userunlock(s); + poperror(); + unlock(s); + runlock(s->semaltlock); } static void -semsleep(Sem *s, int dontblock) +semsleep(Ksem *s) { - DBG("semsleep up %#p sem %#p\n", up, s->np); - if(dontblock){ - /* - * User tried to down non-blocking, but someone else - * got the ticket between looking at n and adec(n). - * we have to safely undo our temporary down here. - * Adjust the value of the semaphore to reflect that we - * wanted a ticket for a while but no longer want one. - * Make sure that no other process is waiting because we - * made a temporary down. - */ - semainc(s->np); - semwakeup(s, 1, 1); - return; - } + DBG("semsleep up %#p sem %#p\n", up, s->sem); + + rlock(s->semaltlock); lock(s); - if(*s->np >= 0){ - /* - * A ticket came, either it came while calling the kernel, - * or it was a temporary sleep that didn't block. - * Either way, we are done. - */ + + if(waserror()){ unlock(s); - goto Done; + pprint("suicide: semsleep, userlock is dead\n"); + pexit("Suicide", 0); } - /* - * Commited to wait, we'll have to wait until - * some other process changes our state. - */ - s->q = realloc(s->q, (s->nq+1) * sizeof s->q[0]); - if(s->q == nil) - panic("semsleep: no memory"); - s->q[s->nq++] = up; + _userlock(s); + s->sem->waiting++; + s->sem->going--; + _userunlock(s); + poperror(); + up->semawaken = 0; up->waitsem = nil; + semqueue(s, up); up->state = Semdown; unlock(s); - DBG("semsleep up %#p blocked\n", up); + runlock(s->semaltlock); sched(); -Done: - DBG("semsleep up %#p awaken\n", up); + + lock(s); if(up->waitsem == nil){ /* - * nobody did awake us, we are probably being + * Nobody did awake us, we are probably being * killed; we no longer want a ticket. */ - lock(s); - semainc(s->np); /* we are no longer waiting; killed */ - semwakeup(s, 1, 0); + semdequeueme(s); + if(waserror()){ + unlock(s); + pprint("suicide: semsleep, userlock is dead\n"); + pexit("Suicide", 0); + } + _userlock(s); + s->sem->waiting--; + _userunlock(s); unlock(s); + poperror(); + error(Edownint); } + unlock(s); } void syssemsleep(Ar0*, va_list list) { - int *np; - int dontblock; - Sem *s; + Ksem *s; Segment *sg; + Sem *ns; /* - * void semsleep(int*); + * void semsleep(Sem*, int, int); */ - np = va_arg(list, int*); - np = validaddr(np, sizeof *np, 1); - evenaddr(PTR2UINT(np)); - dontblock = va_arg(list, int); - if((sg = seg(up, PTR2UINT(np), 0)) == nil) + ns = va_arg(list, Sem*); + ns = validaddr(ns, sizeof *ns, 1); + evenaddr(PTR2UINT(ns)); + if((sg = seg(up, PTR2UINT(ns), 0)) == nil) error(Ebadarg); - s = segmksem(sg, np); - semsleep(s, dontblock); + s = segmksem(sg, ns); + semsleep(s); } void syssemwakeup(Ar0*, va_list list) { - int *np; - Sem *s; + Ksem *s; Segment *sg; + Sem *ns; /* - * void semwakeup(int*); + * void semwakeup(Sem*, int); */ - np = va_arg(list, int*); - np = validaddr(np, sizeof *np, 1); - evenaddr(PTR2UINT(np)); - if((sg = seg(up, PTR2UINT(np), 0)) == nil) + ns = va_arg(list, Sem*); + ns = validaddr(ns, sizeof *ns, 1); + evenaddr(PTR2UINT(ns)); + if((sg = seg(up, PTR2UINT(ns), 0)) == nil) error(Ebadarg); - s = segmksem(sg, np); - semwakeup(s, 1, 1); + s = segmksem(sg, ns); + semwakeup(s); } -static void -semdequeue(Sem *s) -{ - int i; - - assert(s != nil); - lock(s); - for(i = 0; i < s->nq; i++) - if(s->q[i] == up) - break; - - if(i == s->nq){ - /* - * We didn't perform a down on s, yet we are no longer queued - * on it; it must be because someone gave us its - * ticket in the mean while. We must put it back. - */ - semainc(s->np); - semwakeup(s, 0, 0); - }else{ - s->nq--; - s->q[i] = s->q[s->nq]; - } - unlock(s); -} /* - * Alternative down of a Sem in ss[]. - * The logic is similar to multiple downs, see comments in semsleep(). + * Alt makes its best efford to get a token from any sem in the array. + * It ignores the dead sems and only crashes if all sems are dead. */ static int -semalt(Sem *ss[], int n) +semalt(Ksem *ss[], int n) { int i, j, r; - Sem *s; + Ksem *s; + RWlock *rwl; + int queued; + + if(n < 1) + error(Ebadarg); + + /* + * While searching an available sem, the proc + * should not be awaken in a previously processed sem. + * The segment's rwlock (semaltlock) prevents this. + */ + rwl = ss[0]->semaltlock; + wlock(rwl); + up->waitsem = nil; + up->semawaken = 0; + queued = 0; - DBG("semalt up %#p ss[0] %#p\n", up, ss[0]->np); - r = -1; for(i = 0; i < n; i++){ s = ss[i]; - n = semadec(s->np); - if(n >= 0){ - r = i; + /* + * if the sem is dead, ignore it an keep searching + */ + if(waserror()) + continue; + _userlock(s); + if(s->sem->tickets > 0){ + s->sem->tickets--; + _userunlock(s); + poperror(); + up->waitsem = s; + up->semawaken = 1; + i++; + wunlock(rwl); goto Done; } - lock(s); - s->q = realloc(s->q, (s->nq+1) * sizeof s->q[0]); - if(s->q == nil) - panic("semalt: not enough memory"); - s->q[s->nq++] = up; - unlock(s); + s->sem->waiting++; + _userunlock(s); + poperror(); + /* + * Note that other proc could call semdequeme from this + * fuction (see bellow), so holding the rwlock is not + * sufficient to protect the queue: we need to hold the + * sem's lock. + */ + lock(s); + semqueue(s, up); + unlock(s); + queued++; } - - DBG("semalt up %#p blocked\n", up); - up->state = Semdown; + if(queued == 0){ + pprint("suicide: semalt, all the sems are dead\n"); + wunlock(rwl); + pexit("Suicide", 0); + } + up->state = Semalt; + wunlock(rwl); sched(); - + wlock(rwl); + if(up->waitsem == nil){ + /* + * We are probably being killed. + */ + for(i = 0; i < n; i++){ + s = ss[i]; + lock(s); + if(semdequeueme(s) != 0) + panic("semalt: the proc is not in the queue"); + unlock(s); + if(! waserror()){ + _userlock(s); + s->sem->waiting--; + _userunlock(s); + poperror(); + } + } + wunlock(rwl); + error(Esemaltint); + } + wunlock(rwl); Done: DBG("semalt up %#p awaken\n", up); + r = -1; for(j = 0; j < i; j++){ - assert(ss[j] != nil); - if(ss[j] != up->waitsem) - semdequeue(ss[j]); - else + s = ss[j]; + if(s == up->waitsem) r = j; + else{ + /* + * Cancel the reservation for the sem. + * Note that the sem could be already + * dequeued by semwakeup. + */ + lock(s); + if(semdequeueme(s) == 0){ + if(! waserror()){ + _userlock(s); + s->sem->waiting--; + _userunlock(s); + poperror(); + } + } + unlock(s); + } } - if(r < 0) + if(r == -1) panic("semalt"); return r; } @@ -228,82 +404,112 @@ void syssemalt(Ar0 *ar0, va_list list) { - int **sl; - int i, *np, ns; + Sem **sl; + Sem *ns; + + int i, j, nums; Segment *sg; - Sem *ksl[16]; + Ksem *ksl[Maxaltsems]; /* - * void semalt(int*[], int); + * void semalt(Sem*[], int); */ ar0->i = -1; - sl = va_arg(list, int**); - ns = va_arg(list, int); - sl = validaddr(sl, ns * sizeof(int*), 1); - if(ns > nelem(ksl)) - panic("syssemalt: bug: too many semaphores in alt"); - for(i = 0; i < ns; i++){ - np = sl[i]; - np = validaddr(np, sizeof(int), 1); - evenaddr(PTR2UINT(np)); - if((sg = seg(up, PTR2UINT(np), 0)) == nil) + sl = va_arg(list, Sem**); + nums = va_arg(list, int); + sl = validaddr(sl, nums * sizeof *ns, 1); + if(nums > nelem(ksl)) + error(Etoomanysems); + for(i = 0; i < nums; i++){ + ns = sl[i]; + ns = validaddr(ns, sizeof(Sem), 1); + evenaddr(PTR2UINT(ns)); + /* + * Are there duplicated sems in the array? + */ + for(j = 0; j < nums; j++) + if(i != j && ns == sl[j]) + error(Ebadargalt); + + if((sg = seg(up, PTR2UINT(ns), 0)) == nil) error(Ebadarg); - ksl[i] = segmksem(sg, np); + ksl[i] = segmksem(sg, ns); } - ar0->i = semalt(ksl, ns); + ar0->i = semalt(ksl, nums); } /* - * These are the entry points from the C library, adapted - * for use within the kernel, so that kprocs may share sems - * with users. - * They must be run in a process context. - * Within the kernel, semaphores are used through their - * kernel Sem structures, and not directly by their int* value. - * Otherwise, we would have to look up each time they are used. + * Kernel version of the libc's upsem. + * It must be called in the context of a process */ - void -upsem(Sem *s) +upsem(Ksem *s) { - int n; - - n = semainc(s->np); - if(n <= 0) - semwakeup(s, 1, 1); + if(waserror()){ + pprint("suicide: upsem, userlock is dead\n"); + pexit("Suicide", 0); + } + _userlock(s); + if(s->sem->tickets == 0 && (s->sem->waiting > 0 || s->sem->going > 0)){ + _userunlock(s); + poperror(); + semwakeup(s); + return; + } + s->sem->tickets++; + _userunlock(s); + poperror(); + return; } +/* + * Kernel version of the libc's downsem. + * It must be called in the context of a process. + * Returns 0 if it is non-blocking and there are no tickets. + * Returns 1 if it got a ticket. + */ int -downsem(Sem *s, int dontblock) +downsem(Ksem *s, int block) { - int n, i; - - for(i = 0; *s->np <= 0 && i < semtrytimes; i++) - yield(); - if(*s->np <= 0 && dontblock) - return -1; - n = semadec(s->np); - if(n < 0) - semsleep(s, dontblock); - return 0; + if(waserror()){ + pprint("suicide: downsem, userlock is dead\n"); + pexit("Suicide", 0); + } + _userlock(s); + if(! block && s->sem->tickets == 0){ + _userunlock(s); + poperror(); + return 0; + } + if(s->sem->tickets == 0){ + s->sem->going++; + _userunlock(s); + poperror(); + semsleep(s); + return 1; + } + s->sem->tickets--; + _userunlock(s); + poperror(); + return 1; } int -altsems(Sem *ss[], int n) +altsems(Ksem *ss[], int n) { int i, w; /* busy wait */ - for(w = 0; w < semtrytimes; w++){ + for(w = 0; w < Semtrytimes; w++){ for(i = 0; i < n; i++) - if(*ss[i]->np > 0) + if(ss[i]->sem->tickets > 0) break; if(i < n) break; } for(i = 0; i < n; i++) - if(downsem(ss[i], 1) != -1) + if(downsem(ss[i], 0) == 1) return i; + return semalt(ss, n); } - --- /sys/src/nix/port/devproc.c Thu Apr 12 12:26:28 2012 +++ /sys/src/nix/port/devproc.c Wed Jun 6 10:19:31 2012 @@ -1485,6 +1485,7 @@ break; case Stopped: case Semdown: + case Semalt: p->procctl = Proc_exitme; postnote(p, 0, "sys: killed", NExit); ready(p); --- /sys/src/nix/port/proc.c Sun Apr 15 19:46:44 2012 +++ /sys/src/nix/port/proc.c Wed Jun 6 10:20:02 2012 @@ -1184,7 +1184,7 @@ splx(pl); if(p->state != Rendezvous){ - if(p->state == Semdown) + if(p->state == Semdown || p->state == Semalt) ready(p); return ret; } --- /sys/src/nix/port/error.h Thu Apr 12 12:26:28 2012 +++ /sys/src/nix/port/error.h Wed Jun 6 10:21:02 2012 @@ -50,3 +50,9 @@ extern char Ecmdargs[]; /* wrong #args in control message */ extern char Ebadip[]; /* bad ip address syntax */ extern char Edirseek[]; /* seek in directory */ +extern char Esemtimeout[]; /* timeout to hold the userspace lock */ +extern char Edownint[]; /* down interrupted */ +extern char Esemaltint[]; /* semalt interrupted */ +extern char Ebadargalt[]; /* duplicated sems in the list */ +extern char Etoomanysems[]; /* too many semaphores for alt */ +extern char Esemdead[]; /* dead sem */ --- /sys/include/libc.h Sun Apr 15 19:45:24 2012 +++ /sys/include/libc.h Wed Jun 6 10:22:30 2012 @@ -768,27 +768,34 @@ NIXAC, }; +typedef struct Sem Sem; +struct Sem +{ + int tickets; + int waiting; + int going; + Lock; +}; /* * NIX system calls and library functions. */ extern int execac(int, char*, char*[]); -extern int altsems(int *ss[], int n); -extern int downsem(int *s, int dontblock); -extern void semstats(void); -extern void upsem(int *s); -extern int semtrytimes; +extern void initsem(Sem *, int); +extern int altsems(Sem *[], int); +extern int downsem(Sem *, int); +extern void upsem(Sem *); +extern int semtrytimes; /* * Internal NIX system calls, used by library functions. */ -extern void semsleep(int*, int); -extern void semwakeup(int*); -extern int semalt(int*[], int); +extern int semsleep(Sem*); +extern void semwakeup(Sem*); +extern int semalt(Sem*[], int); extern void semstats(void); extern int semdebug; - /* * Performance counters --- /sys/src/libc/9sys/upsem.c Thu Apr 12 12:26:16 2012 +++ /sys/src/libc/9sys/upsem.c Wed Jun 6 19:31:06 2012 @@ -1,5 +1,5 @@ #include -#include +#include "libc.h" typedef struct Cnt Cnt; /* debug */ struct Cnt @@ -23,88 +23,112 @@ static Cnt c; +/* + * The genuine spin lock + */ static void -typesok(void) +_lock(Lock *lk) { - /* - * The C library uses long*, but the kernel is - * going to use int*; f*ck. - */ - assert(sizeof(int) == sizeof(long)); + while(_tas(&lk->val)) + ; + return; +} + +static void +_yield(void) +{ + int nixtype; + + getcoreno(&nixtype); + if(nixtype != NIXAC) + sleep(0); } void -upsem(int *s) +initsem(Sem *s, int val) +{ + memset(s, 0, sizeof(Sem)); + if(val > 0) + s->tickets = val; +} + +void +upsem(Sem *s) { - int n; assert(s != nil); - typesok(); - n = ainc(s); - dprint(2, "upsem: %#p = %d\n", s, n); - if(n <= 0){ + + _lock(s); + if(s->tickets == 0 && (s->waiting > 0 || s->going > 0)){ + unlock(s); ainc(&c.kup); semwakeup(s); - }else - ainc(&c.uup); + return; + } + s->tickets++; + unlock(s); + ainc(&c.uup); } int -downsem(int *s, int dontblock) +downsem(Sem *s, int block) { - int n; int i; - assert(s != nil); - typesok(); + /* busy wait */ - for(i = 0; *s <= 0 && i < semtrytimes; i++) - ; // sleep(0); + for(i = 0; s->tickets == 0 && i < semtrytimes; i++) + ; - if(*s <= 0 && dontblock) - return -1; - n = adec(s); - dprint(2, "downsem: %#p = %d\n", s, n); - if(n < 0){ + _lock(s); + if(! block && s->tickets == 0){ + unlock(s); + return 0; + } + if(s->tickets == 0){ + s->going++; + unlock(s); ainc(&c.kdown); - semsleep(s, dontblock); - if(dontblock == 0) - dprint(2, "downsem: %#p awaken\n", s); - }else - ainc(&c.udown); - return 0; + if(semsleep(s) < 0) + return -1; + return 1; + } + s->tickets--; + unlock(s); + ainc(&c.udown); + return 1; } + +/* + * No optimistic for now. + */ int -altsems(int *ss[], int n) +altsems(Sem *ss[], int n) { - int i, w, r; - - typesok(); - assert(ss != nil); - assert(n > 0); + int w, i, j; + i = 0; + if(ss == nil || n <= 0){ + werrstr("altsems: bad args"); + return -1; + } /* busy wait */ for(w = 0; w < semtrytimes; w++){ for(i = 0; i < n; i++) - if(ss[i] == nil) - sysfatal("altsems: nil sem"); - else if(*ss[i] > 0) + if(ss[i]->tickets > 0) break; if(i < n) break; } - - for(i = 0; i < n; i++) - if(downsem(ss[i], 1) != -1){ + for(j = 0; j < n; j++) + if(downsem(ss[(i+j)%n], 0) == 1){ ainc(&c.ualt); - return i; + return (i+j)%n; } - ainc(&c.kalt); - r = semalt(ss, n); - return r; + return semalt(ss, n); } void --- /sys/man/2/upsem Thu Apr 12 12:24:58 2012 +++ /sys/man/2/upsem Wed Jun 6 19:39:08 2012 @@ -1,19 +1,22 @@ .TH UPSEM 2 .SH NAME -upsem, downsem, altsems, semstats \- optimistic user level semaphores +upsem, downsem, altsems, initsem, semstats \- optimistic user level semaphores .SH SYNOPSIS .B #include .br .B #include .PP .B -void upsem(int *s); +void upsem(Sem *s); .PP .B -int downsem(int *s, int dontblock); +int downsem(Sem *s, int block); .PP .B -int altsems(inst *ss[], int n); +int altsems(Sem *ss[], int n); +.PP +.B +void initsem(Sem *s, int tickets); .PP .B void semstats(void) @@ -28,22 +31,32 @@ kernel when they can proceed, and call the kernel only when it is really necessary (e.g., to block or to unblock another process). .PP -A semaphore is an integer kept at memory, shared among synchronizing processes. -Initialization is done by the user program using either zero or a positive number. After that, -only these functions should be used to operate on it. +A semaphore is a struct shared among synchronizing processes. +Initialization is done by the user program by calling +.I initsem. +The parameter +.I tickets +must be a natural number. It +sets the initial state of the semaphore. +After the initialization, only the +following functions should be used to operate on the semaphore. .PP .I Downsem tries to acquire one unit from the semaphore. If it can proceed, the call works without calling the kernel. When it cannot proceed, the global .I semtrytimes controls for how long (how many times) the function will try to acquire without entering the -kernel. If this fails, the kernel is entered to block the process until it can be acquired. +kernel, doing a busy wait. +If this fails and block is set, the kernel is +entered to block the process until a ticket can be acquired. +If block is not set, the process does not enter the kernel and the function returns 0. +When a ticket is acquired, the function returns 1. +If the system call fails, it returns a +negative value. .PP .I Upsem -releases one unit into the semaphore. If a process is waiting, it will take it and proceed. -Otherwise, the unit is kept in the semaphore for further -.I downsem -calls. The call does not enter the kernel unless a process must be awaken. +releases one ticket. +The call does not enter the kernel unless a process must be awaken. .PP .I Altsems tries to perform a @@ -52,14 +65,14 @@ .I ss (there are .I n -entries in that array). If no semaphore can be acquired, and the operation has been -attempted for +entries in that array). After a busy wait determined by .IR semtrytimes , -the kernel is entered and the process blocks until it can proceed. Otherwise, the +if no semaphore can be acquired, +the kernel is entered and the process blocks +until it can proceed. Otherwise, the operation is performed without calling the kernel. -.PP -The semaphore's value is negative when there are processes waiting for it, -and zero or positive otherwise. +The function returns the semaphore that has been acquired. +If the operation fails, it returns a negative value. .PP .I Semstats prints several statistics for debugging, and may be useful to learn if the @@ -80,3 +93,9 @@ .SH DIAGNOSTICS These functions set .IR errstr . +If the semaphore's internal lock is corrupted (note that this +is indistinguishable from being extremely busy) the process +can get a suicide note. +.SH BUGS +.I Semalt +only can be used with semaphores located in the same shared segment. --- /sys/src/libtube/tube.c Thu Apr 12 12:26:26 2012 +++ /sys/src/libtube/tube.c Wed Jun 6 10:24:23 2012 @@ -33,7 +33,8 @@ t = mallocz(sizeof *t + (msz+1) * n, 1); t->msz = msz; t->tsz = n; - t->nhole = n; + initsem(&t->nhole, n); + initsem(&t->nmsg, 0); return t; } @@ -51,7 +52,7 @@ uchar *c; assert(t != nil && p != nil); - if(nb != Already && downsem(&t->nhole, nb) < 0) + if(nb != Already && downsem(&t->nhole, !nb) < 0) return -1; n = ainc(&t->tl) - 1; n %= t->tsz; @@ -71,7 +72,7 @@ uchar *c; assert(t != nil && p != nil); - if(nb != Already && downsem(&t->nmsg, nb) < 0) + if(nb != Already && downsem(&t->nmsg, !nb) < 0) return -1; n = ainc(&t->hd) - 1; n %= t->tsz; @@ -114,10 +115,10 @@ talt(Talt a[], int na) { int i, n; - int **ss; + Sem **ss; assert(a != nil && na > 0); - ss = malloc(sizeof(int*) * na); + ss = malloc(sizeof(Sem*) * na); n = 0; for(i = 0; i < na; i++) switch(a[i].op){ --- /sys/src/libtube/namedtube.c Thu Apr 12 12:26:26 2012 +++ /sys/src/libtube/namedtube.c Wed Jun 6 10:24:30 2012 @@ -128,7 +128,8 @@ dir->end = p; nt->t->msz = elsz; nt->t->tsz = n; - nt->t->nhole = n; + initsem(&nt->t->nhole, n); + initsem(&nt->t->nmsg, 0); nt->next = dir->t; dir->t = nt; } --- /sys/include/tube.h Thu Apr 12 12:24:41 2012 +++ /sys/include/tube.h Wed Jun 6 10:24:34 2012 @@ -20,8 +20,8 @@ { int msz; /* message size */ int tsz; /* tube size (# of messages) */ - int nmsg; /* semaphore: # of messages in tube */ - int nhole; /* semaphore: # of free slots in tube */ + Sem nmsg; /* semaphore: # of messages in tube */ + Sem nhole; /* semaphore: # of free slots in tube */ int hd; int tl; };