aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-07-04 22:52:47 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-07-04 22:52:47 +0300
commitfdbc5adf83741d7e3af0c3f0ffdb59bdf42493b6 (patch)
tree2eb538b9ecc2898d1f94305fe56d0625060834de
parentb82479a69cd6110c9099031e133fda62f8e11222 (diff)
downloadkmi-fdbc5adf83741d7e3af0c3f0ffdb59bdf42493b6.tar.gz
kmi-fdbc5adf83741d7e3af0c3f0ffdb59bdf42493b6.zip
generalize orphants
+ Allow threads to make themselves become orphants
-rw-r--r--arch/riscv64/kernel/vmem.c21
-rw-r--r--include/arch/tcb.h8
-rw-r--r--include/kmi/orphanage.h9
-rw-r--r--include/kmi/syscalls.h3
-rw-r--r--include/kmi/tcb.h20
-rw-r--r--include/kmi/uapi.h15
-rw-r--r--src/orphanage.c13
-rw-r--r--src/tcb.c12
-rw-r--r--src/uapi/dispatch.c1
-rw-r--r--src/uapi/ipc.c19
-rw-r--r--src/uapi/proc.c29
11 files changed, 110 insertions, 40 deletions
diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c
index f87c76d..d4ae937 100644
--- a/arch/riscv64/kernel/vmem.c
+++ b/arch/riscv64/kernel/vmem.c
@@ -396,11 +396,16 @@ static void __populate_dmap(struct vmem *branch)
(struct vmem *)to_pte(TOP_PAGE_SIZE * i, flags);
}
+/** How many base pages we use for the rpc stack. Used fairly often so calculate
+ * it at the start and then reference it. */
+static size_t rpc_pages;
+
struct vmem *init_vmem(void *fdt)
{
UNUSED(fdt);
struct vmem *b = create_vmem();
__populate_dmap(b);
+ rpc_pages = order_size(MM_O1) / BASE_PAGE_SIZE;
/* update which memory branch to use */
use_vmem(b);
return b;
@@ -489,8 +494,7 @@ void setup_rpc_stack(struct tcb *t)
* access so as to ease stack usage tracking */
vmflags_t flags = VM_V | VM_R | VM_W | VM_U;
- size_t pages = order_size(MM_O1) / BASE_PAGE_SIZE;
- for (size_t i = 0; i < pages; ++i) {
+ for (size_t i = 0; i < rpc_pages; ++i) {
pm_t page = alloc_page(BASE_PAGE);
map_vpage(t->rpc.vmem, page,
RPC_STACK_BASE + BASE_PAGE_SIZE * i,
@@ -503,19 +507,19 @@ void setup_rpc_stack(struct tcb *t)
/* we allocated a second order page for rpc stack usage */
t->rpc_stack = RPC_STACK_BASE + order_size(MM_O1);
+
/* slightly hacky maybe but we know the first pte is at RPC_STACK_BASE,
* which means that it must also be the leaf */
t->arch.rpc_leaf = (struct vmem *)__find_vmem(t->rpc.vmem,
RPC_STACK_BASE,
BASE_PAGE);
- /* 'reserve' top page of stack for kernel use */
- t->arch.rpc_idx = 511;
+ /* we count downward in base pages */
+ t->arch.rpc_idx = rpc_pages;
}
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) {
+ for (size_t i = 0; i < rpc_pages; ++i) {
pm_t page; enum mm_order order;
stat_vpage(t->rpc.vmem, RPC_STACK_BASE + BASE_PAGE_SIZE * i,
&page, &order, NULL);
@@ -523,6 +527,11 @@ void destroy_rpc_stack(struct tcb *t)
}
}
+bool rpc_stack_empty(pm_t addr)
+{
+ return addr == RPC_STACK_BASE + (BASE_PAGE_SIZE * rpc_pages);
+}
+
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 115f49d..22d3b59 100644
--- a/include/arch/tcb.h
+++ b/include/arch/tcb.h
@@ -40,13 +40,17 @@ void setup_rpc_stack(struct tcb *t);
void destroy_rpc_stack(struct tcb *t);
/**
- * Maximum size of one individual RPC stack instance.
- *
* @return Max size of one individual RPC stack instance.
*/
size_t max_rpc_size();
/**
+ * @param addr Address of current top of stack, generally from `ctx->rpc_stack`.
+ * @return \ref true if next rpc return would be into the root process.
+ */
+bool rpc_stack_empty(pm_t addr);
+
+/**
* Current highest address in RPC stack. Allowed to be inaccurate to one base page.
*
* @param t Thread whose position in the RPC stack is to be determined.
diff --git a/include/kmi/orphanage.h b/include/kmi/orphanage.h
index 530d10c..09e36b3 100644
--- a/include/kmi/orphanage.h
+++ b/include/kmi/orphanage.h
@@ -23,6 +23,13 @@
bool orphan(struct tcb *t);
/**
+ * Mark \p t orphaned.
+ *
+ * @param t Thread to orphanize.
+ */
+void orphanize(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
@@ -30,6 +37,6 @@ bool orphan(struct tcb *t);
*
* @param t Orphaned thread.
*/
-__noreturn void orphanize(struct tcb *t);
+__noreturn void unorphanize(struct tcb *t);
#endif /* KMI_OPRHANAGE_H */
diff --git a/include/kmi/syscalls.h b/include/kmi/syscalls.h
index 3619469..93d0ac9 100644
--- a/include/kmi/syscalls.h
+++ b/include/kmi/syscalls.h
@@ -157,6 +157,9 @@ enum sys_code {
/** Request a thread exits. */
SYS_EXIT,
+ /** Detach a thread from its root process, becoming an orphant. */
+ SYS_DETACH,
+
/** @} */
SYS_NUM,
diff --git a/include/kmi/tcb.h b/include/kmi/tcb.h
index fac9df7..df161b3 100644
--- a/include/kmi/tcb.h
+++ b/include/kmi/tcb.h
@@ -72,16 +72,13 @@ struct tcb_ctx {
struct vmem *vmem;
};
-/** Enum for notification states. */
-enum tcb_notify {
- /** Thread is free to be notified. */
- NOTIFY_WAITING = 0,
+/** State of thread. */
+enum tcb_state {
+ /** Thread is a zombie. */
+ TCB_ZOMBIE = (1 << 0),
- /** Thread has notifcations queued. */
- NOTIFY_QUEUED,
-
- /** Thread is running notification handler. */
- NOTIFY_RUNNING,
+ /** Thread is an orphan. */
+ TCB_ORPHAN = (1 << 1),
};
/** Thread control block. Main way to handle threads. */
@@ -180,9 +177,8 @@ struct tcb {
/** Queue that connects together threads waiting for an ipi */
struct queue_head ipi_queue;
- /** Whether thread is dead. If thread is process, then corresponds to
- * whole process. */
- bool dead;
+ /** Current state of thread. */
+ enum tcb_state state;
};
/**
diff --git a/include/kmi/uapi.h b/include/kmi/uapi.h
index 684f0da..76a759f 100644
--- a/include/kmi/uapi.h
+++ b/include/kmi/uapi.h
@@ -841,6 +841,21 @@ SYSCALL_DECLARE1(irq_req, id);
*/
SYSCALL_DECLARE1(exit, tid);
+/**
+ * Request that a thread becomes orphant, i.e. eventually attached to the init
+ * process. Can be used to stop threads within a process by sending an
+ * appropriate signal to the troublesome thread which then detaches itself from
+ * its root process.
+ *
+ * @param t Current tcb.
+ * @param a Unused.
+ * @param b Unused.
+ * @param c Unused.
+ * @param d Unused.
+ * @param e Unused.
+ */
+SYSCALL_DECLARE0(detach);
+
/** @} */
/**
diff --git a/src/orphanage.c b/src/orphanage.c
index 7415e8b..4760f73 100644
--- a/src/orphanage.c
+++ b/src/orphanage.c
@@ -14,17 +14,17 @@
bool orphan(struct tcb *t)
{
- struct tcb *r = get_rproc(t);
- return !r || r->dead;
+ return is_set(t->state, TCB_ORPHAN);
}
void orphanize(struct tcb *t)
{
- catastrophic_assert(!is_rpc(t));
+ set_bit(t->state, TCB_ORPHAN);
+}
- struct tcb *r = get_tcb(t->rid);
- if (r)
- unreference_proc(r);
+void unorphanize(struct tcb *t)
+{
+ catastrophic_assert(!is_rpc(t));
/* attach to init process */
struct tcb *init = get_tcb(1);
@@ -35,6 +35,7 @@ void orphanize(struct tcb *t)
t->eid = 1;
t->proc = init->proc;
+ t->rpc_stack = RPC_STACK_BASE;
use_vmem(t->proc.vmem);
catastrophic_assert(init->callback);
diff --git a/src/tcb.c b/src/tcb.c
index 73711c3..1287cef 100644
--- a/src/tcb.c
+++ b/src/tcb.c
@@ -127,7 +127,7 @@ struct tcb *create_thread(struct tcb *p)
id_t tid = __alloc_tid(t);
tcbs[tid] = t;
t->tid = tid;
- t->dead = false;
+ t->state = 0;
if (likely(p)) {
t->pid = p->pid;
@@ -235,6 +235,10 @@ stat_t destroy_thread(struct tcb *t)
unqueue_ipi(t);
+ /** @todo timers, irqs? theoretically we could allow them to stay and
+ * let the handler check if the thread is still interested in the
+ * interrupt */
+
/* someone still relies on us existing, don't actually free thread data
* quite yet */
if (t->refcount)
@@ -255,9 +259,7 @@ stat_t destroy_proc(struct tcb *p)
* the segfault handler if the thread has become orphaned.
* Currently no segfault handler exists, though. */
- p->dead = true;
- /* unreference ourselves */
- unreference_proc(p);
+ set_bits(p->state, TCB_ZOMBIE);
/* clear all privately owned memory regions, keep shared ones alive for
* now */
@@ -284,7 +286,7 @@ void unreference_proc(struct tcb *p)
hard_assert(is_proc(p), RETURN_VOID);
p->refcount--;
- if (p->dead && p->refcount == 0) {
+ if (zombie(p) && p->refcount == 0) {
dbg("thread %d is completely destroyed\n", p->tid);
__destroy_thread_data(p);
}
diff --git a/src/uapi/dispatch.c b/src/uapi/dispatch.c
index 7cfce4c..c4ad811 100644
--- a/src/uapi/dispatch.c
+++ b/src/uapi/dispatch.c
@@ -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_DETACH: sys_detach(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,
diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c
index f3896f5..bc49dbb 100644
--- a/src/uapi/ipc.c
+++ b/src/uapi/ipc.c
@@ -83,7 +83,7 @@ static vm_t enter_rpc(struct tcb *t, struct sys_ret a,
{
/* 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);
+ is_rpc(t)) ? t->rpc_stack : rpc_position(t);
struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
ctx->regs = t->regs;
@@ -179,8 +179,8 @@ void notify(struct tcb *t, enum notify_flag flag)
return;
struct tcb *r = get_tcb(t->notify_id);
- if (!r || !r->callback) {
- error("notify callback dead\n");
+ if (!r || r->state || !r->callback) {
+ error("notify callback unavailable\n");
t->notify_flags = 0;
return;
}
@@ -218,31 +218,34 @@ static void leave_rpc(struct tcb *t, struct sys_ret a)
{
vm_t rpc_stack = t->rpc_stack + BASE_PAGE_SIZE;
struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
- vm_t top = ctx->rpc_stack;
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) {
+ while (!r || !is_proc(r) || zombie(r)) {
/* we unwound back to our root process which is apparently dead,
* we're orphaned :( */
- if (ctx->pid == t->rid) {
+ if (rpc_stack_empty(ctx->rpc_stack)) {
orphanize(t);
- return;
+ break;
}
rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE;
ctx = (struct call_ctx *)(rpc_stack) - 1;
r = get_tcb(ctx->pid);
+ set_args1(t, ERR_NF);
}
+ if (orphan(t) && !is_rpc(t))
+ unorphanize(t);
+
set_return(t, ctx->exec);
/* if we're returning from a failed rpc, this should essentially be a
* no-op */
- mark_rpc_valid(t, top);
+ mark_rpc_valid(t, ctx->rpc_stack);
t->rpc_stack = ctx->rpc_stack;
t->pid = ctx->pid;
t->eid = ctx->eid;
diff --git a/src/uapi/proc.c b/src/uapi/proc.c
index 3ce808f..231cc32 100644
--- a/src/uapi/proc.c
+++ b/src/uapi/proc.c
@@ -235,6 +235,35 @@ SYSCALL_DEFINE1(exit)(struct tcb *t, sys_arg_t tid)
}
/**
+ * Syscall handler for orphanizing a thread.
+ *
+ * @param t Thread that wants to make itself an orphant.
+ * @return \ref OK on success,
+ * \ref ERR_PERM if current process missing \ref CAP_PROC,
+ * \ref ERR_INVAL if already an orphant.
+ */
+SYSCALL_DEFINE0(detach)(struct tcb *t)
+{
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ if (orphan(t))
+ return_args1(t, ERR_INVAL);
+
+ struct tcb *r = get_tcb(t->rid);
+ if (r)
+ unreference_proc(r);
+
+ orphanize(t);
+
+ if (!is_rpc(t))
+ unorphanize(t);
+
+ return_args1(t, OK);
+}
+
+/**
* Swap syscall handler.
*
* \todo Implement.