Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
296
kernel/sys/sys_sig.c
Normal file
296
kernel/sys/sys_sig.c
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "proc.h"
|
||||
#include "syscall.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
// Linux sigset_t puts signal N at bit N-1; kernel-internal masks
|
||||
// (pending_signals / signal_mask / deliver_signals) put signal N at bit N.
|
||||
// Convert at the user boundary — storing user sets raw silently shifted
|
||||
// every mask by one signal (blocking SIGCHLD actually blocked SIGCONT),
|
||||
// which let mksh's SIGCHLD handler run inside the very critical sections
|
||||
// sigprocmask() was guarding, wrecking its job table.
|
||||
static uint64 sigset_from_user(uint64 uset) { return uset << 1; }
|
||||
static uint64 sigset_to_user(uint64 kset) { return kset >> 1; }
|
||||
|
||||
addr_t
|
||||
sys_sigfillset(void)
|
||||
{
|
||||
uint64 *set;
|
||||
if (argptr(0, (char**)&set, sizeof(uint64)) < 0)
|
||||
return -1;
|
||||
if (set) *set = ~0ULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sigprocmask(void)
|
||||
{
|
||||
int how;
|
||||
addr_t setp, oldsetp;
|
||||
argint(0, &how);
|
||||
argaddr(1, &setp);
|
||||
argaddr(2, &oldsetp);
|
||||
|
||||
// oldset is optional (NULL = don't return previous mask)
|
||||
if (oldsetp) {
|
||||
uint64 old = sigset_to_user(myproc()->signal_mask);
|
||||
put_user(old, oldsetp);
|
||||
}
|
||||
// set is optional (NULL = just query)
|
||||
if (setp) {
|
||||
uint64 uset;
|
||||
if (copy_from_user(&uset, setp, sizeof(uset)) == 0) {
|
||||
uint64 set = sigset_from_user(uset);
|
||||
if (how == 0) myproc()->signal_mask |= set; // SIG_BLOCK
|
||||
else if (how == 1) myproc()->signal_mask &= ~set; // SIG_UNBLOCK
|
||||
else myproc()->signal_mask = set; // SIG_SETMASK
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sigwait(void)
|
||||
{
|
||||
uint64 *set_ptr;
|
||||
int *sig_ptr;
|
||||
if (argptr(0, (char**)&set_ptr, sizeof(uint64)) < 0 ||
|
||||
argptr(1, (char**)&sig_ptr, sizeof(int)) < 0)
|
||||
return -1;
|
||||
uint64 set = sigset_from_user(*set_ptr);
|
||||
acquire(&tickslock);
|
||||
while (!(myproc()->pending_signals & set)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -1; }
|
||||
sleep(myproc(), &tickslock);
|
||||
}
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if ((myproc()->pending_signals & set) & (1ULL << i)) {
|
||||
myproc()->pending_signals &= ~(1ULL << i);
|
||||
*sig_ptr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&tickslock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// musl's struct sigaction (musl 1.2.5 signal.h) for this target:
|
||||
// sa_handler (8 bytes) at offset 0, then a 128-byte sigset_t (musl uses
|
||||
// 128/sizeof(long) = 16 longs regardless of NSIG), sa_flags (4 bytes) +
|
||||
// padding, then sa_restorer (8 bytes) at offset 144. We only care about
|
||||
// those two 8-byte fields.
|
||||
#define SA_HANDLER_OFF 0
|
||||
#define SA_RESTORER_OFF 144
|
||||
|
||||
// rt_sigaction: store the handler + restorer so deliver_signals() (called
|
||||
// from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user
|
||||
// code instead of silently doing nothing. sig 0 is invalid (matches
|
||||
// sigaction(2)); we don't validate against NSIG beyond our own array size.
|
||||
//
|
||||
// User accesses go through copy_from_user/put_user (uaccess.h), which are
|
||||
// page-safe. An earlier version bulk-copied the ~152-byte struct through a
|
||||
// single uva2ka() translation of its start address; when the user's
|
||||
// act/oldact pointer landed close enough to a page boundary, the write ran
|
||||
// past the mapped page into an unrelated physical page, silently crashing
|
||||
// every freshly exec'd /bin/sh (mksh calls sigaction() during startup).
|
||||
addr_t
|
||||
sys_rt_sigaction(void)
|
||||
{
|
||||
int sig;
|
||||
addr_t actp, oldactp;
|
||||
argint(0, &sig);
|
||||
argaddr(1, &actp);
|
||||
argaddr(2, &oldactp);
|
||||
if (sig <= 0 || sig >= 32) return -1;
|
||||
|
||||
if (oldactp) {
|
||||
put_user(myproc()->sig_handler[sig], oldactp + SA_HANDLER_OFF);
|
||||
put_user(myproc()->sig_restorer[sig], oldactp + SA_RESTORER_OFF);
|
||||
}
|
||||
if (actp) {
|
||||
copy_from_user(&myproc()->sig_handler[sig], actp + SA_HANDLER_OFF,
|
||||
sizeof(myproc()->sig_handler[sig]));
|
||||
copy_from_user(&myproc()->sig_restorer[sig], actp + SA_RESTORER_OFF,
|
||||
sizeof(myproc()->sig_restorer[sig]));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// rt_sigreturn: entered via `syscall` (rax=15) from musl's __restore_rt
|
||||
// trampoline, which is exactly what we set as the manufactured return
|
||||
// address in deliver_signals() below — the handler function's own `ret`
|
||||
// lands here once it's done. Restore the trapframe deliver_signals()
|
||||
// stashed before redirecting to the handler, so syscall()'s normal
|
||||
// sysretq path resumes the interrupted code exactly where it left off.
|
||||
addr_t
|
||||
sys_rt_sigreturn(void)
|
||||
{
|
||||
addr_t saved_rax = myproc()->saved_sigtf.rax;
|
||||
*myproc()->tf = myproc()->saved_sigtf;
|
||||
myproc()->in_signal = 0;
|
||||
// If this handler interrupted a sigsuspend(), put its pre-suspend
|
||||
// signal mask back now that the handler has finished.
|
||||
if (myproc()->sigsusp_active) {
|
||||
myproc()->signal_mask = myproc()->sigsusp_oldmask;
|
||||
myproc()->sigsusp_active = 0;
|
||||
}
|
||||
// syscall() does `tf->rax = <our return value>` right after this
|
||||
// returns — hand back the already-restored rax so that assignment is
|
||||
// a harmless no-op instead of clobbering what we just restored.
|
||||
return saved_rax;
|
||||
}
|
||||
|
||||
// Called from syscall()'s tail (kernel/sys/syscall.c), right before
|
||||
// returning to user mode, for every syscall a process makes (this is the
|
||||
// only "check point" we have without also hooking the interrupt-return
|
||||
// path in trap.c — sufficient for what actually needed this: a process
|
||||
// blocked in a syscall, like sigsuspend(), waiting for its own next
|
||||
// syscall return to notice a signal arrived).
|
||||
//
|
||||
// If there's a pending, unblocked signal with a registered handler,
|
||||
// redirect tf to it: rdi = signal number (SysV ABI first arg), rip =
|
||||
// handler.
|
||||
//
|
||||
// The return address (what the handler's own `ret` lands on) can't be
|
||||
// sig_restorer[sig] — this musl build has no SA_RESTORER defined at all
|
||||
// (checked: not defined anywhere in musl 1.2.5), so sigaction() never
|
||||
// fills it in; it's always 0. Pushing 0 as a return address is exactly
|
||||
// what crashed every mksh instance the first time a signal was ever
|
||||
// actually delivered (handler ran, `ret` jumped to address 0). Instead,
|
||||
// inject a tiny trampoline directly onto the user's own stack — 7 bytes
|
||||
// of machine code for `mov eax, 15; syscall` (rt_sigreturn) — below the
|
||||
// current stack pointer (clear of the SysV red zone) and point the
|
||||
// return address at that, the standard way a kernel supplies sigreturn
|
||||
// when the C library doesn't hand it one itself.
|
||||
void
|
||||
deliver_signals(struct trapframe *tf)
|
||||
{
|
||||
if (myproc()->in_signal) return; // no nested delivery
|
||||
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
||||
if (!deliverable) return;
|
||||
|
||||
for (int sig = 1; sig < 32; sig++) {
|
||||
if (!(deliverable & (1ULL << sig))) continue;
|
||||
|
||||
// SIG_DFL is 0 and SIG_IGN is 1 -- neither is an address to jump to.
|
||||
//
|
||||
// Only the 0 case was checked here. A process that had asked to
|
||||
// ignore a signal got its handler "called" at address 1, which is a
|
||||
// page fault at rip=0x1 and a dead process. Nothing sent SIGPIPE
|
||||
// until pipes learned to, at which point every shell that ignores
|
||||
// SIGPIPE -- which is every shell -- died the first time a pipeline
|
||||
// ended early. `cat big | head -c 5` was enough.
|
||||
//
|
||||
// The default action for a signal with no handler has already been
|
||||
// applied by sigsend(); all that is left here is to stop it being
|
||||
// pending, which the old `continue` never did, so an ignored signal
|
||||
// was re-examined on the way out of every syscall forever after.
|
||||
if (myproc()->sig_handler[sig] <= 1) {
|
||||
myproc()->pending_signals &= ~(1ULL << sig);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64 tramp_addr = (tf->rsp - 256) & ~0xFULL;
|
||||
uint8 *tramp = (uint8*)uva2ka(myproc()->pgdir, (char*)tramp_addr);
|
||||
if (!tramp) continue; // can't deliver safely; try another pending signal
|
||||
|
||||
uint64 sp = tramp_addr - 8;
|
||||
uint64 *retaddr = (uint64*)uva2ka(myproc()->pgdir, (char*)sp);
|
||||
if (!retaddr) continue;
|
||||
|
||||
// mov eax, 15 ; syscall
|
||||
tramp[0] = 0xb8; tramp[1] = 0x0f; tramp[2] = 0x00;
|
||||
tramp[3] = 0x00; tramp[4] = 0x00;
|
||||
tramp[5] = 0x0f; tramp[6] = 0x05;
|
||||
*retaddr = tramp_addr;
|
||||
|
||||
myproc()->pending_signals &= ~(1ULL << sig);
|
||||
myproc()->saved_sigtf = *tf;
|
||||
myproc()->in_signal = 1;
|
||||
|
||||
tf->rsp = sp;
|
||||
tf->rdi = (uint64)sig;
|
||||
// Both return paths must point at the handler: iretq-based returns
|
||||
// use tf->rip, but syscall_trapret exits via sysretq, which takes
|
||||
// the user RIP from RCX and *discards* tf->rip (trapasm.S pops rcx
|
||||
// from the frame, then `addq $40` throws rip/cs/rflags away). With
|
||||
// only tf->rip set, the "delivered" handler never actually ran on
|
||||
// the syscall path: user code resumed at the old rcx with the
|
||||
// shifted rsp, and its ret fell into the sigreturn trampoline,
|
||||
// silently undoing everything. exec() handles this same quirk by
|
||||
// setting tf->rcx = elf.entry.
|
||||
tf->rip = myproc()->sig_handler[sig];
|
||||
tf->rcx = myproc()->sig_handler[sig];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// sigaltstack: set/get alternate signal stack.
|
||||
// We don't deliver signals, so just store/return zeros.
|
||||
addr_t sys_sigaltstack(void) { return 0; }
|
||||
|
||||
// rt_sigpending: write the set of pending signals to user buffer.
|
||||
addr_t sys_rt_sigpending(void)
|
||||
{
|
||||
addr_t setp;
|
||||
argaddr(0, &setp);
|
||||
if (setp) {
|
||||
uint64 pend = sigset_to_user(myproc()->pending_signals);
|
||||
put_user(pend, setp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// rt_sigtimedwait: wait for a signal from the set; returns -EAGAIN immediately.
|
||||
addr_t sys_rt_sigtimedwait(void) { return -EAGAIN; }
|
||||
|
||||
// rt_sigsuspend: atomically replace the signal mask and suspend until a
|
||||
// signal not blocked by the *temporary* mask is pending, then return -EINTR.
|
||||
//
|
||||
// The atomic mask swap is the entire point of sigsuspend(2): mksh blocks
|
||||
// SIGCHLD while it inspects its job table, then calls sigsuspend() with the
|
||||
// old (SIGCHLD-unblocked) mask to wait for a child. The previous stub
|
||||
// ignored the mask argument and just slept one tick — SIGCHLD stayed
|
||||
// blocked, deliver_signals() could never fire the handler, and the shell
|
||||
// hung forever after running any external command (unreaped zombie child,
|
||||
// shell parked in sigsuspend: the "can't type after a command" hang).
|
||||
//
|
||||
// The temporary mask must still be in effect when deliver_signals() runs at
|
||||
// syscall()'s tail (that's what allows the delivery), so the old mask can't
|
||||
// be restored here — sys_rt_sigreturn() restores it once the handler
|
||||
// finishes, via sigsusp_oldmask/sigsusp_active.
|
||||
addr_t sys_rt_sigsuspend(void)
|
||||
{
|
||||
addr_t maskp;
|
||||
uint64 newmask;
|
||||
argaddr(0, &maskp);
|
||||
|
||||
uint64 oldmask = myproc()->signal_mask;
|
||||
if (maskp && copy_from_user(&newmask, maskp, sizeof(newmask)) == 0)
|
||||
myproc()->signal_mask = sigset_from_user(newmask);
|
||||
|
||||
acquire(&tickslock);
|
||||
while (!(myproc()->pending_signals & ~myproc()->signal_mask) && !myproc()->killed)
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
|
||||
// Will any pending signal actually reach a handler? If so, the mask is
|
||||
// restored by sigreturn after the handler runs; otherwise (no handler
|
||||
// installed, or we were killed) restore it right now.
|
||||
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
||||
int will_deliver = 0;
|
||||
for (int sig = 1; sig < 32; sig++)
|
||||
if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig])
|
||||
{ will_deliver = 1; break; }
|
||||
|
||||
if (will_deliver && !myproc()->in_signal) {
|
||||
myproc()->sigsusp_oldmask = oldmask;
|
||||
myproc()->sigsusp_active = 1;
|
||||
} else {
|
||||
myproc()->signal_mask = oldmask;
|
||||
}
|
||||
return -EINTR;
|
||||
}
|
||||
Loading…
Reference in a new issue