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:
parent
fd9d392416
commit
2372806862
13 changed files with 366 additions and 42 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue