
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.
679 lines
13 KiB
C
679 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";
|
|
}
|