From 7e828ec1e1479ff9a8afe845539d08ca0dfada5f Mon Sep 17 00:00:00 2001 From: Kimplul Date: Mon, 8 Jul 2024 17:38:59 +0300 Subject: add orphan checking to timer and irq handling --- arch/riscv64/kernel/proc.c | 2 +- include/kmi/orphanage.h | 7 +++++++ include/kmi/syscalls.h | 6 +++--- src/irq.c | 13 ++++++++++--- src/orphanage.c | 13 +++++++++---- src/timer.c | 15 ++++++++++----- src/uapi/irq.c | 2 +- src/uapi/proc.c | 14 +++++++++++++- 8 files changed, 54 insertions(+), 18 deletions(-) diff --git a/arch/riscv64/kernel/proc.c b/arch/riscv64/kernel/proc.c index f21ce5c..73b0984 100644 --- a/arch/riscv64/kernel/proc.c +++ b/arch/riscv64/kernel/proc.c @@ -39,7 +39,7 @@ void run_init(struct tcb *t, vm_t fdt, vm_t initrd) "sret\n" : : "r" (stack_top), - "K" (0), "r" (t->tid), "K" (SYS_USER_BOOTED), + "K" (0), "r" (t->tid), "K" (SYS_USER_SPAWNED), "r" (fdt), "r" (initrd), "K" (1) : "memory"); /* we should never reach this */ diff --git a/include/kmi/orphanage.h b/include/kmi/orphanage.h index 98756c2..0506af3 100644 --- a/include/kmi/orphanage.h +++ b/include/kmi/orphanage.h @@ -26,6 +26,13 @@ bool orphan(struct tcb *t); /** * Mark \p t orphaned. + * Once a thread has been orphaned, it can't 'truly' be adopted by anyone again, + * and the init process will generally just make sure all the thread's resources + * are freed in a controlled manner. + * + * I did think about maybe allowing threads to be reused, I guess to save a bit + * of build/teardown time but I don't think it's worth it for the extra + * complexity. * * @param t Thread to orphanize. */ diff --git a/include/kmi/syscalls.h b/include/kmi/syscalls.h index eece89e..b6cde34 100644 --- a/include/kmi/syscalls.h +++ b/include/kmi/syscalls.h @@ -177,9 +177,9 @@ enum sys_user { /** Thread has been orphaned. */ SYS_USER_ORPHANED, - /** Core has booted an init thread. Only used during booting, should be - * handled specially. */ - SYS_USER_BOOTED, + /** A new process has been spawned. Special case for init where each + * core starts with this, usually it is reserved for exec. */ + SYS_USER_SPAWNED, }; /** Which notifications have arrived. */ diff --git a/src/irq.c b/src/irq.c index bc9e2de..582a06f 100644 --- a/src/irq.c +++ b/src/irq.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -54,6 +55,8 @@ stat_t unregister_irq(struct tcb *t, irq_t id) void handle_irq() { + bkl_lock(); + irq_t id = get_irq(); assert(id < max_irq); @@ -65,14 +68,18 @@ void handle_irq() } struct tcb *t = get_tcb(tid); - if (!t) { - error("tcb %llu dead at irq %llu\n", + if (!t || orphan(t)) { + info("tcb %llu dead at irq %llu\n", (unsigned long long)tid, (unsigned long long)id); + + /* unregister irq handler */ + irq_map[id] = 0; + bkl_unlock(); return; } disable_irqs(); notify(t, NOTIFY_IRQ); - error("misc error when trying to notify irq"); + bkl_unlock(); } diff --git a/src/orphanage.c b/src/orphanage.c index e613e8b..3ca016e 100644 --- a/src/orphanage.c +++ b/src/orphanage.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2024, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ +#include #include #include @@ -30,6 +31,7 @@ void unorphanize(struct tcb *t) struct tcb *init = get_tcb(1); reference_proc(init); + id_t old_rid = t->rid; t->rid = 1; t->pid = 1; t->eid = 1; @@ -41,15 +43,18 @@ void unorphanize(struct tcb *t) use_vmem(t->proc.vmem); alloc_stack(t); - clear_bits(t->state, TCB_ORPHAN); - assert(init->callback); - set_args3(t, 0, SYS_USER_ORPHANED, t->tid); + set_args4(t, 0, t->tid, SYS_USER_ORPHANED, old_rid); set_return(t, init->callback); t->callback = init->callback; - /** @todo release irqs, here or later? */ + /** @todo release irqs, here or later? Currently leaning towards later + * as they don't really take up any resources and the implementation + * doesn't make it too easy to search for a specific owner, we can just + * discard the IRQ if it turns out that the owner has been orphaned by + * that point. */ + bkl_unlock(); ret_userspace_fast(); unreachable(); } diff --git a/src/timer.c b/src/timer.c index 2d4ff01..573ed7b 100644 --- a/src/timer.c +++ b/src/timer.c @@ -16,14 +16,15 @@ */ #include -#include #include #include #include #include -#include #include #include +#include + +#include #include /** Timer resolution. */ @@ -185,7 +186,7 @@ stat_t remove_timer(struct timer *t) struct sp_node *n = &timer_node_container(t)->sp_n; sp_remove(&sp_root(__cpu_timers()), n); - + free_node(&node_root, t); return OK; } @@ -198,12 +199,16 @@ ticks_t nsecs_to_ticks(tunit_t nsecs) /* call to this function from exception handlers */ void handle_timer() { + /** @todo should this also disable irqs? */ bkl_lock(); struct timer *t = newest_timer(); + id_t tid = t->tid; remove_timer(t); - struct tcb *r = get_tcb(t->tid); - if (!r) { + struct tcb *r = get_tcb(tid); + if (!r || orphan(r)) { + info("tcb %llu dead at timer\n", + (unsigned long long)tid); bkl_unlock(); return; } diff --git a/src/uapi/irq.c b/src/uapi/irq.c index 29da614..9f3931c 100644 --- a/src/uapi/irq.c +++ b/src/uapi/irq.c @@ -22,7 +22,7 @@ SYSCALL_DEFINE1(irq_req)(struct tcb *t, sys_arg_t id) if (!has_cap(t->caps, CAP_IRQ)) return_args1(t, ERR_PERM); - if (!t->callback) + if (!t->callback || !t->notify_id) return_args1(t, ERR_NF); return_args1(t, register_irq(t, id)); diff --git a/src/uapi/proc.c b/src/uapi/proc.c index 293a01f..eddbab8 100644 --- a/src/uapi/proc.c +++ b/src/uapi/proc.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -127,7 +128,18 @@ SYSCALL_DEFINE2(exec)(struct tcb *t, sys_arg_t bin, sys_arg_t interp) if (interp) clear_bit(b->flags, MR_KEEP); - return_args1(t, prepare_proc(t, bin, interp)); + /* should hopefully never actually fail, but if it does, we don't really + * have any choice but to kill the thread. */ + if (prepare_proc(t, bin, interp)) { + /* this kills the thread */ + orphanize(t); + unorphanize(t); + /* should never be reached as we control the thread so we should + * be able to directly jump to pid 1 */ + assert(false); + } + + return_args4(t, 0, t->tid, SYS_USER_SPAWNED, t->pid); } /** -- cgit v1.3