From 363d0ec55f1d7e0d44f14cb1d63fdca4bc72efde Mon Sep 17 00:00:00 2001 From: Kimplul Date: Mon, 9 Oct 2023 16:13:47 +0300 Subject: experimenting with removing lists threads per proc + Gives a very slight improvement to RPC speeds, but mostly cleans up code a little bit. --- arch/riscv64/include/tcb.h | 4 ++ arch/riscv64/kernel/vmem.c | 22 ++-------- common/dmem.c | 11 +++-- common/tcb.c | 97 +++++++++--------------------------------- common/uapi/ipc.c | 12 +++--- common/vmem.c | 43 +++++++------------ include/arch/tcb.h | 32 ++++++++++++++ include/kmi/tcb.h | 104 ++++++++++++++------------------------------- 8 files changed, 118 insertions(+), 207 deletions(-) diff --git a/arch/riscv64/include/tcb.h b/arch/riscv64/include/tcb.h index a804175..f0cce37 100644 --- a/arch/riscv64/include/tcb.h +++ b/arch/riscv64/include/tcb.h @@ -16,7 +16,11 @@ struct arch_tcbd { /** Extra scratch register. */ long scratch; + /** RPC stack page table leaf node. */ struct vmem *rpc_leaf; + + /** Index into \p rpc_leaf with the lowest accessed page so far in a + * certain context. */ int rpc_idx; }; diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c index 7d1d3cc..37b9040 100644 --- a/arch/riscv64/kernel/vmem.c +++ b/arch/riscv64/kernel/vmem.c @@ -177,11 +177,7 @@ stat_t set_vpage_flags(struct vmem *branch, vm_t vaddr, vmflags_t flags) pm_t *pte = __find_vmem(branch, vaddr, &order); if (pte) { set_bits(*pte, vp_flags(flags)); - - if (order == __mm_max_order) - return INFO_SEFF; - else - return OK; + return OK; } return ERR_NF; @@ -193,11 +189,7 @@ stat_t clear_vpage_flags(struct vmem *branch, vm_t vaddr, vmflags_t flags) pm_t *pte = __find_vmem(branch, vaddr, &order); if (pte) { clear_bits(*pte, vp_flags(flags)); - - if (order == __mm_max_order) - return INFO_SEFF; - else - return OK; + return OK; } return ERR_NF; @@ -211,13 +203,7 @@ stat_t mod_vpage(struct vmem *branch, vm_t vaddr, pm_t paddr, vmflags_t flags) pm_t *pte = __find_vmem(branch, vaddr, &order); if (pte) { *pte = to_pte((pm_t)__pa(paddr), vp_flags(flags)); - /* if we're modifying a top level mapping, we will have to - * update the same one for all the other threads in this process - * */ - if (order == __mm_max_order) - return INFO_SEFF; - else - return OK; + return OK; } return ERR_NF; @@ -323,7 +309,7 @@ stat_t map_vpage(struct vmem *branch, pm_t paddr, vm_t vaddr, vmflags_t flags, (struct vmem *)to_pte((pm_t)__pa(paddr), vp_flags(flags)); __add_graves(root, vm_to_index(vaddr, __mm_max_order)); - return top == __mm_max_order ? INFO_SEFF : OK; + return OK; } /** diff --git a/common/dmem.c b/common/dmem.c index b903cbe..bbabe09 100644 --- a/common/dmem.c +++ b/common/dmem.c @@ -106,14 +106,15 @@ vm_t alloc_devmem(struct tcb *t, pm_t dev_start, size_t bytes, vmflags_t flags) region = alloc_region(&post_ram, bytes, 0, flags); if (!region) - return 0; + return NULL; stat_t status = OK; const vm_t w = map_fill_region(t->proc.vmem, &dev_alloc_wrapper, dev_start, region, bytes, flags, &status); - if (is_rpc(t) && status == INFO_SEFF) - clone_rpc_maps(t); + + if (status) + return NULL; return w; } @@ -142,8 +143,6 @@ stat_t free_devmem(struct tcb *t, vm_t dev_start) stat_t status = OK; map_fill_region(t->proc.vmem, &dev_free_wrapper, dev_paddr, dev_start, region_size, 0, &status); - if (is_rpc(t) && status == INFO_SEFF) - clone_rpc_maps(t); if (dev_paddr < __pre_top) free_region(&pre_ram, dev_paddr); @@ -151,5 +150,5 @@ stat_t free_devmem(struct tcb *t, vm_t dev_start) if (dev_paddr > __post_base) free_region(&post_ram, dev_paddr); - return OK; + return status; } diff --git a/common/tcb.c b/common/tcb.c index e69f2f0..4b57c06 100644 --- a/common/tcb.c +++ b/common/tcb.c @@ -125,6 +125,7 @@ struct tcb *create_thread(struct tcb *p) id_t tid = __alloc_tid(t); tcbs[tid] = t; t->tid = tid; + t->dead = false; if (likely(p)) { t->pid = p->pid; @@ -144,6 +145,7 @@ struct tcb *create_thread(struct tcb *p) t->rid = p->rid; t->rpc.vmem = create_vmem(); setup_rpc_stack(t); + reference_proc(p); t->regs = (vm_t)t; @@ -215,10 +217,12 @@ stat_t destroy_thread(struct tcb *t) 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; - /* remove thread from process list */ - detach_proc(get_rproc(t), t); + /* remove reference to root process */ + unreference_proc(get_rproc(t)); return __destroy_thread_data(t); } @@ -228,67 +232,28 @@ stat_t destroy_proc(struct tcb *p) hard_assert(tcbs, ERR_NOINIT); hard_assert(is_proc(p), ERR_INVAL); - for (struct tcb *iter = p; (iter = iter->proc.next);) - destroy_thread(iter); + p->dead = true; + /* unreference ourselves */ + unreference_proc(p); catastrophic_assert(destroy_uvmem(p)); return __destroy_thread_data(p); } -stat_t attach_rpc(struct tcb *r, struct tcb *t) +void reference_proc(struct tcb *p) { - hard_assert(r != t, ERR_INVAL); - struct tcb *next = r->server.next; - t->rpc.next = next; - - if (next) { next->rpc.prev = t; } - - t->rpc.prev = r; - r->server.next = t; - return OK; -} - -stat_t attach_proc(struct tcb *r, struct tcb *t) -{ - hard_assert(r != t, ERR_INVAL); - struct tcb *next = r->proc.next; - t->proc.next = next; - - if (next) { next->proc.prev = t; } - - t->proc.prev = r; - r->proc.next = t; - return OK; -} - -stat_t detach_rpc(struct tcb *r, struct tcb *t) -{ - /* rpc handling is slightly more complex since we have separate members - * for server and rpc contexts, where server is the server that - * currently hosts some number of rpc guests. */ - hard_assert(r != t, ERR_INVAL); - struct tcb *prev = t->rpc.prev; - struct tcb *next = t->rpc.next; - - if (prev == r) { r->server.next = next; } - else if (prev) { prev->rpc.next = next; } - - if (next) { next->rpc.prev = prev; } - - return OK; + hard_assert(is_proc(p), RETURN_VOID); + p->refcount++; } -stat_t detach_proc(struct tcb *r, struct tcb *t) +void unreference_proc(struct tcb *p) { - MAYBE_UNUSED(r); - hard_assert(r != t, ERR_INVAL); - struct tcb *prev = t->proc.prev; - struct tcb *next = t->proc.next; - - if (prev) { prev->proc.next = next; } - if (next) { next->proc.prev = prev; } - - return OK; + 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 */ + } } /* weak to allow optimisation on risc-v, but provide fallback for future */ @@ -333,30 +298,6 @@ struct tcb *get_tcb(id_t tid) return tcbs[tid]; } -stat_t clone_rpc_maps(struct tcb *r) -{ - hard_assert(r && is_proc(r), ERR_INVAL); - struct tcb *t = r->server.next; - if (!t) - return OK; - - do { - clone_uvmem(r->proc.vmem, t->rpc.vmem); - } while ((t = t->rpc.next)); - - return OK; -} - -stat_t clone_proc_maps(struct tcb *r) -{ - hard_assert(r && is_proc(r), ERR_INVAL); - struct tcb *t = r; - while ((t = t->proc.next)) - clone_uvmem(r->proc.vmem, t->proc.vmem); - - return OK; -} - void set_return(struct tcb *t, vm_t v) { t->exec = v; diff --git a/common/uapi/ipc.c b/common/uapi/ipc.c index 13682c7..b9a435b 100644 --- a/common/uapi/ipc.c +++ b/common/uapi/ipc.c @@ -54,13 +54,13 @@ enum ipc_kind { * * @param t Thread to migrate. * @param r Process to migrate to. - * @param sd RPC stack regions to mark inaccessible. + * @param s RPC stack regions to mark inaccessible. */ static void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s) { clone_uvmem(r->proc.vmem, t->rpc.vmem); set_return(t, r->callback); - attach_rpc(r, t); + reference_proc(r); t->pid = r->rid; /* make sure updates are visible when swapping to the new virtual memory */ @@ -135,6 +135,7 @@ static void leave_rpc(struct tcb *t, struct sys_ret a) 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; @@ -224,6 +225,10 @@ static void do_ipc(struct tcb *t, } r = get_rproc(r); + if (unlikely(r->dead)) { + leave_rpc(t, SYS_RET1(ERR_INVAL)); + return; + } if (unlikely(!r->callback)) { leave_rpc(t, SYS_RET1(ERR_NOINIT)); @@ -310,10 +315,7 @@ SYSCALL_DEFINE4(ipc_resp)(struct tcb *t, sys_arg_t d0, sys_arg_t d1, if (unlikely(!is_rpc(t))) return_args1(t, ERR_MISC); - /* we need the current proc before leaving the rpc */ - struct tcb *r = get_cproc(t); leave_rpc(t, SYS_RET6(OK, t->tid, d0, d1, d2, d3)); - detach_rpc(r, t); } /** diff --git a/common/vmem.c b/common/vmem.c index e90d27b..f52f43e 100644 --- a/common/vmem.c +++ b/common/vmem.c @@ -124,8 +124,7 @@ static stat_t __free_mapped_shared_region(struct tcb *t, struct mem_region *m) * * @param t Thread to work in. * @param m Memory region to free. - * @return \ref INFO_SEFF if other thread in process should be synced, \ref OK - * otherwise. + * @return \ref OK */ static stat_t __free_mapped_region(struct tcb *t, struct mem_region *m) { @@ -190,13 +189,6 @@ vm_t alloc_uvmem(struct tcb *t, size_t size, vmflags_t flags) stat_t status = OK; const vm_t v = alloc_region(&t->sp_r, size, &size, flags); const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status); - /** \todo this could be changed so that each thread allocated the memory - * region for itself to start with, and only when someone tries to - * access it from some other thread, is it actually cloned. Would likely - * need some major reworkings, so this is good enough for now. */ - if (is_rpc(t) && status == INFO_SEFF) - clone_rpc_maps(t); - return w; } @@ -218,8 +210,8 @@ vm_t alloc_uvpage(struct tcb *t, size_t size, vmflags_t flags, size_t *asize, return NULL; status = map_vpage(t->proc.vmem, addr, w, flags, order); - if (is_rpc(t) && status == INFO_SEFF) - clone_rpc_maps(t); + if (status) + return NULL; if (asize) *asize = actual_size; @@ -237,12 +229,6 @@ vm_t alloc_fixed_uvmem(struct tcb *t, vm_t start, size_t size, vmflags_t flags) stat_t status = OK; const vm_t v = alloc_fixed_region(&t->sp_r, start, size, &size, flags); const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status); - - /** @todo should probably update rpc maps even if the thread that does - * the allocation isn't in an ipc? */ - if (is_rpc(t) && status == INFO_SEFF) - clone_rpc_maps(t); - return w; } @@ -281,15 +267,15 @@ stat_t alloc_shared_uvmem(struct tcb *s, struct tcb *c, BASE_PAGE); } - if (cstatus == INFO_SEFF) - clone_rpc_maps(c); - - if (sstatus == INFO_SEFF) - clone_rpc_maps(s); - *sstart = sv; *cstart = cv; + if (sstatus) + return sstatus; + + if (cstatus) + return cstatus; + return OK; } @@ -301,8 +287,8 @@ stat_t free_uvmem(struct tcb *r, vm_t va) return ERR_NF; stat_t status = __free_mapped_region(r, m); - if (is_rpc(r) && status == INFO_SEFF) - return clone_rpc_maps(r); + if (status) + return ERR_MISC; return free_known_region(&r->sp_r, m); } @@ -319,7 +305,7 @@ stat_t alloc_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, if (status) *status = ret; - return (ret == INFO_SEFF) ? OK : ret; + return ret; } stat_t alloc_shared_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, @@ -334,7 +320,8 @@ stat_t alloc_shared_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, ret = map_vpage(b, *offset, vaddr, flags, order); if (status) *status = ret; - return (ret == INFO_SEFF) ? OK : ret; + + return ret; } stat_t copy_allocd_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, @@ -387,5 +374,5 @@ stat_t free_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, free_page(order, paddr); - return (ret == INFO_SEFF) ? OK : ret; + return ret; } diff --git a/include/arch/tcb.h b/include/arch/tcb.h index af12e0b..4e82ab1 100644 --- a/include/arch/tcb.h +++ b/include/arch/tcb.h @@ -25,11 +25,43 @@ */ void tcb_assign(struct tcb *t); +/** + * Set up RPC stack in a way that is convenient for the underlying architecture. + * + * @param t Thread whose RPC stack should be set up. + */ void setup_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(); +/** + * 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. + * @return Virtual address corresponding to the current RPC stack position. + */ vm_t rpc_position(struct tcb *t); + +/** + * Mark RPC stack up to \p top accessible from userspace. + * + * @param t Thread whose RPC stack is being modified. + * @param top Address up to where stack should be accessible from userspace. + */ void mark_rpc_valid(struct tcb *t, vm_t top); + +/** + * Mark RPC stack down to \p bottom inaccessible from userspace. + * + * @param t Thread whose RPC stack is being modified. + * @param bottom Address down to where stack should be inaccessible from + * userspace. + */ void mark_rpc_invalid(struct tcb *t, vm_t bottom); #endif /* KMI_ARCH_TCB_H */ diff --git a/include/kmi/tcb.h b/include/kmi/tcb.h index ba71dd3..6614d62 100644 --- a/include/kmi/tcb.h +++ b/include/kmi/tcb.h @@ -13,6 +13,7 @@ struct tcb; #include +#include #include #include #include /* arch-specific data */ @@ -67,12 +68,6 @@ struct tcb; struct tcb_ctx { /** Virtual address space of context. */ struct vmem *vmem; - - /** Next thread in context. */ - struct tcb *next; - - /** Previous thread in context. */ - struct tcb *prev; }; /** Enum for notification states. */ @@ -121,6 +116,9 @@ struct tcb { /** Possible thread local storage. */ vm_t thread_storage; + /** Reference count to process. */ + atomic_int_fast32_t refcount; + /** Process context of thread. */ struct tcb_ctx proc; @@ -184,6 +182,10 @@ struct tcb { /** Whether thread has gotten an IPI */ bool ipi; + + /** Whether thread is dead. If thread is process, then corresponds to + * whole process. */ + bool dead; }; /** @@ -253,49 +255,6 @@ stat_t destroy_thread(struct tcb *t); */ stat_t destroy_proc(struct tcb *p); -/** - * Attach a thread to an RPC context. - * - * Essentially inserts thread \c t into the process \c r, with access to the - * same memory except for the RPC stack. - * - * @param r Process to attach to. - * @param t Thread to attach. - * @return \ref OK on success, \ref ERR_INVAL if pointers are the same. - */ -stat_t attach_rpc(struct tcb *r, struct tcb *t); - -/** - * Detach a thread from an RPC context. - * - * \see attach_rpc(). - * - * @param r Process to detach from. - * @param t Thread to detach. - * @return \ref OK on success, \ref ERR_INVAL if pointers are the same. - * - * \todo Should probably check that thread exists in the process? - */ -stat_t detach_rpc(struct tcb *r, struct tcb *t); - -/** - * Attach a thread in a process context. - * - * @param r Process to attach to. - * @param t Thread to attach. - * @return \ref OK on success, \ref ERR_INVAL if pointers are the same. - */ -stat_t attach_proc(struct tcb *r, struct tcb *t); - -/** - * Detach a thread from a process context. - * - * @param r Process to detach from. - * @param t Thread to detach. - * @return \ref OK on success, \ref ERR_INVAL if pointers are the same. - */ -stat_t detach_proc(struct tcb *r, struct tcb *t); - /** * Get currently executing thread. * @@ -342,29 +301,6 @@ void use_tcb(struct tcb *t); */ struct tcb *get_tcb(id_t tid); -/** - * Clone process context memory mappings. - * - * Essentially make sure all threads in the process have identical memory - * mappings. - * - * @param p Process whose memory mappings to clone. - * @return \ref OK on success, something else otherwise. - * \todo Check up on return codes. - */ -stat_t clone_proc_maps(struct tcb *p); - -/** - * Clone RPC context memory mappings. - * - * \see clone_proc_maps(). - * - * @param r Server whose memory mappings to clone to threads in RPC to it. - * @return \ref OK on success, something else otherwise. - * \todo Check up on return codes. - */ -stat_t clone_rpc_maps(struct tcb *r); - /** * Allocate stacks for thread. * @@ -391,4 +327,28 @@ void set_return(struct tcb *t, vm_t r); */ bool running(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 + * set, and the thread that owns the process is unreferenced. All data + * associated with the process can immediately be freed, but the tid is still + * reserved until the reference count reaches zero. + * + * We have to make sure that all ways a process might be entered check that the + * process is still alive, and unmapping pages causes other threads to update + * their page tables as well. Then if a segfault happens, we can check if it was + * due to being in a dead process. This is still largely TODO. + * + * @param p Process to reference. + */ +void reference_proc(struct tcb *p); + +/** + * Unreference a process. + * + * @param p Process to unreference. + */ +void unreference_proc(struct tcb *p); + #endif /* KMI_TCB_H */ -- cgit v1.3