fastfetch reported 3 seconds of uptime after 15 real ones, `time` claimed
3.12s for a 20-second command, and ping's one-second interval was closer to
six. One cause, not three: `ticks` was advancing at ~14.5 Hz instead of 100.
It was not the timer. virtio-gpu-pci shares IRQ 11 with the e1000 and
registered no interrupt handler at all. PCI INTx is level-triggered, so the
device held the line asserted, the kernel signalled EOI, and it fired again
immediately -- 72,000 interrupts a second, 83% of CPU0, starving the tick.
The e1000's handler read ICR as zero half a million times, correctly
answering "not mine", which is what made it look like a network-free problem.
Reading virtio's ISR byte clears it and drops the line. That is the fix.
irq_dispatch now measures each line's rate against the TSC -- not ticks,
which is the first casualty of a storm -- and names the attached handlers
once above 20000/s. It reproduces the original fault as a single line.
Found along the way, and fixed because they were real:
- lapicinit loaded TICR with xv6's uncalibrated 10000000, whose own comment
admitted it was never measured. calibrate_timers() now measures the LAPIC
and the TSC against the 8254 PIT. (It came back at 999.9 MHz, so this was
not the bug -- but the next machine is not obliged to be 1 GHz.)
- microdelay() was an empty function body. Every microdelay(1000) in the
xHCI reset path and the USB hub power-on sequence returned instantly.
- HZ is a constant in param.h now, not the literal 100 written out at a
dozen ticks-to-seconds conversions. USER_HZ is separate and deliberately
does not follow it: musl answers sysconf(_SC_CLK_TCK) with 100 always, so
times() and /proc/stat scale at the syscall boundary the way Linux does.
- TIOCGWINSZ answered a hardcoded 24x80. gfx_cols()/gfx_rows() have known
the real geometry all along; at 1024x768 the console is 128x48, so every
program that lays out a screen was formatting for the wrong one.
- TCFLSH fell through to the catch-all `return 0`, claiming to discard
input without discarding any.
uptime now advances 5.01 seconds per 5 real seconds. schfs-check passes.
Full investigation, including the two wrong hypotheses and the measurement
flaw that protected them, in docs/history/kernel.md section 13.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
139 lines
3.3 KiB
C
139 lines
3.3 KiB
C
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "date.h"
|
|
#include "spinlock.h"
|
|
#include "proc.h"
|
|
#include "x86.h"
|
|
#include "syscall.h"
|
|
#include "posix.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
|
|
addr_t
|
|
sys_alarm(void)
|
|
{
|
|
int seconds;
|
|
if (argint(0, &seconds) < 0)
|
|
return -1;
|
|
myproc()->alarm_ticks = seconds * HZ;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_gettimeofday(void)
|
|
{
|
|
struct timeval *tv;
|
|
if (argptr(0, (char**)&tv, sizeof(*tv)) < 0)
|
|
return -1;
|
|
if (tv) {
|
|
// Real time, from the hardware clock read at boot plus uptime -- not
|
|
// uptime alone, which is what this used to return and why every machine
|
|
// thought it was 1970. See kernel/core/time.c.
|
|
uint64 sec, usec;
|
|
wallclock(&sec, &usec);
|
|
tv->tv_sec = (long)sec;
|
|
tv->tv_usec = (long)usec;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_nanosleep(void)
|
|
{
|
|
addr_t tsaddr, raw;
|
|
if (argaddr(0, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
|
|
return -1;
|
|
int64 sec = (int64)raw;
|
|
if (sec > 0) {
|
|
acquire(&tickslock);
|
|
uint t0 = ticks;
|
|
while (ticks - t0 < (uint)(sec * HZ)) {
|
|
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
|
sleep(&ticks, &tickslock);
|
|
}
|
|
release(&tickslock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_clock_gettime(void)
|
|
{
|
|
int clkid;
|
|
struct timespec *tp;
|
|
argint(0, &clkid);
|
|
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0)
|
|
return -1;
|
|
// CLOCK_MONOTONIC (1) counts from boot; everything else is treated as a
|
|
// realtime clock and counts from the epoch. Returning boot-relative values
|
|
// for CLOCK_REALTIME is what made timestamps land in 1970.
|
|
uint64 sec, usec;
|
|
wallclock(&sec, &usec);
|
|
if (clkid == 1)
|
|
sec -= boot_epoch;
|
|
tp->tv_sec = (long)sec;
|
|
tp->tv_nsec = (long)(usec * 1000);
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_getitimer(void) { return 0; } // stub
|
|
|
|
addr_t
|
|
sys_setitimer(void)
|
|
{
|
|
int which;
|
|
addr_t newp;
|
|
argint(0, &which); argaddr(1, &newp);
|
|
// We don't implement interval timers; just track the alarm if it's ITIMER_REAL.
|
|
if (which == 0 && newp) {
|
|
struct itimerval iv;
|
|
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
|
|
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * HZ +
|
|
iv.it_value.tv_usec / USEC_PER_TICK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_time(void)
|
|
{
|
|
addr_t tloc;
|
|
argaddr(0, &tloc);
|
|
uint64 sec;
|
|
wallclock(&sec, 0);
|
|
long t = (long)sec;
|
|
if (tloc)
|
|
put_user(t, tloc);
|
|
return (addr_t)t;
|
|
}
|
|
|
|
addr_t
|
|
sys_clock_getres(void)
|
|
{
|
|
struct timespec *tp;
|
|
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0) return 0;
|
|
if (tp) { tp->tv_sec = 0; tp->tv_nsec = 10000000L; } // 10ms resolution
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_clock_nanosleep(void)
|
|
{
|
|
// Treat the same as nanosleep: arg2 is the timespec, arg1 is flags.
|
|
// Linux: clock_nanosleep(clockid, flags, rqtp, rmtp)
|
|
addr_t tsaddr, raw;
|
|
if (argaddr(2, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
|
|
return -1;
|
|
int64 sec = (int64)raw;
|
|
if (sec > 0) {
|
|
acquire(&tickslock);
|
|
uint t0 = ticks;
|
|
while (ticks - t0 < (uint)(sec * HZ)) {
|
|
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
|
sleep(&ticks, &tickslock);
|
|
}
|
|
release(&tickslock);
|
|
}
|
|
return 0;
|
|
}
|