time: read the clock from the TSC, not from counting interrupts
A timer interrupt that arrives while interrupts are disabled is not delayed,
it is lost -- the LAPIC in periodic mode holds one pending interrupt and drops
the rest. So anything the kernel does with interrupts off for longer than
10 ms makes `ticks` run slow, and it never catches up.
That has now presented twice as "uptime is wrong": once as an interrupt storm
taking 83% of the CPU, once as heavy console output on a display that is
expensive to draw to. Both times the tick was the messenger, and both times it
looked like a timekeeping bug.
monotonic() reads the TSC instead. It is a counter in the silicon that
advances whether or not this kernel is paying attention, it is already
measured against the PIT at boot, and reading it is one instruction and no
lock. wallclock(), /proc/uptime, sysinfo, getrusage and times() all go through
it now.
`ticks` keeps the job it is good at -- the scheduler preempts on it, sleep()
counts it -- where losing one means a slightly longer sleep rather than a
wrong answer about what time it is.
Lost ticks are no longer harmful, which makes them invisible, which is worse.
So /proc/interrupts now says
timer: 3383 ticks taken, 3387 expected
and names the gap when it grows, because a machine dropping timer interrupts
is spending too long with interrupts disabled, and that is worth knowing
before it turns into a clock that lies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
65906639da
commit
bbc512a51c
7 changed files with 192 additions and 37 deletions
|
|
@ -392,11 +392,10 @@ addr_t sys_getrusage(void)
|
|||
argint(0, &who);
|
||||
if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -1;
|
||||
memset(ru, 0, sizeof(*ru));
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
ru->ru_utime.tv_sec = (long)(xt / HZ);
|
||||
ru->ru_utime.tv_usec = (long)((xt % HZ) * (1000000 / HZ));
|
||||
uint64 rsec, rusec;
|
||||
monotonic(&rsec, &rusec);
|
||||
ru->ru_utime.tv_sec = (long)rsec;
|
||||
ru->ru_utime.tv_usec = (long)rusec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -408,14 +407,15 @@ addr_t sys_times(void)
|
|||
struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t));
|
||||
if (t) memset(t, 0, sizeof(*t));
|
||||
}
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
// Scaled to USER_HZ, not returned raw. musl answers sysconf(_SC_CLK_TCK)
|
||||
// with 100 unconditionally, so a shell's `time` builtin divides whatever
|
||||
// this returns by 100 -- handing it the internal tick count is only right
|
||||
// while HZ happens to equal 100, and silently wrong the day it does not.
|
||||
return (addr_t)((uint64)xt * USER_HZ / HZ);
|
||||
// USER_HZ units, measured from the TSC.
|
||||
//
|
||||
// musl answers sysconf(_SC_CLK_TCK) with 100 unconditionally, so a shell's
|
||||
// `time` builtin divides whatever this returns by 100. Deriving it from
|
||||
// the tick count made `time` under-report whenever ticks were being lost
|
||||
// -- which is how a 20-second command came to print 3.12s.
|
||||
uint64 tsec, tusec;
|
||||
monotonic(&tsec, &tusec);
|
||||
return (addr_t)(tsec * USER_HZ + (tusec * USER_HZ) / 1000000);
|
||||
}
|
||||
|
||||
addr_t sys_sysinfo(void)
|
||||
|
|
@ -425,10 +425,10 @@ addr_t sys_sysinfo(void)
|
|||
struct sysinfo *si = (struct sysinfo*)user_ptr(bufaddr, sizeof(*si));
|
||||
if (!si) return -EFAULT;
|
||||
memset(si, 0, sizeof(*si));
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
si->uptime = (long)(xt / HZ);
|
||||
// TSC, like everything else that reports elapsed time.
|
||||
uint64 upsec, upusec;
|
||||
monotonic(&upsec, &upusec);
|
||||
si->uptime = (long)upsec;
|
||||
// Real values: PHYSTOP is the top of usable physical memory and the page
|
||||
// allocator knows exactly how much of it is still on the free list. These
|
||||
// used to be the constants 256MB/128MB, invented and always wrong.
|
||||
|
|
|
|||
Loading…
Reference in a new issue