kernel: timers that fire, raw ping, and the sockets X needs

alarm() and setitimer() stored a countdown nothing counted down, so no
timer has ever fired; setitimer and getitimer were also registered with
no Linux number. alarmd now posts SIGALRM on the tick, while any process
has a timer armed. busybox's ping, /bin/ping on the Alpine root, sends
every request after the first from its SIGALRM handler.

It also opens SOCK_RAW, which was EPROTOTYPE. A raw ICMP socket is now a
ping socket whose reads start at the IP header. And a blocked socket
call gives up for a signal with a handler, not only for a kill: the
alarm woke recvfrom, and the wait put it straight back to sleep.

For Xorg: AF_UNIX abstract names, which read as the empty path and so as
EADDRINUSE ("server already running"); and getsockname and getpeername,
which did not exist, so libxcb sent no cookie and the server refused
every client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-24 19:44:10 +02:00
commit a4ed556543
9 changed files with 428 additions and 28 deletions

View file

@ -311,6 +311,7 @@ found:
memset(p->sig_handler, 0, sizeof(p->sig_handler));
memset(p->sig_restorer, 0, sizeof(p->sig_restorer));
p->pending_signals = 0;
p->alarm_at = p->alarm_every = 0; // a child's timers start disarmed
p->utime = p->stime = p->cutime = p->cstime = 0;
// A recycled slot must not inherit a dead process's place in the queue --
@ -1543,6 +1544,97 @@ sig_kills_now(struct proc *p, int sig)
return p->sig_handler[sig] == 0 && sig_default_kills(sig);
}
// SIGALRM, on time.
//
// alarm() and setitimer() stored a countdown that nothing ever counted down,
// so no timer anyone set has ever fired. Most programs never noticed, because
// what they used it for was a timeout; busybox's ping sends every request but
// the first from its SIGALRM handler, and on the Alpine root it is /bin/ping,
// so ping printed one reply and then nothing.
//
// A kernel thread rather than the timer interrupt, which is in trap.c, and
// which would have to walk the process table with interrupts off: this walks
// it once a tick, and only while some process has a timer armed.
static int alarms_armed; // processes with alarm_at set; a hint, not a lock
void
alarm_arm(struct proc *p, uint after, uint every)
{
acquire(&ptable.lock);
if (p->alarm_at == 0 && after)
alarms_armed++;
else if (p->alarm_at && after == 0)
alarms_armed--;
p->alarm_at = after ? ticks + after : 0;
if (p->alarm_at == 0 && after)
p->alarm_at = 1; // 0 means disarmed; a tick late is fine
p->alarm_every = after ? every : 0;
release(&ptable.lock);
}
// Ticks until p's timer is due, 0 if it is not armed.
uint
alarm_left(struct proc *p)
{
uint at = p->alarm_at;
if (at == 0)
return 0;
int left = (int)(at - ticks);
return left > 0 ? (uint)left : 1;
}
static void
alarm_thread(void)
{
for (;;) {
acquire(&tickslock);
sleep(&ticks, &tickslock);
uint now = ticks;
release(&tickslock);
if (alarms_armed == 0)
continue;
acquire(&ptable.lock);
for (struct proc *p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
if (p->alarm_at == 0 || (int)(now - p->alarm_at) < 0)
continue;
if (p->state == UNUSED || p->state == ZOMBIE) {
p->alarm_at = 0;
alarms_armed--;
continue;
}
if (p->alarm_every) {
p->alarm_at = now + p->alarm_every;
} else {
p->alarm_at = 0;
alarms_armed--;
}
// sigsend takes ptable.lock itself, and with this many lines it is
// clearer to repeat its three effects than to split it.
if (!init_ignores(p, SIGALRM)) {
p->pending_signals |= 1ULL << SIGALRM;
if (sig_kills_now(p, SIGALRM)) {
p->killed = 1;
if (p->killsig == 0)
p->killsig = SIGALRM;
}
if (p->state == SLEEPING) {
p->state = RUNNABLE;
sched_enqueue(p);
}
}
}
release(&ptable.lock);
}
}
void
alarm_thread_start(void)
{
if (kthread_create(alarm_thread, "alarmd") < 0)
cprintf("alarm: no kernel thread; alarm() and setitimer() will not fire\n");
}
int
sigsend(int pid, int sig)
{