From fb2dd3ff8908928cc0c91989033e03da68934eb3 Mon Sep 17 00:00:00 2001 From: Lemaitre Date: Wed, 29 Sep 2021 01:39:56 +0200 Subject: [PATCH] Proper thread creation and exit --- examples/thread.c | 15 ++-- libc/thread/attr.c | 67 ++++++++++++++++ libc/thread/attr.h | 36 +++++++++ libc/thread/create.c | 108 ++++++++++++++++++++++++++ libc/thread/create.h | 18 +++++ libc/thread/descriptor.h | 22 ++++++ libc/thread/{threadspawn.S => exit.c} | 63 +++++++-------- libc/thread/exit.h | 14 ++++ libc/thread/thread.c | 79 ------------------- libc/thread/thread.h | 20 ----- libc/thread/thread.mk | 4 + 11 files changed, 305 insertions(+), 141 deletions(-) create mode 100644 libc/thread/attr.c create mode 100644 libc/thread/attr.h create mode 100644 libc/thread/create.c create mode 100644 libc/thread/create.h create mode 100644 libc/thread/descriptor.h rename libc/thread/{threadspawn.S => exit.c} (63%) create mode 100644 libc/thread/exit.h delete mode 100644 libc/thread/thread.c delete mode 100644 libc/thread/thread.h diff --git a/examples/thread.c b/examples/thread.c index e7acd2308..4b4073922 100644 --- a/examples/thread.c +++ b/examples/thread.c @@ -8,7 +8,8 @@ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "libc/stdio/stdio.h" -#include "libc/thread/thread.h" +#include "libc/thread/create.h" +#include "libc/time/time.h" int worker(void* arg) { (void)arg; @@ -16,13 +17,13 @@ int worker(void* arg) { } int main() { - thread_descriptor_t* thread = allocate_thread(); - - if (thread) { - long rc = start_thread(thread, &worker, NULL); - printf("thread created: %ld\n", rc); + cthread_t thread; + int rc = cthread_create(&thread, NULL, &worker, NULL); + if (rc == 0) { + printf("thread created: %p\n", thread); + sleep(1000); } else { - printf("ERROR: thread stack could not be allocated\n"); + printf("ERROR: thread could not be started: %d\n", rc); } return 0; } diff --git a/libc/thread/attr.c b/libc/thread/attr.c new file mode 100644 index 000000000..506ee800a --- /dev/null +++ b/libc/thread/attr.c @@ -0,0 +1,67 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/attr.h" +#include "libc/errno.h" + +#define MIN_STACKSIZE (8*PAGESIZE) +#define MIN_GUARDSIZE PAGESIZE + +// CTOR/DTOR +int cthread_attr_init(cthread_attr_t* attr) { + attr->stacksize = 1024*PAGESIZE; // 4 MiB + attr->guardsize = 16*PAGESIZE; // 64 KiB + attr->mode = CTHREAD_CREATE_JOINABLE; + return 0; +} +int cthread_attr_destroy(cthread_attr_t* attr) { + (void)attr; + return 0; +} + +// stacksize +int cthread_attr_setstacksize(cthread_attr_t* attr, size_t size) { + if (size & (PAGESIZE-1)) return EINVAL; + if (size < MIN_STACKSIZE) return EINVAL; + attr->stacksize = size; + return 0; +} +size_t cthread_attr_getstacksize(const cthread_attr_t* attr) { + return attr->stacksize; +} + +// guardsize +int cthread_attr_setguardsize(cthread_attr_t* attr, size_t size) { + if (size & (PAGESIZE-1)) return EINVAL; + if (size < MIN_GUARDSIZE) return EINVAL; + attr->guardsize = size; + return 0; +} +size_t cthread_attr_getguardsize(const cthread_attr_t* attr) { + return attr->guardsize; +} + +// detachstate +int cthread_attr_setdetachstate(cthread_attr_t* attr, int mode) { + if (mode & ~(CTHREAD_CREATE_JOINABLE | CTHREAD_CREATE_DETACHED)) return EINVAL; + attr->mode = mode; + return 0; +} +int cthread_attr_getdetachstate(const cthread_attr_t* attr) { + return attr->mode; +} diff --git a/libc/thread/attr.h b/libc/thread/attr.h new file mode 100644 index 000000000..cc682d530 --- /dev/null +++ b/libc/thread/attr.h @@ -0,0 +1,36 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_ATTR_H_ +#define COSMOPOLITAN_LIBC_THREAD_ATTR_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview cosmopolitan thread attributes + */ + +#define CTHREAD_CREATE_DETACHED 1 +#define CTHREAD_CREATE_JOINABLE 0 + +typedef struct cthread_attr_t { + size_t stacksize, guardsize; + int mode; +} cthread_attr_t; + +// CTOR/DTOR +int cthread_attr_init(cthread_attr_t*); +int cthread_attr_destroy(cthread_attr_t*); + +// stacksize +int cthread_attr_setstacksize(cthread_attr_t*, size_t); +size_t thread_attr_getstacksize(const cthread_attr_t*); + +// guardsize +int cthread_attr_setguardsize(cthread_attr_t*, size_t); +size_t cthread_attr_getguardsize(const cthread_attr_t*); + +// detachstate +int cthread_attr_setdetachstate(cthread_attr_t*, int); +int cthread_attr_getdetachstate(const cthread_attr_t*); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_ATTR_H_ */ diff --git a/libc/thread/create.c b/libc/thread/create.c new file mode 100644 index 000000000..3f016defc --- /dev/null +++ b/libc/thread/create.c @@ -0,0 +1,108 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/create.h" +#include "libc/linux/clone.h" +#include "libc/runtime/runtime.h" +#include "libc/linux/mmap.h" +#include "libc/sysv/consts/clone.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/errno.h" + + +static cthread_t _thread_allocate(const cthread_attr_t* attr) { + size_t stacksize = attr->stacksize; + size_t guardsize = 0;//attr->guardsize; + // FIXME: properly count TLS size + size_t tlssize = 0; + + size_t totalsize = 3*guardsize + stacksize + tlssize + sizeof(struct cthread_descriptor_t); + totalsize = (totalsize + PAGESIZE-1) & -PAGESIZE; + + uintptr_t mem = (uintptr_t)mmap(NULL, totalsize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (mem == -1) return NULL; + + void* alloc_bottom = (void*) mem; + void* stack_bottom = (void*)(mem + guardsize); + void* stack_top = (void*)(mem + guardsize + stacksize); + void* tls_bottom = (void*)(mem + guardsize + stacksize + guardsize); + void* tls_top = (void*)(mem + totalsize - guardsize); + void* alloc_top = (void*)(mem + totalsize); + + if (mprotect(stack_bottom, (uintptr_t)stack_top - (uintptr_t)stack_bottom, PROT_READ | PROT_WRITE) != 0 || + mprotect(tls_bottom, (uintptr_t)tls_top - (uintptr_t)tls_bottom, PROT_READ | PROT_WRITE) != 0) { + munmap(alloc_bottom, totalsize); + return NULL; + } + + cthread_t td = (cthread_t)tls_top - 1; + td->self = td; + td->stack.top = stack_top; + td->stack.bottom = stack_bottom; + td->tls.top = tls_top; + td->tls.bottom = tls_bottom; + td->alloc.top = alloc_top; + td->alloc.bottom = alloc_bottom; + td->tid = 0; + + return td; +} + +int cthread_create(cthread_t*restrict p, const cthread_attr_t*restrict attr, int (*func)(void*), void*restrict arg) { + extern wontreturn void _thread_run(int(*func)(void*), void* arg); + + cthread_attr_t default_attr; + if (!attr) cthread_attr_init(&default_attr); + cthread_t td = _thread_allocate(attr ? attr : &default_attr); + if (!attr) cthread_attr_destroy(&default_attr); + if (!td) return errno; + + *p = td; + + uintptr_t stack = (uintptr_t)(td->stack.top); + + stack -= sizeof(void*); *(void**)stack = func; + stack -= sizeof(void*); *(void**)stack = arg; + + long flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_PARENT | CLONE_THREAD | /*CLONE_IO |*/ CLONE_SETTLS; + + // It is not necessary to check the return of the syscall here as the return address for the thread is setup to to `_thread_run`. + // In case of success: the parent callee returns immediately to the caller forwarding the success to the callee + // the child return immediately to the entry point of `thread_spawn` + // In case of error: the parent callee returns immediately to the caller forwarding the error + // the child is never created (and so cannot returned in the wrong place) + int rc = LinuxClone(flags, (void*)stack, NULL, NULL, (void*)td); + if (!rc) { + // child + asm volatile( + "xor %rbp,%rbp\n\t" + /* pop arguments */ + "pop %rdi\n\t" + "pop %rax\n\t" + /* call function */ + "call *%rax\n\t" + /* thread exit */ + "mov %rax, %rdi\n\t" + "jmp cthread_exit" + ); + unreachable; + } + if (rc < 0) return rc; + return 0; +} diff --git a/libc/thread/create.h b/libc/thread/create.h new file mode 100644 index 000000000..35007ba92 --- /dev/null +++ b/libc/thread/create.h @@ -0,0 +1,18 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_CREATE_H_ +#define COSMOPOLITAN_LIBC_THREAD_CREATE_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview thread + */ + +#include "libc/thread/attr.h" +#include "libc/thread/descriptor.h" + +int cthread_create(cthread_t*restrict, const cthread_attr_t*restrict, int (*)(void*), void*restrict); + + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_CREATE_H_ */ diff --git a/libc/thread/descriptor.h b/libc/thread/descriptor.h new file mode 100644 index 000000000..7d2ee0661 --- /dev/null +++ b/libc/thread/descriptor.h @@ -0,0 +1,22 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ +#define COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview thread types + */ + +struct cthread_descriptor_t { + struct cthread_descriptor_t* self; // mandatory for TLS + struct { + void *top, *bottom; + } stack, tls, alloc; + int tid; +}; + +typedef struct cthread_descriptor_t* cthread_t; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ */ diff --git a/libc/thread/threadspawn.S b/libc/thread/exit.c similarity index 63% rename from libc/thread/threadspawn.S rename to libc/thread/exit.c index ffb359866..b7e7a598d 100644 --- a/libc/thread/threadspawn.S +++ b/libc/thread/exit.c @@ -1,5 +1,5 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ @@ -16,38 +16,31 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" +#include "libc/thread/exit.h" +#include "libc/thread/descriptor.h" +#include "libc/linux/munmap.h" -// wontreturn void thread_spawn(int(*func)(void*), void* arg); -// -// This function is the root function of a new thread. -// It calls `func` with `arg`. -// The return value of `func` is passed as argument to the `exit` syscall. -// -// All arguments are passed onto the (newly created) stack. -// The stack must have been set as followed: -// Top -// +------------+ -// | func | -// +------------+ -// | arg | -// %rsp -> +------------+ -// +static wontreturn void _self_exit(void* p, size_t s, int r) { + // asm is necessary as the stack does not exist between the unmap and the actual exit + asm volatile( + "mov $11, %%rax\n\t" + "syscall\n\t" + "mov %%rbx, %%rdi\n\t" + "mov $60, %%rax\n\t" + "syscall" + : + : "D"(p), "S"(s), "b"(r) + : "memory" + ); + unreachable; +} -thread_spawn: - .cfi_startproc - /* stop stack trace from going deeper than this function */ - .cfi_undefined rip - xor %rbp,%rbp - /* pop arguments */ - pop %rdi - pop %rax - /* call function */ - call *%rax - /* thread exit */ - mov %rax, %rdi - mov 60, %rax - syscall - .cfi_endproc - .endfn thread_spawn,globl - .source __FILE__ \ No newline at end of file +wontreturn void cthread_exit(int rc) { + // TODO: wait joiners + + cthread_t td; + asm("mov %%fs:0, %0" : "=r"(td)); + size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom); + + _self_exit(td->alloc.top, size, rc); +} diff --git a/libc/thread/exit.h b/libc/thread/exit.h new file mode 100644 index 000000000..ad4844ddd --- /dev/null +++ b/libc/thread/exit.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_EXIT_H_ +#define COSMOPOLITAN_LIBC_THREAD_EXIT_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview internal function called after a thread exits + */ + +wontreturn void cthread_exit(int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_EXIT_H_ */ diff --git a/libc/thread/thread.c b/libc/thread/thread.c deleted file mode 100644 index 3a44b8e65..000000000 --- a/libc/thread/thread.c +++ /dev/null @@ -1,79 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/thread/thread.h" -#include "libc/linux/mmap.h" -#include "libc/linux/clone.h" - -// sched.h -#define CLONE_VM 0x00000100 -#define CLONE_FS 0x00000200 -#define CLONE_FILES 0x00000400 -#define CLONE_SIGHAND 0x00000800 -#define CLONE_PARENT 0x00008000 -#define CLONE_THREAD 0x00010000 -#define CLONE_SETTLS 0x00080000 -#define CLONE_IO 0x80000000 - -// sys/mman.h -#define MAP_GROWSDOWN 0x0100 -#define MAP_ANONYMOUS 0x0020 -#define MAP_PRIVATE 0x0002 -#define PROT_READ 0x1 -#define PROT_WRITE 0x2 -#define PROT_EXEC 0x4 - - -thread_descriptor_t* allocate_thread() { - const long pagesize = 4096; - long stacksize = 1024*pagesize; - // FIXME: properly count TLS size - long tlssize = 0; - long totalsize = stacksize + tlssize + sizeof(thread_descriptor_t); - - // round-up totalsize to pagesize - totalsize = (totalsize + pagesize-1) & -pagesize; - - intptr_t mem = LinuxMmap(NULL, totalsize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (mem == -1) return NULL; - - thread_descriptor_t* descriptor = (thread_descriptor_t*)(mem + totalsize - sizeof(thread_descriptor_t)); - descriptor->self = descriptor; // setup TLS (System-V ABI) - descriptor->stack = (void*)(mem + stacksize); - - // FIXME: properly copy .tdata - return descriptor; -} -long start_thread(thread_descriptor_t* descriptor, int (*func)(void*), void* arg) { - extern wontreturn void thread_spawn(int(*func)(void*), void* arg); - - // Set-up thread stack - uintptr_t stack = (uintptr_t)(descriptor->stack) - 3*sizeof(void*); - *(void**)(stack + 2*sizeof(void*)) = func; - *(void**)(stack + 1*sizeof(void*)) = arg; - *(void**)(stack + 0*sizeof(void*)) = &thread_spawn; - - long flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_PARENT | CLONE_THREAD | CLONE_IO | CLONE_SETTLS; - - // It is not necessary to check the return of the syscall here as the return address for the thread is setup to to `thread_spawn`. - // In case of success: the parent callee returns immediately to the caller forwarding the success to the callee - // the child return immediately to the entry point of `thread_spawn` - // In case of error: the parent callee returns immediately to the caller forwarding the error - // the child is never created (and so cannot returned in the wrong place) - return LinuxClone(flags, (void*)stack, NULL, NULL, descriptor); -} \ No newline at end of file diff --git a/libc/thread/thread.h b/libc/thread/thread.h deleted file mode 100644 index 2d033201d..000000000 --- a/libc/thread/thread.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_THREAD_THREAD_H_ -#define COSMOPOLITAN_LIBC_THREAD_THREAD_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -/** - * @fileoverview thread - */ - -typedef struct thread_descriptor_t { - struct thread_descriptor_t* self; // mandatory for TLS - void* stack; -} thread_descriptor_t; - -thread_descriptor_t* allocate_thread(); -long start_thread(thread_descriptor_t*, int (*)(void*), void*); - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ -#endif /* COSMOPOLITAN_LIBC_THREAD_THREAD_H_ */ diff --git a/libc/thread/thread.mk b/libc/thread/thread.mk index 9e9d5c9f4..137c3e001 100644 --- a/libc/thread/thread.mk +++ b/libc/thread/thread.mk @@ -25,8 +25,12 @@ LIBC_THREAD_A_CHECKS = \ LIBC_THREAD_A_DIRECTDEPS = \ LIBC_STUBS \ + LIBC_CALLS \ LIBC_INTRIN \ LIBC_BITS \ + LIBC_RUNTIME \ + LIBC_SYSV \ + LIBC_SYSV_CALLS \ LIBC_NEXGEN32E LIBC_THREAD_A_DEPS := \