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
308 lines
7.8 KiB
C
308 lines
7.8 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
// SchOS — POSIX-compatibility types used by kernel syscall handlers.
|
|
#pragma once
|
|
#include "types.h"
|
|
#include "date.h"
|
|
|
|
struct iovec {
|
|
void *iov_base;
|
|
uint64 iov_len;
|
|
};
|
|
|
|
// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal,
|
|
// since POSIX only requires 16 -- and returned EINVAL above it. But programs
|
|
// are written against the platform they run on, and on Linux that limit is
|
|
// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop,
|
|
// so `yes | head -2` failed with "Invalid argument" before producing a single
|
|
// byte. Nothing here costs per-entry memory; the count is just a loop bound.
|
|
#define UIO_MAXIOV 1024
|
|
|
|
// Simplified termios (matches Linux x86_64 layout enough for basic use).
|
|
struct termios {
|
|
uint32 c_iflag;
|
|
uint32 c_oflag;
|
|
uint32 c_cflag;
|
|
uint32 c_lflag;
|
|
uint8 c_line;
|
|
uint8 c_cc[19];
|
|
uint32 c_ispeed;
|
|
uint32 c_ospeed;
|
|
};
|
|
|
|
// Terminal flags, with Linux's values. The kernel honours the ones marked
|
|
// below; the rest are accepted and stored so a program that sets them and
|
|
// reads them back sees what it wrote, which is what cfmakeraw() and every
|
|
// line-editing library actually check.
|
|
//
|
|
// c_iflag
|
|
#define IGNBRK 0000001
|
|
#define BRKINT 0000002
|
|
#define IGNPAR 0000004
|
|
#define PARMRK 0000010
|
|
#define INPCK 0000020
|
|
#define ISTRIP 0000040 // honoured: strip the eighth bit
|
|
#define INLCR 0000100 // honoured: NL -> CR on input
|
|
#define IGNCR 0000200 // honoured: drop CR
|
|
#define ICRNL 0000400 // honoured: CR -> NL on input
|
|
#define IUCLC 0001000
|
|
#define IXON 0002000
|
|
#define IXANY 0004000
|
|
#define IXOFF 0010000
|
|
#define IMAXBEL 0020000
|
|
#define IUTF8 0040000
|
|
|
|
// c_oflag
|
|
#define OPOST 0000001 // honoured: enable output processing
|
|
#define OLCUC 0000002
|
|
#define ONLCR 0000004 // honoured: NL -> CR NL on output
|
|
#define OCRNL 0000010
|
|
#define ONOCR 0000020
|
|
#define ONLRET 0000040
|
|
|
|
// c_cflag
|
|
#define CS8 0000060
|
|
#define CREAD 0000200
|
|
#define HUPCL 0002000
|
|
#define CLOCAL 0004000
|
|
|
|
// c_lflag
|
|
#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals
|
|
#define ICANON 0000002 // honoured: line-at-a-time with editing
|
|
#define ECHO 0000010 // honoured
|
|
#define ECHOE 0000020 // honoured: ERASE rubs the character out
|
|
#define ECHOK 0000040
|
|
#define ECHONL 0000100 // honoured: echo NL even with ECHO off
|
|
#define NOFLSH 0000200
|
|
#define TOSTOP 0000400
|
|
#define IEXTEN 0100000
|
|
|
|
// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is
|
|
// why they are only meaningful in the mode the other pair is not.
|
|
#define VINTR 0
|
|
#define VQUIT 1
|
|
#define VERASE 2
|
|
#define VKILL 3
|
|
#define VEOF 4
|
|
#define VTIME 5
|
|
#define VMIN 6
|
|
#define VSWTC 7
|
|
#define VSTART 8
|
|
#define VSTOP 9
|
|
#define VSUSP 10
|
|
#define VEOL 11
|
|
#define VREPRINT 12
|
|
#define VDISCARD 13
|
|
#define VWERASE 14
|
|
#define VLNEXT 15
|
|
#define VEOL2 16
|
|
|
|
struct winsize {
|
|
uint16 ws_row;
|
|
uint16 ws_col;
|
|
uint16 ws_xpixel;
|
|
uint16 ws_ypixel;
|
|
};
|
|
|
|
struct rlimit {
|
|
uint64 rlim_cur;
|
|
uint64 rlim_max;
|
|
};
|
|
|
|
// Linux ioctl codes we handle.
|
|
#define TCGETS 0x5401
|
|
#define TCSETS 0x5402
|
|
#define TCSETSW 0x5403
|
|
#define TCSETSF 0x5404
|
|
#define TCFLSH 0x540B
|
|
// TCFLSH queue selectors. Not a pointer -- these are the argument itself.
|
|
#define TCIFLUSH 0
|
|
#define TCOFLUSH 1
|
|
#define TCIOFLUSH 2
|
|
#define TIOCGPGRP 0x540F
|
|
#define TIOCSPGRP 0x5410
|
|
#define TIOCGWINSZ 0x5413
|
|
#define TIOCSWINSZ 0x5414
|
|
#define TIOCSCTTY 0x540E
|
|
#define TIOCGSERIAL 0x541E
|
|
#define FIONREAD 0x541B
|
|
// FICLONE: share another file's data blocks with this one. The argument is a
|
|
// file descriptor, not a pointer. Linux's value, so `cp --reflink` works.
|
|
#define FICLONE 0x40049409
|
|
#define TIOCEXCL 0x540C
|
|
#define TIOCNXCL 0x540D
|
|
|
|
// arch_prctl commands
|
|
#define ARCH_SET_GS 0x1001
|
|
#define ARCH_SET_FS 0x1002
|
|
#define ARCH_GET_FS 0x1003
|
|
#define ARCH_GET_GS 0x1004
|
|
|
|
// clone flags (subset)
|
|
#define CLONE_VM 0x00000100
|
|
#define CLONE_THREAD 0x00010000
|
|
#define CLONE_SIGHAND 0x00000800
|
|
|
|
// futex ops
|
|
#define FUTEX_WAIT 0
|
|
#define FUTEX_WAKE 1
|
|
#define FUTEX_WAIT_PRIVATE 128
|
|
#define FUTEX_WAKE_PRIVATE 129
|
|
|
|
// Resource limit codes (POSIX)
|
|
#define RLIMIT_AS 9
|
|
#define RLIMIT_STACK 3
|
|
#define RLIMIT_NOFILE 7
|
|
|
|
// clock IDs
|
|
#define CLOCK_REALTIME 0
|
|
#define CLOCK_MONOTONIC 1
|
|
|
|
struct timespec {
|
|
long tv_sec;
|
|
long tv_nsec;
|
|
};
|
|
|
|
struct itimerval {
|
|
struct timeval it_interval;
|
|
struct timeval it_value;
|
|
};
|
|
|
|
struct rusage {
|
|
struct timeval ru_utime;
|
|
struct timeval ru_stime;
|
|
long ru_maxrss;
|
|
long ru_ixrss;
|
|
long ru_idrss;
|
|
long ru_isrss;
|
|
long ru_minflt;
|
|
long ru_majflt;
|
|
long ru_nswap;
|
|
long ru_inblock;
|
|
long ru_oublock;
|
|
long ru_msgsnd;
|
|
long ru_msgrcv;
|
|
long ru_nsignals;
|
|
long ru_nvcsw;
|
|
long ru_nivcsw;
|
|
};
|
|
|
|
struct tms {
|
|
long tms_utime;
|
|
long tms_stime;
|
|
long tms_cutime;
|
|
long tms_cstime;
|
|
};
|
|
|
|
struct sysinfo {
|
|
long uptime;
|
|
uint64 loads[3];
|
|
uint64 totalram;
|
|
uint64 freeram;
|
|
uint64 sharedram;
|
|
uint64 bufferram;
|
|
uint64 totalswap;
|
|
uint64 freeswap;
|
|
uint16 procs;
|
|
uint16 pad;
|
|
uint32 pad2;
|
|
uint64 totalhigh;
|
|
uint64 freehigh;
|
|
uint32 mem_unit;
|
|
char _f[8];
|
|
};
|
|
|
|
// Must match Linux's x86-64 struct statfs field for field, because the programs
|
|
// reading it were compiled against Linux's.
|
|
//
|
|
// f_fsid is the trap. Linux's fsid_t is `struct { int val[2]; }` -- eight bytes
|
|
// in total, not two eight-byte words -- and declaring it as int64[2] made this
|
|
// struct one slot too long from f_fsid onwards. Everything after it landed in
|
|
// the wrong place: userland read f_frsize out of f_namelen and got 255, so df
|
|
// computed its block counts in units of 255 bytes and reported a 32 MB root
|
|
// filesystem as 1992 KB. It went unnoticed for as long as it did because every
|
|
// field here used to be an invented constant, so the numbers were meaningless
|
|
// whichever slot they came from.
|
|
struct statfs {
|
|
int64 f_type;
|
|
int64 f_bsize;
|
|
int64 f_blocks;
|
|
int64 f_bfree;
|
|
int64 f_bavail;
|
|
int64 f_files;
|
|
int64 f_ffree;
|
|
int32 f_fsid[2];
|
|
int64 f_namelen;
|
|
int64 f_frsize;
|
|
int64 f_flags;
|
|
int64 f_spare[4];
|
|
};
|
|
|
|
// at-syscall dirfd sentinel
|
|
#define AT_FDCWD (-100)
|
|
#define AT_REMOVEDIR 0x200
|
|
#define AT_SYMLINK_NOFOLLOW 0x100
|
|
#define AT_EMPTY_PATH 0x1000
|
|
#define AT_NO_AUTOMOUNT 0x800
|
|
|
|
// prctl options
|
|
#define PR_SET_NAME 15
|
|
#define PR_GET_NAME 16
|
|
#define PR_SET_DUMPABLE 4
|
|
#define PR_GET_DUMPABLE 3
|
|
|
|
// signal numbers (Linux x86_64 values), used by kill dispatch and
|
|
// default-action classification in sigsend()
|
|
#define SIGHUP 1
|
|
#define SIGINT 2
|
|
#define SIGQUIT 3
|
|
#define SIGILL 4
|
|
#define SIGABRT 6
|
|
#define SIGBUS 7
|
|
#define SIGFPE 8
|
|
#define SIGKILL 9
|
|
#define SIGUSR1 10
|
|
#define SIGSEGV 11
|
|
#define SIGUSR2 12
|
|
#define SIGPIPE 13
|
|
#define SIGALRM 14
|
|
#define SIGTERM 15
|
|
#define SIGCHLD 17
|
|
#define SIGCONT 18
|
|
#define SIGSTOP 19
|
|
// Job-control and the remaining standard signals. A shell needs the TSTP/
|
|
// TTIN/TTOU trio to implement ^Z and background-job terminal access at all;
|
|
// they were simply absent, so any reference to them failed to compile.
|
|
#define SIGTRAP 5
|
|
#define SIGTSTP 20
|
|
#define SIGTTIN 21
|
|
#define SIGTTOU 22
|
|
#define SIGURG 23
|
|
#define SIGXCPU 24
|
|
#define SIGXFSZ 25
|
|
#define SIGVTALRM 26
|
|
#define SIGPROF 27
|
|
#define SIGWINCH 28
|
|
#define SIGIO 29
|
|
#define SIGPWR 30
|
|
#define SIGSYS 31
|
|
|
|
// getrusage who
|
|
#define RUSAGE_SELF 0
|
|
#define RUSAGE_CHILDREN (-1)
|
|
|
|
// fallocate mode
|
|
#define FALLOC_FL_KEEP_SIZE 1
|
|
|
|
// poll(2) event bits (Linux values)
|
|
#define POLLIN 0x001
|
|
#define POLLPRI 0x002
|
|
#define POLLOUT 0x004
|
|
#define POLLERR 0x008
|
|
#define POLLHUP 0x010
|
|
#define POLLNVAL 0x020
|
|
|
|
struct pollfd {
|
|
int fd;
|
|
short events;
|
|
short revents;
|
|
};
|