kernel: the clock ran 7x slow because the GPU never acknowledged its interrupt

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
This commit is contained in:
Nexoniarz 2026-09-12 21:34:36 +02:00
commit 9d4cdb44fc
17 changed files with 479 additions and 37 deletions

View file

@ -49,6 +49,85 @@ lapicw(int index, int value)
lapic[ID]; // wait for write to finish, by reading
}
// ---------------------------------------------------------------------
// Timer calibration
// ---------------------------------------------------------------------
// The LAPIC timer counts down at the APIC bus frequency, and nothing in the
// architecture tells you what that frequency is. xv6 loaded TICR with
// 10000000 and hoped; its own comment admitted the gap -- "if xv6 cared more
// about precise timekeeping, TICR would be calibrated using an external time
// source." That value yields the intended 100 Hz only if the bus happens to
// run at exactly 1 GHz.
//
// Under KVM it does not, and the error is not subtle. The kernel believed
// ticks arrived 100 times a second while they were arriving roughly 18 times
// a second, so every number derived from ticks was wrong by that same factor,
// in the same direction, everywhere at once: uptime ran slow, `time` reported
// three seconds for twenty, ping's one-second interval stretched to six. It
// read as three unrelated bugs in three unrelated programs. It was this line.
//
// So measure it. The 8254 PIT is exactly the external time source the comment
// asked for: its input clock is fixed at 1193182 Hz by the hardware, it is
// always present, and channel 2 is the one whose gate software can raise and
// whose output software can poll -- a stopwatch that needs no interrupt, which
// matters because this runs before the IDT is ready to take one.
#define PIT_HZ 1193182 // 8254 input clock, fixed by definition
#define PIT_CH2 0x42
#define PIT_CMD 0x43
#define PIT_GATE 0x61 // bit 0: ch2 gate, bit 1: speaker, bit 5: ch2 output
uint lapic_timer_hz; // measured LAPIC timer counts per second
uint64 tsc_hz; // measured TSC cycles per second
static void
calibrate_timers(void)
{
// 50 ms: long enough that the port I/O around the measurement is noise,
// short enough that nobody notices it in boot time. The count fits in the
// 16 bits the PIT has (59659), which is the real upper bound here.
const uint us = 50000;
uint count = (uint)(((uint64)PIT_HZ * us) / 1000000);
// Channel 2, mode 0 (interrupt on terminal count), lobyte then hibyte.
// Gate high so it counts; the speaker bit stays clear so the box is silent.
outb(PIT_GATE, (inb(PIT_GATE) & ~0x02) | 0x01);
outb(PIT_CMD, 0xB0);
outb(PIT_CH2, count & 0xFF);
outb(PIT_CH2, (count >> 8) & 0xFF);
// A free-running LAPIC counter for the duration: one-shot, masked, started
// at the largest count there is so it cannot wrap inside 50 ms.
lapicw(TDCR, X1);
lapicw(TIMER, MASKED);
lapicw(TICR, 0xFFFFFFFF);
uint64 tsc0 = rdtsc();
uint lap0 = lapic[TCCR];
while (!(inb(PIT_GATE) & 0x20)) // ch2 output rises at terminal count
;
uint lap1 = lapic[TCCR];
uint64 tsc1 = rdtsc();
lapicw(TICR, 0); // stop counting
// TCCR counts down, so elapsed counts are start minus end.
lapic_timer_hz = (uint)(((uint64)(lap0 - lap1) * 1000000) / us);
tsc_hz = ((tsc1 - tsc0) * 1000000) / us;
// If the measurement came back implausible -- no LAPIC, a hypervisor that
// stops the counter, a PIT that never raises its output -- fall back to the
// old assumption rather than dividing by zero and taking the machine down.
// Wrong-but-running beats a triple fault, and the printed value says which
// case this is.
if (lapic_timer_hz < HZ * 1000u) {
cprintf("cpu0: lapic calibration implausible (%u Hz), assuming 1 GHz bus\n",
lapic_timer_hz);
lapic_timer_hz = 1000000000u; // the 1 GHz that TICR=10000000 implied
}
}
void
lapicinit(void)
{
@ -58,13 +137,19 @@ lapicinit(void)
// Enable local APIC; set spurious interrupt vector.
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
// The timer repeatedly counts down at bus frequency
// from lapic[TICR] and then issues an interrupt.
// If xv6 cared more about precise timekeeping,
// TICR would be calibrated using an external time source.
// The timer counts down at the APIC bus frequency and interrupts at zero.
// What that frequency *is*, calibrate_timers() had to go and measure --
// see the comment above it for why loading a constant here does not work.
if (lapic_timer_hz == 0) {
calibrate_timers();
cprintf("cpu0: lapic timer %u Hz, tsc %u MHz, tick = %u counts at %d Hz\n",
lapic_timer_hz, (uint)(tsc_hz / 1000000),
lapic_timer_hz / HZ, HZ);
}
lapicw(TDCR, X1);
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
lapicw(TICR, 10000000);
lapicw(TICR, lapic_timer_hz / HZ);
// Disable logical interrupt lines.
lapicw(LINT0, MASKED);
@ -132,10 +217,36 @@ lapiceoi(void)
}
// Spin for a given number of microseconds.
// On real hardware would want to tune this dynamically.
//
// This was an empty function body -- xv6's stub, carrying a comment saying
// real hardware would want to tune it dynamically. Nothing ever did, so every
// microdelay() in the tree returned immediately: the xHCI reset sequence, the
// USB hub power-on wait, the UART drain. Those delays are written into the
// specifications because the hardware genuinely needs the time, and a port
// that "works anyway" is one that is racing and winning.
//
// The TSC is the right clock for this: calibrate_timers() measured it in the
// same pass as the LAPIC, and unlike the LAPIC timer it is not already busy
// driving the tick.
void
microdelay(int us)
{
if (us <= 0)
return;
// Before calibration (early boot, and the AP trampoline) there is no
// measured frequency to work from. Spin on a rough count instead: the
// figure is wrong, but being approximately late is the entire point of the
// call, and being instantly early is what broke it.
if (tsc_hz == 0) {
for (volatile int i = 0; i < us * 100; i++)
;
return;
}
uint64 end = rdtsc() + ((uint64)us * tsc_hz) / 1000000;
while (rdtsc() < end)
asm volatile("pause");
}
#define CMOS_PORT 0x70

