/* * This replaces __pthread_initialize_minimal() from libnacl and * __pthread_initialize() from libpthread. */ void __pthread_initialize(void) { struct nc_combined_tdb *tdb; /* * Allocate the area. If malloc fails here, we'll crash before it returns. */ size_t combined_size = __nacl_tls_combined_size(sizeof(*tdb)); void *combined_area = malloc(combined_size); /* * Initialize TLS proper (i.e., __thread variable initializers). */ void *tp = __nacl_tls_initialize_memory(combined_area, sizeof(*tdb)); tdb = get_irt_tdb(tp); __nc_initialize_unjoinable_thread(tdb); tdb->tdb.irt_thread_data = combined_area; /* * Now install it for later fetching. * This is what our private version of __nacl_read_tp will read. */ NACL_SYSCALL(second_tls_set)(tp); /* * Finally, do newlib per-thread initialization. */ __newlib_thread_init(); __nc_initialize_globals(); }
static int nacl_irt_thread_create(void (*start_func)(void), void *stack, void *thread_ptr) { struct nc_combined_tdb *tdb; /* * Before we start the thread, allocate the IRT-private TLS area for it. */ size_t combined_size = __nacl_tls_combined_size(sizeof(*tdb)); void *combined_area = malloc(combined_size); if (combined_area == NULL) return EAGAIN; /* * Note that __nacl_tls_initialize_memory() is not reversible, * because it takes a pointer that need not be aligned and can * return a pointer that is aligned. In order to * free(combined_area) later, we must save the value of * combined_area. */ void *irt_tp = __nacl_tls_initialize_memory(combined_area, sizeof(*tdb)); tdb = get_irt_tdb(irt_tp); __nc_initialize_unjoinable_thread(tdb); tdb->tdb.irt_thread_data = combined_area; /* * We overload the libpthread start_func field to store a function * of a different type. */ tdb->tdb.start_func = (void *(*)(void *)) start_func; int error = -NACL_SYSCALL(thread_create)( (void *) (uintptr_t) &irt_start_thread, stack, thread_ptr, irt_tp); if (error != 0) free(combined_area); return error; }
void *__nacl_tls_initialize_memory(void *combined_area, size_t tdb_size) { const struct tls_info *info = get_tls_info(); size_t tls_size = info->tdata_size + info->tbss_size; char *combined_area_end = (char *) combined_area + __nacl_tls_combined_size(tdb_size); void *tp = tp_from_combined_area(info, combined_area, tdb_size); char *start = tp; if (__nacl_tp_tls_offset(0) > 0) { /* * From $tp, we skip the header size and then must round up from * there to the required alignment (which is what the linker will * will do when calculating TPOFF relocations at link time). The * end result is that the offset from $tp matches the one chosen * by the linker exactly and that the final address is aligned to * info->tls_alignment (since $tp was already aligned to at least * that much). */ start += aligned_size(__nacl_tp_tls_offset(tls_size), info->tls_alignment); } else { /* * We'll subtract the aligned size of the TLS block from $tp, which * must itself already be adequately aligned. */ start += __nacl_tp_tls_offset(aligned_size(tls_size, info->tls_alignment)); } /* Sanity check. (But avoid pulling in assert() here.) */ if (start + info->tdata_size + info->tbss_size > combined_area_end) simple_abort(); memcpy(start, info->tdata_start, info->tdata_size); memset(start + info->tdata_size, 0, info->tbss_size); if (__nacl_tp_tdb_offset(tdb_size) == 0) { /* * On x86 (but not on ARM), the TDB sits directly at $tp and the * first word there must hold the $tp pointer itself. */ void *tdb = (char *) tp + __nacl_tp_tdb_offset(tdb_size); *(void **) tdb = tdb; } return tp; }
/* See tls.h. */ int __pthread_initialize_minimal(size_t tdb_size) { /* Use sbrk not malloc here since the library is not initialized yet. */ size_t combined_size = __nacl_tls_combined_size(tdb_size); /* Adapt size for sbrk. */ /* TODO(robertm): this is somewhat arbitrary - re-examine). */ combined_size = (combined_size + 15) & ~15; void *combined_area = sbrk(combined_size); /* (uses nacl_sysbrk) */ __nacl_tls_data_bss_initialize_from_template(combined_area); /* Patch the first word of the TDB to contain the "base" of the TLS area. */ void *tdb = __nacl_tls_tdb_start(combined_area); *(void**) tdb = tdb; /* Set %gs, r9, or equivalent platform-specific mechanism. Requires * a syscall since certain bitfields of these registers are trusted. */ NACL_SYSCALL(tls_init)(tdb, tdb_size); /* initialize newlib's thread-specific pointer. */ __newlib_thread_init(); return 0; }
int pthread_create(pthread_t *thread_id, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { int retval = EAGAIN; void *esp; /* Declare the variables outside of the while scope. */ nc_thread_memory_block_t *stack_node = NULL; char *thread_stack = NULL; nc_thread_descriptor_t *new_tdb = NULL; nc_basic_thread_data_t *new_basic_data = NULL; nc_thread_memory_block_t *tls_node = NULL; size_t stacksize = PTHREAD_STACK_DEFAULT; void *new_tp; /* TODO(gregoryd) - right now a single lock is used, try to optimize? */ pthread_mutex_lock(&__nc_thread_management_lock); do { /* Allocate the combined TLS + TDB block---see tls.h for explanation. */ tls_node = nc_allocate_memory_block_mu(TLS_AND_TDB_MEMORY, __nacl_tls_combined_size(TDB_SIZE)); if (NULL == tls_node) break; new_tp = __nacl_tls_initialize_memory(nc_memory_block_to_payload(tls_node), TDB_SIZE); new_tdb = (nc_thread_descriptor_t *) ((char *) new_tp + __nacl_tp_tdb_offset(TDB_SIZE)); /* * TODO(gregoryd): consider creating a pool of basic_data structs, * similar to stack and TLS+TDB (probably when adding the support for * variable stack size). */ new_basic_data = malloc(sizeof(*new_basic_data)); if (NULL == new_basic_data) { /* * The tdb should be zero intialized. * This just re-emphasizes this requirement. */ new_tdb->basic_data = NULL; break; } nc_tdb_init(new_tdb, new_basic_data); new_tdb->tls_node = tls_node; /* * All the required members of the tdb must be initialized before * the thread is started and actually before the global lock is released, * since another thread can call pthread_join() or pthread_detach(). */ new_tdb->start_func = start_routine; new_tdb->state = arg; if (attr != NULL) { new_tdb->joinable = attr->joinable; stacksize = attr->stacksize; } /* Allocate the stack for the thread. */ stack_node = nc_allocate_memory_block_mu(THREAD_STACK_MEMORY, stacksize); if (NULL == stack_node) { retval = EAGAIN; break; } thread_stack = align((uint32_t) nc_memory_block_to_payload(stack_node), kStackAlignment); new_tdb->stack_node = stack_node; retval = 0; } while (0); if (0 != retval) { pthread_mutex_unlock(&__nc_thread_management_lock); goto ret; /* error */ } /* * Speculatively increase the thread count. If thread creation * fails, we will decrease it back. This way the thread count will * never be lower than the actual number of threads, but can briefly * be higher than that. */ ++__nc_running_threads_counter; /* * Save the new thread id. This can not be done after the syscall, * because the child thread could have already finished by that * time. If thread creation fails, it will be overriden with -1 * later. */ *thread_id = new_basic_data; pthread_mutex_unlock(&__nc_thread_management_lock); /* * Calculate the top-of-stack location. The very first location is a * zero address of architecture-dependent width, needed to satisfy the * normal ABI alignment requirements for the stack. (On some machines * this is the dummy return address of the thread-start function.) * * Both thread_stack and stacksize are multiples of 16. */ esp = (void *) (thread_stack + stacksize - kStackPadBelowAlign); memset(esp, 0, kStackPadBelowAlign); /* Start the thread. */ retval = irt_thread.thread_create( FUN_TO_VOID_PTR(nc_thread_starter), esp, new_tp); if (0 != retval) { pthread_mutex_lock(&__nc_thread_management_lock); /* TODO(gregoryd) : replace with atomic decrement? */ --__nc_running_threads_counter; pthread_mutex_unlock(&__nc_thread_management_lock); goto ret; } assert(0 == retval); ret: if (0 != retval) { /* Failed to create a thread. */ pthread_mutex_lock(&__nc_thread_management_lock); nc_release_tls_node(tls_node, new_tdb); if (new_basic_data) { nc_release_basic_data_mu(new_basic_data); } if (stack_node) { stack_node->is_used = 0; nc_free_memory_block_mu(THREAD_STACK_MEMORY, stack_node); } pthread_mutex_unlock(&__nc_thread_management_lock); *thread_id = NACL_PTHREAD_ILLEGAL_THREAD_ID; } return retval; }