From 7a1e5c77b56f493d9d31dfb772bdcff6f083995b Mon Sep 17 00:00:00 2001 From: Clement Dieperink Date: Mon, 3 Nov 2025 18:37:53 +0100 Subject: [PATCH 1/2] rework thread creation to match pthread --- so3/arch/arm32/asm-offsets.c | 1 + so3/arch/arm32/context.S | 68 -------- so3/arch/arm32/exception.S | 28 ++- so3/arch/arm32/include/asm/processor.h | 5 +- so3/arch/arm32/thread.c | 77 ++++++++- so3/arch/arm64/asm-offsets.c | 1 + so3/arch/arm64/context.S | 73 +------- so3/arch/arm64/exception.S | 43 +++-- so3/arch/arm64/include/asm/processor.h | 22 ++- so3/arch/arm64/thread.c | 77 ++++++++- so3/include/process.h | 38 +++- so3/include/thread.h | 50 ++++-- so3/kernel/process.c | 196 +++++++++++---------- so3/kernel/thread.c | 229 +++++++------------------ so3/syscall.tbl | 2 + 15 files changed, 446 insertions(+), 464 deletions(-) diff --git a/so3/arch/arm32/asm-offsets.c b/so3/arch/arm32/asm-offsets.c index 6f57cecef..2a542dec4 100644 --- a/so3/arch/arm32/asm-offsets.c +++ b/so3/arch/arm32/asm-offsets.c @@ -78,6 +78,7 @@ int main(void) DEFINE(OFFSET_PSR, offsetof(cpu_regs_t, psr)); DEFINE(OFFSET_SP_USR, offsetof(cpu_regs_t, sp_usr)); DEFINE(OFFSET_LR_USR, offsetof(cpu_regs_t, lr_usr)); + DEFINE(OFFSET_TLS_USR, offsetof(cpu_regs_t, tls_usr)); BLANK(); diff --git a/so3/arch/arm32/context.S b/so3/arch/arm32/context.S index 5e4f3b0a6..0234098bd 100644 --- a/so3/arch/arm32/context.S +++ b/so3/arch/arm32/context.S @@ -31,9 +31,7 @@ .global __switch_context .global __thread_prologue_kernel -.global __thread_prologue_user .global __exec_prologue_user -.global __thread_prologue_user_pre_launch .globl __get_syscall_args_ext .globl __get_syscall_arg @@ -41,7 +39,6 @@ .global __mmu_switch_ttbr0 .global __exec .global __write -.global __save_context .global __enable_vfp @@ -51,7 +48,6 @@ #ifdef CONFIG_MMU .extern __check_ptrace_traceme -.extern ret_from_fork .extern pre_launch_proc #endif @@ -84,39 +80,6 @@ __thread_prologue_kernel: bl thread_prologue -@ User thread initial entry point -@ Called once per thread -@ r4: th_fn, r5: th_arg, r6: user stack -__thread_prologue_user: - - @ Prepare to jump into C code - mov r0, r4 @ tcb->th_fn - mov r1, r5 @ tcb->th_arg - -#ifdef CONFIG_MMU - @ Check if the thread must stopped because of ptrace/tracee - stmfd sp!, {r0, r1} - bl __check_ptrace_traceme - ldmfd sp!, {r0, r1} -#endif - - @ IRQ enabling - must be done in SVC mode of course ;-) - @ We should take care about protecting against signal receipt: - @ since the stack is not initialized yet, the signal processing should be kept disabled. - cpsie i - - @ Switch into user mode - mrs r4, cpsr - bic r4, r4, #PSR_MODE_MASK - orr r4, r4, #PSR_USR_MODE - msr cpsr, r4 - - @ User stack initialisation - mov sp, r6 - - bl thread_prologue - - #ifdef CONFIG_AVZ ENTRY(cpu_do_idle) @@ -208,37 +171,6 @@ __mmu_switch_ttbr0: nop nop -@ Store the current registers into a cpu_regs structure passed in r0 (as first argument) -__save_context: - - @ Adjust the kernel stack pointer so that we can proceed with ret_from_fork - @ SVC_STACK_FRAME_SIZE/4 registers are preserved when at the syscall vector entry point - - @ Adjust the sp which is stored on the stack. Make sure - @ it refers to this stack and not the one issue from the copy - @ as during fork(). - - str r1, [r1, #(OFFSET_SP-SVC_STACK_FRAME_SIZE)] - - sub r2, r1, #SVC_STACK_FRAME_SIZE - - @ Prepare to configure sp during the context switch. - str r2, [r0, #(OFFSET_TCB_CPU_REGS + OFFSET_SP)] - - @ Prepare the lr to branch to ret_from_fork - ldr r1, .LCret_from_fork - str r1, [r0, #(OFFSET_TCB_CPU_REGS + OFFSET_LR)] - - @ Preserve r7 which contains the syscall number (used to compare against SIG_RETURN) - str r7, [r0, #(OFFSET_TCB_CPU_REGS + OFFSET_R7)] - - @ The other registers are not important. - - mov pc, lr - -.LCret_from_fork: - .word ret_from_fork - .LCcurrent: .word current_thread diff --git a/so3/arch/arm32/exception.S b/so3/arch/arm32/exception.S index 6f7ac347a..0c44feced 100644 --- a/so3/arch/arm32/exception.S +++ b/so3/arch/arm32/exception.S @@ -118,6 +118,10 @@ __prepare_sig_handler: tst sp, #0x7 @ 8-bytes aligned bne __stack_alignment_fault + @ Copy TLS to the new stack frame + ldr r0, [sp, #OFFSET_TLS_USR] + str r0, [sp, #(-SVC_STACK_FRAME_SIZE + OFFSET_TLS_USR)] + str sp, [sp, #(-SVC_STACK_FRAME_SIZE + OFFSET_SP)] @ save sp @ Build a new stack frame based on the current @@ -169,7 +173,7 @@ __prepare_sig_handler: @ ARM EABI: the syscall nr is stored in r7 .align 5 syscall_interrupt: - + @ At the exception entry, the stack must be 8-byte aligned. @ If it is not the case (gcc might not respect the AAPCS convention for optimization purposes), @ sp will be adjusted. The original sp is preserved and will be correctly restored at the exit. @@ -199,9 +203,13 @@ syscall_interrupt: add lr, sp, #OFFSET_SP_USR stmia lr, {sp, lr}^ + @ Save user space TLS context + mrc p15, 0, r0, c13, c0, 0 + str r0, [sp, #OFFSET_TLS_USR] + cmp r7, #SYSCALL_sigreturn beq __after_push_sp_usr - + ldr r0, [sp, #OFFSET_SP_USR] ldr r1, .LCcurrent ldr r1, [r1] @@ -254,6 +262,10 @@ __ret_from_fork: check_pending_signal #endif /* CONFIG_IPC_SIGNAL */ + @ Restore user space TLS context + ldr lr, [sp, #OFFSET_TLS_USR] + mcr p15, 0, lr, c13, c0, 0 + @ get the saved spsr and adjust the stack pointer ldr lr, [sp, #OFFSET_PSR] msr spsr, lr @@ -270,14 +282,14 @@ __ret_from_fork: ldmia sp, {sp, lr, pc}^ - + @ Used at entry point of a fork'd process (setting the return value to 0) ret_from_fork: mov r0, #0 b __ret_from_fork - + .align 5 prefetch_abort: @@ -363,6 +375,10 @@ irq: addeq lr, sp, #OFFSET_SP_USR stmeqia lr, {sp, lr}^ + @ Save user space TLS context + mrc p15, 0, r0, c13, c0, 0 + str r0, [sp, #OFFSET_TLS_USR] + @ Retrieve the lr_irq to set the pc out of this routine ldr lr, [r0, #4] @ retrieve lr_irq to set lr_svc sub lr, lr, #4 @ Adjust the lr since it is automatically set from pc (in advance of 2 instructions due to the pipeline) @@ -388,6 +404,10 @@ irq: check_pending_signal #endif /* CONFIG_IPC_SIGNAL */ + @ Restore user space TLS context + ldr lr, [sp, #OFFSET_TLS_USR] + mcr p15, 0, lr, c13, c0, 0 + ldr lr, [sp, #OFFSET_PSR] @ get the saved spsr and adjust the stack pointer msr spsr, lr diff --git a/so3/arch/arm32/include/asm/processor.h b/so3/arch/arm32/include/asm/processor.h index a651835c7..56b082d31 100644 --- a/so3/arch/arm32/include/asm/processor.h +++ b/so3/arch/arm32/include/asm/processor.h @@ -349,9 +349,10 @@ typedef struct cpu_regs { __u32 lr; __u32 pc; __u32 psr; - __u32 sp_usr; + __u32 sp_usr; __u32 lr_usr; - __u32 padding; /* padding to keep 8-bytes alignment */ + __u32 tls_usr; + /* Already aligned to 8-bytes, no padding required */ } cpu_regs_t; #define cpu_relax() wfe() diff --git a/so3/arch/arm32/thread.c b/so3/arch/arm32/thread.c index 577a327c8..596f8d882 100644 --- a/so3/arch/arm32/thread.c +++ b/so3/arch/arm32/thread.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2022 Daniel Rossier - * + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -16,21 +16,74 @@ * */ +#include #include #include /** * Set the CPU registers with thread related information * - * @param tcb + * @param tcb Thread to set registers. + * @param args Clone arguments with values to set to registers. */ -void arch_prepare_cpu_regs(tcb_t *tcb) +void arch_prepare_cpu_regs(tcb_t *tcb, clone_args_t *args) { - tcb->cpu_regs.r4 = (unsigned long) tcb->th_fn; - tcb->cpu_regs.r5 = (unsigned long) tcb->th_arg; /* First argument */ + cpu_regs_t *user_regs; + + if (args->pcb == NULL) { + /* Kernel thread must have a function to call */ + BUG_ON(args->fn == NULL); + tcb->cpu_regs.r4 = (unsigned long) args->fn; + tcb->cpu_regs.r5 = (unsigned long) args->fn_arg; + + tcb->cpu_regs.lr = (unsigned long) __thread_prologue_kernel; + tcb->cpu_regs.sp = get_kernel_stack_top(tcb->stack_slotID); + } else { + user_regs = (cpu_regs_t *) arch_get_kernel_stack_frame(tcb); + + if (args->fn) { + /* Special userspace case to start root process. */ + BUG_ON(args->stack == 0); + arch_restart_user_thread(tcb, args->fn, args->stack); + } else { + /* Normal userspace that will copy userspace registers */ + if (args->stack) { + user_regs->sp_usr = args->stack; + } + + if (args->flags & CLONE_SETTLS) { + user_regs->tls_usr = args->tls; + } - if (tcb->pcb) - tcb->cpu_regs.r6 = get_user_stack_top(tcb->pcb, tcb->pcb_stack_slotID); + /* Copy userspace registers */ + *user_regs = *(cpu_regs_t *) arch_get_kernel_stack_frame(current()); + } + + tcb->cpu_regs.lr = (unsigned long) ret_from_fork; + /* Take into account the user registers frame on the kernel stack */ + tcb->cpu_regs.sp = (addr_t) user_regs; + } +} + +/** + * Restart a user thread by reseting all registers to 0 and settings entry point and stack. + * + * @param tcb Thread to restart. + * @param fn_entry Userspace entry point of the thread. + * @param stack_top New top address of the userspace stack. + */ +void arch_restart_user_thread(tcb_t *tcb, th_fn_t fn_entry, addr_t stack_top) +{ + cpu_regs_t *user_regs = (cpu_regs_t *) arch_get_kernel_stack_frame(tcb); + + /* Reset all user's registers to zero except for PC, PSTATE and SP + * which needs to be set to the properly start the thread. + */ + *user_regs = (cpu_regs_t) { + .pc = (u32) fn_entry, + .psr = PSR_USR_MODE, + .sp_usr = stack_top, + }; } /** @@ -41,3 +94,13 @@ addr_t arch_get_args_base(void) { return (CONFIG_KERNEL_VADDR - PAGE_SIZE); } + +/** + * Get the top address of the kernel stack of a user thread with space for registers values. + * + * @param tcb Thread to get the stack address. + */ +addr_t arch_get_kernel_stack_frame(tcb_t *tcb) +{ + return get_kernel_stack_top(tcb->stack_slotID) - SVC_STACK_FRAME_SIZE; +} diff --git a/so3/arch/arm64/asm-offsets.c b/so3/arch/arm64/asm-offsets.c index da4117c0a..7eee77e69 100644 --- a/so3/arch/arm64/asm-offsets.c +++ b/so3/arch/arm64/asm-offsets.c @@ -85,6 +85,7 @@ int main(void) DEFINE(OFFSET_SP, offsetof(struct cpu_regs, sp)); DEFINE(OFFSET_PC, offsetof(struct cpu_regs, pc)); DEFINE(OFFSET_PSTATE, offsetof(struct cpu_regs, pstate)); + DEFINE(OFFSET_TLS_USR, offsetof(struct cpu_regs, tls_usr)); BLANK(); diff --git a/so3/arch/arm64/context.S b/so3/arch/arm64/context.S index 02dadb14e..30e6d8850 100644 --- a/so3/arch/arm64/context.S +++ b/so3/arch/arm64/context.S @@ -28,7 +28,6 @@ .global __switch_context .global __thread_prologue_kernel -.global __thread_prologue_user .global __exec_prologue_user .globl __get_syscall_args_ext @@ -37,7 +36,6 @@ .global __mmu_switch .global __exec .global __write -.global __save_context .global __root_proc .extern thread_prologue @@ -46,7 +44,6 @@ .global __enable_vfp .extern __check_ptrace_traceme -.extern ret_from_fork #ifdef CONFIG_AVZ @@ -67,7 +64,7 @@ ____switch_to: save_ctx: // store callee-saved registers - stp x19, x20, [x8], #16 + stp x19, x20, [x8], #16 stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 @@ -83,7 +80,7 @@ load_ctx: add x8, x1, x10 // restore callee-saved registers - ldp x19, x20, [x8], #16 + ldp x19, x20, [x8], #16 ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 @@ -141,7 +138,7 @@ ENTRY(__mmu_switch_ttbr1) isb msr ttbr1_el1, x0 - + tlbi alle1 isb @@ -163,7 +160,7 @@ ENTRY(__mmu_switch_vttbr) tlbi alle2 dsb nsh - + ret ENTRY(cpu_do_idle) @@ -197,68 +194,6 @@ __thread_prologue_kernel: bl thread_prologue // Never reached -// User thread initial entry point -// Called once per thread -// x19: th_fn, x20: th_arg, x21: user stack - -__thread_prologue_user: - - msr elr_el1, x19 - - msr sp_el0, x21 - - mov x0, #PSR_MODE_EL0t - - msr spsr_el1, x0 - - // Prepare to jump into C code - mov x0, x20 - -#if 0 - at s1e0r, x19 - isb - mrs x0, par_el1 -#endif - - eret -#if 0 -#ifdef CONFIG_MMU - // Check if the thread must stopped because of ptrace/tracee - stmfd sp!, {r0, r1} - bl __check_ptrace_traceme - ldmfd sp!, {r0, r1} -#endif -#endif - -// Store the current registers into a cpu_regs structure passed in r0 (as first argument) -__save_context: - - // Adjust the kernel stack pointer so that we can proceed with ret_from_fork. - // S_FRAME_SIZE/8 registers are preserved when at the syscall vector entry point - - // Adjust the sp which is stored on the stack. Make sure - // it refers to this stack and not the one issue from the copy - // as during fork(). - - sub x1, x1, #S_FRAME_SIZE - - // Prepare to configure sp during the context switch - str x1, [x0, #(OFFSET_TCB_CPU_REGS + OFFSET_SP)] - - // Prepare the lr to branch to ret_from_fork - ldr x1, .LCret_from_fork - str x1, [x0, #(OFFSET_TCB_CPU_REGS + OFFSET_LR)] - - // Preserve x8 which contains the syscall number (used to compare against SIG_RETURN) - str x8, [x0, #(OFFSET_TCB_CPU_REGS + OFFSET_X8)] - - // The other registers are not important. - - ret - -.LCret_from_fork: - .quad ret_from_fork - .LCcurrent: .quad current_thread diff --git a/so3/arch/arm64/exception.S b/so3/arch/arm64/exception.S index 4d8cd4a7f..a1d6a0c58 100644 --- a/so3/arch/arm64/exception.S +++ b/so3/arch/arm64/exception.S @@ -124,7 +124,7 @@ ENTRY(__vectors) // Current EL with SPx / Synchronous .align 7 - + mov x0, lr b trap_handle_error @@ -158,9 +158,9 @@ ENTRY(__vectors) #ifdef CONFIG_AVZ b el12_sync_handler -#else +#else b el01_sync_handler -#endif +#endif // Lower EL using AArch64 / IRQ .align 7 @@ -206,7 +206,7 @@ ENTRY(__vectors) mov x0, lr b trap_handle_error - + #ifdef CONFIG_CPU_SPIN_TABLE @@ -317,7 +317,7 @@ ENTRY(pre_ret_to_el1) .endm -// Exit macro at the end of an exception routine +// Exit macro at the end of an exception routine // It restores the sp_el0 as well. .macro prepare_to_exit_to_el1 ldr x0, [sp, #OFFSET_PC] @@ -375,7 +375,7 @@ cpu_entrypoint: __prepare_to_sig_el1_handler: - /* Preserve the SP_EL2 in the new stack frame used by + /* Preserve the SP_EL2 in the new stack frame used by * the signal handler. */ @@ -387,7 +387,7 @@ __prepare_to_sig_el1_handler: // Build a new stack frame based on the current sub sp, sp, #S_FRAME_SIZE - + // Set the handler to the PC str x21, [sp, #OFFSET_PC] @@ -438,7 +438,7 @@ ENTRY(pre_ret_to_user) ldr x2, [sp, #OFFSET_LR] // Entry point of the guest msr elr_el2, x2 - /* + /* * The MMU must be disabled so that the guest can keep its initial boot code. * Make sure CPU EL1 has MMU disabled. */ @@ -455,7 +455,7 @@ ENTRY(pre_ret_to_user) msr spsr_el2, x2 kernel_exit - + // Ready to jump into the domain... eret @@ -476,23 +476,26 @@ ENTRY(pre_ret_to_user) __prepare_sig_handler: - /* Preserve the SP_EL1 in the new stack frame used by - * the signal handler + /* Preserve the SP_EL1 and the TLS in the new stack frame used + * by the signal handler */ ldr x2, [sp, #OFFSET_SP_USR] str x2, [sp, #(OFFSET_SP_USR - S_FRAME_SIZE)] + ldr x2, [sp, #OFFSET_TLS_USR] + str x2, [sp, #(OFFSET_TLS_USR - S_FRAME_SIZE)] + // Build a new stack frame based on the current sub sp, sp, #S_FRAME_SIZE mov x2, #PSR_MODE_EL0t bic x2, x2, #PSR_I_BIT str x2, [sp, #OFFSET_PSTATE] - + ldr x1, [x0, #OFFSET_SYS_SIGNUM] str x1, [sp, #OFFSET_X0] - + ldr x1, [x0, #OFFSET_SYS_SA] ldr x2, [x1, #OFFSET_SA_HANDLER] str x2, [sp, #OFFSET_X1] @@ -526,12 +529,15 @@ __prepare_sig_handler: mrs x0, sp_el0 str x0, [sp, #OFFSET_SP_USR] + mrs x0, tpidr_el0 + str x0, [sp, #OFFSET_TLS_USR] + mrs x0, spsr_el1 str x0, [sp, #OFFSET_PSTATE] .endm -// Exit macro at the end of an exception routine +// Exit macro at the end of an exception routine // It restores the sp_el0 as well. .macro prepare_to_exit_to_el0 ldr x0, [sp, #OFFSET_PC] @@ -540,6 +546,9 @@ __prepare_sig_handler: ldr x0, [sp, #OFFSET_SP_USR] msr sp_el0, x0 + ldr x0, [sp, #OFFSET_TLS_USR] + msr tpidr_el0, x0 + ldr x0, [sp, #OFFSET_PSTATE] msr spsr_el1, x0 .endm @@ -559,11 +568,11 @@ el01_sync_handler: // clean the stack frame which has been used to manage the user handler. cmp x8, #SYSCALL_rt_sigreturn bne __ret_from_fork - + // Reset the stack frame by removing the one issued from sigreturn add sp, sp, #S_FRAME_SIZE - + __ret_from_fork: #ifdef CONFIG_IPC_SIGNAL @@ -610,7 +619,7 @@ ret_from_fork: ldmfd sp!, {r0-r4} #endif #endif - + #if !defined(CONFIG_AVZ) && defined(CONFIG_SOO) diff --git a/so3/arch/arm64/include/asm/processor.h b/so3/arch/arm64/include/asm/processor.h index 5dfbcef17..b04e79589 100644 --- a/so3/arch/arm64/include/asm/processor.h +++ b/so3/arch/arm64/include/asm/processor.h @@ -1091,7 +1091,7 @@ enum trap_return { * addressed by index */ typedef struct __attribute__((packed, aligned(8))) cpu_regs { - u64 x0; + u64 x0; u64 x1; u64 x2; u64 x3; @@ -1127,8 +1127,12 @@ typedef struct __attribute__((packed, aligned(8))) cpu_regs { u64 pstate; /* is used to keep track of the sp at the higher EL - used for signal-like handler */ - u64 sp_usr; - u64 padding; + u64 sp_usr; + + /* TLS is used by userspace to store thread context */ + u64 tls_usr; + + /* Already aligned on 16 bytes no padding required */ } cpu_regs_t; #ifdef CONFIG_AVZ @@ -1138,12 +1142,12 @@ typedef struct vcpu { cpu_regs_t regs; /* All CPU registers */ /* System registers at EL1 */ - u64 sctlr_el1; - u64 vbar_el1; - u64 ttbr0_el1; - u64 ttbr1_el1; - u64 tcr_el1; - u64 mair_el1; + u64 sctlr_el1; + u64 vbar_el1; + u64 ttbr0_el1; + u64 ttbr1_el1; + u64 tcr_el1; + u64 mair_el1; } vcpu_t; diff --git a/so3/arch/arm64/thread.c b/so3/arch/arm64/thread.c index e3c73425a..80a23493d 100644 --- a/so3/arch/arm64/thread.c +++ b/so3/arch/arm64/thread.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2022 Daniel Rossier - * + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -16,20 +16,73 @@ * */ +#include #include /** * Set the CPU registers with thread related information * - * @param tcb + * @param tcb Thread to set registers. + * @param args Clone arguments with values to set to registers. */ -void arch_prepare_cpu_regs(tcb_t *tcb) +void arch_prepare_cpu_regs(tcb_t *tcb, clone_args_t *args) { - tcb->cpu_regs.x19 = (unsigned long) tcb->th_fn; - tcb->cpu_regs.x20 = (unsigned long) tcb->th_arg; /* First argument */ + cpu_regs_t *user_regs; + + if (args->pcb == NULL) { + /* Kernel thread must have a function to call */ + BUG_ON(args->fn == NULL); + tcb->cpu_regs.x19 = (unsigned long) args->fn; + tcb->cpu_regs.x20 = (unsigned long) args->fn_arg; + + tcb->cpu_regs.lr = (unsigned long) __thread_prologue_kernel; + tcb->cpu_regs.sp = get_kernel_stack_top(tcb->stack_slotID); + } else { + user_regs = (cpu_regs_t *) arch_get_kernel_stack_frame(tcb); + + if (args->fn) { + /* Special userspace case to start root process. */ + BUG_ON(args->stack == 0); + arch_restart_user_thread(tcb, args->fn, args->stack); + } else { + /* Normal userspace that will copy userspace registers */ + if (args->stack) { + user_regs->sp_usr = args->stack; + } + + if (args->flags & CLONE_SETTLS) { + user_regs->tls_usr = args->tls; + } - if (tcb->pcb) - tcb->cpu_regs.x21 = get_user_stack_top(tcb->pcb, tcb->pcb_stack_slotID); + /* Copy userspace registers */ + *user_regs = *(cpu_regs_t *) arch_get_kernel_stack_frame(current()); + } + + tcb->cpu_regs.lr = (unsigned long) ret_from_fork; + /* Take into account the user registers frame on the kernel stack */ + tcb->cpu_regs.sp = (addr_t) user_regs; + } +} + +/** + * Restart a user thread by reseting all registers to 0 and settings entry point and stack. + * + * @param tcb Thread to restart. + * @param fn_entry Userspace entry point of the thread. + * @param stack_top New top address of the userspace stack. + */ +void arch_restart_user_thread(tcb_t *tcb, th_fn_t fn_entry, addr_t stack_top) +{ + cpu_regs_t *user_regs = (cpu_regs_t *) arch_get_kernel_stack_frame(tcb); + + /* Reset all user's registers to zero except for PC, PSTATE and SP + * which needs to be set to the properly start the program + */ + *user_regs = (cpu_regs_t) { + .pc = (u64) fn_entry, + .pstate = PSR_MODE_EL0t, + .sp_usr = stack_top, + }; } /** @@ -44,3 +97,13 @@ addr_t arch_get_args_base(void) { return (addr_t) (USER_STACK_TOP_VADDR - PAGE_SIZE); } + +/** + * Get the top address of the kernel stack of a user thread with space for registers values. + * + * @param tcb Thread to get the stack address. + */ +addr_t arch_get_kernel_stack_frame(tcb_t *tcb) +{ + return get_kernel_stack_top(tcb->stack_slotID) - S_FRAME_SIZE; +} diff --git a/so3/include/process.h b/so3/include/process.h index 5b444deab..faf1335cd 100644 --- a/so3/include/process.h +++ b/so3/include/process.h @@ -36,7 +36,7 @@ #define PROC_MAX_THREADS CONFIG_MAX_THREADS /* Maximum stack size for a process, including all thread stacks */ -#define PROC_STACK_SIZE (PROC_MAX_THREADS * CONFIG_THREAD_STACK_SIZE_KB * SZ_1K) +#define INITIAL_STACK_SIZE (CONFIG_THREAD_STACK_SIZE_KB * SZ_1K) #define FD_MAX 64 #define N_MUTEX 5 @@ -46,6 +46,33 @@ typedef unsigned int thread_t; #define PROC_NAME_LEN 80 +/* Clone flags */ +#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */ +#define CLONE_VM 0x00000100 /* set if VM shared between processes */ +#define CLONE_FS 0x00000200 /* set if fs info shared between processes */ +#define CLONE_FILES 0x00000400 /* set if open files shared between processes */ +#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */ +#define CLONE_PIDFD 0x00001000 /* set if a pidfd should be placed in parent */ +#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */ +#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */ +#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */ +#define CLONE_THREAD 0x00010000 /* Same thread group? */ +#define CLONE_NEWNS 0x00020000 /* New mount namespace group */ +#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */ +#define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */ +#define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */ +#define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */ +#define CLONE_DETACHED 0x00400000 /* Unused, ignored */ +#define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */ +#define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */ +#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ +#define CLONE_NEWUTS 0x04000000 /* New utsname namespace */ +#define CLONE_NEWIPC 0x08000000 /* New ipc namespace */ +#define CLONE_NEWUSER 0x10000000 /* New user namespace */ +#define CLONE_NEWPID 0x20000000 /* New pid namespace */ +#define CLONE_NEWNET 0x40000000 /* New network namespace */ +#define CLONE_IO 0x80000000 /* Clone io context */ + /* A page might be linked to several processes, hence this type */ typedef struct { struct list_head list; @@ -67,9 +94,6 @@ struct pcb { /* Full descending stack - refers to a "full" word */ addr_t stack_top; - /* Thread stack slots */ - bool stack_slotID[PROC_MAX_THREADS]; - /* Heap management */ addr_t heap_base; @@ -130,9 +154,6 @@ typedef struct pcb pcb_t; extern struct list_head proc_list; -int get_user_stack_slot(pcb_t *pcb); -void free_user_stack_slot(pcb_t *pcb, int slotID); - void add_page_to_proc(pcb_t *pcb, page_t *page); void create_root_process(void); @@ -141,7 +162,8 @@ SYSCALL_DECLARE(getpid, void); SYSCALL_DECLARE(execve, const char *filename, char **argv, char **envp); SYSCALL_DECLARE(fork, void); -SYSCALL_DECLARE(exit, int exit_status); +SYSCALL_DECLARE(clone, unsigned long flags, unsigned long newsp, int *parent_tid, unsigned long tls, int *child_tid); +SYSCALL_DECLARE(exit_group, int exit_status); SYSCALL_DECLARE(wait4, int pid, uint32_t *wstatus, uint32_t options, void *rusage); pcb_t *find_proc_by_pid(uint32_t pid); diff --git a/so3/include/thread.h b/so3/include/thread.h index 32721149e..7c53b09d4 100644 --- a/so3/include/thread.h +++ b/so3/include/thread.h @@ -73,18 +73,14 @@ struct tcb { #endif /* CONFIG_SCHED_PRIO_DYN */ - /* Threaded function */ - th_fn_t th_fn; - void *th_arg; - thread_t state; int stack_slotID; /* Thread kernel slot ID */ /* Reference to the process, if any - typically NULL for kernel threads */ pcb_t *pcb; - int pcb_stack_slotID; /* This is the user space stack slot ID (for user threads) */ - int *exit_status; + int exit_status; + addr_t child_clear_tid; struct list_head list; /* List of threads belonging to a process */ @@ -95,19 +91,40 @@ struct tcb { }; typedef struct tcb tcb_t; -addr_t get_user_stack_top(pcb_t *pcb, uint32_t slotID); +typedef struct { + /* Clone flags to create/copy the current thread */ + uint64_t flags; + const char *name; + + pcb_t *pcb; + + uint32_t prio; + + /* Userspace stack and TLS address */ + addr_t stack; + addr_t tls; + + /* Pointer to userspace parent/child tid to use depending on flags */ + int *parent_tid; + int *child_tid; + + /* Function to call for kernel thread or root user process */ + th_fn_t fn; + + /* Arguments passed to the kernel thread function. For userspace thread, this is ignored + * and arguments must put on top of the stack */ + void *fn_arg; +} clone_args_t; void threads_init(void); -SYSCALL_DECLARE(thread_create, uint32_t *pthread_id, addr_t attr_p, addr_t thread_fn, addr_t arg_p); -SYSCALL_DECLARE(thread_join, uint32_t pthread_id, int **value_p); -SYSCALL_DECLARE(thread_exit, int *exit_status); +SYSCALL_DECLARE(exit, int exit_status); tcb_t *kernel_thread(th_fn_t start_routine, const char *name, void *arg, uint32_t prio); -tcb_t *user_thread(th_fn_t start_routine, const char *name, void *arg, pcb_t *pcb); +tcb_t *user_thread(clone_args_t *args); -int *thread_join(tcb_t *tcb); -void thread_exit(int *exit_status); +int thread_join(tcb_t *tcb); +void thread_exit(int exit_status); void clean_thread(tcb_t *tcb); SYSCALL_DECLARE(thread_yield, void); @@ -117,15 +134,16 @@ addr_t get_kernel_stack_top(uint32_t slotID); extern void __switch_context(tcb_t *prev, tcb_t *next); extern void __thread_prologue_kernel(void); -extern void __thread_prologue_user(void); -extern void __thread_prologue_user_pre_launch(void); +extern void ret_from_fork(void); char *print_state(struct tcb *tcb); void *app_thread_main(void *args); -void arch_prepare_cpu_regs(tcb_t *tcb); +void arch_prepare_cpu_regs(tcb_t *tcb, clone_args_t *args); +void arch_restart_user_thread(tcb_t *tcb, th_fn_t fn_entry, addr_t stack); addr_t arch_get_args_base(void); +addr_t arch_get_kernel_stack_frame(tcb_t *tcb); #endif /* __ASSEMBLY__ */ diff --git a/so3/kernel/process.c b/so3/kernel/process.c index 13ec54092..15521d8a6 100644 --- a/so3/kernel/process.c +++ b/so3/kernel/process.c @@ -62,9 +62,6 @@ char *proc_state_str(proc_state_t state) static uint32_t pid_current = 1; static pcb_t *root_process = NULL; /* root process */ -/* Used to update regs during fork */ -extern void __save_context(tcb_t *newproc, addr_t stack_addr); - /* only the following sections are supported */ #define SUPPORTED_SECTION_COUNT 6 static const char *supported_section_names[SUPPORTED_SECTION_COUNT] = { @@ -207,9 +204,6 @@ pcb_t *new_process(void) pcb->pid = pid_current++; - for (i = 0; i < PROC_MAX_THREADS; i++) - pcb->stack_slotID[i] = false; - /* Init the list of child threads */ INIT_LIST_HEAD(&pcb->threads); @@ -245,7 +239,7 @@ pcb_t *new_process(void) void reset_process_stack(pcb_t *pcb) { /* Set up the main process stack (including all thread stacks) */ - pcb->page_count = ALIGN_UP(PROC_STACK_SIZE, PAGE_SIZE) >> PAGE_SHIFT; + pcb->page_count = ALIGN_UP(INITIAL_STACK_SIZE, PAGE_SIZE) >> PAGE_SHIFT; /* * The stack virtual top is under the page of arguments, from the top @@ -316,6 +310,7 @@ void create_root_process(void) { pcb_t *pcb; int i; + clone_args_t thread_args; local_irq_disable(); @@ -336,9 +331,14 @@ void create_root_process(void) create_mapping(pcb->pgtable, USER_SPACE_VADDR, __pa(__root_proc_start), (void *) __root_proc_end - (void *) __root_proc_start, false); - /* Start main thread of the thread is not used in this context. - */ - pcb->main_thread = user_thread((th_fn_t) USER_SPACE_VADDR, "root_proc", NULL, pcb); + /* Start main user thread. */ + thread_args = (clone_args_t) { + .name = "root_proc", + .pcb = pcb, + .stack = pcb->stack_top, + .fn = (th_fn_t) USER_SPACE_VADDR, + }; + pcb->main_thread = user_thread(&thread_args); /* init process? */ if (!root_process) @@ -701,7 +701,6 @@ SYSCALL_DEFINE3(execve, const char *, filename, char **, argv, char **, envp) pcb_t *pcb; unsigned long flags; th_fn_t start_routine; - queue_thread_t *cur; int ret, argc; /* Count the number of arguments */ @@ -751,42 +750,16 @@ SYSCALL_DEFINE3(execve, const char *, filename, char **, argv, char **, envp) /* Release the kernel buffer used to store the ELF binary image */ elf_clean_image(&elf_img_info); - /* Now, we need to create the main user thread associated to this binary - * image. */ - /* start main thread */ + /* Now, we need to restart the user thread from the new program starting address */ start_routine = (th_fn_t) pcb->bin_image_entry; - /* We start the new thread */ - pcb->main_thread = user_thread(start_routine, pcb->name, (void *) arch_get_args_base(), pcb); - - /* Transfer the waiting thread if any */ - - /* Make sure it is the main thread of the dying thread, i.e. another - * process is waiting on it (the parent) */ - if (!list_empty(¤t()->joinQueue)) { - cur = list_entry(current()->joinQueue.next, queue_thread_t, list); - - ASSERT(cur->tcb->pcb == current()->pcb->parent); - - /* Migrate the entry to the new main thread */ - list_move(&cur->list, &pcb->main_thread->joinQueue); - } - - /* Now, make sure there is no other waiting threads on the dying thread - */ - ASSERT(list_empty(¤t()->joinQueue)); - - /* We detach the thread from its pcb so that thread_exit() can - * distinguish between this kind of (replaced) thread and a standard - * thread which will be stay in zombie state. - */ - current()->pcb = NULL; + /* Rename thread to inlcude new PCB name */ + snprintf(pcb->main_thread->name, THREAD_NAME_LEN, "%s_%d", pcb->name, pcb->main_thread->tid); - /* Finishing the running thread. The final clean_thread() function will - * be called in thread_exit(). */ - thread_exit(0); + /* Arguments base is used as stack address and arguments will be retrieved from it by MUSL. */ + arch_restart_user_thread(pcb->main_thread, start_routine, arch_get_args_base()); - /* IRQs never restored here... */ + local_irq_restore(flags); return 0; } @@ -812,10 +785,6 @@ pcb_t *duplicate_process(pcb_t *parent) pcb->heap_base = parent->heap_base; pcb->heap_pointer = parent->heap_pointer; - /* Duplicate the array of allocated stack slots dedicated to user - * threads */ - memcpy(pcb->stack_slotID, parent->stack_slotID, sizeof(parent->stack_slotID)); - /* Clone all file descriptors */ if (vfs_clone_fd(parent->fd_array, pcb->fd_array)) { LOG_CRITICAL("!! Error while cloning fds\n"); @@ -825,70 +794,109 @@ pcb_t *duplicate_process(pcb_t *parent) return pcb; } -/* - * For a new process from the current running process. - */ -SYSCALL_DEFINE0(fork) +static long do_clone(clone_args_t *args) { - pcb_t *newp, *parent; + /* Flags used by pthread_create in MUSL, only those are supported. */ + static const unsigned long THREAD_FLAGS = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | + CLONE_DETACHED; + unsigned long flags; + long ret; + pcb_t *new_pcb, *current_pcb; + tcb_t *new_tcb; flags = local_irq_save(); + current_pcb = current()->pcb; - parent = current()->pcb; + if (args->flags & CLONE_THREAD) { + if (args->flags != THREAD_FLAGS) { + LOG_WARNING("Only pthread_create clone flags are supported for thread\n"); + ret = -EINVAL; + goto error; + } - /* For the time being, we *only* authorize to fork() from the main - * thread */ - if (current() != parent->main_thread) { - LOG_WARNING("%s: forking from a thread other than the main thread " - "is not allowed so far ...\n", - __func__); - return -1; - } + /* Don't need to create a new PCB, use current as "new" PCB to create the thread. */ + new_pcb = current_pcb; + } else { + if (args->flags != 0) { + LOG_WARNING("Clone flags not support for forking\n"); + ret = -EINVAL; + goto error; + } - /* Duplicate the elements of the parent process into the child */ - newp = duplicate_process(parent); + /* For the time being, we *only* authorize to fork() from the main + * thread */ + if (current() != current_pcb->main_thread) { + LOG_WARNING("%s: forking from a thread other than the main thread " + "is not allowed so far ...\n", + __func__); + ret = -EINVAL; + goto error; + } - /* Copy the user space area of the parent process */ - duplicate_user_space(parent, newp); + /* Duplicate the elements of the parent process into the child */ + new_pcb = duplicate_process(current_pcb); - /* At the moment, we spawn the main_thread only in the child. In the - * future, we will have to create a thread for each existing threads in - * the parent process. - */ - sprintf(newp->name, "%s_child_%d", parent->name, newp->pid); + /* Copy the user space area of the parent process */ + duplicate_user_space(current_pcb, new_pcb); - newp->main_thread = user_thread(NULL, newp->name, (void *) arch_get_args_base(), newp); + /* At the moment, we spawn the main_thread only in the child. In the + * future, we will have to create a thread for each existing threads in + * the parent process. + */ + snprintf(new_pcb->name, PROC_NAME_LEN, "%s_child_%d", current_pcb->name, new_pcb->pid); + } + args->pcb = new_pcb; - /* Copy the kernel stack of the main thread */ - memcpy((void *) get_kernel_stack_top(newp->main_thread->stack_slotID) - CONFIG_THREAD_STACK_SIZE_KB * SZ_1K, - (void *) get_kernel_stack_top(parent->main_thread->stack_slotID) - CONFIG_THREAD_STACK_SIZE_KB * SZ_1K, - CONFIG_THREAD_STACK_SIZE_KB * SZ_1K); + /* Use PCB name as thread name. The TID will be appended to it. */ + args->name = new_pcb->name; - /* - * Preserve the current value of all registers concerned by this - * execution so that the new thread will be able to pursue its execution - * once scheduled. - */ + new_tcb = user_thread(args); - __save_context(newp->main_thread, get_kernel_stack_top(newp->main_thread->stack_slotID)); + /* Child thread will return in ret_from_fork and so only parent thread reachs this */ + ret = new_tcb->tid; - /* The main process thread is ready to be scheduled for its execution.*/ - newp->state = PROC_STATE_READY; + if (!(args->flags & CLONE_THREAD)) { + /* The main process thread is ready to be scheduled for its execution.*/ + new_pcb->state = PROC_STATE_READY; - BUG_ON(!local_irq_is_disabled()); + BUG_ON(!local_irq_is_disabled()); - /* Prepare to perform scheduling to check if a context switch is - * required. */ - raise_softirq(SCHEDULE_SOFTIRQ); + /* Prepare to perform scheduling to check if a context switch is + * required. */ + raise_softirq(SCHEDULE_SOFTIRQ); + /* Process clone should return the PID instead of the TID. */ + ret = new_pcb->pid; + } + +error: local_irq_restore(flags); + return ret; +} - /* Return the PID of the child process. The child will do not execute - * this code, since it jumps to the ret_from_fork in context.S - */ +/* + * For a new process from the current running process. + */ +SYSCALL_DEFINE0(fork) +{ + clone_args_t args = {}; - return newp->pid; + return do_clone(&args); +} + +SYSCALL_DEFINE5(clone, unsigned long, flags, unsigned long, newsp, int *, parent_tid, unsigned long, tls, int *, child_tid) +{ + clone_args_t args = { + .flags = flags & ~CSIGNAL, + .stack = newsp, + .parent_tid = parent_tid, + .tls = tls, + .child_tid = child_tid, + }; + + return do_clone(&args); } /* @@ -896,7 +904,7 @@ SYSCALL_DEFINE0(fork) * All allocated resources should be released except its PCB which still * contains the exit code. */ -SYSCALL_DEFINE1(exit, int, exit_status) +SYSCALL_DEFINE1(exit_group, int, exit_status) { pcb_t *pcb; unsigned i; @@ -955,7 +963,7 @@ SYSCALL_DEFINE1(exit, int, exit_status) * will lead to the wake up of the parent. */ - thread_exit(NULL); + thread_exit(0); return 0; } @@ -1121,7 +1129,7 @@ SYSCALL_DEFINE1(brk, long, increment) pcb->page_count += 1; allocate_page(pcb, pcb->heap_base, page_count); pcb->heap_pointer = pcb->heap_base + PAGE_SIZE; - } + } return -1; } diff --git a/so3/kernel/thread.c b/so3/kernel/thread.c index 51062add5..7c53ddb76 100644 --- a/so3/kernel/thread.c +++ b/so3/kernel/thread.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2014-2019 Daniel Rossier * Copyright (C) 2017 Xavier Ruppen - * + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -138,7 +138,7 @@ bool kernel_stack_slot[CONFIG_MAX_THREADS]; * Get the kernel stack (top), full descending - The kernel stack is divided into small stack areas * used for individual kernel *and* user threads (in SVC mode). * - * The first stack area is the initial system stack. + * The first stack area is the initial system stack. * The first thread stack slot ID #0 starts right after the initial system stack. */ addr_t get_kernel_stack_top(uint32_t slotID) @@ -187,41 +187,6 @@ void free_kernel_stack_slot(int slotID) kernel_stack_slot[slotID] = false; } -/* Process thread stack management */ - -/* Get the kernel stack (top), full descending */ -addr_t get_user_stack_top(pcb_t *pcb, uint32_t slotID) -{ - return (addr_t) ((void *) pcb->stack_top - slotID * CONFIG_THREAD_STACK_SIZE_KB * SZ_1K); -} - -/* - * Reserve a (user space) stack slot dedicated to a user thread. - */ -int get_user_stack_slot(pcb_t *pcb) -{ - unsigned int i; - - for (i = 0; i < PROC_MAX_THREADS; i++) { - if (!pcb->stack_slotID[i]) { - pcb->stack_slotID[i] = true; - - return i; - } - } - - /* No free stack slot */ - return -1; -} - -/* - * Release the user space stack slot. - */ -void free_user_stack_slot(pcb_t *pcb, int slotID) -{ - pcb->stack_slotID[slotID] = false; -} - #warning free_queue_thread() unused at the moment... void free_queue_thread(struct list_head *aList) { @@ -239,7 +204,7 @@ void free_queue_thread(struct list_head *aList) /* * Thread exit routine */ -void thread_exit(int *exit_status) +void thread_exit(int exit_status) { queue_thread_t *cur; struct list_head *pos, *q; @@ -272,7 +237,7 @@ void thread_exit(int *exit_status) remove_tcb_from_pcb(current()); #ifdef CONFIG_PROC_ENV - sys_do_exit(0); + sys_do_exit_group(0); #endif } else { @@ -306,6 +271,7 @@ void thread_exit(int *exit_status) * (This is currently a limitation: kernel threads can not join other threads). */ #warning Kernel threads cannot join other threads! +#warning TODO: check for clear_child_tid usage if (current()->pcb != NULL) zombie(); @@ -335,7 +301,7 @@ void thread_prologue(void (*th_fn)(void *arg), void *arg) * If we reach this point, it means that a kernel thread has finished its execution. */ - thread_exit(NULL); + thread_exit(0); } /* @@ -377,16 +343,12 @@ void set_thread_registers(tcb_t *thread, cpu_regs_t *regs) memcpy(&thread->cpu_regs, regs, sizeof(cpu_regs_t)); } -/* +/** * Thread creation routine * - * @start_routine: function to be threaded; if NULL, it means we are along a fork() processing. - * @name: name of the thread. Warning ! The caller must foresee a concatenation of the tid to the string. - * @arg: pointer to possible args - * @pcb: NULL means it is a pure kernel thread, otherwise is is a user thread. - * @prio: 0 means the default priority, otherwise set to the thread to the corresponding priority + * @param args Aguments to create the thread */ -tcb_t *thread_create(th_fn_t start_routine, const char *name, void *arg, pcb_t *pcb, uint32_t prio) +tcb_t *thread_create(clone_args_t *args) { tcb_t *tcb; unsigned long flags; @@ -405,18 +367,15 @@ tcb_t *thread_create(th_fn_t start_routine, const char *name, void *arg, pcb_t * tcb->tid = tid_next++; /* We append the tid to the thread name */ - snprintf(tcb->name, THREAD_NAME_LEN, "%s_%d", name, tcb->tid); - - tcb->th_fn = start_routine; - tcb->th_arg = arg; + snprintf(tcb->name, THREAD_NAME_LEN, "%s_%d", args->name, tcb->tid); - if (prio) - tcb->prio = prio; + if (args->prio) + tcb->prio = args->prio; else tcb->prio = THREAD_PRIO_DEFAULT; tcb->state = THREAD_STATE_NEW; - tcb->pcb = pcb; + tcb->pcb = args->pcb; /* Init the thread kernel stack (svc mode) for both kernel and user thread. */ tcb->stack_slotID = get_kernel_stack_slot(); @@ -426,57 +385,75 @@ tcb_t *thread_create(th_fn_t start_routine, const char *name, void *arg, pcb_t * kernel_panic(); } - /* Prepare the user stack if any related PCB */ - if (tcb->pcb) { - /* Prepare the user stack */ - tcb->pcb_stack_slotID = get_user_stack_slot(tcb->pcb); - if (tcb->pcb_stack_slotID < 0) { - printk("No available user stack for a new thread\n"); - kernel_panic(); - } - } - /* Prepare registers for future restore in switch_context() */ - arch_prepare_cpu_regs(tcb); - - /* Quite common registers on various architectures */ - tcb->cpu_regs.sp = get_kernel_stack_top(tcb->stack_slotID); - - if (tcb->pcb) - tcb->cpu_regs.lr = (unsigned long) __thread_prologue_user; - else - tcb->cpu_regs.lr = (unsigned long) __thread_prologue_kernel; + arch_prepare_cpu_regs(tcb, args); /* Initialize the join queue associated to this thread */ INIT_LIST_HEAD(&tcb->joinQueue); /* We do not want to have the idle thread as a "ready" thread */ - if (start_routine != thread_idle) + if (args->fn != thread_idle) ready(tcb); + if (args->flags & CLONE_PARENT_SETTID) { + /* Return the TID of the child thread. The child will not execute + * this code, since it jumps to the ret_from_fork in context.S + */ + *args->parent_tid = tcb->tid; + } + + if (args->flags & CLONE_CHILD_CLEARTID) { + tcb->child_clear_tid = (addr_t) args->child_tid; + } + local_irq_restore(flags); return tcb; } +/** + * Create a new kernel thread. + * + * @param start_routine Kernel function to call in the thread. + * @param name Name of the thread. + * @param arg Arguments passed to the function called by the thread. + * @param prio The thread scheduling priority. + */ tcb_t *kernel_thread(th_fn_t start_routine, const char *name, void *arg, uint32_t prio) { - return thread_create(start_routine, name, arg, NULL, prio); + clone_args_t args = { + .name = name, + .fn = start_routine, + .fn_arg = arg, + .prio = prio, + }; + + return thread_create(&args); } /** - * Should not be called directly. - * Called by create_process() or create_child_thread() instead. + * Create a new user thread based on given arguments. * - * @param start_routine Address ot the thread routine - * @param name Name of the thread - * @param arg Argument of the thread - * @param pcb PCB which the thread belongs to - * @return Address of the corresponding TCB + * @param args + * @return Address of the corresponding TCB */ -tcb_t *user_thread(th_fn_t start_routine, const char *name, void *arg, pcb_t *pcb) +tcb_t *user_thread(clone_args_t *args) { - return thread_create(start_routine, name, arg, pcb, (pcb->main_thread ? pcb->main_thread->prio : 0)); + tcb_t *tcb; + + args->prio = (args->flags & CLONE_THREAD) ? args->pcb->main_thread->prio : 0; + + tcb = thread_create(args); + + if (args->flags & CLONE_THREAD) { + /* Insert the newly thread to the thread list of this process */ + list_add_tail(&tcb->list, &args->pcb->threads); + } else { + BUG_ON(args->pcb->main_thread != NULL); + args->pcb->main_thread = tcb; + } + + return tcb; } /* @@ -505,10 +482,10 @@ void clean_thread(tcb_t *tcb) * The target thread which we are trying to join will be definitively removed * from the system when no other threads are joining it too. */ -int *thread_join(tcb_t *tcb) +int thread_join(tcb_t *tcb) { queue_thread_t *cur; - int *exit_status; + int exit_status; tcb_t *_tcb; struct list_head *pos, *q; unsigned long flags; @@ -565,14 +542,14 @@ int *thread_join(tcb_t *tcb) /* Check if the child is a tracee (and therefore we have a tracer on it) */ if ((tcb != NULL) && (tcb->pcb->ptrace_pending_req != PTRACE_NO_REQUEST)) { - exit_status = NULL; + exit_status = 0; } else { /* The joined thread *must* be in zombie */ ASSERT(tcb->state == THREAD_STATE_ZOMBIE); if (is_main_thread) - exit_status = (void *) ((unsigned long) tcb->pcb->exit_status); + exit_status = tcb->pcb->exit_status; else exit_status = tcb->exit_status; @@ -597,86 +574,12 @@ int *thread_join(tcb_t *tcb) return exit_status; } -/* - * do_thread_create is the syscall implementation behind the pthread_create() called in the libc. - * - * @pthread_p refers to the address of the resulting pthread_t id - * @attr_p refers to the address of pthread attributes - * @thread_fn refers to the threaded function in the user space - * @arg_p refers to the arguments passed to the threaded function - * - * The function returns 0 if successful. - */ - -SYSCALL_DEFINE4(thread_create, uint32_t *, pthread_id, addr_t, attr_p, addr_t, thread_fn, addr_t, arg_p) -{ - unsigned long flags; - tcb_t *tcb; - char *name; - - flags = local_irq_save(); - - /* Create a child thread for the running process */ - - /* Temporary name for this thread */ - name = (char *) malloc(THREAD_NAME_LEN); - if (!name) { - printk("%s: heap overflow...\n", __func__); - kernel_panic(); - } - snprintf(name, THREAD_NAME_LEN, "thread_p%d", current()->pcb->pid); - - tcb = user_thread((th_fn_t) thread_fn, name, (void *) arg_p, current()->pcb); - - /* The name has been copied in thread creation */ - free(name); - - /* Insert the newly thread to the thread list of this process */ - list_add_tail(&tcb->list, ¤t()->pcb->threads); - - *pthread_id = tcb->tid; - - local_irq_restore(flags); - - return 0; -} - -/* - * Join an existing thread - */ -SYSCALL_DEFINE2(thread_join, uint32_t, pthread_id, int **, value_p) -{ - tcb_t *tcb; - int *ret; - unsigned long flags; - - flags = local_irq_save(); - - tcb = find_thread_by_tid(current()->pcb, pthread_id); - - if (tcb == NULL) { - return -EINVAL; - } - - ret = thread_join(tcb); - - if (value_p != NULL) - *value_p = ret; - - local_irq_restore(flags); - - return 0; -} - /* * do_thread_exit() is called when pthread_exit() is executed. */ -SYSCALL_DEFINE1(thread_exit, int *, exit_status) +SYSCALL_DEFINE1(exit, int, exit_status) { /* Unallocate the user space stack slot if it is not the main thread */ - if (current() != current()->pcb->main_thread) - free_user_stack_slot(current()->pcb, current()->pcb_stack_slotID); - thread_exit(exit_status); return 0; diff --git a/so3/syscall.tbl b/so3/syscall.tbl index 1a3921119..b90fa3a58 100644 --- a/so3/syscall.tbl +++ b/so3/syscall.tbl @@ -46,7 +46,9 @@ rt_sigreturn IPC_SIGNAL getpid MMU execve MMU fork MMU +clone MMU exit MMU +exit_group MMU wait4 MMU ptrace MMU gettimeofday MMU From 7bf11399ca7bc91cc0ef47639656653f12ce0096 Mon Sep 17 00:00:00 2001 From: Clement Dieperink Date: Mon, 10 Nov 2025 08:45:46 +0100 Subject: [PATCH 2/2] fix style and missing comments --- so3/arch/arm32/thread.c | 6 ++---- so3/arch/arm64/thread.c | 6 ++---- so3/kernel/thread.c | 12 ++++++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/so3/arch/arm32/thread.c b/so3/arch/arm32/thread.c index 596f8d882..ba2ad7973 100644 --- a/so3/arch/arm32/thread.c +++ b/so3/arch/arm32/thread.c @@ -47,13 +47,11 @@ void arch_prepare_cpu_regs(tcb_t *tcb, clone_args_t *args) arch_restart_user_thread(tcb, args->fn, args->stack); } else { /* Normal userspace that will copy userspace registers */ - if (args->stack) { + if (args->stack) user_regs->sp_usr = args->stack; - } - if (args->flags & CLONE_SETTLS) { + if (args->flags & CLONE_SETTLS) user_regs->tls_usr = args->tls; - } /* Copy userspace registers */ *user_regs = *(cpu_regs_t *) arch_get_kernel_stack_frame(current()); diff --git a/so3/arch/arm64/thread.c b/so3/arch/arm64/thread.c index 80a23493d..e9416929f 100644 --- a/so3/arch/arm64/thread.c +++ b/so3/arch/arm64/thread.c @@ -46,13 +46,11 @@ void arch_prepare_cpu_regs(tcb_t *tcb, clone_args_t *args) arch_restart_user_thread(tcb, args->fn, args->stack); } else { /* Normal userspace that will copy userspace registers */ - if (args->stack) { + if (args->stack) user_regs->sp_usr = args->stack; - } - if (args->flags & CLONE_SETTLS) { + if (args->flags & CLONE_SETTLS) user_regs->tls_usr = args->tls; - } /* Copy userspace registers */ *user_regs = *(cpu_regs_t *) arch_get_kernel_stack_frame(current()); diff --git a/so3/kernel/thread.c b/so3/kernel/thread.c index 7c53ddb76..86efae402 100644 --- a/so3/kernel/thread.c +++ b/so3/kernel/thread.c @@ -347,6 +347,8 @@ void set_thread_registers(tcb_t *thread, cpu_regs_t *regs) * Thread creation routine * * @param args Aguments to create the thread + * + * @return TCB of the newly created thread. */ tcb_t *thread_create(clone_args_t *args) { @@ -402,9 +404,8 @@ tcb_t *thread_create(clone_args_t *args) *args->parent_tid = tcb->tid; } - if (args->flags & CLONE_CHILD_CLEARTID) { + if (args->flags & CLONE_CHILD_CLEARTID) tcb->child_clear_tid = (addr_t) args->child_tid; - } local_irq_restore(flags); @@ -418,6 +419,8 @@ tcb_t *thread_create(clone_args_t *args) * @param name Name of the thread. * @param arg Arguments passed to the function called by the thread. * @param prio The thread scheduling priority. + * + * @return TCB of the newly created thread. */ tcb_t *kernel_thread(th_fn_t start_routine, const char *name, void *arg, uint32_t prio) { @@ -434,8 +437,9 @@ tcb_t *kernel_thread(th_fn_t start_routine, const char *name, void *arg, uint32_ /** * Create a new user thread based on given arguments. * - * @param args - * @return Address of the corresponding TCB + * @param args Aguments to create the user thread + * + * @return TCB of the newly created thread. */ tcb_t *user_thread(clone_args_t *args) {