View file

@ -567,6 +567,23 @@ cons_set_termios(const struct termios *in)
wakeup(&input.r);
}
// Discard everything typed but not yet consumed: completed lines waiting in
// the buffer and the partial line still being edited. TCFLSH (tcflush) is the
// caller, and a line editor issues one whenever it takes the terminal back --
// after an interrupt, or when a command that read from the tty exits. Without
// it the keys pressed while the last command ran arrive at the next prompt.
//
// A pending EOF goes too: end of input is buffer state like any other, and
// leaving it set would end the *next* read instead of the one it was typed at.
void
cons_flush_input(void)
{
acquire(&input.lock);
input.e = input.w = input.r;
input.eof = 0;
release(&input.lock);
}
static void
echo(int c)
{

View file

@ -257,7 +257,7 @@ gather(char *reason, addr_t *pcs, int npcs)
{
char *p = put_dec(f->value, e, (uint64)ticks);
p = put_str(p, e, " ticks (about ");
p = put_dec(p, e, (uint64)ticks / 100);
p = put_dec(p, e, (uint64)ticks / HZ);
put_str(p, e, " s)");
}

View file

@ -91,7 +91,7 @@ time_init(void)
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
// the moment this function ran. It is only a fraction of a second, but it
// keeps boot_epoch meaning exactly what its name says.
uint64 up = ticks / 100;
uint64 up = ticks / HZ;
boot_epoch = now > up ? now - up : now;
cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n",
@ -110,7 +110,7 @@ wallclock(uint64 *sec, uint64 *usec)
release(&tickslock);
if (sec)
*sec = boot_epoch + xticks / 100;
*sec = boot_epoch + xticks / HZ;
if (usec)
*usec = (uint64)(xticks % 100) * 10000;
*usec = (uint64)(xticks % HZ) * (1000000 / HZ);
}

View file

@ -95,8 +95,8 @@ input_report(int dev, int type, int code, int value)
d->r++;
e = &d->ev[d->w % NEVENT];
e->time.tv_sec = ticks / 100;
e->time.tv_usec = (ticks % 100) * 10000;
e->time.tv_sec = ticks / HZ;
e->time.tv_usec = (ticks % HZ) * USEC_PER_TICK;
e->type = (uint16)type;
e->code = (uint16)code;
e->value = (int32)value;

