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