1
0
Fork 0
mirror of https://github.com/hermitcore/libhermit.git synced 2025-03-09 00:00:03 +01:00

hermit/kernel: implement signal handling for communication between tasks

When a signal is sent, the operating system interrupts the target task's
normal flow of execution to deliver the signal. Execution can be
interrupted during any non-atomic instruction. If the process has
previously registered a signal handler, that routine is executed when the
task's execution would continue.
This commit is contained in:
daniel-k 2016-08-25 17:14:47 +02:00
parent e03887a1ab
commit 56349176da
8 changed files with 322 additions and 3 deletions

View file

@ -354,9 +354,9 @@ isrstub_pseudo_error 9
%assign i i+1
%endrep
; Create entries for the interrupts 80 to 81
; Create entries for the interrupts 80 to 82
%assign i 80
%rep 2
%rep 3
irqstub i
%assign i i+1
%endrep
@ -674,6 +674,34 @@ is_single_kernel:
mov eax, DWORD [single_kernel]
ret
global sighandler_epilog
sighandler_epilog:
; restore only those registers that might have changed between returning
; from IRQ and execution of signal handler
add rsp, 2 * 8 ; ignore fs, gs
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
add rsp, 8 ; ignore rsp
pop rbx
pop rdx
pop rcx
pop rax
add rsp, 4 * 8 ; ignore int_no, error, rip, cs
popfq
add rsp, 2 * 8 ; ignore userrsp, ss
jmp [rsp - 5 * 8] ; jump to rip from saved state
SECTION .data
align 4096

View file

@ -74,6 +74,7 @@ extern void irq22(void);
extern void irq23(void);
extern void irq80(void);
extern void irq81(void);
extern void irq82(void);
extern void apic_timer(void);
extern void apic_lint0(void);
extern void apic_lint1(void);
@ -230,6 +231,8 @@ static int irq_install(void)
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP, 1);
idt_set_gate(113, (size_t)irq81, KERNEL_CODE_SELECTOR,
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP, 1);
idt_set_gate(114, (size_t)irq82, KERNEL_CODE_SELECTOR,
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP, 1);
idt_set_gate(121, (size_t)wakeup, KERNEL_CODE_SELECTOR,
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP, 1);

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2016, Daniel Krebs, RWTH Aachen University
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @author Daniel Krebs
* @file include/hermit/signal.h
* @brief Signal related functions
*/
#ifndef __SIGNAL_H__
#define __SIGNAL_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <hermit/stddef.h>
#include <hermit/semaphore_types.h>
#define MAX_SIGNALS 32
typedef void (*signal_handler_t)(int);
// This is used in deqeue.h (HACK)
typedef struct _sig {
tid_t dest;
int signum;
} sig_t;
/** @brief Send signal to kernel task
*
* @param dest Send signal to this task
* @param signum Signal number
* @return
* - 0 on success
* - -ENOENT (-2) if task not found
*/
int hermit_kill(tid_t dest, int signum);
/** @brief Register signal handler
*
* @param handler Signal handler
* @return
* - 0 on success
*/
int hermit_signal(signal_handler_t handler);
#ifdef __cplusplus
}
#endif
#endif // __SIGNAL_H__

View file

@ -40,6 +40,7 @@
#include <hermit/stddef.h>
#include <hermit/spinlock_types.h>
#include <hermit/vma.h>
#include <hermit/signal.h>
#include <asm/tasks_types.h>
#include <asm/atomic.h>
@ -104,6 +105,8 @@ typedef struct task {
size_t tls_size;
/// LwIP error code
int lwip_err;
/// Handler for (POSIX) Signals
signal_handler_t signal_handler;
/// FPU state
union fpu_state fpu;
} task_t;

View file

@ -1,4 +1,4 @@
C_source := main.c tasks.c syscall.c timer.c
C_source := main.c tasks.c syscall.c timer.c signal.c
MODULE := kernel
include $(TOPDIR)/Makefile.inc

