Job control, function keys, and real sleeps

- SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU stop, SIGCONT resumes; the parent gets
  SIGCHLD and wait4 reports WUNTRACED/WCONTINUED; ps shows T. Ctrl-Z works,
  and mc no longer hangs waiting for its subshell to stop.
- Blocking reads (console, pty, pipe) and sleeps take a pending stop and
  carry on after SIGCONT; a caught signal without SA_RESTART ends them
  with EINTR.
- nanosleep reads tv_nsec (sleep 0.5 used to return at once), writes the
  remainder, and clock_nanosleep understands TIMER_ABSTIME.
- rt_sigaction uses the k_sigaction layout for Linux-ABI processes: the old
  action's restorer was written 112 bytes past the caller's struct. Flags
  are kept; SIGKILL and SIGSTOP cannot be caught.
- F1..F12 reach programs, as the Linux console sends them (PS/2, USB, I2C).
- TIOCLINUX subcode 6 answers "no modifiers" instead of echoing the 6 back.

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-25 02:37:45 +01:00
commit 2372806862
13 changed files with 366 additions and 42 deletions

View file

@ -318,6 +318,7 @@ found:
p->alarm_at = p->alarm_every = 0; // a child's timers start disarmed
p->utime = p->stime = p->cutime = p->cstime = 0;
p->strace = 0;
p->stopsig = p->stop_report = p->cont_report = 0;
p->umask = 022;
// A recycled slot must not inherit a dead process's place in the queue --
@ -486,6 +487,7 @@ fork(void)
// aren't inherited. allocproc() already zeroed pending_signals/
// in_signal/saved_sigtf for this fresh slot.
memmove(np->sig_handler, myproc()->sig_handler, sizeof(np->sig_handler));
memmove(np->sig_flags, myproc()->sig_flags, sizeof(np->sig_flags));
memmove(np->sig_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer));
np->signal_mask = myproc()->signal_mask;
@ -779,6 +781,25 @@ waitpid(int pid, int *status, int options, uint64 *ut, uint64 *st)
if (status) *status = rstatus;
return rpid;
}
// WUNTRACED and WCONTINUED: a stop or a resume, each reported once.
if((options & 2) && p->stop_report){
int rpid = p->pid, rstatus = (p->stop_report << 8) | 0x7f;
p->stop_report = 0;
if (ut) *ut = p->utime;
if (st) *st = p->stime;
release(&ptable.lock);
if (status) *status = rstatus;
return rpid;
}
if((options & 8) && p->cont_report){
int rpid = p->pid;
p->cont_report = 0;
if (ut) *ut = p->utime;
if (st) *st = p->stime;
release(&ptable.lock);
if (status) *status = 0xffff;
return rpid;
}
}
// Two different failures, and they used to share one bare -1.
@ -1645,6 +1666,139 @@ alarm_thread_start(void)
cprintf("alarm: no kernel thread; alarm() and setitimer() will not fire\n");
}
// --- Stopping and continuing --------------------------------------------
//
// SIGSTOP, and SIGTSTP/SIGTTIN/SIGTTOU with no handler, stop a process; SIGCONT
// resumes it. Neither existed: Ctrl-Z did nothing, and mc -- which stops its
// subshell and waits to be told it has stopped -- hung before drawing.
//
// The stop itself happens on the way back to user mode, in deliver_signals(),
// where the process can sleep. SIGCONT acts when it is sent, because a
// stopped process will not reach that point by itself.
int
sig_default_stops(int sig)
{
return sig == SIGSTOP || sig == SIGTSTP || sig == SIGTTIN || sig == SIGTTOU;
}
#define STOP_SIGS ((1ULL << SIGSTOP) | (1ULL << SIGTSTP) | \
(1ULL << SIGTTIN) | (1ULL << SIGTTOU))
// Tell the parent about a stop or a resume: SIGCHLD, and a wake for a wait()
// or a sigsuspend() that is waiting to hear it. Called with ptable.lock.
static void
notify_parent(struct proc *p)
{
struct proc *pp = p->parent;
if(pp == 0)
return;
pp->pending_signals |= 1ULL << SIGCHLD;
if(pp->state == SLEEPING){
pp->state = RUNNABLE;
sched_enqueue(pp);
}
}
// The side effects of sending sig to p, beyond setting it pending. With
// ptable.lock held.
static void
sig_job_control(struct proc *p, int sig)
{
if(sig == SIGCONT){
// A resume cancels any stop still on its way, and the other way round.
p->pending_signals &= ~STOP_SIGS;
if(p->stopsig){
p->stopsig = 0;
p->stop_report = 0;
p->cont_report = 1;
notify_parent(p);
}
} else if(sig_default_stops(sig)){
p->pending_signals &= ~(1ULL << SIGCONT);
}
}
// Stop the calling process with sig until SIGCONT or SIGKILL.
void
proc_stop(int sig)
{
struct proc *p = myproc();
acquire(&ptable.lock);
p->stopsig = sig;
p->stop_report = sig;
p->cont_report = 0;
notify_parent(p);
// Other signals wake it too; only a resume or a kill ends the stop.
while(p->stopsig && !p->killed)
sleep(&p->stopsig, &ptable.lock);
p->stopsig = 0;
release(&ptable.lock);
}
// A handler that ends a blocking call. Installed with SA_RESTART -- which
// musl's signal() always asks for -- the call goes on waiting instead, and
// the handler runs when it returns: Linux restarts the call around the
// handler, and a program that asked for that does not check for EINTR.
#define SA_RESTART 0x10000000
static int
sig_interrupts(struct proc *p, int sig)
{
return p->sig_handler[sig] > 1 && !(p->sig_flags[sig] & SA_RESTART);
}
// Whether a pending signal should end a blocking wait: one that will stop the
// process, or one with a handler to run. A lockless look, for the inside of a
// sleep loop that holds some other lock; sig_blocking_check() does the work.
int
sig_attention(struct proc *p)
{
uint64 d = p->pending_signals & ~p->signal_mask;
if(d == 0)
return 0;
for(int sig = 1; sig < 32; sig++)
if((d & (1ULL << sig)) &&
(sig == SIGSTOP || sig_interrupts(p, sig) ||
(sig_default_stops(sig) && p->sig_handler[sig] == 0)))
return 1;
return 0;
}
// For a blocking call that saw sig_attention(), with no locks held. A stop is
// taken here, and the call carries on waiting once continued -- which is what
// Linux's restart does, and what lets Ctrl-Z stop a `sleep` or a `cat`. A
// signal with a handler ends the call with -EINTR, as POSIX has it.
int
sig_blocking_check(void)
{
struct proc *p = myproc();
for(;;){
int stop = 0, caught = 0;
acquire(&ptable.lock);
uint64 d = p->pending_signals & ~p->signal_mask;
for(int sig = 1; sig < 32 && !stop; sig++){
if(!(d & (1ULL << sig)))
continue;
if(sig == SIGSTOP || (sig_default_stops(sig) && p->sig_handler[sig] == 0)){
p->pending_signals &= ~(1ULL << sig);
stop = sig;
} else if(sig_interrupts(p, sig)){
caught = 1;
}
}
release(&ptable.lock);
if(stop){
proc_stop(stop);
if(p->killed)
return -EINTR;
continue;
}
return (caught || p->killed) ? -EINTR : 0;
}
}
int
sigsend(int pid, int sig)
{
@ -1661,6 +1815,7 @@ sigsend(int pid, int sig)
return 0; // silently dropped, as POSIX requires
}
p->pending_signals |= 1ULL << sig;
sig_job_control(p, sig);
if(sig_kills_now(p, sig))
p->killed = 1;
if(sig == SIGKILL)
@ -1705,6 +1860,7 @@ sigsend_pgrp(int pgid, int sig)
if(init_ignores(p, sig))
continue; // see init_ignores(): this is the Ctrl-C case
p->pending_signals |= 1ULL << sig;
sig_job_control(p, sig);
if(sig_kills_now(p, sig)){
p->killed = 1;
if(p->killsig == 0)
@ -1929,6 +2085,7 @@ proc_snapshot(int idx, struct procinfo *out)
safestrcpy(out->name, p->name, sizeof(out->name));
safestrcpy(out->exe, p->exe, sizeof(out->exe));
out->kthread = p->kfn != 0;
out->stopped = p->stopsig != 0;
release(&ptable.lock);
return 0;
}