From dc44b65d01be651a4a78eabfb45bfcb1c8f6aabe Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 31 Aug 2013 18:04:36 +0200 Subject: [PATCH] Add pthread_create(3). --- libpthread/Makefile | 1 + libpthread/include/pthread.h | 11 +- libpthread/pthread_create.c++ | 235 ++++++++++++++++++++++++++++++++++ libpthread/pthread_exit.c++ | 4 +- 4 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 libpthread/pthread_create.c++ diff --git a/libpthread/Makefile b/libpthread/Makefile index aaa209ac..a3d81e12 100644 --- a/libpthread/Makefile +++ b/libpthread/Makefile @@ -21,6 +21,7 @@ pthread_cond_init.o \ pthread_cond_signal.o \ pthread_cond_timedwait.o \ pthread_cond_wait.o \ +pthread_create.o \ pthread_equal.o \ pthread_exit.o \ pthread_getspecific.o \ diff --git a/libpthread/include/pthread.h b/libpthread/include/pthread.h index 56ba4e48..0c62461d 100644 --- a/libpthread/include/pthread.h +++ b/libpthread/include/pthread.h @@ -138,6 +138,8 @@ typedef __pthread_t pthread_t; struct pthread { struct uthread uthread; + void* (*entry_function)(void*); + void* entry_cookie; void** keys; size_t keys_length; }; @@ -175,6 +177,10 @@ extern pthread_mutex_t __pthread_keys_lock; extern struct pthread_key* __pthread_keys; extern size_t __pthread_keys_used; extern size_t __pthread_keys_length; +extern pthread_mutex_t __pthread_num_threads_lock; +extern size_t __pthread_num_threads; + +struct pthread* pthread_allocate_tls(void); #endif @@ -223,7 +229,10 @@ int pthread_condattr_getclock(const pthread_condattr_t* __restrict, int pthread_condattr_init(pthread_condattr_t*); int pthread_condattr_setclock(pthread_condattr_t*, clockid_t); /* TODO: pthread_condattr_setpshared */ -/* TODO: pthread_create */ +int pthread_create(pthread_t* __restrict, + const pthread_attr_t* __restrict, + void* (*)(void*), + void* __restrict); /* TODO: pthread_detach */ int pthread_equal(pthread_t, pthread_t); __attribute__((__noreturn__)) diff --git a/libpthread/pthread_create.c++ b/libpthread/pthread_create.c++ new file mode 100644 index 00000000..afb6d81e --- /dev/null +++ b/libpthread/pthread_create.c++ @@ -0,0 +1,235 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013, 2014. + + This file is part of Sortix libpthread. + + Sortix libpthread is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + Sortix libpthread is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with Sortix libpthread. If not, see . + + pthread_create.c++ + Creates a new thread. + +*******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Utility function that rounds size upwards to a multiple of alignment. +static size_t align_size(size_t size, size_t alignment) +{ + return -(-size & ~(alignment-1)); +} + +__attribute__((noreturn)) +static void pthread_entrance(struct pthread* thread) +{ + pthread_exit(thread->entry_function(thread->entry_cookie)); +} + +#if defined(__i386__) || defined(__x86_64__) +const unsigned long FLAGS_CARRY = 1 << 0; // 0x000001 +const unsigned long FLAGS_RESERVED1 = 1 << 1; // 0x000002, read as one +const unsigned long FLAGS_PARITY = 1 << 2; // 0x000004 +const unsigned long FLAGS_RESERVED2 = 1 << 3; // 0x000008 +const unsigned long FLAGS_AUX = 1 << 4; // 0x000010 +const unsigned long FLAGS_RESERVED3 = 1 << 5; // 0x000020 +const unsigned long FLAGS_ZERO = 1 << 6; // 0x000040 +const unsigned long FLAGS_SIGN = 1 << 7; // 0x000080 +const unsigned long FLAGS_TRAP = 1 << 8; // 0x000100 +const unsigned long FLAGS_INTERRUPT = 1 << 9; // 0x000200 +const unsigned long FLAGS_DIRECTION = 1 << 10; // 0x000400 +const unsigned long FLAGS_OVERFLOW = 1 << 11; // 0x000800 +const unsigned long FLAGS_IOPRIVLEVEL = 1 << 12 | 1 << 13; +const unsigned long FLAGS_NESTEDTASK = 1 << 14; // 0x004000 +const unsigned long FLAGS_RESERVED4 = 1 << 15; // 0x008000 +const unsigned long FLAGS_RESUME = 1 << 16; // 0x010000 +const unsigned long FLAGS_VIRTUAL8086 = 1 << 17; // 0x020000 +const unsigned long FLAGS_ALIGNCHECK = 1 << 18; // 0x040000 +const unsigned long FLAGS_VIRTINTR = 1 << 19; // 0x080000 +const unsigned long FLAGS_VIRTINTRPEND = 1 << 20; // 0x100000 +const unsigned long FLAGS_ID = 1 << 21; // 0x200000 +#endif + +#if defined(__i386__) +static const unsigned long MINIMUM_STACK_SIZE = 4 * sizeof(unsigned long); +static void setup_thread_state(struct pthread* thread, tforkregs_t* regs) +{ + assert(MINIMUM_STACK_SIZE <= thread->uthread.stack_size); + + memset(regs, 0, sizeof(*regs)); + regs->eip = (uintptr_t) pthread_entrance; + regs->eflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->gsbase = (unsigned long) thread; + + unsigned long* stack = + (unsigned long*) ((uint8_t*) thread->uthread.stack_mmap + + thread->uthread.stack_size); + + *--stack = 0; // rip=0 + *--stack = 0; // rbp=0 + + regs->ebp = (uintptr_t) stack; + + *--stack = (unsigned long) thread; + *--stack = 0; + + regs->esp = (uintptr_t) stack; +} + +#endif + +#if defined(__x86_64__) +static const unsigned long MINIMUM_STACK_SIZE = 2 * sizeof(unsigned long); +static void setup_thread_state(struct pthread* thread, tforkregs_t* regs) +{ + assert(MINIMUM_STACK_SIZE <= thread->uthread.stack_size); + + memset(regs, 0, sizeof(*regs)); + regs->rip = (uintptr_t) pthread_entrance; + regs->rdi = (uintptr_t) thread; + regs->rflags = FLAGS_RESERVED1 | FLAGS_INTERRUPT | FLAGS_ID; + regs->fsbase = (unsigned long) thread; + + unsigned long* stack = + (unsigned long*) ((uint8_t*) thread->uthread.stack_mmap + + thread->uthread.stack_size); + + *--stack = 0; // rip=0 + *--stack = 0; // rbp=0 + + regs->rsp = (uintptr_t) stack; + regs->rbp = regs->rsp; +} +#endif + +static const unsigned long DEFAULT_STACK_SIZE = 64 * 1024; + +extern "C" +{ + pthread_mutex_t __pthread_num_threads_lock = PTHREAD_MUTEX_INITIALIZER; + size_t __pthread_num_threads = 1; +} + +extern "C" +int pthread_create(pthread_t* restrict thread_ptr, + const pthread_attr_t* restrict /*attr*/, + void* (*entry_function)(void*), + void* restrict entry_cookie) +{ + assert(thread_ptr); + + struct pthread* self = pthread_self(); + + // We can't create a thread local storage copy (and thus a new thread) if + // the kernel failed to allocate the thread local storage master copy. + if ( self->uthread.tls_master_size && !self->uthread.tls_master_mmap ) + return errno = ENOMEM; + + size_t raw_tls_size = self->uthread.tls_master_size; + size_t raw_tls_size_aligned = align_size(raw_tls_size, self->uthread.tls_master_align); + if ( raw_tls_size && raw_tls_size_aligned == 0 /* overflow */ ) + return errno = EINVAL; + + size_t tls_size = raw_tls_size_aligned + sizeof(struct pthread); + size_t tls_offset_tls = 0; + size_t tls_offset_pthread = raw_tls_size_aligned; + if ( self->uthread.tls_master_align < alignof(struct pthread) ) + { + size_t more_aligned = align_size(raw_tls_size_aligned, alignof(struct pthread)); + if ( raw_tls_size_aligned && more_aligned == 0 /* overflow */ ) + return errno = EINVAL; + size_t difference = more_aligned - raw_tls_size_aligned; + tls_size += difference; + tls_offset_tls += difference; + tls_offset_pthread += difference; + } + assert((tls_offset_tls & (self->uthread.tls_master_align-1)) == 0); + assert((tls_offset_pthread & (alignof(struct pthread)-1)) == 0); + + // Allocate a copy of the base thread-local storage area for use by the new + // thread, followed by the struct pthread for the new thread. + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + uint8_t* mapping = (uint8_t*) mmap(NULL, tls_size, prot, flags, -1, 0); + if ( (void*) mapping == MAP_FAILED ) + return errno; + + // We'll put the struct pthread nicely aligned in the new mapping. + struct pthread* thread = + (struct pthread*) (mapping + tls_offset_pthread); + assert((((uintptr_t) thread) & (alignof(struct pthread)-1)) == 0); + + // Let's put the thread-local storage just before the struct pthread. + uint8_t* tls = mapping + tls_offset_tls; + + // We will now initialize the thread-local storage of the new thread. + memcpy(tls, self->uthread.tls_master_mmap, self->uthread.tls_master_size); + + // We'll clean up the new struct pthread before initializing it. + memset(thread, 0, sizeof(struct pthread)); + + // Initialize the thread object. + thread->uthread.uthread_pointer = &thread->uthread; + thread->uthread.uthread_size = sizeof(struct pthread); + thread->uthread.uthread_flags = 0; + thread->uthread.tls_master_mmap = self->uthread.tls_master_mmap; + thread->uthread.tls_master_size = self->uthread.tls_master_size; + thread->uthread.tls_master_align = self->uthread.tls_master_align; + thread->uthread.tls_mmap = mapping; + thread->uthread.tls_size = tls_size; + thread->uthread.arg_mmap = self->uthread.arg_mmap; + thread->uthread.arg_size = self->uthread.arg_size; + thread->entry_function = entry_function; + thread->entry_cookie = entry_cookie; + + // Set up a stack for the new thread. + int stack_prot = PROT_READ | PROT_WRITE; + int stack_flags = MAP_PRIVATE | MAP_ANONYMOUS; + thread->uthread.stack_size = DEFAULT_STACK_SIZE; + thread->uthread.stack_mmap = + mmap(NULL, thread->uthread.stack_size, stack_prot, stack_flags, -1, 0); + if ( thread->uthread.stack_mmap == MAP_FAILED ) + { + munmap(thread->uthread.tls_mmap, thread->uthread.tls_size); + return errno; + } + + // Prepare the registers and initial stack for the new thread. + tforkregs_t regs; + setup_thread_state(thread, ®s); + + // Create a new thread with the requested state. + pthread_mutex_lock(&__pthread_num_threads_lock); + if ( tfork(SFTHREAD, ®s) < 0 ) + { + pthread_mutex_unlock(&__pthread_num_threads_lock); + munmap(thread->uthread.stack_mmap, thread->uthread.stack_size); + munmap(thread->uthread.tls_mmap, thread->uthread.tls_size); + return errno; + } + __pthread_num_threads++; + pthread_mutex_unlock(&__pthread_num_threads_lock); + + *thread_ptr = thread; + + return 0; +} diff --git a/libpthread/pthread_exit.c++ b/libpthread/pthread_exit.c++ index bb30871b..edb2348b 100644 --- a/libpthread/pthread_exit.c++ +++ b/libpthread/pthread_exit.c++ @@ -56,7 +56,9 @@ void pthread_exit(void* /*return_value*/) thread->keys_length = 0; pthread_mutex_unlock(&__pthread_keys_lock); - size_t num_threads = 1; + pthread_mutex_lock(&__pthread_num_threads_lock); + size_t num_threads = __pthread_num_threads--; + pthread_mutex_unlock(&__pthread_num_threads_lock); if ( num_threads == 1 ) exit(0); struct exit_thread extended;