diff --git a/arch/sysv_x86-64/thread-entry.asm b/arch/sysv_x86-64/thread-entry.asm index 6bea7e2..1a9cf5c 100644 --- a/arch/sysv_x86-64/thread-entry.asm +++ b/arch/sysv_x86-64/thread-entry.asm @@ -2,7 +2,9 @@ bits 64 section .text -global _cia_start_thread +global _rt_thread_start + +extern _rt_thread_finish ; flags, &stack[-2], &parent_tid, &child_tid, 0 @@ -23,7 +25,7 @@ global _cia_start_thread ; 0 if returning as a parent ; 1 if returning as a child ; negative value if there was an error making the thread -_cia_start_thread: +_rt_thread_start: mov r10, rcx ; Setup child stack sub rsi, 24 @@ -43,10 +45,9 @@ _cia_start_thread: pop rax ; thread_fn pop rdi ; ctx call rax - ; Make return value the first arg and call exit syscall + ; Make return value the first arg and call thread finish routine mov rdi, rax - mov rax, 60 - syscall + call _rt_thread_finish ; Just to be sure... hlt .exit: diff --git a/cia.c b/cia.c index edbc8d4..bff0250 100644 --- a/cia.c +++ b/cia.c @@ -3,7 +3,6 @@ // Base includes #include -#include // Platform-dependent sources #include _CIA_OS_CONF diff --git a/include/cia/internal.h b/include/cia/internal.h index da71abc..499d37a 100644 --- a/include/cia/internal.h +++ b/include/cia/internal.h @@ -11,13 +11,28 @@ struct Cia_CRT_Params { void *tls_image_base; }; +// These control how the thread should behave once it's terminated +// if the main thread didn't call join nor detach, the thread +// would just wait until one of them is set. +// If the thread is joined, +// would signal main thread that it has finished and exit without +// cleaning up the resources (thrd_join does that for us) +// If the thread is detached, it would clear it's resources and exit +#define _CIA_THREAD_BEHAVIOUR_NOT_SET 0x0 +#define _CIA_THREAD_BEHAVIOUR_JOIN 0x1 +#define _CIA_THREAD_BEHAVIOUR_DETACH 0x2 +#define _CIA_THREAD_BEHAVIOUR_FINISH 0x3 + struct Cia_TCB typedef Cia_TCB; struct Cia_TCB { - u64 thread_id; - u64 parent_id; - u64 pad0[3]; - u64 stack_canary; - u64 pad1[2]; + /* +0x00 */ u64 thread_id; + /* +0x08 */ u64 parent_id; + /* +0x10 */ u32 thread_behaviour; /* One of the CIA_THREAD_BEHAVIOR_* constants */ + /* +0x14 */ u32 thread_finished; + /* +0x18 */ u32 pad0; + /* +0x1c */ i32 exit_code; + /* +0x20 */ u64 pad1; + /* +0x28 */ u64 stack_canary; }; diff --git a/include/threads.h b/include/threads.h index 56a3372..a3ed831 100644 --- a/include/threads.h +++ b/include/threads.h @@ -17,3 +17,5 @@ enum { }; int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); +int thrd_join(thrd_t thr, int *out_exit_code); +int thrd_detach(thrd_t thr); \ No newline at end of file diff --git a/include/tinyrt.h b/include/tinyrt.h index 6212855..78735fc 100644 --- a/include/tinyrt.h +++ b/include/tinyrt.h @@ -55,7 +55,7 @@ struct _RT_Thread { }; static _RT_Status _rt_thread_current(_RT_Thread *thread); static _RT_Status _rt_thread_create(_RT_Thread *thread, int (*thread_fn)(void *ctx), void *ctx); -static _RT_Status _rt_thread_join(_RT_Thread *thread); +static _RT_Status _rt_thread_join(_RT_Thread *thread, int *out_exit_code); static _RT_Status _rt_thread_detach(_RT_Thread *thread); static _RT_Status _rt_thread_terminate(_RT_Thread *thread); static _RT_Status _rt_thread_sleep(u64 time); diff --git a/os/linux/conf.h b/os/linux/conf.h index 3419014..3a344be 100644 --- a/os/linux/conf.h +++ b/os/linux/conf.h @@ -5,6 +5,8 @@ static u64 cia_stack_size; static u64 cia_tls_image_size; static void *cia_tls_image_base; +#include + #include #include #include diff --git a/os/linux/tinyrt-threads.c b/os/linux/tinyrt-threads.c index 9e53b41..ba7e0c5 100644 --- a/os/linux/tinyrt-threads.c +++ b/os/linux/tinyrt-threads.c @@ -1,5 +1,5 @@ -extern i64 _cia_start_thread( +extern i64 _rt_thread_start( u64 flags, void *stack_base, int *parent_tid, @@ -33,6 +33,7 @@ static _RT_Status _rt_thread_create(_RT_Thread *thread, int (*thread_fn)(void *c // Initialize the _RT_Thread handle, which would point to // the TCB thread->handle = tcb; + tcb->thread_finished = 0; // Create the new thread u64 flags = 0; // flags |= CLONE_CHILD_CLEARTID; @@ -47,19 +48,52 @@ static _RT_Status _rt_thread_create(_RT_Thread *thread, int (*thread_fn)(void *c int *parent_tid = (int *)&tcb->parent_id; *child_tid = 1; *parent_tid = 0; - i64 ret = _cia_start_thread(flags, stack, parent_tid, child_tid, 0, thread_fn, ctx); + i64 ret = _rt_thread_start(flags, stack, parent_tid, child_tid, 0, thread_fn, ctx); if(ret < 0) { return _RT_ERROR_GENERIC; } return _RT_STATUS_OK; } -static _RT_Status _rt_thread_join(_RT_Thread *thread) { - return _RT_ERROR_NOT_IMPLEMENTED; +void _rt_thread_finish(int exit_code) { + Cia_TCB *tcb = (void *)((u64)__builtin_frame_address(0) & ~(cia_stack_size - 1)); + // Wait until the main thread decides what to do with the child thread + while(tcb->thread_behaviour == _CIA_THREAD_BEHAVIOUR_NOT_SET) { + syscall(SYS_futex, &tcb->thread_behaviour, FUTEX_WAIT, _CIA_THREAD_BEHAVIOUR_NOT_SET, NULL, 0, 0); + } + if(tcb->thread_behaviour == _CIA_THREAD_BEHAVIOUR_JOIN) { + tcb->exit_code = exit_code; + // Idk if a memory barrier should be here, because we don't want the compiler + // to reorder these two lines. If that happens, and we get a spurious wake up + // after the thread_finished is set and before exit_code is set, the main thread + // will proceed to fetch an invalid exit code from the thread. + tcb->thread_finished = 1; + syscall(SYS_futex, &tcb->thread_finished, FUTEX_WAKE, 0, NULL, 0, 0); + sys_exit(exit_code); + } + else if(tcb->thread_behaviour == _CIA_THREAD_BEHAVIOUR_DETACH) { + // TODO: clean up the thread resources + sys_exit(exit_code); + } +} + +static _RT_Status _rt_thread_join(_RT_Thread *thread, int *out_exit_code) { + Cia_TCB *tcb = thread->handle; + // Signal the thread that we want it to be joined + tcb->thread_behaviour = _CIA_THREAD_BEHAVIOUR_JOIN; + // Wait until the thread signals that it has completed the execution + while(tcb->thread_finished != 1) { + syscall(SYS_futex, &tcb->thread_finished, FUTEX_WAIT, 0, NULL, 0, 0); + } + // Set the exit code + *out_exit_code = tcb->exit_code; + return _RT_STATUS_OK; } static _RT_Status _rt_thread_detach(_RT_Thread *thread) { - return _RT_ERROR_NOT_IMPLEMENTED; + Cia_TCB *tcb = thread->handle; + tcb->thread_behaviour = _CIA_THREAD_BEHAVIOUR_DETACH; + return _RT_STATUS_OK; } static _RT_Status _rt_thread_terminate(_RT_Thread *thread) { diff --git a/src/stdlib-thread/thread.c b/src/stdlib-thread/thread.c index 152ab02..58d5627 100644 --- a/src/stdlib-thread/thread.c +++ b/src/stdlib-thread/thread.c @@ -5,4 +5,20 @@ int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { return thrd_success; } return thrd_error; -} \ No newline at end of file +} + +int thrd_detach(thrd_t thr) { + _RT_Status status = _rt_thread_detach(&thr.thread); + if(status == _RT_STATUS_OK) { + return thrd_success; + } + return thrd_error; +} + +int thrd_join(thrd_t thr, int *out_exit_code) { + _RT_Status status = _rt_thread_join(&thr.thread, out_exit_code); + if(status == _RT_STATUS_OK) { + return thrd_success; + } + return thrd_error; +} diff --git a/tests/threaded.c b/tests/threaded.c index 3ef6952..e356287 100644 --- a/tests/threaded.c +++ b/tests/threaded.c @@ -36,7 +36,6 @@ static void print_int(i64 number) { static Cia_Mutex g_print_mutex; static Cia_Mutex g_mutex; -static volatile _Atomic i64 n_completed = 0; static volatile i64 counter = 0; int thrd_func(void *arg) { @@ -46,12 +45,6 @@ int thrd_func(void *arg) { counter += 1; cia_mutex_unlock(&g_mutex); } - atomic_fetch_add(&n_completed, 1); - for(;;) { - if(n_completed == 2) { - break; - } - } cia_mutex_lock(&g_print_mutex); print_string("child thread: counter = "); print_int(counter); @@ -76,12 +69,8 @@ int main() { counter += 1; cia_mutex_unlock(&g_mutex); } - atomic_fetch_add(&n_completed, 1); - for(;;) { - if(n_completed == 2) { - break; - } - } + int exit_code; + thrd_join(thrd, &exit_code); cia_mutex_lock(&g_print_mutex); print_string("main thread: counter = "); print_int(counter);