#define _GNU_SOURCE #include "usr_lib_define.h" #include "pthread_impl.h" #include "libc.h" #include "lock.h" #include "string.h" #include "stdbool.h" _LIBC_TEXT_SECTION _Noreturn void __pthread_exit(void *result) { pthread_t self = pthread_self(); self->canceldisable = 1; self->cancelasync = 0; self->result = result; while (self->cancelbuf) { void (*f)(void *) = self->cancelbuf->__f; void *x = self->cancelbuf->__x; self->cancelbuf = self->cancelbuf->__next; f(x); } /* This atomic potentially competes with a concurrent pthread_detach * call; the loser is responsible for freeing thread resources. */ int state = a_cas(&self->detach_state, DT_JOINABLE, DT_EXITING); /* After the kernel thread exits, its tid may be reused. Clear it * to prevent inadvertent use and inform functions that would use * it that it's no longer available. */ if (self->detach_state == DT_DETACHED) { /* Detached threads must block even implementation-internal * signals, since they will not have a stack in their last * moments of existence. */ self->tid = 0; } UNLOCK(self->killlock); for (;;) __syscall(SYS_exit, 0); } struct start_args { void *(*start_func)(void *); void *start_arg; }; _LIBC_TEXT_SECTION static int start(void *p) { struct start_args *args = (struct start_args *)p; __pthread_exit(args->start_func(args->start_arg)); return 0; } _LIBC_TEXT_SECTION static int start_c11(void *p) { struct start_args *args = (struct start_args *)p; int (*start)(void*) = (int(*)(void*)) args->start_func; __pthread_exit((void *)(uintptr_t)start(args->start_arg)); return 0; } _LIBC_TEXT_SECTION static int __pthread_init_and_check_attr(const pthread_attr_t *restrict attrp, pthread_attr_t *attr) { int policy = 0; struct sched_param param = { 0 }; int c11 = (attrp == __ATTRP_C11_THREAD); int ret; if (attrp && !c11) memcpy(attr, attrp, sizeof(pthread_attr_t)); if (!attrp || c11) { pthread_attr_init(attr); } if (!attr->_a_sched) { ret = pthread_getschedparam(pthread_self(), &policy, ¶m); if (ret) return ret; attr->_a_policy = policy; attr->_a_prio = param.sched_priority; } if (attr->_a_policy != SCHED_RR && attr->_a_policy != SCHED_FIFO) { return EINVAL; } if (attr->_a_prio < 0 || attr->_a_prio > PTHREAD_PRIORITY_LOWEST) { return EINVAL; } return 0; } _LIBC_TEXT_SECTION static int __thread_clone(int (*func)(void *), int flags, struct pthread *thread, unsigned char *sp) { int ret; bool join_flag = false; unsigned long user_area, user_sp; if (thread->detach_state == DT_JOINABLE) { join_flag = true; } user_area = (unsigned long)TP_ADJ(thread); user_sp = (unsigned long)sp; ret = __syscall(SYS_create_user_thread, func, user_area, user_sp, join_flag); if (ret < 0) { return ret; } thread->tid = (unsigned long)ret; return 0; } _LIBC_TEXT_SECTION int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg) { int ret, c11 = (attrp == __ATTRP_C11_THREAD); size_t size; struct pthread *self, *new; unsigned char *stack = 0, *stack_limit, *map_base = 0; pthread_attr_t attr = { 0 }; if (!entry) return EINVAL; self = pthread_self(); ret = __pthread_init_and_check_attr(attrp, &attr); if (ret) { return ret; } size = attr._a_stacksize; if (attr._a_stackaddr) { stack = (void *)(attr._a_stackaddr & -16); stack_limit = (void *)(attr._a_stackaddr - size); } else { map_base = malloc(size); if (map_base == NULL) goto fail; stack_limit = map_base; stack = map_base + size; } new = (struct pthread *)(((uintptr_t)stack - sizeof(struct pthread)) & -16); new->map_base = stack_limit; new->map_size = size; new->stack = new; new->stack_size = (uintptr_t)new->stack - (uintptr_t)stack_limit; new->guard_size = 0; new->self = new; new->tsd = (void *)NULL; new->locale = NULL; if (attr._a_detach) { new->detach_state = DT_DETACHED; } else { new->detach_state = DT_JOINABLE; } new->robust_list.head = &new->robust_list.head; new->CANARY = self->CANARY; new->sysinfo = self->sysinfo; /* Setup argument structure for the new thread on its stack. * It's safe to access from the caller only until the thread * list is unlocked. */ stack -= (uintptr_t)new->stack % sizeof(uintptr_t); stack -= sizeof(struct start_args); struct start_args *args = (void *)stack; args->start_func = entry; args->start_arg = arg; ret = __thread_clone((c11 ? start_c11 : start), 0, new, stack); /* All clone failures translate to EAGAIN. If explicit scheduling * was requested, attempt it before unlocking the thread list so * that the failed thread is never exposed and so that we can * clean up all transient resource usage before returning. */ if (ret < 0) { ret = -EAGAIN; } else { *res = new; ret = __syscall(SYS_sched_setscheduler, new->tid, attr._a_policy, attr._a_prio); } if (ret < 0) { if (map_base) free(map_base); return -ret; } return 0; fail: return EAGAIN; } weak_alias(__pthread_exit, pthread_exit); weak_alias(__pthread_create, pthread_create);