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
455 lines
11 KiB
C
455 lines
11 KiB
C
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "x86.h"
|
|
#include "syscall.h"
|
|
#include "posix.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
|
|
addr_t sys_fork(void) { return fork(); }
|
|
addr_t sys_wait(void) { return wait(); }
|
|
|
|
// _exit(status)/exit_group(status): the low 8 bits of the argument are what
|
|
// wait() reports back through WEXITSTATUS.
|
|
addr_t
|
|
sys_exit(void)
|
|
{
|
|
int code = 0;
|
|
argint(0, &code);
|
|
exit(W_EXITCODE(code, 0));
|
|
return 0;
|
|
}
|
|
addr_t sys_exit_group(void) { return sys_exit(); }
|
|
|
|
|
|
addr_t
|
|
sys_kill(void)
|
|
{
|
|
int pid, sig;
|
|
argint(0, &pid);
|
|
argint(1, &sig);
|
|
|
|
// POSIX target selection:
|
|
// pid > 0 -> that process
|
|
// pid == 0 -> every process in the caller's own group
|
|
// pid == -1 -> every process we may signal (approximated as our group)
|
|
// pid < -1 -> the process group with id -pid
|
|
if (pid == 0 || pid == -1)
|
|
return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0;
|
|
if (pid < -1)
|
|
return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0;
|
|
|
|
// sig == 0: existence check only
|
|
if (sig == 0)
|
|
return proc_exists(pid) ? 0 : -ESRCH;
|
|
|
|
// Raise the signal for real: pending bit + wakeup, handler delivery on
|
|
// the target's next syscall boundary, default action if unhandled.
|
|
return sigsend(pid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
addr_t sys_getpid(void) { return myproc()->pid; }
|
|
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->pid : 1; }
|
|
addr_t sys_gettid(void) { return myproc()->pid; } // single-threaded: tid == pid
|
|
|
|
addr_t
|
|
sys_waitpid(void)
|
|
{
|
|
int pid, options;
|
|
addr_t statusp;
|
|
// status is optional (NULL is standard, valid usage — waitpid(pid, NULL,
|
|
// WNOHANG) — so this must NOT go through argptr(), which rejects a NULL/
|
|
// unmapped pointer outright).
|
|
if (argint(0, &pid) < 0 || argaddr(1, &statusp) < 0 || argint(2, &options) < 0)
|
|
return -1;
|
|
int status;
|
|
int wpid = waitpid(pid, &status, options);
|
|
if (wpid > 0 && statusp)
|
|
put_user(status, statusp);
|
|
return wpid;
|
|
}
|
|
|
|
addr_t
|
|
sys_wait4(void)
|
|
{
|
|
int pid, options;
|
|
addr_t statusp;
|
|
argint(0, &pid);
|
|
argaddr(1, &statusp);
|
|
argint(2, &options);
|
|
int status;
|
|
int wpid = waitpid(pid, &status, options);
|
|
if (wpid <= 0)
|
|
return wpid;
|
|
if (statusp)
|
|
put_user(status, statusp);
|
|
return wpid;
|
|
}
|
|
|
|
addr_t
|
|
sys_clone(void)
|
|
{
|
|
int flags;
|
|
argint(0, &flags);
|
|
if (flags & (CLONE_VM | CLONE_THREAD | CLONE_SIGHAND))
|
|
return -ENOSYS;
|
|
return fork();
|
|
}
|
|
|
|
addr_t
|
|
sys_sleep(void)
|
|
{
|
|
int n;
|
|
uint ticks0;
|
|
if (argint(0, &n) < 0)
|
|
return -1;
|
|
acquire(&tickslock);
|
|
ticks0 = ticks;
|
|
while (ticks - ticks0 < (uint)n) {
|
|
if (myproc()->killed) { release(&tickslock); return -1; }
|
|
sleep(&ticks, &tickslock);
|
|
}
|
|
release(&tickslock);
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_uptime(void)
|
|
{
|
|
uint xticks;
|
|
acquire(&tickslock);
|
|
xticks = ticks;
|
|
release(&tickslock);
|
|
return xticks;
|
|
}
|
|
|
|
addr_t sys_reboot(void)
|
|
{
|
|
cprintf("System is rebooting...\n");
|
|
outb(0xCF9, 0x06);
|
|
outb(0x64, 0xFE);
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_shutdown(void)
|
|
{
|
|
cprintf("System is shutting down...\n");
|
|
outw(0x604, 0x2000);
|
|
outw(0xB004, 0x2000);
|
|
outw(0x4004, 0x3400);
|
|
return 0;
|
|
}
|
|
|
|
// --- Sessions and process groups ---------------------------------------
|
|
// Job control is defined entirely in terms of these. They used to be stubs
|
|
// returning the caller's own pid, which made every process look like its own
|
|
// group leader and left a shell's wait loop unable to make progress.
|
|
|
|
addr_t sys_setsid(void) { return proc_setsid(); }
|
|
|
|
addr_t
|
|
sys_getsid(void)
|
|
{
|
|
int pid;
|
|
argint(0, &pid);
|
|
if (pid == 0 || pid == myproc()->pid)
|
|
return myproc()->sid;
|
|
return proc_exists(pid) ? (addr_t)myproc()->sid : (addr_t)-ESRCH;
|
|
}
|
|
|
|
addr_t
|
|
sys_getpgid(void)
|
|
{
|
|
int pid;
|
|
argint(0, &pid);
|
|
if (pid == 0 || pid == myproc()->pid)
|
|
return myproc()->pgid;
|
|
int g = proc_pgid(pid);
|
|
return g < 0 ? (addr_t)-ESRCH : (addr_t)g;
|
|
}
|
|
|
|
addr_t
|
|
sys_setpgid(void)
|
|
{
|
|
int pid, pgid;
|
|
argint(0, &pid);
|
|
argint(1, &pgid);
|
|
return proc_setpgid(pid, pgid);
|
|
}
|
|
|
|
addr_t sys_getpgrp(void) { return myproc()->pgid; }
|
|
|
|
// --- User and group identity -------------------------------------------
|
|
// Tracked for real now. Nothing *enforces* permissions with these yet -- the
|
|
// on-disk format has no owner field until SchFS -- but they are no longer
|
|
// invented, so getuid() after setuid() reports what actually happened.
|
|
|
|
addr_t sys_getuid(void) { return myproc()->uid; }
|
|
addr_t sys_geteuid(void) { return myproc()->euid; }
|
|
addr_t sys_getgid(void) { return myproc()->gid; }
|
|
addr_t sys_getegid(void) { return myproc()->egid; }
|
|
|
|
// Privileged (euid 0) callers may set any id; an unprivileged caller may only
|
|
// switch between its real and saved ids. Standard POSIX rule.
|
|
addr_t
|
|
sys_setuid(void)
|
|
{
|
|
int uid;
|
|
argint(0, &uid);
|
|
if (uid < 0)
|
|
return -EINVAL;
|
|
if (myproc()->euid == 0) {
|
|
myproc()->uid = myproc()->euid = myproc()->suid = (uint)uid;
|
|
return 0;
|
|
}
|
|
if ((uint)uid != myproc()->uid && (uint)uid != myproc()->suid)
|
|
return -EPERM;
|
|
myproc()->euid = (uint)uid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setgid(void)
|
|
{
|
|
int gid;
|
|
argint(0, &gid);
|
|
if (gid < 0)
|
|
return -EINVAL;
|
|
if (myproc()->euid == 0) {
|
|
myproc()->gid = myproc()->egid = myproc()->sgid = (uint)gid;
|
|
return 0;
|
|
}
|
|
if ((uint)gid != myproc()->gid && (uint)gid != myproc()->sgid)
|
|
return -EPERM;
|
|
myproc()->egid = (uint)gid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_umask(void) { return 022; }
|
|
|
|
addr_t
|
|
sys_setreuid(void)
|
|
{
|
|
int ruid, euid;
|
|
argint(0, &ruid); argint(1, &euid);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (ruid >= 0) myproc()->uid = (uint)ruid;
|
|
if (euid >= 0) myproc()->euid = (uint)euid;
|
|
myproc()->suid = myproc()->euid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setregid(void)
|
|
{
|
|
int rgid, egid;
|
|
argint(0, &rgid); argint(1, &egid);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (rgid >= 0) myproc()->gid = (uint)rgid;
|
|
if (egid >= 0) myproc()->egid = (uint)egid;
|
|
myproc()->sgid = myproc()->egid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setresuid(void)
|
|
{
|
|
int r, e, sv;
|
|
argint(0, &r); argint(1, &e); argint(2, &sv);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (r >= 0) myproc()->uid = (uint)r;
|
|
if (e >= 0) myproc()->euid = (uint)e;
|
|
if (sv >= 0) myproc()->suid = (uint)sv;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setresgid(void)
|
|
{
|
|
int r, e, sv;
|
|
argint(0, &r); argint(1, &e); argint(2, &sv);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (r >= 0) myproc()->gid = (uint)r;
|
|
if (e >= 0) myproc()->egid = (uint)e;
|
|
if (sv >= 0) myproc()->sgid = (uint)sv;
|
|
return 0;
|
|
}
|
|
|
|
// Linux-specific; the fs id follows the effective id here.
|
|
addr_t sys_setfsuid(void) { return myproc()->euid; }
|
|
addr_t sys_setfsgid(void) { return myproc()->egid; }
|
|
|
|
addr_t
|
|
sys_getresuid(void)
|
|
{
|
|
addr_t rp, ep, sp;
|
|
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
|
if (rp) put_user(myproc()->uid, rp);
|
|
if (ep) put_user(myproc()->euid, ep);
|
|
if (sp) put_user(myproc()->suid, sp);
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_getresgid(void)
|
|
{
|
|
addr_t rp, ep, sp;
|
|
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
|
if (rp) put_user(myproc()->gid, rp);
|
|
if (ep) put_user(myproc()->egid, ep);
|
|
if (sp) put_user(myproc()->sgid, sp);
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_getgroups(void)
|
|
{
|
|
int size;
|
|
argint(0, &size);
|
|
return 0; // no supplementary groups
|
|
}
|
|
addr_t sys_setgroups(void) { return 0; }
|
|
|
|
// --- Process/scheduling extensions ---
|
|
|
|
addr_t sys_sched_yield(void) { return 0; }
|
|
|
|
addr_t sys_sched_getaffinity(void)
|
|
{
|
|
int pid, cpusetsize;
|
|
addr_t maskp;
|
|
argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp);
|
|
if (maskp && cpusetsize > 0) {
|
|
char *m = (char*)user_ptr(maskp, (uint64)cpusetsize);
|
|
if (m) { memset(m, 0, cpusetsize); m[0] = 1; } // CPU 0 only
|
|
}
|
|
return 0;
|
|
}
|
|
addr_t sys_sched_setaffinity(void) { return 0; }
|
|
|
|
addr_t sys_prctl(void)
|
|
{
|
|
int op;
|
|
addr_t arg2;
|
|
argint(0, &op);
|
|
argaddr(1, &arg2);
|
|
if (op == PR_SET_NAME) {
|
|
char *name;
|
|
if (argstr(1, &name) < 0) return -1;
|
|
safestrcpy(myproc()->name, name, sizeof(myproc()->name));
|
|
return 0;
|
|
}
|
|
if (op == PR_GET_NAME) {
|
|
char *buf = (char*)user_ptr(arg2, 16);
|
|
if (!buf) return -EFAULT;
|
|
safestrcpy(buf, myproc()->name, 16);
|
|
return 0;
|
|
}
|
|
return 0; // all other prctl ops: silently succeed
|
|
}
|
|
|
|
addr_t sys_tgkill(void)
|
|
{
|
|
int tgid, tid, sig;
|
|
argint(0, &tgid); argint(1, &tid); argint(2, &sig);
|
|
// In our single-threaded model tgid == tid == pid.
|
|
if (tgid <= 0) return 0;
|
|
if (sig == 0) return proc_exists(tgid) ? 0 : -ESRCH;
|
|
return sigsend(tgid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
addr_t sys_tkill(void)
|
|
{
|
|
int tid, sig;
|
|
argint(0, &tid); argint(1, &sig);
|
|
if (tid <= 0) return 0;
|
|
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
|
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
addr_t sys_pause(void)
|
|
{
|
|
// Sleep until next timer tick and return -EINTR (no real signal delivery).
|
|
acquire(&tickslock);
|
|
uint t0 = ticks;
|
|
while (ticks == t0 && !myproc()->killed)
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
return -EINTR;
|
|
}
|
|
|
|
addr_t sys_getrusage(void)
|
|
{
|
|
int who;
|
|
struct rusage *ru;
|
|
argint(0, &who);
|
|
if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -1;
|
|
memset(ru, 0, sizeof(*ru));
|
|
uint64 rsec, rusec;
|
|
monotonic(&rsec, &rusec);
|
|
ru->ru_utime.tv_sec = (long)rsec;
|
|
ru->ru_utime.tv_usec = (long)rusec;
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_times(void)
|
|
{
|
|
addr_t bufaddr;
|
|
argaddr(0, &bufaddr);
|
|
if (bufaddr) {
|
|
struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t));
|
|
if (t) memset(t, 0, sizeof(*t));
|
|
}
|
|
// 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)
|
|
{
|
|
addr_t bufaddr;
|
|
argaddr(0, &bufaddr);
|
|
struct sysinfo *si = (struct sysinfo*)user_ptr(bufaddr, sizeof(*si));
|
|
if (!si) return -EFAULT;
|
|
memset(si, 0, sizeof(*si));
|
|
// 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.
|
|
si->totalram = (uint64)PHYSTOP;
|
|
si->freeram = kalloc_free_bytes();
|
|
si->mem_unit = 1;
|
|
si->procs = (uint16)proc_num();
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_waitid(void)
|
|
{
|
|
// Simplified: wait for any child, ignore idtype/id/options.
|
|
int wpid = wait();
|
|
if (wpid < 0) return -ECHILD;
|
|
// siginfo_t pointer (arg 2) — zero-fill if provided.
|
|
addr_t infop;
|
|
argaddr(2, &infop);
|
|
if (infop) {
|
|
char *p = (char*)user_ptr(infop, 128); // sizeof(siginfo_t)
|
|
if (p) memset(p, 0, 128);
|
|
}
|
|
return 0;
|
|
}
|