tsleep I (checked in for marc)
[unix-history] / usr / src / sys / kern / kern_synch.c
/*
* Copyright (c) 1982, 1986 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
*
* @(#)kern_synch.c 7.10 (Berkeley) %G%
*/
#include "machine/pte.h"
#include "machine/psl.h"
#include "machine/mtpr.h"
#include "param.h"
#include "systm.h"
#include "user.h"
#include "proc.h"
#include "vm.h"
#include "kernel.h"
#include "buf.h"
#include "tsleep.h"
/*
* Force switch among equal priority processes every 100ms.
*/
roundrobin()
{
runrun++;
aston();
timeout(roundrobin, (caddr_t)0, hz / 10);
}
/*
* constants for digital decay and forget
* 90% of (p_cpu) usage in 5*loadav time
* 95% of (p_pctcpu) usage in 60 seconds (load insensitive)
* Note that, as ps(1) mentions, this can let percentages
* total over 100% (I've seen 137.9% for 3 processes).
*
* Note that hardclock updates p_cpu and p_cpticks independently.
*
* We wish to decay away 90% of p_cpu in (5 * loadavg) seconds.
* That is, the system wants to compute a value of decay such
* that the following for loop:
* for (i = 0; i < (5 * loadavg); i++)
* p_cpu *= decay;
* will compute
* p_cpu *= 0.1;
* for all values of loadavg:
*
* Mathematically this loop can be expressed by saying:
* decay ** (5 * loadavg) ~= .1
*
* The system computes decay as:
* decay = (2 * loadavg) / (2 * loadavg + 1)
*
* We wish to prove that the system's computation of decay
* will always fulfill the equation:
* decay ** (5 * loadavg) ~= .1
*
* If we compute b as:
* b = 2 * loadavg
* then
* decay = b / (b + 1)
*
* We now need to prove two things:
* 1) Given factor ** (5 * loadavg) ~= .1, prove factor == b/(b+1)
* 2) Given b/(b+1) ** power ~= .1, prove power == (5 * loadavg)
*
* Facts:
* For x close to zero, exp(x) =~ 1 + x, since
* exp(x) = 0! + x**1/1! + x**2/2! + ... .
* therefore exp(-1/b) =~ 1 - (1/b) = (b-1)/b.
* For x close to zero, ln(1+x) =~ x, since
* ln(1+x) = x - x**2/2 + x**3/3 - ... -1 < x < 1
* therefore ln(b/(b+1)) = ln(1 - 1/(b+1)) =~ -1/(b+1).
* ln(.1) =~ -2.30
*
* Proof of (1):
* Solve (factor)**(power) =~ .1 given power (5*loadav):
* solving for factor,
* ln(factor) =~ (-2.30/5*loadav), or
* factor =~ exp(-1/((5/2.30)*loadav) =~ exp(-1/(2*loadav)) =
* exp(-1/b) =~ (b-1)/b =~ b/(b+1). QED
*
* Proof of (2):
* Solve (factor)**(power) =~ .1 given factor == (b/(b+1)):
* solving for power,
* power*ln(b/(b+1)) =~ -2.30, or
* power =~ 2.3 * (b + 1) = 4.6*loadav + 2.3 =~ 5*loadav. QED
*
* Actual power values for the implemented algorithm are as follows:
* loadav: 1 2 3 4
* power: 5.68 10.32 14.94 19.55
*/
/* calculations for digital decay to forget 90% of usage in 5*loadav sec */
#define get_b(loadav) (2 * (loadav))
#define get_pcpu(b, cpu) (((b) * ((cpu) & 0377)) / ((b) + FSCALE))
/* decay 95% of `p_pctcpu' in 60 seconds; see CCPU_SHIFT before changing */
fixpt_t ccpu = 0.95122942450071400909 * FSCALE; /* exp(-1/20) */
/*
* If `ccpu' is not equal to `exp(-1/20)' and you still want to use the
* faster/more-accurate formula, you'll have to estimate CCPU_SHIFT below
* and possibly adjust FSHIFT in "param.h" so that (FSHIFT >= CCPU_SHIFT).
*
* To estimate CCPU_SHIFT for exp(-1/20), the following formula was used:
* 1 - exp(-1/20) ~= 0.0487 ~= 0.0488 == 1 (fixed pt, *11* bits).
*
* If you dont want to bother with the faster/more-accurate formula, you
* can set CCPU_SHIFT to (FSHIFT + 1) which will use a slower/less-accurate
* (more general) method of calculating the %age of CPU used by a process.
*/
#define CCPU_SHIFT 11
/*
* Recompute process priorities, once a second
*/
schedcpu()
{
register fixpt_t b = get_b(averunnable[0]);
register struct proc *p;
register int s, a;
wakeup((caddr_t)&lbolt);
for (p = allproc; p != NULL; p = p->p_nxt) {
if (p->p_time != 127)
p->p_time++;
if (p->p_stat==SSLEEP || p->p_stat==SSTOP)
if (p->p_slptime != 127)
p->p_slptime++;
p->p_pctcpu = (p->p_pctcpu * ccpu) >> FSHIFT;
/*
* If the process has slept the entire second,
* stop recalculating its priority until it wakes up.
*/
if (p->p_slptime > 1)
continue;
/*
* p_pctcpu is only for ps.
*/
#if (FSHIFT >= CCPU_SHIFT)
p->p_pctcpu += (hz == 100)?
((fixpt_t) p->p_cpticks) << (FSHIFT - CCPU_SHIFT):
100 * (((fixpt_t) p->p_cpticks)
<< (FSHIFT - CCPU_SHIFT)) / hz;
#else
p->p_pctcpu += ((FSCALE - ccpu) *
(p->p_cpticks * FSCALE / hz)) >> FSHIFT;
#endif
p->p_cpticks = 0;
a = (int) get_pcpu(b, p->p_cpu) + p->p_nice;
if (a < 0)
a = 0;
if (a > 255)
a = 255;
p->p_cpu = a;
(void) setpri(p);
s = splhigh(); /* prevent state changes */
if (p->p_pri >= PUSER) {
#define PPQ (128 / NQS)
if ((p != u.u_procp || noproc) &&
p->p_stat == SRUN &&
(p->p_flag & SLOAD) &&
(p->p_pri / PPQ) != (p->p_usrpri / PPQ)) {
remrq(p);
p->p_pri = p->p_usrpri;
setrq(p);
} else
p->p_pri = p->p_usrpri;
}
splx(s);
}
vmmeter();
if (runin!=0) {
runin = 0;
wakeup((caddr_t)&runin);
}
if (bclnlist != NULL)
wakeup((caddr_t)&proc[2]);
timeout(schedcpu, (caddr_t)0, hz);
}
/*
* Recalculate the priority of a process after it has slept for a while.
*/
updatepri(p)
register struct proc *p;
{
register int a = p->p_cpu & 0377;
register fixpt_t b = get_b(averunnable[0]);
p->p_slptime--; /* the first time was done in schedcpu */
while (a && --p->p_slptime)
a = (int) get_pcpu(b, a) /* + p->p_nice */;
p->p_slptime = 0;
if (a < 0)
a = 0;
if (a > 255)
a = 255;
p->p_cpu = a;
(void) setpri(p);
}
#define SQSIZE 0100 /* Must be power of 2 */
#define HASH(x) (( (int) x >> 5) & (SQSIZE-1))
struct slpque {
struct proc *sq_head;
struct proc **sq_tailp;
} slpque[SQSIZE];
/*
* XXX - redo comments once interface is set
*
* Give up the processor till a wakeup occurs
* on chan, at which time the process
* enters the scheduling queue at priority pri.
* The most important effect of pri is that when
* pri<=PZERO a signal cannot disturb the sleep;
* if pri>PZERO signals will be processed.
* Callers of this routine must be prepared for
* premature return, and check that the reason for
* sleeping has gone away.
*/
/*
* interruptable sleep with longjmp processing.
* TEMPORARY UNTIL ALL CALLERS ARE TAUGHT TO UNWIND
*/
tsleep(chan, pri, wmesg, timeout)
caddr_t chan;
int pri;
char *wmesg;
int timeout;
{
if (pri <= PZERO)
panic("tsleep: pri <= PZERO");
if (isleep(chan, pri, wmesg, timeout) == EINTR)
longjmp(&u.u_qsave);
}
/*
* Interruptable sleep.
* Sleeps on chan for time of at most timo/hz seconds (0 means no timeout).
* Returns 0 if awakened, EINTR if a signal needs to be delivered,
* or EWOULDBLOCK if the timeout expires.
*/
isleep(chan, pri, wmesg, timo)
caddr_t chan;
int pri;
char *wmesg;
int timo;
{
register struct proc *rp;
register struct slpque *qp;
register s;
extern int cold;
int endtsleep();
rp = u.u_procp;
s = splhigh();
if (cold || panicstr) {
/*
* After a panic, or during autoconfiguration,
* just give interrupts a chance, then just return;
* don't run any other procs or panic below,
* in case this is the idle process and already asleep.
* The splnet should be spl0 if the network was being used
* by the filesystem, but for now avoid network interrupts
* that might cause another panic.
*/
(void) splnet();
splx(s);
return (0);
}
#ifdef DIAGNOSTIC
if (chan==0 || rp->p_stat != SRUN || rp->p_rlink)
panic("isleep");
#endif
rp->p_wchan = chan;
rp->p_wmesg = wmesg;
rp->p_slptime = 0;
rp->p_pri = pri;
qp = &slpque[HASH(chan)];
if (qp->sq_head == 0)
qp->sq_head = rp;
else
*qp->sq_tailp = rp;
*(qp->sq_tailp = &rp->p_link) = 0;
/*
* If we stop in issig(), wakeup may already have happened
* when we return (rp->p_wchan will then be 0).
*/
if (CURSIG(rp)) {
if (rp->p_wchan)
unsleep(rp);
rp->p_stat = SRUN;
splx(s);
return (EINTR);
}
if (rp->p_wchan == 0) {
splx(s);
return (0);
}
rp->p_stat = SSLEEP;
if (timo)
timeout(endtsleep, (caddr_t)rp, timo);
(void) spl0();
u.u_ru.ru_nvcsw++;
swtch();
curpri = rp->p_usrpri;
splx(s);
if (rp->p_flag & STIMO) {
rp->p_flag &= ~STIMO;
return (EWOULDBLOCK);
}
if (timo)
untimeout(endtsleep, (caddr_t)rp);
if (CURSIG(rp))
return (EINTR);
return (0);
}
/*
* Implement timeout for tsleep.
* If process hasn't been awakened (wchan non-zero),
* set timeout flag and undo the sleep. If proc
* is stopped, just unsleep so it will remain stopped.
*/
endtsleep(p)
register struct proc *p;
{
int s = splhigh();
if (p->p_wchan) {
if (p->p_stat == SSLEEP)
setrun(p);
else
unsleep(p);
p->p_flag |= STIMO;
}
splx(s);
}
int sleepdebug = 1; /* XXX */
sleep(chan, pri)
caddr_t chan;
int pri;
{
register struct proc *rp;
register struct slpque *qp;
register s;
extern int cold;
rp = u.u_procp;
s = splhigh();
if (cold || panicstr) {
/*
* After a panic, or during autoconfiguration,
* just give interrupts a chance, then just return;
* don't run any other procs or panic below,
* in case this is the idle process and already asleep.
* The splnet should be spl0 if the network was being used
* by the filesystem, but for now avoid network interrupts
* that might cause another panic.
*/
(void) splnet();
splx(s);
return;
}
#ifdef DIAGNOSTIC
if (chan==0 || rp->p_stat != SRUN || rp->p_rlink)
panic("sleep");
#endif
rp->p_wchan = chan;
rp->p_wmesg = NULL;
rp->p_slptime = 0;
rp->p_pri = pri;
qp = &slpque[HASH(chan)];
if (qp->sq_head == 0)
qp->sq_head = rp;
else
*qp->sq_tailp = rp;
*(qp->sq_tailp = &rp->p_link) = 0;
if (pri > PZERO) {
if (sleepdebug)
printf("sleep called with pri > PZERO, wchan: %x\n",
chan);
/*
* If we stop in issig(), wakeup may already have happened
* when we return (rp->p_wchan will then be 0).
*/
if (CURSIG(rp)) {
if (rp->p_wchan)
unsleep(rp);
rp->p_stat = SRUN;
(void) spl0();
goto psig;
}
if (rp->p_wchan == 0)
goto out;
rp->p_stat = SSLEEP;
(void) spl0();
u.u_ru.ru_nvcsw++;
swtch();
if (CURSIG(rp))
goto psig;
} else {
rp->p_stat = SSLEEP;
(void) spl0();
u.u_ru.ru_nvcsw++;
swtch();
}
curpri = rp->p_usrpri;
out:
splx(s);
return;
/*
* If priority was low (>PZERO) and
* there has been a signal, execute non-local goto through
* u.u_qsave, aborting the system call in progress (see trap.c)
*/
psig:
longjmp(&u.u_qsave);
/*NOTREACHED*/
}
/*
* Remove a process from its wait queue
*/
unsleep(p)
register struct proc *p;
{
register struct slpque *qp;
register struct proc **hp;
int s;
s = splhigh();
if (p->p_wchan) {
hp = &(qp = &slpque[HASH(p->p_wchan)])->sq_head;
while (*hp != p)
hp = &(*hp)->p_link;
*hp = p->p_link;
if (qp->sq_tailp == &p->p_link)
qp->sq_tailp = hp;
p->p_wchan = 0;
}
splx(s);
}
/*
* Wake up all processes sleeping on chan.
*/
wakeup(chan)
register caddr_t chan;
{
register struct slpque *qp;
register struct proc *p, **q;
int s;
s = splhigh();
qp = &slpque[HASH(chan)];
restart:
for (q = &qp->sq_head; p = *q; ) {
#ifdef DIAGNOSTIC
if (p->p_rlink || p->p_stat != SSLEEP && p->p_stat != SSTOP)
panic("wakeup");
#endif
if (p->p_wchan==chan) {
p->p_wchan = 0;
*q = p->p_link;
if (qp->sq_tailp == &p->p_link)
qp->sq_tailp = q;
if (p->p_stat == SSLEEP) {
/* OPTIMIZED INLINE EXPANSION OF setrun(p) */
if (p->p_slptime > 1)
updatepri(p);
p->p_stat = SRUN;
if (p->p_flag & SLOAD)
setrq(p);
/*
* Since curpri is a usrpri,
* p->p_pri is always better than curpri.
*/
runrun++;
aston();
if ((p->p_flag&SLOAD) == 0) {
if (runout != 0) {
runout = 0;
wakeup((caddr_t)&runout);
}
wantin++;
}
/* END INLINE EXPANSION */
goto restart;
}
} else
q = &p->p_link;
}
splx(s);
}
/*
* Initialize the (doubly-linked) run queues
* to be empty.
*/
rqinit()
{
register int i;
for (i = 0; i < NQS; i++)
qs[i].ph_link = qs[i].ph_rlink = (struct proc *)&qs[i];
}
/*
* Set the process running;
* arrange for it to be swapped in if necessary.
*/
setrun(p)
register struct proc *p;
{
register int s;
s = splhigh();
switch (p->p_stat) {
case 0:
case SWAIT:
case SRUN:
case SZOMB:
default:
panic("setrun");
case SSTOP:
case SSLEEP:
unsleep(p); /* e.g. when sending signals */
break;
case SIDL:
break;
}
p->p_stat = SRUN;
if (p->p_flag & SLOAD)
setrq(p);
splx(s);
if (p->p_slptime > 1)
updatepri(p);
if (p->p_pri < curpri) {
runrun++;
aston();
}
if ((p->p_flag&SLOAD) == 0) {
if (runout != 0) {
runout = 0;
wakeup((caddr_t)&runout);
}
wantin++;
}
}
/*
* Set user priority.
* The rescheduling flag (runrun)
* is set if the priority is better
* than the currently running process.
*/
setpri(pp)
register struct proc *pp;
{
register int p;
p = (pp->p_cpu & 0377)/4;
p += PUSER + 2 * pp->p_nice;
if (pp->p_rssize > pp->p_maxrss && freemem < desfree)
p += 2*4; /* effectively, nice(4) */
if (p > 127)
p = 127;
if (p < curpri) {
runrun++;
aston();
}
pp->p_usrpri = p;
return (p);
}