aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/riscv64/kernel/vmem.c11
-rw-r--r--include/arch/tcb.h7
-rw-r--r--include/kmi/ipi.h6
-rw-r--r--include/kmi/orphanage.h35
-rw-r--r--include/kmi/queue.h20
-rw-r--r--include/kmi/syscalls.h12
-rw-r--r--include/kmi/tcb.h21
-rw-r--r--include/kmi/uapi.h27
-rw-r--r--src/ipi.c5
-rw-r--r--src/orphanage.c49
-rw-r--r--src/tcb.c51
-rw-r--r--src/uapi/conf.c1
-rw-r--r--src/uapi/dispatch.c3
-rw-r--r--src/uapi/ipc.c40
-rw-r--r--src/uapi/proc.c100
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);
+
/** @} */
/**
diff --git a/src/ipi.c b/src/ipi.c
index e9bc4d3..d00ba1c 100644
--- a/src/ipi.c
+++ b/src/ipi.c
@@ -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();
+}
diff --git a/src/tcb.c b/src/tcb.c
index c0f82cc..8280deb 100644
--- a/src/tcb.c
+++ b/src/tcb.c
@@ -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);
}