jehanne/sys/src/kern/port/awake.c

288 lines
5.5 KiB
C

/*
* This file is part of Jehanne.
*
* Copyright (C) 2015-2016 Giacomo Tesio <giacomo@tesio.it>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
typedef struct PendingWakeup PendingWakeup;
struct PendingWakeup
{
uint64_t time;
Proc *p;
PendingWakeup *next;
};
/* alarms: linked list, sorted by wakeup time, protected by qlock(&l)
* wakeupafter inserts new items, forgivewakeup remove them,
* awakekproc consume the expired ones and clearwakeups remove those
* survived to their process.
*/
static PendingWakeup *alarms;
static QLock l;
static Rendez awaker;
/*
* Actually wakeup a process
*/
static void
wakeupProc(Proc *p)
{
Mpl pl;
Rendez *r;
Proc *d, **l;
/* this loop is to avoid lock ordering problems. */
for(;;){
pl = splhi();
lock(&p->rlock);
r = p->r;
/* waiting for a wakeup? */
if(r == nil)
break; /* no */
/* try for the second lock */
if(canlock(&r->l)){
if(p->state != Wakeme || r->p != p)
panic("wakeupProc: state %d %d %d", r->p != p, p->r != r, p->state);
p->r = nil;
r->p = nil;
ready(p);
unlock(&r->l);
break;
}
/* give other process time to get out of critical section and try again */
unlock(&p->rlock);
splx(pl);
sched();
}
unlock(&p->rlock);
splx(pl);
if(p->state != Rendezvous)
return;
/* Try and pull out of a rendezvous */
lock(&p->rgrp->l);
if(p->state == Rendezvous) {
p->rendval = ~0;
l = &REND(p->rgrp, p->rendtag);
for(d = *l; d; d = d->rendhash) {
if(d == p) {
*l = p->rendhash;
break;
}
l = &d->rendhash;
}
ready(p);
}
unlock(&p->rgrp->l);
}
void
awakekproc(void* v)
{
Proc *p;
PendingWakeup *toAwake, *tail;
uint64_t now;
for(;;){
now = sys->ticks;
toAwake = nil;
/* search for processes to wakeup */
qlock(&l);
tail = alarms;
while(tail && tail->time <= now){
if(tail->p->pendingWakeup > tail->p->lastWakeup && tail->p->state >= Ready)
break;
toAwake = tail;
--toAwake->p->wakeups;
toAwake->p->pendingWakeup = toAwake->time;
tail = tail->next;
}
if(toAwake != nil){
toAwake->next = nil;
toAwake = alarms;
alarms = tail;
}
qunlock(&l);
/* wakeup sleeping processes */
while(toAwake != nil){
p = toAwake->p;
if(p->lastWakeup < toAwake->time && p->state > Ready) {
/* debugged processes will miss wakeups,
* but the alternatives seem even worse
*/
if(canqlock(&p->debug)){
if(!waserror()){
wakeupProc(p);
poperror();
}
qunlock(&p->debug);
}
}
tail = toAwake->next;
free(toAwake);
toAwake = tail;
}
sleep(&awaker, return0, 0);
}
}
/*
* called on pexit
*/
void
clearwakeups(Proc *p)
{
PendingWakeup *w, **next, *freelist, **fend;
freelist = nil;
/* find all PendingWakeup* to free and remove them from alarms */
qlock(&l);
if(p->wakeups){
/* note: qlock(&l) && p->wakeups > 0 => alarms != nil */
next = &alarms;
fend = &freelist;
clearnext:
w = *next;
while (w != nil && w->p == p) {
/* found one to remove */
*fend = w; /* append to freelist */
*next = w->next; /* remove from alarms */
fend = &w->next; /* move fend to end of freelist */
*fend = nil; /* clean the end of freelist */
--p->wakeups;
w = *next; /* next to analyze */
}
/* while exited => w == nil || w->p != p */
if(p->wakeups){
/* note: p->wakeups > 0 => w != nil
* p->wakeups > 0 && w->p != p => w->next != nil
*/
next = &w->next;
goto clearnext;
}
}
qunlock(&l);
/* free the found PendingWakeup* (out of the lock) */
w = freelist;
while(w != nil) {
freelist = w->next;
free(w);
w = freelist;
}
}
/*
* called every clock tick
*/
void
checkwakeups(void)
{
PendingWakeup *p;
uint64_t now;
p = alarms;
now = sys->ticks;
if(p && p->time <= now)
wakeup(&awaker);
}
static int64_t
wakeupafter(int64_t ms)
{
PendingWakeup *w, *new, **last;
int64_t when;
when = ms2tk(ms) + sys->ticks + 2; /* +2 against round errors and cpu's clocks misalignment */
new = mallocz(sizeof(PendingWakeup), 1);
if(new == nil)
return 0;
new->p = up;
new->time = when;
qlock(&l);
last = &alarms;
for(w = *last; w != nil && w->time <= when; w = w->next) {
last = &w->next;
}
new->next = w;
*last = new;
++up->wakeups;
qunlock(&l);
return -when;
}
static int64_t
forgivewakeup(int64_t time)
{
PendingWakeup *w, **last;
if(up->lastWakeup >= time || up->pendingWakeup >= time)
return 0;
qlock(&l);
if(alarms == nil || up->wakeups == 0){
qunlock(&l);
return 0; // nothing to do
}
last = &alarms;
for(w = *last; w != nil && w->time < time; w = w->next) {
last = &w->next;
}
while(w != nil && w->time == time && w->p != up){
last = &w->next;
w = w->next;
}
if(w == nil || w->time > time || w->p != up){
/* wakeup not found */
qunlock(&l);
return 0;
}
*last = w->next;
--up->wakeups;
qunlock(&l);
free(w);
return -time;
}
int64_t
procawake(int64_t ms)
{
if(ms == 0)
return -up->lastWakeup; // nothing to do
if(ms < 0)
return forgivewakeup(-ms);
return wakeupafter(ms);
}