30 Jan 2014
Time for another live rewrite - or at least a restructuring.
Newlib has a kind of signals built in without any help from the kernel. Those signals can't be sent between processes, though, so I'd like to implement my own.
I'll just look at signal
and kill
and ignore things like
sigaction
for now.
First of all, in order to compile newlib with kernel supported signals, you need the line
newlib_cflags="${newlib_cflags} -DSIGNAL_PROVIDED"
in your entry in newlib/configure.host
as described in the osdev
wiki.
Then you need the syscalls:
sig_t signal(int signum, sig_t handler); int kill(int pid, int sig);
The posix specification contains a lot of rules about how signals should behave in a number of situations. I probably got a lot of them wrong, but I'll try to fix them as I go along later.
The way I chose to implement signals was through a list for each
process. When a signal is sent using kill
or similar, a signal_t
typedef struct { uint32_t sig; uint32_t sender; list_head_t queue; } signal_t;
is created and added to the processes list:
int signal_process(int pid, int signum) { process_t *p = get_process(pid); ... signal_t *signal = calloc(1, sizeof(signal_t)); signal->sig = signum; signal->sender = current->proc->pid; init_list(signal->queue); append_to_list(p->signal_queue, signal->queue); if(p == current->proc) { handle_signals(current); } return 0; }
If the currently running thread is in the process being signalled, we handle the signals immediately. Otherwise it can wait for a bit.
signal_process
is called by the kill
syscall.
The signal
syscall lets the process select how to handle a certain
signal. Each process also contains a table of sig_t
and the signal
syscall calls the following function:
sig_t switch_handler(int signum, sig_t handler) { ... sig_t old = current->proc->signal_handler[signum]; current->proc->signal_handler[signum] = handler; return old; }
The cut out part of this function contains code to make sure the handler
of signal 9 (SIGKILL
) is never changed from SIG_DFL
.
Signals should be handled as soon as possible, though there's no reason to let the receiving process skip the line in the scheduler. So as soon as the process continues running should be soon enough.
I chose to hook into the my interrupt handler:
registers_t *idt_handler(registers_t *r) { ... if(int_handlers[r->int_no) { ... registers_t *ret = int_handlers[r->int_no](r); if((ret->cs & 0x3) == 0x3) { ret = (registers_t *)handle_signals((thread_t *)ret); ... } ... return ret } ... }
So, what does handle_signals
actually do?
It start by finding the signal queue of the process to which the passed
thread belongs and steps through it one entry at a time. If a signal is
queued and not blocked (actual blocking is not implemented yet) it
checks what the handler for that signal number is. The handler may be
SIG_IGN
, SIG_DFL
or a pointer to a function in userspace.
If the handler is set to SIG_DFL
the function looks up the correct
default signal handler in a table and calls it. It may be one of the
following:
void sighandler_ignore(int num) { (void)num; } void sighandler_terminate(int num) { fprintf(stderr,, "Process %x terminated by signal %x\n", \ current->proc->pid, num); _exit(num); } void sighandler_coredump(int num) { /* Same as above */ } void sighandler_stop(int num) { /* Not implemented yet */ } void sighandler_continue(int num) { /* Not implemented yet */ }
If the handler is a function a new thread is created to handle it:
... sig_t handler = th->proc->signal_handler[signal->sig]; thread_t *h = new_thread((void (*)(void))handler, 1); append_to_list(th->proc->threads, h->process_threads); h->proc = th->proc; uint32_t *stack = ((uint32_t *)th->r.useresp; *--stack = signal->sig; *--stack = SIGNAL_RETURN_ADDRESS; h->r.useresp = h->r.ebp = (uint32_t)stack; remove_from_list(signal->queue); free(signal); scheduler_remove(h); scheduler_sleep(th, &h->waiting); scheduler_cheat(h); schedule(); }
This creates a new thread and pushes the signal number (as an argument) and a return address to the threads stack. It then places the new thread at the beginning of the schedulers queue and switches to it.
SIGNAL_RETURN_ADDRESS
is chosen to cause a page fault when the signal
handler returns. The page fault handler catches this and recognizes the
address so that the thread can be safely destroyed.
Now the page fault handler can also be setup to send a SIGSEGV
signal
when a userspace thread page faults.
Git commit 1ec132c