/* * This file is part of Jehanne. * * Copyright (C) 2016-2019 Giacomo Tesio * * Jehanne 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, version 2 of the License. * * Jehanne 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 Jehanne. If not, see . */ #include #include static struct { QLp *p; QLp x[1024]; } ql = { ql.x }; /* The possible state transitions of a QLp are * * Free -> Queuing -> Timedout|Done -> Free * Free -> QueuingR -> Timedout|Done -> Free * Free -> QueuingW -> Timedout|Done -> Free * Free -> Sleeping -> Timedout -> Free * Free -> Sleeping -> Queuing -> Done -> Free * * QLp starts as Free and move to one of the custom states on getqlp. * Timedout is optional and alternative to Done. * Timedout can only occur if the QLp was allocated by a variant that * support timeouts (qlockt, rlockt, wlockt and rsleept). * Done is reached just before wakeup if and only if the current state * is the expected custom one (all transitions use CAS). * * All QLp need to reach the Free state to be released. * * All transitions are protected by a lock, except * - the ones that lead to Timedout, so that if a QLp is not in * the expected state, it's Timedout. * - the ones from Done to Free since Done is * a terminal state: Freeing is just book keeping. * * NOTE: The rsleep/rwakeup transitions mean that a you should never * use a qlockt a Qlock that is going to be used with a rsleept. */ enum { Free, Queuing, QueuingR, QueuingW, Sleeping, Timedout, Done, }; #if 0 typedef struct RendezvousLog { void *tag; long pid; long start; long end; void *addr; void *caller; void *r; } RendezvousLog; RendezvousLog logs[1024]; int logidx; static void* debugrendezvous(void *tag, void *val) { static void** pidp; int i; if(pidp == nil) pidp = jehanne_privalloc(); if(*pidp == 0) *pidp = (void*)(long)jehanne_getpid(); i = jehanne_ainc(&logidx) - 1; logs[i].tag = tag; logs[i].pid = (long)*pidp; logs[i].start = jehanne_nsec(); logs[i].addr = __builtin_return_address(0); logs[i].caller = __builtin_return_address(1); logs[i].r = (void*)0xffabcdefffabcdef; logs[i].r = sys_rendezvous(tag, (void*)logs[i].addr); logs[i].end = jehanne_nsec(); return logs[i].r; } void printdebugrendezvouslogs(void) { int i; for(i = 0; i < logidx; ++i) jehanne_print("[%d] %#p @ %#p sys_rendezvous(%#p, %#p) -> %#p @ %#p\n", logs[i].pid, logs[i].caller, logs[i].start, logs[i].tag, logs[i].addr, logs[i].r, logs[i].end); } static void* (*_rendezvousp)(void*, void*) = debugrendezvous; #else static void* __rendezvous(void* tag, void* val) { return sys_rendezvous(tag, val); } static void* (*_rendezvousp)(void*, void*) = __rendezvous; #endif # define RENDEZVOUS(...) (*_rendezvousp)(__VA_ARGS__) //# define RENDEZVOUS(...) sys_rendezvous(__VA_ARGS__) //# define RENDEZVOUS(tag, val) __rendezvous(tag, __builtin_return_address(0)) /* this gets called by the thread library ONLY to get us to use its rendezvous */ void jehanne__qlockinit(void* (*r)(void*, void*)) { _rendezvousp = r; } /* find a free shared memory location to queue ourselves in */ static QLp* getqlp(uint8_t use) { QLp *p, *op; op = ql.p; for(p = op+1; ; p++){ if(p == &ql.x[nelem(ql.x)]) p = ql.x; if(p == op) abort(); if(cas(&p->state, Free, use)){ ql.p = p; p->next = nil; break; } } return p; } void jehanne_qlock(QLock *q) { QLp *p, *mp; jehanne_lock(&q->lock); if(!q->locked){ q->locked = 1; jehanne_unlock(&q->lock); return; } /* chain into waiting list */ mp = getqlp(Queuing); p = q->tail; if(p == nil) q->head = mp; else p->next = mp; q->tail = mp; jehanne_unlock(&q->lock); /* wait */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) ; if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } } void jehanne_qunlock(QLock *q) { QLp *p, *tmp; jehanne_lock(&q->lock); if (q->locked == 0) jehanne_fprint(2, "qunlock called with qlock not held, from %#p\n", jehanne_getcallerpc()); p = q->head; while(p != nil && !cas(&p->state, Queuing, Done)){ /* the lock in p timed out */ if(p->state != Timedout){ jehanne_print("qunlock mp %#p: state %d (should be Timedout) pc %#p\n", p, p->state, (uintptr_t)__builtin_return_address(0)); abort(); } tmp = p->next; p->state = Free; p = tmp; } if(p != nil){ /* wakeup head waiting process */ if(p->state != Done){ jehanne_print("qunlock: p %#p p->state %d\n", p, p->state); abort(); } q->head = p->next; if(q->head == nil) q->tail = nil; jehanne_unlock(&q->lock); while(RENDEZVOUS(p, (void*)0x12345) == (void*)~0) ; return; } else { /* all pending locks timedout */ q->head = nil; q->tail = nil; } q->locked = 0; jehanne_unlock(&q->lock); } int jehanne_qlockt(QLock *q, uint32_t ms) { QLp *p, *mp; int64_t wkup; if(!jehanne_lockt(&q->lock, ms)) return 0; if(!q->locked){ q->locked = 1; jehanne_unlock(&q->lock); return 1; } /* chain into waiting list */ mp = getqlp(Queuing); p = q->tail; if(p == nil) q->head = mp; else p->next = mp; q->tail = mp; jehanne_unlock(&q->lock); /* set up awake to interrupt rendezvous */ wkup = sys_awake(ms); /* wait */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) if (jehanne_awakened(wkup)){ /* interrupted by awake */ if(cas(&mp->state, Queuing, Timedout)) /* if we can atomically mark the QLp * the next qunlock will release it... */ return 0; /* ... otherwise we are going to take the lock * on the next rendezvous from qunlock */ assert(mp->state == Done); } jehanne_forgivewkp(wkup); if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } return 1; } int jehanne_canqlock(QLock *q) { if(!jehanne_canlock(&q->lock)) return 0; if(!q->locked){ q->locked = 1; jehanne_unlock(&q->lock); return 1; } jehanne_unlock(&q->lock); return 0; } void jehanne_rlock(RWLock *q) { QLp *p, *mp; jehanne_lock(&q->lock); if(q->writer == 0 && q->head == nil){ /* no writer, go for it */ q->_readers++; jehanne_unlock(&q->lock); return; } mp = getqlp(QueuingR); p = q->tail; if(p == 0) q->head = mp; else p->next = mp; q->tail = mp; mp->next = nil; jehanne_unlock(&q->lock); /* wait in kernel */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) ; if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } } int jehanne_rlockt(RWLock *q, uint32_t ms) { QLp *p, *mp; int64_t wkup; if(!jehanne_lockt(&q->lock, ms)) return 0; if(q->writer == 0 && q->head == nil){ /* no writer, go for it */ q->_readers++; jehanne_unlock(&q->lock); return 1; } /* chain into waiting list */ mp = getqlp(QueuingR); p = q->tail; if(p == 0) q->head = mp; else p->next = mp; q->tail = mp; mp->next = nil; jehanne_unlock(&q->lock); /* set up awake to interrupt rendezvous */ wkup = sys_awake(ms); /* wait in kernel */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) if (jehanne_awakened(wkup)){ /* interrupted by awake */ if(cas(&mp->state, QueuingR, Timedout)) /* if we can atomically mark the QLp * a future wunlock will release it... */ return 0; /* ... otherwise we are going to take the lock * on the next rendezvous from wunlock */ assert(mp->state == Done); } jehanne_forgivewkp(wkup); if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } return 1; } int jehanne_canrlock(RWLock *q) { jehanne_lock(&q->lock); if (q->writer == 0 && q->head == nil) { /* no writer; go for it */ q->_readers++; jehanne_unlock(&q->lock); return 1; } jehanne_unlock(&q->lock); return 0; } void jehanne_runlock(RWLock *q) { QLp *p, *tmp; jehanne_lock(&q->lock); if(q->_readers <= 0) abort(); p = q->head; if(--(q->_readers) > 0 || p == nil){ runlockWithoutWriters: jehanne_unlock(&q->lock); return; } /* start waiting writer */ while(p != nil && !cas(&p->state, QueuingW, Done)){ /* the lock in p timed out * * Note that p cannot have reached Done or Free already * since we hold q->lock, and the only transactions that * do not require this lock are timeout ones. */ if(p->state != Timedout) abort(); tmp = p->next; p->state = Free; p = tmp; } if(p == nil) goto runlockWithoutWriters; q->head = p->next; if(q->head == 0) q->tail = 0; q->writer = 1; jehanne_unlock(&q->lock); /* wakeup waiter */ while(RENDEZVOUS(p, 0) == (void*)~0) ; } void jehanne_wlock(RWLock *q) { QLp *p, *mp; jehanne_lock(&q->lock); if(q->_readers == 0 && q->writer == 0){ /* noone waiting, go for it */ q->writer = 1; jehanne_unlock(&q->lock); return; } /* chain into waiting list */ p = q->tail; mp = getqlp(QueuingW); if(p == nil) q->head = mp; else p->next = mp; q->tail = mp; mp->next = nil; jehanne_unlock(&q->lock); /* wait in kernel */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) ; if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } } int jehanne_wlockt(RWLock *q, uint32_t ms) { QLp *p, *mp; int64_t wkup; if(!jehanne_lockt(&q->lock, ms)) return 0; if(q->_readers == 0 && q->writer == 0){ /* noone waiting, go for it */ q->writer = 1; jehanne_unlock(&q->lock); return 1; } /* chain into waiting list */ p = q->tail; mp = getqlp(QueuingW); if(p == nil) q->head = mp; else p->next = mp; q->tail = mp; mp->next = nil; jehanne_unlock(&q->lock); /* set up awake to interrupt rendezvous */ wkup = sys_awake(ms); /* wait in kernel */ while(RENDEZVOUS(mp, (void*)1) == (void*)~0) if (jehanne_awakened(wkup)){ /* interrupted by awake */ if(cas(&mp->state, QueuingW, Timedout)) /* if we can atomically mark the QLp * a future runlock/wunlock will release it... */ return 0; /* ... otherwise we are going to take the lock * on the next rendezvous from runlock/wunlock */ assert(mp->state == Done); } jehanne_forgivewkp(wkup); if(!cas(&mp->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", mp, mp->state, __builtin_return_address(0)); abort(); } return 1; } int jehanne_canwlock(RWLock *q) { jehanne_lock(&q->lock); if (q->_readers == 0 && q->writer == 0) { /* no one waiting; go for it */ q->writer = 1; jehanne_unlock(&q->lock); return 1; } jehanne_unlock(&q->lock); return 0; } void jehanne_wunlock(RWLock *q) { QLp *p, *tmp; jehanne_lock(&q->lock); if(q->writer == 0) abort(); p = q->head; if(p == nil){ q->writer = 0; jehanne_unlock(&q->lock); return; } while(p != nil){ wakeupPendingWriter: /* when we jump here, we know p is not nil */ switch(casv(&p->state, QueuingW, Done)){ case QueuingW: /* start waiting writer */ q->head = p->next; if(q->head == nil) q->tail = nil; jehanne_unlock(&q->lock); while(RENDEZVOUS(p, 0) == (void*)~0) ; return; case Timedout: /* R and W have the same fate, once Timedout */ tmp = p->next; p->state = Free; p = tmp; break; case QueuingR: /* wakeup pending readers */ goto wakeupPendingReaders; default: jehanne_print("wunlock: %#p has state %d instead of QueuingW\n", p, p->state); abort(); break; } } /* wake waiting readers */ while(p != nil){ wakeupPendingReaders: /* when we jump here, we know p is not nil */ switch(casv(&p->state, QueuingR, Done)){ case QueuingR: q->_readers++; tmp = p->next; jehanne_unlock(&q->lock); while(RENDEZVOUS(p, 0) == (void*)~0) ; /* after the rendezvous, p->state will be set to Free * from the reader we are going to wakeup, thus p could * be reused before we are scheduled again: we need tmp * to keep track of the next QLp to test */ jehanne_lock(&q->lock); p = tmp; break; case Timedout: /* R and W have the same fate, once Timedout */ tmp = p->next; p->state = Free; p = tmp; break; case QueuingW: if(q->_readers > 0){ goto allPendingReadersStarted; } else { /* all readers timedout: wakeup the pending writer */ goto wakeupPendingWriter; } default: jehanne_print("wunlock: %#p has state %d instead of QueuingR\n", p, p->state); abort(); break; } } allPendingReadersStarted: q->head = p; if(q->head == nil) q->tail = nil; q->writer = 0; jehanne_unlock(&q->lock); } void jehanne_rsleep(Rendez *r) { QLp *t, *me, *tmp; if(!r->l) abort(); jehanne_lock(&r->l->lock); /* we should hold the qlock */ if(!r->l->locked) abort(); /* add ourselves to the wait list */ me = getqlp(Sleeping); if(r->head == nil) r->head = me; else r->tail->next = me; me->next = nil; r->tail = me; /* pass the qlock to the next guy */ t = r->l->head; while(t != nil && !cas(&t->state, Queuing, Done)){ /* the lock in t timed out */ if(t->state != Timedout){ jehanne_print("rsleep mp %#p: state %d (should be Timedout) pc %#p\n", t, t->state, __builtin_return_address(0)); abort(); } tmp = t->next; t->state = Free; t = tmp; } if(t != nil){ r->l->head = t->next; if(r->l->head == nil) r->l->tail = nil; jehanne_unlock(&r->l->lock); while(RENDEZVOUS(t, (void*)0x12345) == (void*)~0) ; }else{ r->l->head = nil; r->l->tail = nil; r->l->locked = 0; jehanne_unlock(&r->l->lock); } /* wait for a wakeup */ while(RENDEZVOUS(me, (void*)1) == (void*)~0) ; if(!cas(&me->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", me, me->state, __builtin_return_address(0)); abort(); } } int jehanne_rsleept(Rendez *r, uint32_t ms) { QLp *t, *me, *tmp; int64_t wkup; if(!r->l) abort(); if(!jehanne_lockt(&r->l->lock, ms)) return 0; /* we should hold the qlock */ if(!r->l->locked) abort(); /* add ourselves to the wait list */ me = getqlp(Sleeping); if(r->head == nil) r->head = me; else r->tail->next = me; me->next = nil; r->tail = me; /* pass the qlock to the next guy */ t = r->l->head; while(t != nil && !cas(&t->state, Queuing, Done)){ /* the lock in t timed out */ if(t->state != Timedout){ jehanne_print("rsleept mp %#p: state %d (should be Timedout) pc %#p\n", t, t->state, __builtin_return_address(0)); abort(); } tmp = t->next; t->state = Free; t = tmp; } if(t != nil){ r->l->head = t->next; if(r->l->head == nil) r->l->tail = nil; jehanne_unlock(&r->l->lock); while(RENDEZVOUS(t, (void*)0x12345) == (void*)~0) ; }else{ r->l->head = nil; r->l->tail = nil; r->l->locked = 0; jehanne_unlock(&r->l->lock); } /* set up awake to interrupt rendezvous */ wkup = sys_awake(ms); /* wait for a rwakeup (or a timeout) */ while(RENDEZVOUS(me, (void*)1) == (void*)~0) if (jehanne_awakened(wkup)){ if(cas(&me->state, Sleeping, Timedout)){ /* if we can atomically mark the QLp * a future rwakeup will release it... */ jehanne_qlock(r->l); return 0; } /* ... otherwise we are going to take the lock * on the next rendezvous from rwakeup */ assert(me->state == Done); } jehanne_forgivewkp(wkup); if(!cas(&me->state, Done, Free)){ jehanne_print("mp %#p: state %d pc %#p\n", me, me->state, __builtin_return_address(0)); abort(); } return 1; } int jehanne_rwakeup(Rendez *r) { QLp *t, *tmp; /* * take off wait and put on front of queue * put on front so guys that have been waiting will not get starved */ if(!r->l) abort(); jehanne_lock(&r->l->lock); if(!r->l->locked) abort(); t = r->head; if(t != nil && t->state == Free) abort(); while(t != nil && !cas(&t->state, Sleeping, Queuing)){ if(t->state != Timedout){ jehanne_print("rwakeup mp %#p: state %d (should be Timedout) pc %#p\n", t, t->state, __builtin_return_address(0)); abort(); } tmp = t->next; t->state = Free; t = tmp; } if(t == nil){ r->head = nil; r->tail = nil; jehanne_unlock(&r->l->lock); return 0; } r->head = t->next; if(r->head == nil) r->tail = nil; t->next = r->l->head; r->l->head = t; if(r->l->tail == nil) r->l->tail = t; jehanne_unlock(&r->l->lock); return 1; } int jehanne_rwakeupall(Rendez *r) { int i; for(i=0; jehanne_rwakeup(r); i++) ; return i; }