View file

@ -6,6 +6,7 @@
#include "traps.h"
#include "spinlock.h"
#include "irq.h"
#include "x86.h"
struct irq_action {
irq_handler_t fn;
@ -134,6 +135,62 @@ irq_unregister(int irq, irq_handler_t fn, void *arg)
release(&irqlock);
}
// Notice a line that is firing far faster than any real device would, and say
// so once.
//
// A level-triggered PCI interrupt that nobody acknowledges re-fires the instant
// EOI is signalled, so the line runs at whatever rate the CPU can take traps --
// tens of thousands per second. That starves everything else on the machine,
// and it does so silently: the symptom appears wherever the missing CPU time
// hurts first, which is nowhere near the device at fault.
//
// It cost a long afternoon to find one of these. virtio-gpu shared IRQ 11 with
// the e1000 and registered no handler, so the line storm at ~72 kHz and took
// 83% of CPU0. What that looked like from userspace was a clock running seven
// times slow -- uptime, `time`, and ping's interval all wrong together, which
// reads as a timekeeping bug and sends you into the timer code. The timer was
// fine the whole time. This is the message that would have said so.
//
// The rate is measured against the TSC, deliberately: `ticks` is the first
// casualty of a storm, so a storm detector that counted ticks would go quiet
// exactly when it was needed.
#define STORM_SAMPLE 4096 // dispatches between rate checks
#define STORM_HZ 20000 // per second; no real device sustains this
static void
storm_check(int irq)
{
static uint64 sample_tsc[NIRQ];
static uint sample_n[NIRQ];
static uchar reported[NIRQ];
if(reported[irq] || !tsc_hz)
return;
if(++sample_n[irq] < STORM_SAMPLE)
return;
uint64 now = rdtsc(), prev = sample_tsc[irq];
sample_n[irq] = 0;
sample_tsc[irq] = now;
if(!prev)
return; // first sample: no interval to divide by
uint64 elapsed = now - prev;
if(elapsed == 0)
return;
uint64 rate = (uint64)STORM_SAMPLE * tsc_hz / elapsed;
if(rate < STORM_HZ)
return;
reported[irq] = 1;
cprintf("irq %d: %d interrupts/second -- a device on this line is not "
"acknowledging it.\n", irq, (int)rate);
for(int i = 0; i < line[irq].n; i++)
cprintf("irq %d: handler '%s' is attached\n", irq, line[irq].act[i].name);
if(line[irq].n == 0)
cprintf("irq %d: no handler is attached at all\n", irq);
}
void
irq_dispatch(int irq)
{
@ -145,6 +202,7 @@ irq_dispatch(int irq)
n = line[irq].n;
if(n == 0){
line[irq].unhandled++;
storm_check(irq);
return;
}
@ -154,6 +212,7 @@ irq_dispatch(int irq)
line[irq].act[i].count++;
line[irq].act[i].fn(line[irq].act[i].arg);
}
storm_check(irq);
}
// ---------------------------------------------------------------------------

View file

