189 lines
5.4 KiB
Markdown
189 lines
5.4 KiB
Markdown
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
|
|
|
|
:::make
|
|
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:
|
|
|
|
:::c
|
|
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`
|
|
|
|
:::c
|
|
typedef struct
|
|
{
|
|
uint32_t sig;
|
|
uint32_t sender;
|
|
list_head_t queue;
|
|
} signal_t;
|
|
|
|
is created and added to the processes list:
|
|
|
|
:::c
|
|
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:
|
|
|
|
:::c
|
|
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:
|
|
|
|
:::c
|
|
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:
|
|
|
|
:::c
|
|
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:
|
|
|
|
:::c
|
|
...
|
|
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)
|