diff --git a/arch/sysv_x86-64/thread-entry.asm b/arch/sysv_x86-64/thread-entry.asm index e8395af..f46ad7f 100644 --- a/arch/sysv_x86-64/thread-entry.asm +++ b/arch/sysv_x86-64/thread-entry.asm @@ -4,6 +4,7 @@ bits 64 section .text global _rt_thread_start +extern _rt_thread_setup extern _rt_thread_finish ; flags, &stack[-2], &parent_tid, &child_tid, 0 @@ -42,11 +43,11 @@ _rt_thread_start: test eax, eax jnz .exit ; If child, jump to thread function - pop rax ; thread_fn - pop rdi ; ctx + pop rdi ; thread_fn + pop rsi ; ctx ; Align the stack and rsp, -16 - call rax + call _rt_thread_setup ; Make return value the first arg and call thread finish routine mov rdi, rax call _rt_thread_finish diff --git a/include/cia-ld/tcb.h b/include/cia-ld/tcb.h index ac9d926..e01a1a6 100644 --- a/include/cia-ld/tcb.h +++ b/include/cia-ld/tcb.h @@ -11,27 +11,36 @@ struct _LD_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 _LD_THREAD_BEHAVIOUR_NOT_SET 0x0 -#define _LD_THREAD_BEHAVIOUR_JOIN 0x1 -#define _LD_THREAD_BEHAVIOUR_DETACH 0x2 -#define _LD_THREAD_BEHAVIOUR_FINISH 0x3 +// These signal a decision made by the main thread +// about whether a thread should be detached or joined +// If its not set, upon finishing the thread will wait +// until one of these flags is set. +// If the flag was set to detached, the thread will perform +// cleanup on its own after it reached the end of execution. +// If the flag was set to joined, it cannot perform the +// cleanup on its own. For if it did, the check on the state_finish +// futex might fail due to TCB being destroyed by the thread. +// Which is why we first need to make sure the thread stopped working +// and we don't need any of its TCB fields before cleaning it up on +// the main thread. +#define _LD_THREAD_STATE_NOT_YET 0x0 +#define _LD_THREAD_STATE_DETACHED 0x1 +#define _LD_THREAD_STATE_JOINED 0x2 struct _LD_Thread_Block typedef _LD_Thread_Block; struct _LD_Thread_Block { - /* +0x00 */ u64 thread_id; - /* +0x08 */ u64 parent_id; - /* +0x10 */ _Atomic(u32) thread_behaviour; /* One of the CIA_THREAD_BEHAVIOR_* constants */ - /* +0x14 */ _Atomic(i32) thread_finished; - /* +0x18 */ u32 pad0; - /* +0x1c */ _Atomic(i32) exit_code; - /* +0x20 */ u64 pad1; + /* DO NOT REORDER OR CHANGE SIZES (these comments are supposed to make it hard) */ + /* +0x00 */ i32 thread_id; + /* +0x04 */ i32 state_finish; + /* +0x08 */ _Atomic(u32) state_detach; + /* +0x0c */ u32 exit_code; + /* +0x10 */ u64 pad0; + /* +0x18 */ u64 pad1; + /* +0x20 */ u64 pad2; /* +0x28 */ u64 stack_canary; + /* Not ABI dependent as far as I care (for now) */ + _LD_Thread_Block *next_tcb; + _LD_Thread_Block *prev_tcb; + _Atomic(u32) is_cancelled; }; diff --git a/include/linux/signal.h b/include/linux/signal.h new file mode 100644 index 0000000..e878a72 --- /dev/null +++ b/include/linux/signal.h @@ -0,0 +1,105 @@ + +#pragma once + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGPOLL 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGCLD SIGCHLD +#define SIGIO SIGPOLL +#define SIGIOT SIGABRT + +// Custom guy +#define SIGCANCEL 33 + +#define SA_NOCLDSTOP 1 +#define SA_NOCLDWAIT 2 +#define SA_SIGINFO 4 +#define SA_ONSTACK 0x08000000 +#define SA_RESTART 0x10000000 +#define SA_INTERRUPT 0x20000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 +#define SA_NOMASK SA_NODEFER +#define SA_ONESHOT SA_RESETHAND +#define SA_STACK SA_ONSTACK + +#define SIG_BLOCK 0 +#define SIG_SETMASK 2 +#define SIG_UNBLOCK 1 + +union sigval { + i32 sival_int; + void *sival_ptr; +}; + +struct siginfo_t typedef siginfo_t; +struct siginfo_t { + i32 si_signo; + i32 si_errno; + i32 si_code; + i32 si_trapno; + u32 si_pid; + u32 si_uid; + i32 si_status; + u64 si_utime; + u64 si_stime; + union sigval si_value; + i32 si_int; + void *si_ptr; + i32 si_overrun; + i32 si_timerid; + void *si_addr; + long si_band; + i32 si_fd; + short si_addr_lsb; + void *si_lower; + void *si_upper; + i32 si_pkey; + void *si_call_addr; + i32 si_syscall; + u32 si_arch; +}; + + +struct sigaction { + union { + void (*sa_handler)(i32); + void (*sa_sigaction)(i32, siginfo_t *, void *); + }; + u64 sa_mask; + i32 sa_flags; + void (*sa_restorer)(void); +}; + +static inline i32 sys_sigaction(i32 signum, const struct sigaction *restrict act, struct sigaction *restrict oldact) { + return (i32)syscall(SYS_rt_sigaction, signum, act, oldact, 1); +} + diff --git a/include/linux/sys/syscall.h b/include/linux/sys/syscall.h index 6d6e650..62cfef9 100644 --- a/include/linux/sys/syscall.h +++ b/include/linux/sys/syscall.h @@ -417,6 +417,19 @@ static inline i64 sys_arch_prctl(int code, u64 value) { return syscall(SYS_arch_prctl, code, value); } -static inline i64 sys_gettid() { +static inline u32 sys_gettid() { return syscall(SYS_gettid); } + +static inline u32 sys_getpid() { + return syscall(SYS_getpid); +} + +static inline i32 sys_tkill(u32 tid, i32 sig) { + return syscall(SYS_tkill, tid, sig); +} + +static inline i32 sys_tgkill(u32 tgid, u32 tid, i32 sig) { + return syscall(SYS_tgkill, tgid, tid, sig); +} + diff --git a/include/threads.h b/include/threads.h index 893f1c1..d94568b 100644 --- a/include/threads.h +++ b/include/threads.h @@ -22,4 +22,6 @@ 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); -void thrd_yield(); \ No newline at end of file +void thrd_yield(); + +void thrd_terminate(thrd_t thr); /*TODO remove later*/ \ No newline at end of file diff --git a/include/tinyrt.h b/include/tinyrt.h index cd8fc02..3b07f2f 100644 --- a/include/tinyrt.h +++ b/include/tinyrt.h @@ -62,6 +62,9 @@ static _RT_Status _rt_thread_yield(); static _RT_Status _rt_thread_sleep(u64 time); static _RT_Status _rt_thread_get_timer_freq(u64 *freq); +// TODO: maybe replace with kill of single thread +static _RT_Status _rt_thread_cancell_all_running(); + // Environment API static _RT_Status _rt_shell_exec(char const *cmd); static _RT_Status _rt_env_get(char const *name); diff --git a/os/linux/conf.h b/os/linux/conf.h index 61b7aac..e85349c 100644 --- a/os/linux/conf.h +++ b/os/linux/conf.h @@ -9,12 +9,14 @@ static void *cia_tls_image_base; #include #include -#include -#include -#include -#include -#include "entry.c" +#include +#include +#include +#include +#include +#include #include #include "tinyrt.c" #include "tinyrt-threads.c" +#include "entry.c" diff --git a/os/linux/entry.c b/os/linux/entry.c index c2d67a0..785122e 100644 --- a/os/linux/entry.c +++ b/os/linux/entry.c @@ -1,6 +1,4 @@ -#include - static char stack_chk_fail_msg[] = "Stack check failed. " "You've got a stack corruption somewhere. " @@ -13,6 +11,7 @@ void __stack_chk_fail(void) { extern int main(int argc, char **argv, char **envp); static void _fileapi_init(); +static void _rt_threads_setup(); void _start(_LD_CRT_Params *params) { cia_stack_size = params->stack_size; @@ -22,9 +21,8 @@ void _start(_LD_CRT_Params *params) { // char **envp = argv + (argc + 1); // init(argc, argv, envp); _fileapi_init(); + _rt_threads_setup(); int code = main(0, NULL, NULL); - // fini(); - // glibc bug - // dl_fini(); + _rt_thread_cancell_all_running(); sys_exit(code); } diff --git a/os/linux/tinyrt-threads.c b/os/linux/tinyrt-threads.c index 81dcf30..d061674 100644 --- a/os/linux/tinyrt-threads.c +++ b/os/linux/tinyrt-threads.c @@ -1,4 +1,6 @@ +static _LD_Thread_Block *thread_blocks_head = NULL; + extern i64 _rt_thread_start( u64 flags, void *stack_base, @@ -9,6 +11,25 @@ extern i64 _rt_thread_start( void *ctx ); +static void thread_sigcancel_handler(int sig, siginfo_t *info, void *ucontext) { + _LD_Thread_Block *tcb = (void *)((u64)__builtin_frame_address(0) & ~(cia_stack_size - 1)); + u32 is_cancelled = atomic_load_explicit(&tcb->is_cancelled, memory_order_acquire); + if(is_cancelled) { + u32 tgid = sys_getpid(); + sys_exit(0); + } +} + +static void _rt_threads_setup() { + u64 sigs[3] = {(1ul << SIGCANCEL)}; + struct sigaction handler = { + .sa_sigaction = thread_sigcancel_handler, + .sa_flags = SA_SIGINFO|SA_RESTART, + .sa_mask = 0xfffffffc7ffffffful, + }; + syscall(SYS_rt_sigaction, SIGCANCEL, &handler, NULL, 1 * sizeof(u64)); +} + static _RT_Status _rt_thread_current(_RT_Thread *thread) { thread->handle = (void *)((u64)__builtin_frame_address(0) & ~(cia_stack_size - 1)); return _RT_STATUS_OK; @@ -23,76 +44,86 @@ static _RT_Status _rt_thread_create(_RT_Thread *thread, int (*thread_fn)(void *c return _RT_ERROR_GENERIC; } void *stack = (u8*)stack_base + 2*cia_stack_size; - // Find the TLS base and initialize the tls + // Find the TCB base and initialize the TCB _LD_Thread_Block *tcb = (void *)((u64)((u8 *)stack - 1) & ~(cia_stack_size - 1)); - tcb->stack_canary = 0x12345678deadbeef; u8 *tls_base = (u8 *)tcb - cia_tls_image_size; for(int i = 0; i < cia_tls_image_size; ++i) { tls_base[i] = ((u8 *)cia_tls_image_base)[i]; } - // Initialize the _RT_Thread handle, which would point to - // the TCB + tcb->stack_canary = 0x12345678deadbeef; + tcb->next_tcb = NULL; + tcb->prev_tcb = thread_blocks_head; + atomic_store_explicit(&tcb->is_cancelled, 0, memory_order_relaxed); + if(thread_blocks_head != NULL) { + thread_blocks_head->next_tcb = tcb; + } + thread_blocks_head = tcb; + // This futex is reset on thread finish + tcb->state_finish = 1; + // Initialize the _RT_Thread handle, which would point to the TCB thread->handle = tcb; - atomic_store_explicit(&tcb->thread_finished, 0, memory_order_relaxed); // Create the new thread u64 flags = 0; - // flags |= CLONE_CHILD_CLEARTID; - // flags |= CLONE_PARENT_SETTID; + flags |= CLONE_CHILD_CLEARTID; + flags |= CLONE_PARENT_SETTID; flags |= CLONE_FS; flags |= CLONE_FILES; flags |= CLONE_SIGHAND; flags |= CLONE_THREAD; flags |= CLONE_VM; flags |= CLONE_SYSVSEM; - int *child_tid = (int *)&tcb->thread_id; - int *parent_tid = (int *)&tcb->parent_id; - *child_tid = 1; - *parent_tid = 0; - i64 ret = _rt_thread_start(flags, stack, parent_tid, child_tid, 0, thread_fn, ctx); + i64 ret = _rt_thread_start(flags, stack, &tcb->thread_id, &tcb->state_finish, 0, thread_fn, ctx); if(ret < 0) { return _RT_ERROR_GENERIC; } return _RT_STATUS_OK; } +int _rt_thread_setup(int (*thread_fn)(void *ctx), void *ctx) { + // struct sigaction handler = { + // .sa_sigaction = thread_sigcancel_handler, + // .sa_flags = SA_SIGINFO|SA_RESTART, + // .sa_mask = 0, + // }; + // i32 result = sys_sigaction(SIGCANCEL, &handler, NULL); + return thread_fn(ctx); +} + void _rt_thread_finish(int exit_code) { _LD_Thread_Block *tcb = (void *)((u64)__builtin_frame_address(0) & ~(cia_stack_size - 1)); // Wait until the main thread decides what to do with the child thread - u32 thread_behaviour = atomic_load_explicit(&tcb->thread_behaviour, memory_order_relaxed); - while(thread_behaviour == _LD_THREAD_BEHAVIOUR_NOT_SET) { - syscall(SYS_futex, &tcb->thread_behaviour, FUTEX_WAIT, _LD_THREAD_BEHAVIOUR_NOT_SET, NULL, 0, 0); - thread_behaviour = atomic_load_explicit(&tcb->thread_behaviour, memory_order_relaxed); + u32 thread_state = atomic_load_explicit(&tcb->state_detach, memory_order_relaxed); + while(thread_state == _LD_THREAD_STATE_NOT_YET) { + syscall(SYS_futex, &tcb->state_detach, FUTEX_WAIT, _LD_THREAD_STATE_NOT_YET, NULL, 0, 0); + thread_state = atomic_load_explicit(&tcb->state_detach, memory_order_relaxed); } - // If main thread set this thread to be joined, we signal it that we're done here - if(thread_behaviour == _LD_THREAD_BEHAVIOUR_JOIN) { - atomic_store_explicit(&tcb->exit_code, exit_code, memory_order_relaxed); - atomic_store_explicit(&tcb->thread_finished, 1, memory_order_release); - syscall(SYS_futex, &tcb->thread_finished, FUTEX_WAKE, 0, NULL, 0, 0); - sys_exit(exit_code); - } - else if(thread_behaviour == _LD_THREAD_BEHAVIOUR_DETACH) { + if(_LD_THREAD_STATE_DETACHED) { // TODO: clean up the thread resources - sys_exit(exit_code); } + tcb->exit_code = exit_code; + sys_exit(exit_code); } static _RT_Status _rt_thread_join(_RT_Thread *thread, int *out_exit_code) { _LD_Thread_Block *tcb = thread->handle; // Signal the thread that we want it to be joined - atomic_store_explicit(&tcb->thread_behaviour, _LD_THREAD_BEHAVIOUR_JOIN, memory_order_relaxed); - syscall(SYS_futex, &tcb->thread_behaviour, FUTEX_WAKE, 0, NULL, 0, 0); + atomic_store_explicit(&tcb->state_detach, _LD_THREAD_STATE_JOINED, memory_order_relaxed); + syscall(SYS_futex, &tcb->state_detach, FUTEX_WAKE, 0, NULL, 0, 0); // Wait until the thread signals that it has completed the execution - while(atomic_load_explicit(&tcb->thread_finished, memory_order_acquire) != 1) { - syscall(SYS_futex, &tcb->thread_finished, FUTEX_WAIT, 0, NULL, 0, 0); + while(tcb->state_finish != 0) { + syscall(SYS_futex, &tcb->state_finish, FUTEX_WAIT, 0, NULL, 0, 0); } // Set the exit code - *out_exit_code = atomic_load_explicit(&tcb->exit_code, memory_order_acquire); + // NOTE(bumbread): this is not a bug, because calling thrd_detach from one thread + // and thrd_join on the same thrd_t, from a different thread is supposed to be UB. + *out_exit_code = tcb->exit_code; + // TODO(bumbread): thread cleanup: destroy the TCB and thread-local storage return _RT_STATUS_OK; } static _RT_Status _rt_thread_detach(_RT_Thread *thread) { _LD_Thread_Block *tcb = thread->handle; - atomic_store_explicit(&tcb->thread_behaviour, _LD_THREAD_BEHAVIOUR_DETACH, memory_order_relaxed); + atomic_store_explicit(&tcb->state_detach, _LD_THREAD_STATE_DETACHED, memory_order_relaxed); return _RT_STATUS_OK; } @@ -106,7 +137,12 @@ static _RT_Status _rt_thread_yield() { } static _RT_Status _rt_thread_terminate(_RT_Thread *thread) { - return _RT_ERROR_NOT_IMPLEMENTED; + u32 tgid = sys_getpid(); + _LD_Thread_Block *tcb = thread->handle; + atomic_store_explicit(&tcb->is_cancelled, 1, memory_order_release); + sys_tkill(tcb->thread_id, SIGCANCEL); + // syscall(SYS_rt_tgsigqueueinfo, tgid, tcb->thread_id, SIGCANCEL, NULL); + return _RT_STATUS_OK; } static _RT_Status _rt_thread_sleep(u64 time) { @@ -116,3 +152,17 @@ static _RT_Status _rt_thread_sleep(u64 time) { static _RT_Status _rt_thread_get_timer_freq(u64 *freq) { return _RT_ERROR_NOT_IMPLEMENTED; } + +static _RT_Status _rt_thread_cancell_all_running() { + u32 tgid = sys_getpid(); + _LD_Thread_Block *tcb_cur = thread_blocks_head; + while(tcb_cur != NULL) { + _LD_Thread_Block *tcb_next = tcb_cur->next_tcb; + u32 thread_id = tcb_cur->thread_id; + atomic_store_explicit(&tcb_cur->is_cancelled, 1, memory_order_release); + //sys_tgkill(tgid, thread_id, SIGCANCEL); + //SYS_rt_tgsigqueueinfo(tgid) + tcb_cur = tcb_next; + } + return _RT_STATUS_OK; +} diff --git a/src/stdlib-file/fmt.c b/src/stdlib-file/fmt.c index c5690cd..f708169 100644 --- a/src/stdlib-file/fmt.c +++ b/src/stdlib-file/fmt.c @@ -138,14 +138,24 @@ int printf(const char *restrict fmt, ...) { } else if(fmt[i] == 'u') { - u32 arg; + u64 arg; if(int_arg_size == 32) { arg = va_arg(args, u32); } else if(int_arg_size == 64) { arg = va_arg(args, u64); } - arg_chars = print_uint((u64)arg); + arg_chars = print_uint(arg); + } + else if(fmt[i] == 'x') { + u64 arg; + if(int_arg_size == 32) { + arg = va_arg(args, u32); + } + else if(int_arg_size == 64) { + arg = va_arg(args, u64); + } + print_hex(arg); } else if(fmt[i] == 'p') { void *arg = va_arg(args, void *); diff --git a/src/stdlib-thread/thread.c b/src/stdlib-thread/thread.c index e15876e..77b6179 100644 --- a/src/stdlib-thread/thread.c +++ b/src/stdlib-thread/thread.c @@ -25,4 +25,9 @@ int thrd_join(thrd_t thr, int *out_exit_code) { void thrd_yield(void) { _rt_thread_yield(); +} + +/* remove later */ +void thrd_terminate(thrd_t thr) { + _rt_thread_terminate(&thr.thread); } \ No newline at end of file diff --git a/tests/threaded.c b/tests/threaded.c index 845e83f..c2af4e9 100644 --- a/tests/threaded.c +++ b/tests/threaded.c @@ -4,25 +4,33 @@ #include #include #include +#include +#include + +thrd_t thrd; static Cia_Mutex g_mutex; static volatile i64 counter = 0; int thrd_func(void *arg) { + _LD_Thread_Block *tcb = thrd.thread.handle; + printf("child thread TID: %I64d\n", tcb->thread_id); printf("child thread: ok!\n"); for(int i = 0; i < 100000; ++i) { cia_mutex_lock(&g_mutex); counter += 1; cia_mutex_unlock(&g_mutex); } + for(;;); printf("child thread: counter = %I64d\n", counter); return 0; } int main() { + _LD_Thread_Block *tcb = (void *)((u64)__builtin_frame_address(0) & ~(2*MB - 1)); + printf("main thread ID: %I64x\n", tcb->thread_id); printf("main thread: before\n"); cia_mutex_init(&g_mutex); - thrd_t thrd; int status = thrd_create(&thrd, thrd_func, NULL); if(status == thrd_error) { printf("main thread: error creating child thread\n"); @@ -35,7 +43,9 @@ int main() { cia_mutex_unlock(&g_mutex); } int exit_code; - thrd_join(thrd, &exit_code); + thrd_detach(thrd); printf("main thread: counter = %I64d\n", counter); + thrd_terminate(thrd); + printf("terminated child thread\n", counter); return 0; }