plan9fox/sys/src/9/port/portclock.c
cinap_lenrek 28836f3ff5 kernel: avoid useless mmu flushes, implement better wait condition for procflushmmu()
procflushmmu() returns once all *OTHER* processors that had
matching processes running on them flushed ther tlb/mmu state.
the caller of procflush...() takes care of flushing "up" by
calling flushmmu() later.

if the current process matched, then that means m->flushmmu
would be set, and hzclock() would call flushmmu() again.

to avoid this, we now check up->newtlb in addition to m->flushmmu
in hzclock() before calling flushmmu().

we also maintain information on which process on what processor
to wait for locally, which helps making progress when multiple
procflushmmu()'s are running concurrently.

in addition, this makes the wait condition for procflushmmu()
more sophisticated, by validating if the processor still runs
the selected process and only if it matchatches, considers
the MACHP(nm)->flushmmu flag.
2019-12-07 02:13:51 +01:00

299 lines
4.8 KiB
C

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "tos.h"
struct Timers
{
Lock;
Timer *head;
};
static Timers timers[MAXMACH];
ulong intrcount[MAXMACH];
ulong fcallcount[MAXMACH];
static vlong
tadd(Timers *tt, Timer *nt)
{
Timer *t, **last;
/* Called with tt locked */
assert(nt->tt == nil);
switch(nt->tmode){
default:
panic("timer");
break;
case Trelative:
if(nt->tns <= 0)
nt->tns = 1;
nt->twhen = fastticks(nil) + ns2fastticks(nt->tns);
break;
case Tperiodic:
assert(nt->tns >= 100000); /* At least 100 µs period */
if(nt->twhen == 0){
/* look for another timer at same frequency for combining */
for(t = tt->head; t; t = t->tnext){
if(t->tmode == Tperiodic && t->tns == nt->tns)
break;
}
if (t)
nt->twhen = t->twhen;
else
nt->twhen = fastticks(nil);
}
nt->twhen += ns2fastticks(nt->tns);
break;
}
for(last = &tt->head; t = *last; last = &t->tnext){
if(t->twhen > nt->twhen)
break;
}
nt->tnext = *last;
*last = nt;
nt->tt = tt;
if(last == &tt->head)
return nt->twhen;
return 0;
}
static uvlong
tdel(Timer *dt)
{
Timer *t, **last;
Timers *tt;
tt = dt->tt;
if (tt == nil)
return 0;
for(last = &tt->head; t = *last; last = &t->tnext){
if(t == dt){
assert(dt->tt);
dt->tt = nil;
*last = t->tnext;
break;
}
}
if(last == &tt->head && tt->head)
return tt->head->twhen;
return 0;
}
/* add or modify a timer */
void
timeradd(Timer *nt)
{
Timers *tt;
vlong when;
/* Must lock Timer struct before Timers struct */
ilock(nt);
if(tt = nt->tt){
ilock(tt);
tdel(nt);
iunlock(tt);
}
tt = &timers[m->machno];
ilock(tt);
when = tadd(tt, nt);
if(when)
timerset(when);
iunlock(tt);
iunlock(nt);
}
void
timerdel(Timer *dt)
{
Mach *mp;
Timers *tt;
uvlong when;
/* avoid Tperiodic getting re-added */
dt->tmode = Trelative;
ilock(dt);
if(tt = dt->tt){
ilock(tt);
when = tdel(dt);
if(when && tt == &timers[m->machno])
timerset(tt->head->twhen);
iunlock(tt);
}
if((mp = dt->tactive) == nil || mp->machno == m->machno){
iunlock(dt);
return;
}
iunlock(dt);
/* rare, but tf can still be active on another cpu */
while(dt->tactive == mp && dt->tt == nil)
if(up->nlocks == 0 && islo())
sched();
}
void
hzclock(Ureg *ur)
{
m->ticks++;
if(m->proc)
m->proc->pc = ur->pc;
if(m->flushmmu){
if(up && up->newtlb)
flushmmu();
m->flushmmu = 0;
}
accounttime();
kmapinval();
if(kproftimer != nil)
kproftimer(ur->pc);
if(active.machs[m->machno] == 0)
return;
if(active.exiting)
exit(0);
if(m->machno == 0)
checkalarms();
if(up && up->state == Running){
if(userureg(ur)){
/* user profiling clock */
Tos *tos = (Tos*)(USTKTOP-sizeof(Tos));
tos->clock += TK2MS(1);
segclock(ur->pc);
}
hzsched(); /* in proc.c */
}
}
void
timerintr(Ureg *u, Tval)
{
Timer *t;
Timers *tt;
uvlong when, now;
int callhzclock;
intrcount[m->machno]++;
callhzclock = 0;
tt = &timers[m->machno];
now = fastticks(nil);
ilock(tt);
while(t = tt->head){
/*
* No need to ilock t here: any manipulation of t
* requires tdel(t) and this must be done with a
* lock to tt held. We have tt, so the tdel will
* wait until we're done
*/
when = t->twhen;
if(when > now){
timerset(when);
iunlock(tt);
if(callhzclock)
hzclock(u);
return;
}
tt->head = t->tnext;
assert(t->tt == tt);
t->tt = nil;
t->tactive = MACHP(m->machno);
fcallcount[m->machno]++;
iunlock(tt);
if(t->tf)
(*t->tf)(u, t);
else
callhzclock++;
t->tactive = nil;
ilock(tt);
if(t->tmode == Tperiodic)
tadd(tt, t);
}
iunlock(tt);
}
void
timersinit(void)
{
Timer *t;
/*
* T->tf == nil means the HZ clock for this processor.
*/
todinit();
t = malloc(sizeof(*t));
if(t == nil)
panic("timersinit: no memory for Timer");
t->tmode = Tperiodic;
t->tt = nil;
t->tns = 1000000000/HZ;
t->tf = nil;
timeradd(t);
}
Timer*
addclock0link(void (*f)(void), int ms)
{
Timer *nt;
uvlong when;
/* Synchronize to hztimer if ms is 0 */
nt = malloc(sizeof(Timer));
if(nt == nil)
panic("addclock0link: no memory for Timer");
if(ms == 0)
ms = 1000/HZ;
nt->tns = (vlong)ms*1000000LL;
nt->tmode = Tperiodic;
nt->tt = nil;
nt->tf = (void (*)(Ureg*, Timer*))f;
ilock(&timers[0]);
when = tadd(&timers[0], nt);
if(when)
timerset(when);
iunlock(&timers[0]);
return nt;
}
/*
* This tk2ms avoids overflows that the macro version is prone to.
* It is a LOT slower so shouldn't be used if you're just converting
* a delta.
*/
ulong
tk2ms(ulong ticks)
{
uvlong t, hz;
t = ticks;
hz = HZ;
t *= 1000L;
t = t/hz;
ticks = t;
return ticks;
}
ulong
ms2tk(ulong ms)
{
/* avoid overflows at the cost of precision */
if(ms >= 1000000000/HZ)
return (ms/1000)*HZ;
return (ms*HZ+500)/1000;
}