xref: /plan9-contrib/sys/src/9/port/edf.c (revision 9aba0e67f3fdf114973a71f5ffa0b6cd3cc40cfa)
1 /* EDF scheduling */
2 #include	<u.h>
3 #include	"../port/lib.h"
4 #include	"mem.h"
5 #include	"dat.h"
6 #include	"fns.h"
7 #include	"../port/error.h"
8 #include	"../port/edf.h"
9 #include	<trace.h>
10 
11 /* debugging */
12 enum {
13 	Dontprint = 1,
14 };
15 
16 #define DPRINT	if(Dontprint){}else print
17 
18 static long	now;	/* Low order 32 bits of time in µs */
19 extern ulong	delayedscheds;
20 extern Schedq	runq[Nrq];
21 extern int	nrdy;
22 extern ulong	runvec;
23 
24 /* Statistics stuff */
25 ulong		nilcount;
26 ulong		scheds;
27 ulong		edfnrun;
28 int		misseddeadlines;
29 
30 /* Edfschedlock protects modification of admission params */
31 int		edfinited;
32 QLock		edfschedlock;
33 static Lock	thelock;
34 
35 enum{
36 	Dl,	/* Invariant for schedulability test: Dl < Rl */
37 	Rl,
38 };
39 
40 static char *testschedulability(Proc*);
41 static Proc *qschedulability;
42 
43 enum {
44 	Onemicrosecond =	1,
45 	Onemillisecond =	1000,
46 	Onesecond =		1000000,
47 	OneRound = 		Onemillisecond/2,
48 };
49 
50 static int
51 timeconv(Fmt *f)
52 {
53 	char buf[128], *sign;
54 	vlong t;
55 
56 	buf[0] = 0;
57 	switch(f->r) {
58 	case 'U':
59 		t = va_arg(f->args, uvlong);
60 		break;
61 	case 't':			/* vlong in nanoseconds */
62 		t = va_arg(f->args, long);
63 		break;
64 	default:
65 		return fmtstrcpy(f, "(timeconv)");
66 	}
67 	if (t < 0) {
68 		sign = "-";
69 		t = -t;
70 	}
71 	else
72 		sign = "";
73 	if (t > Onesecond){
74 		t += OneRound;
75 		sprint(buf, "%s%d.%.3ds", sign, (int)(t / Onesecond),
76 			(int)(t % Onesecond)/Onemillisecond);
77 	}else if (t > Onemillisecond)
78 		sprint(buf, "%s%d.%.3dms", sign, (int)(t / Onemillisecond),
79 			(int)(t % Onemillisecond));
80 	else
81 		sprint(buf, "%s%dµs", sign, (int)t);
82 	return fmtstrcpy(f, buf);
83 }
84 
85 long edfcycles;
86 
87 Edf*
88 edflock(Proc *p)
89 {
90 	Edf *e;
91 
92 	if (p->edf == nil)
93 		return nil;
94 	ilock(&thelock);
95 	if((e = p->edf) && (e->flags & Admitted)){
96 		thelock.pc = getcallerpc(&p);
97 #ifdef EDFCYCLES
98 		edfcycles -= lcycles();
99 #endif
100 		now = µs();
101 		return e;
102 	}
103 	iunlock(&thelock);
104 	return nil;
105 }
106 
107 void
108 edfunlock(void)
109 {
110 
111 #ifdef EDFCYCLES
112 	edfcycles += lcycles();
113 #endif
114 	edfnrun++;
115 	iunlock(&thelock);
116 }
117 
118 void
119 edfinit(Proc*p)
120 {
121 	if(!edfinited){
122 		fmtinstall('t', timeconv);
123 		edfinited++;
124 	}
125 	now = µs();
126 	DPRINT("%lud edfinit %lud[%s]\n", now, p->pid, statename[p->state]);
127 	p->edf = malloc(sizeof(Edf));
128 	if(p->edf == nil)
129 		error(Enomem);
130 	return;
131 }
132 
133 static void
134 deadlineintr(Ureg*, Timer *t)
135 {
136 	/* Proc reached deadline */
137 	extern int panicking;
138 	Proc *p;
139 	void (*pt)(Proc*, int, vlong);
140 
141 	if(panicking || active.exiting)
142 		return;
143 
144 	p = t->ta;
145 	now = µs();
146 	DPRINT("%lud deadlineintr %lud[%s]\n", now, p->pid, statename[p->state]);
147 	/* If we're interrupting something other than the proc pointed to by t->a,
148 	 * we've already achieved recheduling, so we need not do anything
149 	 * Otherwise, we must cause a reschedule, but if we call sched()
150 	 * here directly, the timer interrupt routine will not finish its business
151 	 * Instead, we cause the resched to happen when the interrupted proc
152 	 * returns to user space
153 	 */
154 	if(p == up){
155 		if(up->trace && (pt = proctrace))
156 			pt(up, SInts, 0);
157 		up->delaysched++;
158  		delayedscheds++;
159 	}
160 }
161 
162 static void
163 release(Proc *p)
164 {
165 	/* Called with edflock held */
166 	Edf *e;
167 	void (*pt)(Proc*, int, vlong);
168 	long n;
169 	vlong nowns;
170 
171 	e = p->edf;
172 	e->flags &= ~Yield;
173 	if(e->d - now < 0){
174 		e->periods++;
175 		e->r = now;
176 		if((e->flags & Sporadic) == 0){
177 			/*
178 			 * Non sporadic processes stay true to their period;
179 			 * calculate next release time.
180 			 * Second test limits duration of while loop.
181 			 */
182 			if((n = now - e->t) > 0){
183 				if(n < e->T)
184 					e->t += e->T;
185 				else
186 					e->t = now + e->T - (n % e->T);
187 			}
188 		}else{
189 			/* Sporadic processes may not be released earlier than
190 			 * one period after this release
191 			 */
192 			e->t = e->r + e->T;
193 		}
194 		e->d = e->r + e->D;
195 		e->S = e->C;
196 		DPRINT("%lud release %lud[%s], r=%lud, d=%lud, t=%lud, S=%lud\n",
197 			now, p->pid, statename[p->state], e->r, e->d, e->t, e->S);
198 		if(pt = proctrace){
199 			nowns = todget(nil);
200 			pt(p, SRelease, nowns);
201 			pt(p, SDeadline, nowns + 1000LL*e->D);
202 		}
203 	}else{
204 		DPRINT("%lud release %lud[%s], too late t=%lud, called from %#p\n",
205 			now, p->pid, statename[p->state], e->t, getcallerpc(&p));
206 	}
207 }
208 
209 static void
210 releaseintr(Ureg*, Timer *t)
211 {
212 	Proc *p;
213 	extern int panicking;
214 	Schedq *rq;
215 
216 	if(panicking || active.exiting)
217 		return;
218 
219 	p = t->ta;
220 	if((edflock(p)) == nil)
221 		return;
222 	DPRINT("%lud releaseintr %lud[%s]\n", now, p->pid, statename[p->state]);
223 	switch(p->state){
224 	default:
225 		edfunlock();
226 		return;
227 	case Ready:
228 		/* remove proc from current runq */
229 		rq = &runq[p->priority];
230 		if(dequeueproc(rq, p) != p){
231 			DPRINT("releaseintr: can't find proc or lock race\n");
232 			release(p);	/* It'll start best effort */
233 			edfunlock();
234 			return;
235 		}
236 		p->state = Waitrelease;
237 		/* fall through */
238 	case Waitrelease:
239 		release(p);
240 		edfunlock();
241 		if(p->state == Wakeme){
242 			iprint("releaseintr: wakeme\n");
243 		}
244 		ready(p);
245 		if(up){
246 			up->delaysched++;
247 			delayedscheds++;
248 		}
249 		return;
250 	case Running:
251 		release(p);
252 		edfrun(p, 1);
253 		break;
254 	case Wakeme:
255 		release(p);
256 		edfunlock();
257 		if(p->trend)
258 			wakeup(p->trend);
259 		p->trend = nil;
260 		if(up){
261 			up->delaysched++;
262 			delayedscheds++;
263 		}
264 		return;
265 	}
266 	edfunlock();
267 }
268 
269 void
270 edfrecord(Proc *p)
271 {
272 	long used;
273 	Edf *e;
274 	void (*pt)(Proc*, int, vlong);
275 
276 	if((e = edflock(p)) == nil)
277 		return;
278 	used = now - e->s;
279 	if(e->d - now <= 0)
280 		e->edfused += used;
281 	else
282 		e->extraused += used;
283 	if(e->S > 0){
284 		if(e->S <= used){
285 			if(pt = proctrace)
286 				pt(p, SSlice, 0);
287 			DPRINT("%lud edfrecord slice used up\n", now);
288 			e->d = now;
289 			e->S = 0;
290 		}else
291 			e->S -= used;
292 	}
293 	e->s = now;
294 	edfunlock();
295 }
296 
297 void
298 edfrun(Proc *p, int edfpri)
299 {
300 	Edf *e;
301 	void (*pt)(Proc*, int, vlong);
302 	long tns;
303 
304 	e = p->edf;
305 	/* Called with edflock held */
306 	if(edfpri){
307 		tns = e->d - now;
308 		if(tns <= 0 || e->S == 0){
309 			/* Deadline reached or resources exhausted,
310 			 * deschedule forthwith
311 			 */
312 			p->delaysched++;
313  			delayedscheds++;
314 			e->s = now;
315 			return;
316 		}
317 		if(e->S < tns)
318 			tns = e->S;
319 		if(tns < 20)
320 			tns = 20;
321 		e->tns = 1000LL * tns;	/* µs to ns */
322 		if(e->tt == nil || e->tf != deadlineintr){
323 			DPRINT("%lud edfrun, deadline=%lud\n", now, tns);
324 		}else{
325 			DPRINT("v");
326 		}
327 		if(p->trace && (pt = proctrace))
328 			pt(p, SInte, todget(nil) + e->tns);
329 		e->tmode = Trelative;
330 		e->tf = deadlineintr;
331 		e->ta = p;
332 		timeradd(e);
333 	}else{
334 		DPRINT("<");
335 	}
336 	e->s = now;
337 }
338 
339 char *
340 edfadmit(Proc *p)
341 {
342 	char *err;
343 	Edf *e;
344 	int i;
345 	Proc *r;
346 	void (*pt)(Proc*, int, vlong);
347 	long tns;
348 
349 	e = p->edf;
350 	if (e->flags & Admitted)
351 		return "task state";	/* should never happen */
352 
353 	/* simple sanity checks */
354 	if (e->T == 0)
355 		return "T not set";
356 	if (e->C == 0)
357 		return "C not set";
358 	if (e->D > e->T)
359 		return "D > T";
360 	if (e->D == 0)	/* if D is not set, set it to T */
361 		e->D = e->T;
362 	if (e->C > e->D)
363 		return "C > D";
364 
365 	qlock(&edfschedlock);
366 	if (err = testschedulability(p)){
367 		qunlock(&edfschedlock);
368 		return err;
369 	}
370 	e->flags |= Admitted;
371 
372 	edflock(p);
373 
374 	if(p->trace && (pt = proctrace))
375 		pt(p, SAdmit, 0);
376 
377 	/* Look for another proc with the same period to synchronize to */
378 	SET(r);
379 	for(i=0; i<conf.nproc; i++) {
380 		r = proctab(i);
381 		if(r->state == Dead || r == p)
382 			continue;
383 		if (r->edf == nil || (r->edf->flags & Admitted) == 0)
384 			continue;
385 		if (r->edf->T == e->T)
386 				break;
387 	}
388 	if (i == conf.nproc){
389 		/* Can't synchronize to another proc, release now */
390 		e->t = now;
391 		e->d = 0;
392 		release(p);
393 		if (p == up){
394 			DPRINT("%lud edfadmit self %lud[%s], release now: r=%lud d=%lud t=%lud\n",
395 				now, p->pid, statename[p->state], e->r, e->d, e->t);
396 			/* We're already running */
397 			edfrun(p, 1);
398 		}else{
399 			/* We're releasing another proc */
400 			DPRINT("%lud edfadmit other %lud[%s], release now: r=%lud d=%lud t=%lud\n",
401 				now, p->pid, statename[p->state], e->r, e->d, e->t);
402 			p->ta = p;
403 			edfunlock();
404 			qunlock(&edfschedlock);
405 			releaseintr(nil, p);
406 			return nil;
407 		}
408 	}else{
409 		/* Release in synch to something else */
410 		e->t = r->edf->t;
411 		if (p == up){
412 			DPRINT("%lud edfadmit self %lud[%s], release at %lud\n",
413 				now, p->pid, statename[p->state], e->t);
414 			edfunlock();
415 			qunlock(&edfschedlock);
416 			return nil;
417 		}else{
418 			DPRINT("%lud edfadmit other %lud[%s], release at %lud\n",
419 				now, p->pid, statename[p->state], e->t);
420 			if(e->tt == nil){
421 				e->tf = releaseintr;
422 				e->ta = p;
423 				tns = e->t - now;
424 				if(tns < 20)
425 					tns = 20;
426 				e->tns = 1000LL * tns;
427 				e->tmode = Trelative;
428 				timeradd(e);
429 			}
430 		}
431 	}
432 	edfunlock();
433 	qunlock(&edfschedlock);
434 	return nil;
435 }
436 
437 void
438 edfstop(Proc *p)
439 {
440 	Edf *e;
441 	void (*pt)(Proc*, int, vlong);
442 
443 	if(e = edflock(p)){
444 		DPRINT("%lud edfstop %lud[%s]\n", now, p->pid, statename[p->state]);
445 		if(p->trace && (pt = proctrace))
446 			pt(p, SExpel, 0);
447 		e->flags &= ~Admitted;
448 		if(e->tt)
449 			timerdel(e);
450 		edfunlock();
451 	}
452 }
453 
454 static int
455 yfn(void *)
456 {
457 	now = µs();
458 	return up->trend == nil || now - up->edf->r >= 0;
459 }
460 
461 void
462 edfyield(void)
463 {
464 	/* sleep until next release */
465 	Edf *e;
466 	void (*pt)(Proc*, int, vlong);
467 	long n;
468 
469 	if((e = edflock(up)) == nil)
470 		return;
471 	if(up->trace && (pt = proctrace))
472 		pt(up, SYield, 0);
473 	if((n = now - e->t) > 0){
474 		if(n < e->T)
475 			e->t += e->T;
476 		else
477 			e->t = now + e->T - (n % e->T);
478 	}
479 	e->r = e->t;
480 	e->flags |= Yield;
481 	e->d = now;
482 	if (up->tt == nil){
483 		n = e->t - now;
484 		if(n < 20)
485 			n = 20;
486 		up->tns = 1000LL * n;
487 		up->tf = releaseintr;
488 		up->tmode = Trelative;
489 		up->ta = up;
490 		up->trend = &up->sleep;
491 		timeradd(up);
492 	}else if(up->tf != releaseintr)
493 		print("edfyield: surprise! %#p\n", up->tf);
494 	edfunlock();
495 	sleep(&up->sleep, yfn, nil);
496 }
497 
498 int
499 edfready(Proc *p)
500 {
501 	Edf *e;
502 	Schedq *rq;
503 	Proc *l, *pp;
504 	void (*pt)(Proc*, int, vlong);
505 	long n;
506 
507 	if((e = edflock(p)) == nil)
508 		return 0;
509 
510 	if(p->state == Wakeme && p->r){
511 		iprint("edfready: wakeme\n");
512 	}
513 	if(e->d - now <= 0){
514 		/* past deadline, arrange for next release */
515 		if((e->flags & Sporadic) == 0){
516 			/*
517 			 * Non sporadic processes stay true to their period;
518 			 * calculate next release time.
519 			 */
520 			if((n = now - e->t) > 0){
521 				if(n < e->T)
522 					e->t += e->T;
523 				else
524 					e->t = now + e->T - (n % e->T);
525 			}
526 		}
527 		if(now - e->t < 0){
528 			/* Next release is in the future, schedule it */
529 			if(e->tt == nil || e->tf != releaseintr){
530 				n = e->t - now;
531 				if(n < 20)
532 					n = 20;
533 				e->tns = 1000LL * n;
534 				e->tmode = Trelative;
535 				e->tf = releaseintr;
536 				e->ta = p;
537 				timeradd(e);
538 				DPRINT("%lud edfready %lud[%s], release=%lud\n",
539 					now, p->pid, statename[p->state], e->t);
540 			}
541 			if(p->state == Running && (e->flags & (Yield|Yieldonblock)) == 0 && (e->flags & Extratime)){
542 				/* If we were running, we've overrun our CPU allocation
543 				 * or missed the deadline, continue running best-effort at low priority
544 				 * Otherwise we were blocked.  If we don't yield on block, we continue
545 				 * best effort
546 				 */
547 				DPRINT(">");
548 				p->basepri = PriExtra;
549 				p->fixedpri = 1;
550 				edfunlock();
551 				return 0;	/* Stick on runq[PriExtra] */
552 			}
553 			DPRINT("%lud edfready %lud[%s] wait release at %lud\n",
554 				now, p->pid, statename[p->state], e->t);
555 			p->state = Waitrelease;
556 			edfunlock();
557 			return 1;	/* Make runnable later */
558 		}
559 		DPRINT("%lud edfready %lud %s release now\n", now, p->pid, statename[p->state]);
560 		/* release now */
561 		release(p);
562 	}
563 	edfunlock();
564 	DPRINT("^");
565 	rq = &runq[PriEdf];
566 	/* insert in queue in earliest deadline order */
567 	lock(runq);
568 	l = nil;
569 	for(pp = rq->head; pp; pp = pp->rnext){
570 		if(pp->edf->d > e->d)
571 			break;
572 		l = pp;
573 	}
574 	p->rnext = pp;
575 	if (l == nil)
576 		rq->head = p;
577 	else
578 		l->rnext = p;
579 	if(pp == nil)
580 		rq->tail = p;
581 	rq->n++;
582 	nrdy++;
583 	runvec |= 1 << PriEdf;
584 	p->priority = PriEdf;
585 	p->readytime = m->ticks;
586 	p->state = Ready;
587 	unlock(runq);
588 	if(p->trace && (pt = proctrace))
589 		pt(p, SReady, 0);
590 	return 1;
591 }
592 
593 
594 static void
595 testenq(Proc *p)
596 {
597 	Proc *xp, **xpp;
598 	Edf *e;
599 
600 	e = p->edf;
601 	e->testnext = nil;
602 	if (qschedulability == nil) {
603 		qschedulability = p;
604 		return;
605 	}
606 	SET(xp);
607 	for (xpp = &qschedulability; *xpp; xpp = &xp->edf->testnext) {
608 		xp = *xpp;
609 		if (e->testtime - xp->edf->testtime < 0
610 		|| (e->testtime == xp->edf->testtime && e->testtype < xp->edf->testtype)){
611 			e->testnext = xp;
612 			*xpp = p;
613 			return;
614 		}
615 	}
616 	assert(xp->edf->testnext == nil);
617 	xp->edf->testnext = p;
618 }
619 
620 static char *
621 testschedulability(Proc *theproc)
622 {
623 	Proc *p;
624 	long H, G, Cb, ticks;
625 	int steps, i;
626 
627 	/* initialize */
628 	DPRINT("schedulability test %lud\n", theproc->pid);
629 	qschedulability = nil;
630 	for(i=0; i<conf.nproc; i++) {
631 		p = proctab(i);
632 		if(p->state == Dead)
633 			continue;
634 		if ((p->edf == nil || (p->edf->flags & Admitted) == 0) && p != theproc)
635 			continue;
636 		p->edf->testtype = Rl;
637 		p->edf->testtime = 0;
638 		DPRINT("\tInit: edfenqueue %lud\n", p->pid);
639 		testenq(p);
640 	}
641 	H=0;
642 	G=0;
643 	for(steps = 0; steps < Maxsteps; steps++){
644 		p = qschedulability;
645 		qschedulability = p->edf->testnext;
646 		ticks = p->edf->testtime;
647 		switch (p->edf->testtype){
648 		case Dl:
649 			H += p->edf->C;
650 			Cb = 0;
651 			DPRINT("\tStep %3d, Ticks %lud, pid %lud, deadline, H += %lud → %lud, Cb = %lud\n",
652 				steps, ticks, p->pid, p->edf->C, H, Cb);
653 			if (H+Cb>ticks){
654 				DPRINT("not schedulable\n");
655 				return "not schedulable";
656 			}
657 			p->edf->testtime += p->edf->T - p->edf->D;
658 			p->edf->testtype = Rl;
659 			testenq(p);
660 			break;
661 		case Rl:
662 			DPRINT("\tStep %3d, Ticks %lud, pid %lud, release, G  %lud, C%lud\n",
663 				steps, ticks, p->pid, p->edf->C, G);
664 			if(ticks && G <= ticks){
665 				DPRINT("schedulable\n");
666 				return nil;
667 			}
668 			G += p->edf->C;
669 			p->edf->testtime += p->edf->D;
670 			p->edf->testtype = Dl;
671 			testenq(p);
672 			break;
673 		default:
674 			assert(0);
675 		}
676 	}
677 	DPRINT("probably not schedulable\n");
678 	return "probably not schedulable";
679 }
680