Initial commit - Begun rewrite of website generator
This commit is contained in:
180
pages/2014-01-30-Signals.md
Normal file
180
pages/2014-01-30-Signals.md
Normal file
@@ -0,0 +1,180 @@
|
||||
layout: post
|
||||
title: "Signals"
|
||||
subtitle: "Another rewrite"
|
||||
tags: [osdev]
|
||||
|
||||
Time for another live rewrite - or at least a restructuring.
|
||||
|
||||
###Signals in newlib
|
||||
|
||||
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](http://wiki.osdev.org/OS_Specific_Toolchain#Signal_handling).
|
||||
|
||||
Then you need the syscalls:
|
||||
|
||||
sig_t signal(int signum, sig_t handler);
|
||||
int kill(int pid, int sig);
|
||||
|
||||
###Raising signals
|
||||
|
||||
The [posix
|
||||
specification](http://pubs.opengroup.org/onlinepubs/9699919799/)
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
###Handling signals
|
||||
|
||||
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.
|
||||
|
||||
###Code
|
||||
|
||||
Git commit
|
||||
[1ec132c](https://github.com/thomasloven/os5/tree/1ec132ce60ba627e522019d456e0cec993193153)
|
||||
Reference in New Issue
Block a user