@ -130,11 +130,12 @@ gen_meminfo(struct sbuf *sb)
static void
gen_uptime(struct sbuf *sb)
{
uint t = uptime(); // timer ticks, 100 Hz
sb_putu(sb, t / 100);
uint t = uptime(); // timer ticks, HZ of them per second
uint frac = (t % HZ) * 100 / HZ; // hundredths, whatever HZ happens to be
sb_putu(sb, t / HZ);
sb_putc(sb, '.');
sb_putu(sb, (t % 100) / 10);
sb_putu(sb, (t % 100) % 10);
sb_putu(sb, frac / 10);
sb_putu(sb, frac % 10);
sb_puts(sb, " 0.00\n"); // idle time: not tracked
}
@ -214,7 +215,8 @@ gen_version(struct sbuf *sb)
static void
gen_stat(struct sbuf *sb)
{
uint t = uptime();
// USER_HZ, like times() -- everything that parses /proc/stat divides by 100.
uint t = (uint)((uint64)uptime() * USER_HZ / HZ);
sb_puts(sb, "cpu ");
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
// Online, for the same reason as cpuinfo: top and friends count the cpuN

View file

@ -14,6 +14,7 @@
#include "types.h"
#include "defs.h"
#include "irq.h"
#include "param.h"
#include "x86.h"
#include "spinlock.h"
@ -35,6 +36,9 @@
struct vgpu {
volatile uint8 *common; // common configuration structure
volatile uint8 *notify; // notification area
// The interrupt status byte. Reading it is what tells the device we have
// noticed, and therefore what makes it drop the PCI interrupt line.
volatile uint8 *isr;
uint32 notify_mult; // queue_notify_off multiplier
// The queue. Allocated as one contiguous block from the DMA arena, because
@ -396,6 +400,33 @@ const struct display_ops virtio_gpu_display_ops = {
// --- probe -----------------------------------------------------------------
// Acknowledge the device's interrupt.
//
// This driver polls for command completion and wants no notifications at all,
// which is why nothing here reacts to the cause -- but "wants none" and "gets
// none" are different things, and a PCI interrupt line is level-triggered.
// The device raises INTx, the CPU takes the interrupt, the kernel signals EOI,
// and if nobody has cleared the device's interrupt status the line is still
// asserted, so it fires again immediately. Forever.
//
// That is not hypothetical. With no handler registered here at all, this
// device shared IRQ 11 with the e1000 under QEMU and storme at roughly 72,000
// interrupts a second, eating 83% of CPU0. The visible symptom was nowhere
// near the graphics driver: the timer interrupt was starved, so `ticks`
// advanced at about 14 Hz instead of 100, and every duration in the system --
// uptime, `time`, sleep, ping's one-second interval -- came out ~7x short.
// See docs/history/kernel.md.
//
// Reading the ISR byte clears it and deasserts the line. That single read is
// the entire handler, and it is not optional.
static void
vgpu_intr(void *arg)
{
struct vgpu *g = arg;
if (g && g->isr)
(void)*g->isr;
}
static int
vgpu_probe(struct pci_dev *d)
{
@ -414,6 +445,7 @@ vgpu_probe(struct pci_dev *d)
g->common = find_cfg(d, VIRTIO_PCI_CAP_COMMON_CFG, 0);
g->notify = find_cfg(d, VIRTIO_PCI_CAP_NOTIFY_CFG, &g->notify_mult);
g->isr = find_cfg(d, VIRTIO_PCI_CAP_ISR_CFG, 0);
if (!g->common || !g->notify) {
cprintf("virtio-gpu: no modern virtio capabilities; not a 1.0 device\n");
return -1;
@ -465,9 +497,16 @@ vgpu_probe(struct pci_dev *d)
return -1;
}
// Level-triggered and active low, like every other PCI INTx line -- see
// vgpu_intr for what happens when this registration is missing.
if (g->isr)
irq_register(d->irq, vgpu_intr, g, "virtio-gpu", 1);
else
cprintf("virtio-gpu: no ISR capability; leaving irq %d unclaimed\n", d->irq);
g->ready = 1;
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d\n",
g->width, g->height, g->fb_bytes / 1024, g->qsize);
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d, irq %d\n",
g->width, g->height, g->fb_bytes / 1024, g->qsize, d->irq);
return 0;
}

View file

@ -15,6 +15,7 @@
#include "uaccess.h"
#include "errno.h"
#include "schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */
#include "gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */
// -----------------------------------------------------------------------
// Linux-ABI-compatible struct stat (x86_64 "struct stat" from
@ -1042,10 +1043,41 @@ addr_t sys_ioctl(void)
if (request == TIOCGWINSZ) {
struct winsize *ws = (struct winsize*)user_ptr(argp, sizeof(*ws));
if (!ws) return -EFAULT;
ws->ws_row = 24; ws->ws_col = 80; ws->ws_xpixel = ws->ws_ypixel = 0;
// The console's real geometry, not a VT100's.
//
// This used to answer 24x80 unconditionally -- the size of a terminal
// SchOS has never drawn. Every program that lays out a screen asks
// this one question first: a line editor to decide where to wrap, `ls`
// to pick a column count, fastfetch to place its logo beside the text.
// At 1024x768 with the 8x16 font the console is 128x48, so all of them
// were formatting for a screen a third the area of the real one, and
// each looked like a separate bug in a separate program.
int cols = gfx_cols(), rows = gfx_rows();
// Serial-only boot, with no framebuffer attached: 80x24 is then a
// stated default rather than a wrong measurement.
ws->ws_col = cols > 0 ? (uint16)cols : 80;
ws->ws_row = rows > 0 ? (uint16)rows : 24;
ws->ws_xpixel = (uint16)gfx_width();
ws->ws_ypixel = (uint16)gfx_height();
return 0;
}
// The console's size is a property of the framebuffer, so a request to
// change it cannot be honoured -- but it is not an error either: programs
// set it to propagate a size they already learned. Accept and ignore.
if (request == TIOCSWINSZ) return 0;
// TCFLSH discards buffered input, output, or both. Unlike every other
// request here the argument is the queue selector *by value*, not a
// pointer -- passing it to user_ptr would fault on a legitimate call.
//
// Output needs no action: the console writes straight to the framebuffer
// and has no queue to drop. Input does, and answering 0 without draining
// it -- which is what the catch-all below did -- is how a keystroke typed
// during a command survives the flush and lands in the next prompt.
if (request == TCFLSH) {
if (argp == TCIFLUSH || argp == TCIOFLUSH)
cons_flush_input();
return 0;
}
// Controlling-terminal foreground process group.
//
// TIOCGPGRP used to answer with myproc()->pid, which happened to agree with

View file

@ -91,7 +91,10 @@ sys_poll(void)
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
int timeout_ticks = timeout_ms < 0 ? -1 : (timeout_ms + 9) / 10;
// Rounded up: a caller asking for any wait at all must not get zero ticks,
// which poll() below treats as "return immediately".
int timeout_ticks = timeout_ms < 0 ? -1
: (timeout_ms + MSEC_PER_TICK - 1) / MSEC_PER_TICK;
int r = do_poll(fds, nfds, timeout_ticks);
if (r >= 0 && nfds > 0 &&
@ -123,7 +126,7 @@ sys_ppoll(void)
int64 ts[2]; // {tv_sec, tv_nsec}
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
}
int r = do_poll(fds, nfds, timeout_ticks);
@ -178,7 +181,7 @@ sys_select(void)
int64 tv[2]; // {tv_sec, tv_usec}
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
return -EFAULT;
timeout_ticks = (int)(tv[0] * 100 + tv[1] / 10000);
timeout_ticks = (int)(tv[0] * HZ + tv[1] / USEC_PER_TICK);
}
for (;;) {
@ -252,7 +255,7 @@ sys_pselect6(void)
int64 ts[2];
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
}
for (;;) {

View file

@ -395,8 +395,8 @@ addr_t sys_getrusage(void)
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
ru->ru_utime.tv_sec = (long)(xt / 100);
ru->ru_utime.tv_usec = (long)((xt % 100) * 10000);
ru->ru_utime.tv_sec = (long)(xt / HZ);
ru->ru_utime.tv_usec = (long)((xt % HZ) * (1000000 / HZ));
return 0;
}
@ -411,7 +411,11 @@ addr_t sys_times(void)
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
return (addr_t)xt; // return clock ticks since boot
// 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);
}
addr_t sys_sysinfo(void)
@ -424,7 +428,7 @@ addr_t sys_sysinfo(void)
acquire(&tickslock);
uint xt = ticks;
release(&tickslock);
si->uptime = (long)(xt / 100);
si->uptime = (long)(xt / HZ);
// 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.

View file

@ -16,7 +16,7 @@ sys_alarm(void)
int seconds;
if (argint(0, &seconds) < 0)
return -1;
myproc()->alarm_ticks = seconds * 100;
myproc()->alarm_ticks = seconds * HZ;
return 0;
}
@ -48,7 +48,7 @@ sys_nanosleep(void)
if (sec > 0) {
acquire(&tickslock);
uint t0 = ticks;
while (ticks - t0 < (uint)(sec * 100)) {
while (ticks - t0 < (uint)(sec * HZ)) {
if (myproc()->killed) { release(&tickslock); return -EINTR; }
sleep(&ticks, &tickslock);
}
@ -89,8 +89,8 @@ sys_setitimer(void)
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 * 100 +
iv.it_value.tv_usec / 10000);
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * HZ +
iv.it_value.tv_usec / USEC_PER_TICK);
}
return 0;
}
@ -129,7 +129,7 @@ sys_clock_nanosleep(void)
if (sec > 0) {
acquire(&tickslock);
uint t0 = ticks;
while (ticks - t0 < (uint)(sec * 100)) {
while (ticks - t0 < (uint)(sec * HZ)) {
if (myproc()->killed) { release(&tickslock); return -EINTR; }
sleep(&ticks, &tickslock);
}