diff --git a/hermit/arch/x86/kernel/entry.asm b/hermit/arch/x86/kernel/entry.asm index 37767864d..d3ced9bf9 100644 --- a/hermit/arch/x86/kernel/entry.asm +++ b/hermit/arch/x86/kernel/entry.asm @@ -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 diff --git a/hermit/arch/x86/kernel/irq.c b/hermit/arch/x86/kernel/irq.c index 89328c6c5..c22c9ff6f 100644 --- a/hermit/arch/x86/kernel/irq.c +++ b/hermit/arch/x86/kernel/irq.c @@ -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); diff --git a/hermit/include/hermit/signal.h b/hermit/include/hermit/signal.h new file mode 100644 index 000000000..9c71de13c --- /dev/null +++ b/hermit/include/hermit/signal.h @@ -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 +#include + +#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__ diff --git a/hermit/include/hermit/tasks_types.h b/hermit/include/hermit/tasks_types.h index 0c63a8ad6..cb4be8b7d 100644 --- a/hermit/include/hermit/tasks_types.h +++ b/hermit/include/hermit/tasks_types.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/hermit/kernel/Makefile b/hermit/kernel/Makefile index 1082e2f15..524b2e44c 100644 --- a/hermit/kernel/Makefile +++ b/hermit/kernel/Makefile @@ -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 diff --git a/hermit/kernel/main.c b/hermit/kernel/main.c index 2a2a52dfe..c7dcf835d 100644 --- a/hermit/kernel/main.c +++ b/hermit/kernel/main.c @@ -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 diff --git a/hermit/kernel/signal.c b/hermit/kernel/signal.c new file mode 100644 index 000000000..4c010c72f --- /dev/null +++ b/hermit/kernel/signal.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + diff --git a/hermit/kernel/tasks.c b/hermit/kernel/tasks.c index d2d15a087..d5d091358 100644 --- a/hermit/kernel/tasks.c +++ b/hermit/kernel/tasks.c @@ -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;