plan9fox/sys/src/9/port/edf.c
cinap_lenrek 0c1110ace2 kernel: fix twakeup()/timerdel() race condition
timerdel() did not make sure that the timer function
is not active (on another cpu). just acquiering the
Timer lock in the timer function only blocks the caller
of timerdel()/timeradd() but not the other way arround
(on a multiprocessor).

this changes the timer code to track activity of
the timer function, having timerdel() wait until
the timer has finished executing.
2017-03-29 00:30:53 +02:00

680 lines
13 KiB
C

/* EDF scheduling */
#include <u.h>
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "../port/edf.h"
#include <trace.h>
/* debugging */
enum {
Dontprint = 1,
};
#define DPRINT if(Dontprint){}else print
static long now; /* Low order 32 bits of time in µs */
extern ulong delayedscheds;
extern Schedq runq[Nrq];
extern int nrdy;
extern ulong runvec;
/* Statistics stuff */
ulong nilcount;
ulong scheds;
ulong edfnrun;
int misseddeadlines;
/* Edfschedlock protects modification of admission params */
int edfinited;
QLock edfschedlock;
static Lock thelock;
enum{
Dl, /* Invariant for schedulability test: Dl < Rl */
Rl,
};
static char *testschedulability(Proc*);
static Proc *qschedulability;
enum {
Onemicrosecond = 1,
Onemillisecond = 1000,
Onesecond = 1000000,
OneRound = Onemillisecond/2,
};
static int
timeconv(Fmt *f)
{
char buf[128], *sign;
vlong t;
buf[0] = 0;
switch(f->r) {
case 'U':
t = va_arg(f->args, uvlong);
break;
case 't': /* vlong in nanoseconds */
t = va_arg(f->args, long);
break;
default:
return fmtstrcpy(f, "(timeconv)");
}
if (t < 0) {
sign = "-";
t = -t;
}
else
sign = "";
if (t > Onesecond){
t += OneRound;
sprint(buf, "%s%d.%.3ds", sign, (int)(t / Onesecond),
(int)(t % Onesecond)/Onemillisecond);
}else if (t > Onemillisecond)
sprint(buf, "%s%d.%.3dms", sign, (int)(t / Onemillisecond),
(int)(t % Onemillisecond));
else
sprint(buf, "%s%dµs", sign, (int)t);
return fmtstrcpy(f, buf);
}
long edfcycles;
Edf*
edflock(Proc *p)
{
Edf *e;
if (p->edf == nil)
return nil;
ilock(&thelock);
if((e = p->edf) && (e->flags & Admitted)){
thelock.pc = getcallerpc(&p);
#ifdef EDFCYCLES
edfcycles -= lcycles();
#endif
now = µs();
return e;
}
iunlock(&thelock);
return nil;
}
void
edfunlock(void)
{
#ifdef EDFCYCLES
edfcycles += lcycles();
#endif
edfnrun++;
iunlock(&thelock);
}
void
edfinit(Proc*p)
{
if(!edfinited){
fmtinstall('t', timeconv);
edfinited++;
}
now = µs();
DPRINT("%lud edfinit %lud[%s]\n", now, p->pid, statename[p->state]);
p->edf = malloc(sizeof(Edf));
if(p->edf == nil)
error(Enomem);
return;
}
static void
deadlineintr(Ureg*, Timer *t)
{
/* Proc reached deadline */
extern int panicking;
Proc *p;
void (*pt)(Proc*, int, vlong);
if(panicking || active.exiting)
return;
p = t->ta;
now = µs();
DPRINT("%lud deadlineintr %lud[%s]\n", now, p->pid, statename[p->state]);
/* If we're interrupting something other than the proc pointed to by t->a,
* we've already achieved recheduling, so we need not do anything
* Otherwise, we must cause a reschedule, but if we call sched()
* here directly, the timer interrupt routine will not finish its business
* Instead, we cause the resched to happen when the interrupted proc
* returns to user space
*/
if(p == up){
if(up->trace && (pt = proctrace))
pt(up, SInts, 0);
up->delaysched++;
delayedscheds++;
}
}
static void
release(Proc *p)
{
/* Called with edflock held */
Edf *e;
void (*pt)(Proc*, int, vlong);
long n;
vlong nowns;
e = p->edf;
e->flags &= ~Yield;
if(e->d - now < 0){
e->periods++;
e->r = now;
if((e->flags & Sporadic) == 0){
/*
* Non sporadic processes stay true to their period;
* calculate next release time.
* Second test limits duration of while loop.
*/
if((n = now - e->t) > 0){
if(n < e->T)
e->t += e->T;
else
e->t = now + e->T - (n % e->T);
}
}else{
/* Sporadic processes may not be released earlier than
* one period after this release
*/
e->t = e->r + e->T;
}
e->d = e->r + e->D;
e->S = e->C;
DPRINT("%lud release %lud[%s], r=%lud, d=%lud, t=%lud, S=%lud\n",
now, p->pid, statename[p->state], e->r, e->d, e->t, e->S);
if(pt = proctrace){
nowns = todget(nil);
pt(p, SRelease, nowns);
pt(p, SDeadline, nowns + 1000LL*e->D);
}
}else{
DPRINT("%lud release %lud[%s], too late t=%lud, called from %#p\n",
now, p->pid, statename[p->state], e->t, getcallerpc(&p));
}
}
static void
releaseintr(Ureg *u, Timer *t)
{
Proc *p;
extern int panicking;
Schedq *rq;
if(panicking || active.exiting)
return;
p = t->ta;
if((edflock(p)) == nil)
return;
DPRINT("%lud releaseintr %lud[%s]\n", now, p->pid, statename[p->state]);
switch(p->state){
default:
edfunlock();
return;
case Ready:
/* remove proc from current runq */
rq = &runq[p->priority];
if(dequeueproc(rq, p) != p){
DPRINT("releaseintr: can't find proc or lock race\n");
release(p); /* It'll start best effort */
edfunlock();
return;
}
p->state = Waitrelease;
/* fall through */
case Waitrelease:
release(p);
edfunlock();
if(p->state == Wakeme){
iprint("releaseintr: wakeme\n");
}
ready(p);
if(up){
up->delaysched++;
delayedscheds++;
}
return;
case Running:
release(p);
edfrun(p, 1);
break;
case Wakeme:
release(p);
edfunlock();
twakeup(u, t);
if(up){
up->delaysched++;
delayedscheds++;
}
return;
}
edfunlock();
}
void
edfrecord(Proc *p)
{
long used;
Edf *e;
void (*pt)(Proc*, int, vlong);
if((e = edflock(p)) == nil)
return;
used = now - e->s;
if(e->d - now <= 0)
e->edfused += used;
else
e->extraused += used;
if(e->S > 0){
if(e->S <= used){
if(pt = proctrace)
pt(p, SSlice, 0);
DPRINT("%lud edfrecord slice used up\n", now);
e->d = now;
e->S = 0;
}else
e->S -= used;
}
e->s = now;
edfunlock();
}
void
edfrun(Proc *p, int edfpri)
{
Edf *e;
void (*pt)(Proc*, int, vlong);
long tns;
e = p->edf;
/* Called with edflock held */
if(edfpri){
tns = e->d - now;
if(tns <= 0 || e->S == 0){
/* Deadline reached or resources exhausted,
* deschedule forthwith
*/
p->delaysched++;
delayedscheds++;
e->s = now;
return;
}
if(e->S < tns)
tns = e->S;
if(tns < 20)
tns = 20;
e->tns = 1000LL * tns; /* µs to ns */
if(e->tt == nil || e->tf != deadlineintr){
DPRINT("%lud edfrun, deadline=%lud\n", now, tns);
}else{
DPRINT("v");
}
if(p->trace && (pt = proctrace))
pt(p, SInte, todget(nil) + e->tns);
e->tmode = Trelative;
e->tf = deadlineintr;
e->ta = p;
timeradd(e);
}else{
DPRINT("<");
}
e->s = now;
}
char *
edfadmit(Proc *p)
{
char *err;
Edf *e;
int i;
Proc *r;
void (*pt)(Proc*, int, vlong);
long tns;
e = p->edf;
if (e->flags & Admitted)
return "task state"; /* should never happen */
/* simple sanity checks */
if (e->T == 0)
return "T not set";
if (e->C == 0)
return "C not set";
if (e->D > e->T)
return "D > T";
if (e->D == 0) /* if D is not set, set it to T */
e->D = e->T;
if (e->C > e->D)
return "C > D";
qlock(&edfschedlock);
if (err = testschedulability(p)){
qunlock(&edfschedlock);
return err;
}
e->flags |= Admitted;
edflock(p);
if(p->trace && (pt = proctrace))
pt(p, SAdmit, 0);
/* Look for another proc with the same period to synchronize to */
SET(r);
for(i=0; i<conf.nproc; i++) {
r = proctab(i);
if(r->state == Dead || r == p)
continue;
if (r->edf == nil || (r->edf->flags & Admitted) == 0)
continue;
if (r->edf->T == e->T)
break;
}
if (i == conf.nproc){
/* Can't synchronize to another proc, release now */
e->t = now;
e->d = 0;
release(p);
if (p == up){
DPRINT("%lud edfadmit self %lud[%s], release now: r=%lud d=%lud t=%lud\n",
now, p->pid, statename[p->state], e->r, e->d, e->t);
/* We're already running */
edfrun(p, 1);
}else{
/* We're releasing another proc */
DPRINT("%lud edfadmit other %lud[%s], release now: r=%lud d=%lud t=%lud\n",
now, p->pid, statename[p->state], e->r, e->d, e->t);
p->ta = p;
edfunlock();
qunlock(&edfschedlock);
releaseintr(nil, p);
return nil;
}
}else{
/* Release in synch to something else */
e->t = r->edf->t;
if (p == up){
DPRINT("%lud edfadmit self %lud[%s], release at %lud\n",
now, p->pid, statename[p->state], e->t);
edfunlock();
qunlock(&edfschedlock);
return nil;
}else{
DPRINT("%lud edfadmit other %lud[%s], release at %lud\n",
now, p->pid, statename[p->state], e->t);
if(e->tt == nil){
e->tf = releaseintr;
e->ta = p;
tns = e->t - now;
if(tns < 20)
tns = 20;
e->tns = 1000LL * tns;
e->tmode = Trelative;
timeradd(e);
}
}
}
edfunlock();
qunlock(&edfschedlock);
return nil;
}
void
edfstop(Proc *p)
{
Edf *e;
void (*pt)(Proc*, int, vlong);
if(e = edflock(p)){
DPRINT("%lud edfstop %lud[%s]\n", now, p->pid, statename[p->state]);
if(p->trace && (pt = proctrace))
pt(p, SExpel, 0);
e->flags &= ~Admitted;
timerdel(e);
edfunlock();
}
}
static int
yfn(void *)
{
now = µs();
return up->trend == nil || now - up->edf->r >= 0;
}
void
edfyield(void)
{
/* sleep until next release */
Edf *e;
void (*pt)(Proc*, int, vlong);
long n;
if((e = edflock(up)) == nil)
return;
if(up->trace && (pt = proctrace))
pt(up, SYield, 0);
if((n = now - e->t) > 0){
if(n < e->T)
e->t += e->T;
else
e->t = now + e->T - (n % e->T);
}
e->r = e->t;
e->flags |= Yield;
e->d = now;
n = e->t - now;
if(n < 20)
n = 20;
up->tns = 1000LL * n;
up->tf = releaseintr;
up->tmode = Trelative;
up->ta = up;
up->trend = &up->sleep;
timeradd(up);
edfunlock();
if(waserror()){
up->trend = nil;
timerdel(up);
nexterror();
}
sleep(&up->sleep, yfn, nil);
poperror();
}
int
edfready(Proc *p)
{
Edf *e;
Schedq *rq;
Proc *l, *pp;
void (*pt)(Proc*, int, vlong);
long n;
if((e = edflock(p)) == nil)
return 0;
if(p->state == Wakeme && p->r){
iprint("edfready: wakeme\n");
}
if(e->d - now <= 0){
/* past deadline, arrange for next release */
if((e->flags & Sporadic) == 0){
/*
* Non sporadic processes stay true to their period;
* calculate next release time.
*/
if((n = now - e->t) > 0){
if(n < e->T)
e->t += e->T;
else
e->t = now + e->T - (n % e->T);
}
}
if(now - e->t < 0){
/* Next release is in the future, schedule it */
if(e->tt == nil || e->tf != releaseintr){
n = e->t - now;
if(n < 20)
n = 20;
e->tns = 1000LL * n;
e->tmode = Trelative;
e->tf = releaseintr;
e->ta = p;
timeradd(e);
DPRINT("%lud edfready %lud[%s], release=%lud\n",
now, p->pid, statename[p->state], e->t);
}
if(p->state == Running && (e->flags & (Yield|Yieldonblock)) == 0 && (e->flags & Extratime)){
/* If we were running, we've overrun our CPU allocation
* or missed the deadline, continue running best-effort at low priority
* Otherwise we were blocked. If we don't yield on block, we continue
* best effort
*/
DPRINT(">");
p->basepri = PriExtra;
p->fixedpri = 1;
edfunlock();
return 0; /* Stick on runq[PriExtra] */
}
DPRINT("%lud edfready %lud[%s] wait release at %lud\n",
now, p->pid, statename[p->state], e->t);
p->state = Waitrelease;
edfunlock();
return 1; /* Make runnable later */
}
DPRINT("%lud edfready %lud %s release now\n", now, p->pid, statename[p->state]);
/* release now */
release(p);
}
edfunlock();
DPRINT("^");
rq = &runq[PriEdf];
/* insert in queue in earliest deadline order */
lock(runq);
l = nil;
for(pp = rq->head; pp; pp = pp->rnext){
if(pp->edf->d > e->d)
break;
l = pp;
}
p->rnext = pp;
if (l == nil)
rq->head = p;
else
l->rnext = p;
if(pp == nil)
rq->tail = p;
rq->n++;
nrdy++;
runvec |= 1 << PriEdf;
p->priority = PriEdf;
p->readytime = m->ticks;
p->state = Ready;
unlock(runq);
if(p->trace && (pt = proctrace))
pt(p, SReady, 0);
return 1;
}
static void
testenq(Proc *p)
{
Proc *xp, **xpp;
Edf *e;
e = p->edf;
e->testnext = nil;
if (qschedulability == nil) {
qschedulability = p;
return;
}
SET(xp);
for (xpp = &qschedulability; *xpp; xpp = &xp->edf->testnext) {
xp = *xpp;
if (e->testtime - xp->edf->testtime < 0
|| (e->testtime == xp->edf->testtime && e->testtype < xp->edf->testtype)){
e->testnext = xp;
*xpp = p;
return;
}
}
assert(xp->edf->testnext == nil);
xp->edf->testnext = p;
}
static char *
testschedulability(Proc *theproc)
{
Proc *p;
long H, G, Cb, ticks;
int steps, i;
/* initialize */
DPRINT("schedulability test %lud\n", theproc->pid);
qschedulability = nil;
for(i=0; i<conf.nproc; i++) {
p = proctab(i);
if(p->state == Dead)
continue;
if ((p->edf == nil || (p->edf->flags & Admitted) == 0) && p != theproc)
continue;
p->edf->testtype = Rl;
p->edf->testtime = 0;
DPRINT("\tInit: edfenqueue %lud\n", p->pid);
testenq(p);
}
H=0;
G=0;
for(steps = 0; steps < Maxsteps; steps++){
p = qschedulability;
qschedulability = p->edf->testnext;
ticks = p->edf->testtime;
switch (p->edf->testtype){
case Dl:
H += p->edf->C;
Cb = 0;
DPRINT("\tStep %3d, Ticks %lud, pid %lud, deadline, H += %lud → %lud, Cb = %lud\n",
steps, ticks, p->pid, p->edf->C, H, Cb);
if (H+Cb>ticks){
DPRINT("not schedulable\n");
return "not schedulable";
}
p->edf->testtime += p->edf->T - p->edf->D;
p->edf->testtype = Rl;
testenq(p);
break;
case Rl:
DPRINT("\tStep %3d, Ticks %lud, pid %lud, release, G %lud, C%lud\n",
steps, ticks, p->pid, p->edf->C, G);
if(ticks && G <= ticks){
DPRINT("schedulable\n");
return nil;
}
G += p->edf->C;
p->edf->testtime += p->edf->D;
p->edf->testtype = Dl;
testenq(p);
break;
default:
assert(0);
}
}
DPRINT("probably not schedulable\n");
return "probably not schedulable";
}