diff options
| -rw-r--r-- | arch/riscv64/kernel/vmem.c | 11 | ||||
| -rw-r--r-- | include/arch/tcb.h | 7 | ||||
| -rw-r--r-- | include/kmi/ipi.h | 6 | ||||
| -rw-r--r-- | include/kmi/orphanage.h | 35 | ||||
| -rw-r--r-- | include/kmi/queue.h | 20 | ||||
| -rw-r--r-- | include/kmi/syscalls.h | 12 | ||||
| -rw-r--r-- | include/kmi/tcb.h | 21 | ||||
| -rw-r--r-- | include/kmi/uapi.h | 27 | ||||
| -rw-r--r-- | src/ipi.c | 5 | ||||
| -rw-r--r-- | src/orphanage.c | 49 | ||||
| -rw-r--r-- | src/tcb.c | 51 | ||||
| -rw-r--r-- | src/uapi/conf.c | 1 | ||||
| -rw-r--r-- | src/uapi/dispatch.c | 3 | ||||
| -rw-r--r-- | src/uapi/ipc.c | 40 | ||||
| -rw-r--r-- | src/uapi/proc.c | 100 |
15 files changed, 320 insertions, 68 deletions
diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c index d00a6c9..f87c76d 100644 --- a/arch/riscv64/kernel/vmem.c +++ b/arch/riscv64/kernel/vmem.c @@ -512,6 +512,17 @@ void setup_rpc_stack(struct tcb *t) t->arch.rpc_idx = 511; } +void destroy_rpc_stack(struct tcb *t) +{ + size_t pages = order_size(MM_O1) / BASE_PAGE_SIZE; + for (size_t i = 0; i < pages; ++i) { + pm_t page; enum mm_order order; + stat_vpage(t->rpc.vmem, RPC_STACK_BASE + BASE_PAGE_SIZE * i, + &page, &order, NULL); + free_page(page, order); + } +} + vm_t rpc_position(struct tcb *t) { /** @todo we assume rpc_idx is updated on every segfault of the rpc stack */ diff --git a/include/arch/tcb.h b/include/arch/tcb.h index 4e82ab1..115f49d 100644 --- a/include/arch/tcb.h +++ b/include/arch/tcb.h @@ -33,6 +33,13 @@ void tcb_assign(struct tcb *t); void setup_rpc_stack(struct tcb *t); /** + * Free memory backing rpc stack. + * + * @param t Thred whose RPC stack should be destroyed. + */ +void destroy_rpc_stack(struct tcb *t); + +/** * Maximum size of one individual RPC stack instance. * * @return Max size of one individual RPC stack instance. diff --git a/include/kmi/ipi.h b/include/kmi/ipi.h index 7a0760a..9c2963f 100644 --- a/include/kmi/ipi.h +++ b/include/kmi/ipi.h @@ -21,6 +21,12 @@ */ void send_ipi(struct tcb *t); +/** + * Remove thread from IPI queue if it is on it. + * @param t Thread to unqueue. + */ +void unqueue_ipi(struct tcb *t); + /** Handle IPI. */ void handle_ipi(); diff --git a/include/kmi/orphanage.h b/include/kmi/orphanage.h new file mode 100644 index 0000000..530d10c --- /dev/null +++ b/include/kmi/orphanage.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#ifndef KMI_ORPHANAGE_H +#define KMI_OPRHANAGE_H + +/** + * @file orphanage.h + * + * Stuff related to orphaned threads. + * + * Oprhaned threads are threads whose owning process has been destroyed. + * They are always assigned to the init process, which will generally destroy + * them whenever it gets a chance. + */ + +#include <kmi/tcb.h> + +/** + * @param t Thread suspected of being an orphan. + * @return \ref true if \p t is an orphan, \ref false otherwise. + */ +bool orphan(struct tcb *t); + +/** + * Assign \p t to the init process and jump to it. + * \p t must be an orphan! + * \p t must be in the process of swapping to its root process, either by + * returning from an rpc or being swapped to. + * + * @param t Orphaned thread. + */ +__noreturn void orphanize(struct tcb *t); + +#endif /* KMI_OPRHANAGE_H */ diff --git a/include/kmi/queue.h b/include/kmi/queue.h index 534318a..c00bf6f 100644 --- a/include/kmi/queue.h +++ b/include/kmi/queue.h @@ -53,4 +53,24 @@ static inline struct queue_head *queue_pop(struct queue_head *head) return container_of(e, struct queue_head, l); } +/** + * @param head Entry to check for queueing status. + * @return \ref true if in queue, \ref false otherwise. + */ +static inline bool in_queue(struct queue_head *head) +{ + return in_list(&head->l); +} + +/** + * Remove entry from queue. + * + * @param head Entry to remove. + */ +static inline void queue_del(struct queue_head *head) +{ + if (in_queue(head)) + list_del(&head->l); +} + #endif /* QUEUE_H */ diff --git a/include/kmi/syscalls.h b/include/kmi/syscalls.h index c78ddae..3619469 100644 --- a/include/kmi/syscalls.h +++ b/include/kmi/syscalls.h @@ -99,8 +99,8 @@ enum sys_code { /** IPC return without visible side effects. */ SYS_IPC_GHOST, - /** IPC notify thread, essentially interrupt or signal. */ - SYS_IPC_NOTIFY, + /** Notify thread, essentially interrupt or signal. */ + SYS_NOTIFY, /** @} */ /** @name Process management. */ @@ -154,6 +154,9 @@ enum sys_code { /** Request a notification handler. */ SYS_REQ_NOTIFICATION, + /** Request a thread exits. */ + SYS_EXIT, + /** @} */ SYS_NUM, @@ -165,11 +168,14 @@ enum sys_user { /** Thread has received one or several notifications, please handle * them. */ SYS_USER_NOTIFY, + + /** Thread has been orphaned. */ + SYS_USER_ORPHANED, }; /** Which notifications have arrived. */ enum notify_flag { - /** A signal (\ref ipc_notify()). */ + /** A signal (\ref sys_notify()). */ NOTIFY_SIGNAL = (1 << 0), /** A timer has expired. */ diff --git a/include/kmi/tcb.h b/include/kmi/tcb.h index b52f5e3..fac9df7 100644 --- a/include/kmi/tcb.h +++ b/include/kmi/tcb.h @@ -98,7 +98,7 @@ struct tcb { /** Arch-specific data. */ struct arch_tcbd arch; - /** Memory mapping data. */ + /** Memory mapping data. Only relevant in root thread. */ struct mem_region_root sp_r; /** Address of callback function in servers. */ @@ -128,16 +128,6 @@ struct tcb { struct tcb_ctx rpc; /** - * RPC server context of thread. When a thread attaches itself to this - * process, its \ref rpc member is added to the list maintained in this - * variable. This allows the original thread to do rpc calls without - * messing up other threads' rpc status. - * - * I think, more testing required. - */ - struct tcb_ctx server; - - /** * Effective process ID. * * This is the ID on which globally visible stuff should occur, such as @@ -335,6 +325,15 @@ void set_return(struct tcb *t, vm_t r); bool running(struct tcb *t); /** + * Check whether \p t is actually dead but just kept around for resource + * management. + * + * @param t \ref tcb to check. + * @return \c true if \p t is a zombie, \c false otherwise. + */ +bool zombie(struct tcb *t); + +/** * Add a reference to a process. * Instead of lists of threads that belong to a process, we give the process' * owning thread a reference counter. When a process is killed, a 'dead' bit is diff --git a/include/kmi/uapi.h b/include/kmi/uapi.h index 00fb45a..684f0da 100644 --- a/include/kmi/uapi.h +++ b/include/kmi/uapi.h @@ -584,7 +584,7 @@ SYSCALL_DECLARE0(ipc_ghost); * * Returns \ref OK and 0. */ -SYSCALL_DECLARE1(ipc_notify, tid); +SYSCALL_DECLARE1(notify, tid); /** @} */ /** @name Process handling syscalls. */ @@ -657,18 +657,17 @@ SYSCALL_DECLARE2(exec, bin, interp); SYSCALL_DECLARE2(spawn, bin, interp); /** - * Kill syscall. + * Kill syscall. Requests that all memory in process \p pid is freed and all + * threads are orphaned. If \p pid is not a process, nothing is done. * * @param t Current tcb. - * @param tid Thread ID to kill. 0 if self. + * @param pid Thread ID to kill. 0 if self. * @param b Unused. * @param c Unused. * @param d Unused. * @param e Unused. - * - * Returns \ref OK if not called on itself, otherwise doesn't return. */ -SYSCALL_DECLARE1(kill, tid); +SYSCALL_DECLARE1(kill, pid); /** * Swap syscall. @@ -826,6 +825,22 @@ SYSCALL_DECLARE0(sleep); */ SYSCALL_DECLARE1(irq_req, id); +/** + * Request that a thread exits, i.e. removes itself from the thread list and + * frees all kernel data associated with thread. + * Note that similarly to \ref kill(), some shared resources may cause the + * thread to stay around until their reference counts go to zero. + * + * @param t Current tcb. + * @param tid Thread to swap to once the current thread no longer exists. 0 for + * sleeping. + * @param b Unused. + * @param c Unused. + * @param d Unused. + * @param e Unused. + */ +SYSCALL_DECLARE1(exit, tid); + /** @} */ /** @@ -26,6 +26,11 @@ void send_ipi(struct tcb *t) cpu_send_ipi(t->cpu_id); } +void unqueue_ipi(struct tcb *t) +{ + queue_del(&t->ipi_queue); +} + void handle_ipi() { struct tcb *t = cur_tcb(); diff --git a/src/orphanage.c b/src/orphanage.c new file mode 100644 index 0000000..7415e8b --- /dev/null +++ b/src/orphanage.c @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2024, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +#include <kmi/assert.h> +#include <kmi/orphanage.h> + +#include <arch/proc.h> + +/** + * @file orphanage.c + * + * Stuff related to orphaned threads implementation. + */ + +bool orphan(struct tcb *t) +{ + struct tcb *r = get_rproc(t); + return !r || r->dead; +} + +void orphanize(struct tcb *t) +{ + catastrophic_assert(!is_rpc(t)); + + struct tcb *r = get_tcb(t->rid); + if (r) + unreference_proc(r); + + /* attach to init process */ + struct tcb *init = get_tcb(1); + reference_proc(init); + + t->rid = 1; + t->pid = 1; + t->eid = 1; + + t->proc = init->proc; + use_vmem(t->proc.vmem); + + catastrophic_assert(init->callback); + set_args3(t, 0, SYS_USER_ORPHANED, t->tid); + set_return(t, init->callback); + t->callback = init->callback; + + /** @todo release irqs, here or later? */ + + ret_userspace_fast(); + unreachable(); +} @@ -7,6 +7,7 @@ */ #include <kmi/tcb.h> +#include <kmi/ipi.h> #include <kmi/mem.h> #include <kmi/conf.h> #include <kmi/pmem.h> @@ -200,15 +201,23 @@ struct tcb *create_proc(struct tcb *p) */ static stat_t __destroy_thread_data(struct tcb *t) { + catastrophic_assert(t->refcount == 0); + + /* free memory backing rpc stack */ + destroy_rpc_stack(t); + /* free rpc vmem */ destroy_vmem(t->rpc.vmem); + /* remove ourselves from the thread pool */ + tcbs[t->tid] = 0; + + /* forcefully free last struggling bits of memory */ + destroy_uvmem(t); + /* free associated kernel stack and the structure itself */ vm_t bottom = align_down((vm_t)t, order_size(MM_O0)); free_page(MM_O0, (pm_t)bottom); - - /** \todo free stacks */ - return OK; } @@ -217,14 +226,19 @@ stat_t destroy_thread(struct tcb *t) hard_assert(tcbs, ERR_NOINIT); hard_assert(!is_proc(t), ERR_INVAL); - /* remove thread id from list */ - /** @todo what about if thread is in rpc? should it rather just be - * marked dead? */ - tcbs[t->tid] = 0; + /* mark us as zombies */ + t->rid = 0; /* remove reference to root process */ unreference_proc(get_rproc(t)); + unqueue_ipi(t); + + /* someone still relies on us existing, don't actually free thread data + * quite yet */ + if (t->refcount) + return OK; + return __destroy_thread_data(t); } @@ -238,22 +252,31 @@ stat_t destroy_proc(struct tcb *p) unreference_proc(p); catastrophic_assert(destroy_uvmem(p)); - return __destroy_thread_data(p); + + /* don't destroy thread data just yet, let the thread destroy itself + * later */ + return OK; } void reference_proc(struct tcb *p) { + if (!p) + return; + hard_assert(is_proc(p), RETURN_VOID); p->refcount++; } void unreference_proc(struct tcb *p) { + if (!p) + return; + hard_assert(is_proc(p), RETURN_VOID); p->refcount--; if (p->dead && p->refcount == 0) { dbg("thread %d is completely destroyed\n", p->tid); - /** @todo actually destroy */ + __destroy_thread_data(p); } } @@ -308,3 +331,13 @@ bool running(struct tcb *t) { return cpu_tcb(t->cpu_id) == t; } + +bool zombie(struct tcb *t) +{ + /* we shouldn't see any NULLs but they're effectively the same thing */ + if (!t) + return true; + + /* thread doesn't belong to any process, a zombie */ + return t->rid == 0; +} diff --git a/src/uapi/conf.c b/src/uapi/conf.c index 242239c..685d294 100644 --- a/src/uapi/conf.c +++ b/src/uapi/conf.c @@ -12,6 +12,7 @@ #include <kmi/sizes.h> #include <kmi/uapi.h> #include <kmi/conf.h> +#include <arch/irq.h> #include <arch/proc.h> diff --git a/src/uapi/dispatch.c b/src/uapi/dispatch.c index f0ff724..7cfce4c 100644 --- a/src/uapi/dispatch.c +++ b/src/uapi/dispatch.c @@ -66,7 +66,7 @@ void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b, case SYS_IPC_KICK: sys_ipc_kick(t, a, b, c, d, e); break; case SYS_IPC_RESP: sys_ipc_resp(t, a, b, c, d, e); break; case SYS_IPC_GHOST: sys_ipc_ghost(t, a, b, c, d, e); break; - case SYS_IPC_NOTIFY: sys_ipc_notify(t, a, b, c, d, e); break; + case SYS_NOTIFY: sys_notify(t, a, b, c, d, e); break; case SYS_CREATE: sys_create(t, a, b, c, d, e); break; case SYS_FORK: sys_fork(t, a, b, c, d, e); break; case SYS_EXEC: sys_exec(t, a, b, c, d, e); break; @@ -81,6 +81,7 @@ void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b, case SYS_POWEROFF: sys_poweroff(t, a, b, c, d, e); break; case SYS_SLEEP: sys_sleep(t, a, b, c, d, e); break; case SYS_IRQ_REQ: sys_irq_req(t, a, b, c, d, e); break; + case SYS_EXIT: sys_exit(t, a, b, c, d, e); break; default: error("Syscall %zu outside allowed range [0 - %i]\n", syscall, SYS_NUM - 1); diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c index 069ca59..f3896f5 100644 --- a/src/uapi/ipc.c +++ b/src/uapi/ipc.c @@ -6,6 +6,7 @@ * Interprocess communication syscall implementations. */ +#include <kmi/orphanage.h> #include <kmi/debug.h> #include <kmi/uapi.h> #include <kmi/tcb.h> @@ -29,9 +30,6 @@ struct call_ctx { /** Current process ID. */ id_t pid; - - /** Whether this context should be skipped when responding. */ - bool kick; }; /** @@ -83,7 +81,9 @@ static void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s) static vm_t enter_rpc(struct tcb *t, struct sys_ret a, enum ipc_kind kind) { - vm_t rpc_stack = rpc_position(t); + /* reuse current rpc stack location if we're being kicked */ + vm_t rpc_stack = (kind == IPC_KICK && + is_rpc(t)) ? t->rpc_stack :rpc_position(t); struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1; ctx->regs = t->regs; @@ -98,9 +98,6 @@ static vm_t enter_rpc(struct tcb *t, struct sys_ret a, ctx->eid = t->eid; ctx->rpc_stack = rpc_stack; - /* only rpcs can be kicked forward */ - ctx->kick = kind == IPC_KICK && is_rpc(t); - /** @todo if we run out of rpc_stack space we should just stop, likely * return a status? except it shouldn't happen after we've run * enough_rpc_stack(). */ @@ -223,17 +220,25 @@ static void leave_rpc(struct tcb *t, struct sys_ret a) struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1; vm_t top = ctx->rpc_stack; - /* find first instance of not kicked context */ - while (ctx->kick) { - rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE; - ctx = (struct call_ctx *)(rpc_stack) - 1; - unreference_proc(get_tcb(ctx->pid)); - } - t->regs = ctx->regs; /* again, get rid of args as fast as possible */ set_args(t, 6, a); + struct tcb *r = get_tcb(ctx->pid); + while (!r || r->dead) { + /* we unwound back to our root process which is apparently dead, + * we're orphaned :( */ + if (ctx->pid == t->rid) { + orphanize(t); + return; + } + + rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE; + ctx = (struct call_ctx *)(rpc_stack) - 1; + + r = get_tcb(ctx->pid); + } + set_return(t, ctx->exec); /* if we're returning from a failed rpc, this should essentially be a * no-op */ @@ -301,13 +306,12 @@ static void do_ipc(struct tcb *t, vm_t s = enter_rpc(t, SYS_RET6(t->eid, t->tid, d0, d1, d2, d3), kind); struct tcb *r = get_tcb(pid); - if (unlikely(!r)) { + if (unlikely(!r || !is_proc(r))) { leave_rpc(t, SYS_RET1(ERR_INVAL)); return; } - r = get_rproc(r); - if (unlikely(r->dead)) { + if (unlikely(zombie(r))) { leave_rpc(t, SYS_RET1(ERR_INVAL)); return; } @@ -430,7 +434,7 @@ SYSCALL_DEFINE0(ipc_ghost)(struct tcb *t) * @param tid Thread ID to notify. * @return \ref OK and 0. */ -SYSCALL_DEFINE1(ipc_notify)(struct tcb *t, sys_arg_t tid){ +SYSCALL_DEFINE1(notify)(struct tcb *t, sys_arg_t tid){ if (t->tid != tid && !has_cap(t->caps, CAP_NOTIFY)) return_args1(t, ERR_PERM); diff --git a/src/uapi/proc.c b/src/uapi/proc.c index 1b672ce..3ce808f 100644 --- a/src/uapi/proc.c +++ b/src/uapi/proc.c @@ -10,7 +10,9 @@ #include <kmi/uapi.h> #include <kmi/proc.h> #include <kmi/bits.h> +#include <kmi/power.h> #include <kmi/notify.h> +#include <kmi/orphanage.h> #include <kmi/mem_regions.h> #include <arch/irq.h> @@ -145,24 +147,94 @@ SYSCALL_DEFINE2(spawn)(struct tcb *t, sys_arg_t bin, sys_arg_t interp) * Kill syscall handler. * * @param t Current tcb. - * @param tid Thread to kill. + * @param pid Process to kill. * \todo Implement. * * @return ERR_PERM if not capable to kill, otherwise OK. */ -SYSCALL_DEFINE1(kill)(struct tcb *t, sys_arg_t tid) +SYSCALL_DEFINE1(kill)(struct tcb *t, sys_arg_t pid) { struct tcb *c = get_cproc(t); if (!(has_cap(c->caps, CAP_PROC))) return_args1(t, ERR_PERM); - /** @todo implement */ - /** @todo remember to unregister IRQ handlers */ + struct tcb *r = get_tcb(pid); + if (is_proc(r)) + return_args1(t, ERR_INVAL); + destroy_proc(r); return_args1(t, OK); } /** + * Actual worker of swapping between threads. + * Assumes that both \p t and \p s exist and that \p s isn't a zombie or + * currently running. + * + * @param t Current tcb. + * @param s Thread to swap to. + */ +static void swap(struct tcb *t, struct tcb *s) +{ + /* switch over to new thread */ + use_tcb(s); + + /* if an irq handler is directly swapping to some other thread, + * interpret it as the thread being finished with its critical section */ + enable_irqs(); + + if (!is_rpc(s) && orphan(s)) { + orphanize(s); + return; + } + + /* set return value for current thread */ + set_args1(t, OK); + + /* handle possible queued notification */ + if (s->notify_flags) + notify(s, 0); + + /* no notifications, so get register state for new thread */ + return_args(s, get_args(s)); +} + +/** + * Syscall handler for exit syscall. + * + * @param t Thread that is exiting. + * @param tid Thread to swap to. + * + * @return \ref OK or \ref ERR_INVAL is \p tid is not a thread + * or \ref ERR_NF if \p tid is a zombie + * or \ref ERR_EXT if \p tid is currently running. + */ +SYSCALL_DEFINE1(exit)(struct tcb *t, sys_arg_t tid) +{ + if (tid != 0) { + struct tcb *s = get_tcb(tid); + if (!s) + return_args1(t, ERR_INVAL); + + if (zombie(s)) + return_args1(t, ERR_NF); + + if (running(s)) + return_args1(t, ERR_EXT); + + + swap(t, s); + } + + destroy_thread(t); + + if (tid == 0) { + enable_irqs(); + sleep(); + } +} + +/** * Swap syscall handler. * * \todo Implement. @@ -183,23 +255,11 @@ SYSCALL_DEFINE1(swap)(struct tcb *t, sys_arg_t tid){ if (!s) return_args1(t, ERR_INVAL); + if (zombie(s)) + return_args1(t, ERR_NF); + if (running(s)) return_args1(t, ERR_EXT); - /* switch over to new thread */ - use_tcb(s); - - /* set return value for current thread */ - set_args1(t, OK); - - /* if an irq handler is directly swapping to some other thread, - * interpret it as the thread being finished with its critical section */ - enable_irqs(); - - /* handle possible queued notification */ - if (s->notify_flags) - notify(s, 0); - - /* no notifications, so get register state for new thread */ - return_args(s, get_args(s)); + return swap(t, s); } |