View file

@ -98,6 +98,8 @@ uint32_t idle_poll = 1;
islelock_t* rcce_lock = NULL;
rcce_mpb_t* rcce_mpb = NULL;
extern void signal_init();
#if 0
static int foo(void* arg)
{
@ -130,6 +132,8 @@ static int hermit_init(void)
timer_init();
multitasking_init();
memory_init();
signal_init();
#ifndef CONFIG_VGA
uart_init();
#endif

203
hermit/kernel/signal.c Normal file
View file

@ -0,0 +1,203 @@
#include <hermit/signal.h>
#include <hermit/stddef.h>
#include <hermit/spinlock.h>
#include <hermit/stdio.h>
#include <hermit/tasks.h>
#include <hermit/dequeue.h>
#include <asm/apic.h>
#include <asm/irq.h>
#include <asm/atomic.h>
#define ENABLE_DEBUG 0
#if !ENABLE_DEBUG
#define kprintf(...)
#endif
#define SIGNAL_IRQ (32 + 82)
#define SIGNAL_BUFFER_SIZE (16)
// Per-core signal queue and buffer
static dequeue_t signal_queue[MAX_CORES];
static sig_t signal_buffer[MAX_CORES][SIGNAL_BUFFER_SIZE];
static void _signal_irq_handler(struct state* s)
{
kprintf("Enter _signal_irq_handler() on core %d\n", CORE_ID);
sig_t signal;
task_t* dest_task;
task_t* curr_task = per_core(current_task);
while(dequeue_pop(&signal_queue[CORE_ID], &signal) == 0) {
kprintf(" Deliver signal %d\n", signal.signum);
if(get_task(signal.dest, &dest_task) == 0) {
kprintf(" Found valid task with ID %d\n", dest_task->id);
// only service signals for tasks on this core
if(dest_task->last_core != CORE_ID) {
kprintf(" Signal dispatched to wrong CPU! Dropping it ...\n");
continue;
}
if(dest_task->signal_handler) {
kprintf(" Has signal handler (%p)\n", dest_task->signal_handler);
/* We will inject the signal handler into the control flow when
* the task will continue it's exection the next time. There are
* 3 cases how the task was interrupted:
*
* 1. call to reschedule() by own intend
* 2. a timer interrupt lead to rescheduling to another task
* 3. this IRQ interrupted the task
*
* Depending on those cases, the state of the task can either be
* saved to it's own stack (1.), it's interrupt stack (IST, 2.)
* or the stack of this interrupt handler (3.).
*
* When the signal handler finishes it's execution, we need to
* restore the task state, so we make the signal handler return
* first to sighandler_epilog() which then restores the original
* state.
*
* For cases 2+3, when task was interrupted by an IRQ, we modify
* the existing state on the interrupt stack to execute the
* signal handler, wherease in case 1, we craft a new state and
* place it on top of the task stack.
*
* The task stack will have the following layout:
*
* | ... | <- task's rsp before interruption
* |----------------------|
* | saved state |
* |----------------------|
* | &sighandler_epilog() | <- rsp after IRQ
* |----------------------|
* |----------------------| Only for case 1:
* | signal handler state | Craft signal handler state, so it
* |----------------------| executes before task is continued
*/
size_t* task_stackptr;
struct state *task_state, *sighandler_state;
const int task_is_running = dest_task == curr_task;
kprintf(" Task is%s running\n", task_is_running ? "" : " not");
// location of task state depends of type of interruption
task_state = (!task_is_running) ?
/* case 1+2: */ (struct state*) dest_task->last_stack_pointer :
/* case 3: */ s;
// pseudo state pushed by reschedule() has INT no. 0
const int state_on_task_stack = task_state->int_no == 0;
if(state_on_task_stack) {
kprintf(" State is already on task stack\n");
// stack pointer was saved by switch_context() after saving
// task state to task stack
task_stackptr = dest_task->last_stack_pointer;
} else {
// stack pointer is last rsp, since task state is saved to
// interrupt stack
task_stackptr = (size_t*) task_state->rsp;
kprintf(" Copy state to task stack\n");
task_stackptr -= sizeof(struct state) / sizeof(size_t);
memcpy(task_stackptr, task_state, sizeof(struct state));
}
// signal handler will return to this function to restore
// register state
extern void sighandler_epilog();
*(--task_stackptr) = (uint64_t) &sighandler_epilog;
size_t* sighandler_rsp = task_stackptr;
if(state_on_task_stack) {
kprintf(" Craft state for signal handler on task stack\n");
// we actually only care for ss, rflags, cs, fs and gs
task_stackptr -= sizeof(struct state) / sizeof(size_t);
sighandler_state = (struct state*) task_stackptr;
memcpy(sighandler_state, task_state, sizeof(struct state));
// advance stack pointer so signal handler state will be
// restored first
dest_task->last_stack_pointer = (size_t*) sighandler_state;
} else {
kprintf(" Reuse state on IST for signal handler\n");
sighandler_state = task_state;
}
// update rsp so that sighandler_epilog() will be executed
// after signal handler
sighandler_state->rsp = (uint64_t) sighandler_rsp;
sighandler_state->userrsp = sighandler_state->rsp;
// call signal handler instead of continuing task's execution
sighandler_state->rdi = (uint64_t) signal.signum;
sighandler_state->rip = (uint64_t) dest_task->signal_handler;
} else {
kprintf(" No signal handler installed\n");
}
} else {
kprintf(" Task %d has already died\n", signal.dest);
}
}
kprintf("Leave _signal_irq_handler() on core %d\n", CORE_ID);
}
int hermit_signal(signal_handler_t handler)
{
task_t* curr_task = per_core(current_task);
curr_task->signal_handler = handler;
return 0;
}
int hermit_kill(tid_t dest, int signum)
{
task_t* task;
if(BUILTIN_EXPECT(get_task(dest, &task), 0)) {
kprintf("Trying to send signal %d to invalid task %d\n", signum, dest);
return -ENOENT;
}
const tid_t dest_core = task->last_core;
kprintf("Send signal %d from task %d (core %d) to task %d (core %d)\n",
signum, per_core(current_task)->id, CORE_ID, dest, dest_core);
if(task == per_core(current_task)) {
kprintf(" Deliver signal to itself, call handler immediately\n");
if(task->signal_handler) {
task->signal_handler(signum);
}
return 0;
}
sig_t signal = {dest, signum};
if(dequeue_push(&signal_queue[dest_core], &signal)) {
kprintf(" Cannot push signal to task's signal queue, dropping it\n");
return -ENOMEM;
}
// send IPI to destination core
kprintf(" Send signal IPI (%d) to core %d\n", SIGNAL_IRQ, dest_core);
apic_send_ipi(dest_core, SIGNAL_IRQ);
return 0;
}
void signal_init()
{
// initialize per-core signal queue
for(int i = 0; i < MAX_CORES; i++) {
dequeue_init(&signal_queue[i], signal_buffer[i],
SIGNAL_BUFFER_SIZE, sizeof(sig_t));
}
irq_install_handler(SIGNAL_IRQ, _signal_irq_handler);
}

View file

@ -387,6 +387,7 @@ int clone_task(tid_t* id, entry_point_t ep, void* arg, uint8_t prio)
task_table[i].tls_size = curr_task->tls_size;
task_table[i].ist_addr = ist;
task_table[i].lwip_err = 0;
task_table[i].signal_handler = NULL;
if (id)
*id = i;
@ -487,6 +488,7 @@ int create_task(tid_t* id, entry_point_t ep, void* arg, uint8_t prio, uint32_t c
task_table[i].tls_addr = 0;
task_table[i].tls_size = 0;
task_table[i].lwip_err = 0;
task_table[i].signal_handler = NULL;
if (id)
*id = i;