From 149b1901b0f9aa101b580241834338fcd704295e Mon Sep 17 00:00:00 2001 From: Lemaitre Date: Mon, 27 Sep 2021 23:16:12 +0200 Subject: [PATCH] Thread creation --- Makefile | 3 ++ examples/examples.mk | 1 + examples/thread.c | 28 ++++++++++++++ libc/libc.mk | 1 + libc/linux/clone.h | 17 +++++++++ libc/thread/thread.c | 79 +++++++++++++++++++++++++++++++++++++++ libc/thread/thread.h | 20 ++++++++++ libc/thread/thread.mk | 51 +++++++++++++++++++++++++ libc/thread/threadspawn.S | 53 ++++++++++++++++++++++++++ 9 files changed, 253 insertions(+) create mode 100644 examples/thread.c create mode 100644 libc/linux/clone.h create mode 100644 libc/thread/thread.c create mode 100644 libc/thread/thread.h create mode 100644 libc/thread/thread.mk create mode 100644 libc/thread/threadspawn.S diff --git a/Makefile b/Makefile index 4978c9358..b3e21d163 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,7 @@ include third_party/gdtoa/gdtoa.mk # │ You can finally call malloc() include libc/time/time.mk # │ include libc/alg/alg.mk # │ include libc/stdio/stdio.mk # │ +include libc/thread/thread.mk # │ include net/net.mk # │ include libc/log/log.mk # │ include third_party/bzip2/bzip2.mk # │ @@ -284,6 +285,7 @@ COSMOPOLITAN_OBJECTS = \ LIBC_NT_ADVAPI32 \ LIBC_FMT \ THIRD_PARTY_COMPILER_RT \ + LIBC_THREAD \ LIBC_TINYMATH \ LIBC_STR \ LIBC_SYSV \ @@ -311,6 +313,7 @@ COSMOPOLITAN_HEADERS = \ LIBC_STDIO \ LIBC_STR \ LIBC_SYSV \ + LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_UNICODE \ diff --git a/examples/examples.mk b/examples/examples.mk index fccdf1b5e..210e31388 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -61,6 +61,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_TESTLIB \ + LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_UNICODE \ diff --git a/examples/thread.c b/examples/thread.c new file mode 100644 index 000000000..e7acd2308 --- /dev/null +++ b/examples/thread.c @@ -0,0 +1,28 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/stdio/stdio.h" +#include "libc/thread/thread.h" + +int worker(void* arg) { + (void)arg; + return 0; +} + +int main() { + thread_descriptor_t* thread = allocate_thread(); + + if (thread) { + long rc = start_thread(thread, &worker, NULL); + printf("thread created: %ld\n", rc); + } else { + printf("ERROR: thread stack could not be allocated\n"); + } + return 0; +} diff --git a/libc/libc.mk b/libc/libc.mk index 91a0d9be0..854099606 100644 --- a/libc/libc.mk +++ b/libc/libc.mk @@ -31,6 +31,7 @@ o/$(MODE)/libc: o/$(MODE)/libc/alg \ o/$(MODE)/libc/stubs \ o/$(MODE)/libc/sysv \ o/$(MODE)/libc/testlib \ + o/$(MODE)/libc/thread \ o/$(MODE)/libc/time \ o/$(MODE)/libc/tinymath \ o/$(MODE)/libc/unicode \ diff --git a/libc/linux/clone.h b/libc/linux/clone.h new file mode 100644 index 000000000..9e5a86222 --- /dev/null +++ b/libc/linux/clone.h @@ -0,0 +1,17 @@ +#ifndef COSMOPOLITAN_LIBC_LINUX_CLONE_H_ +#define COSMOPOLITAN_LIBC_LINUX_CLONE_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +forceinline long LinuxClone(unsigned long flags, void* stack, int* parent_tid, int* child_tid, void* tls) { + long rc; + register int* child_tid_ asm("r10") = child_tid; + register void* tls_ asm("r8") = tls; + asm volatile("syscall" + : "=a"(rc) + : "0"(56), "D"(flags), "S"(stack), "d"(parent_tid), "r"(child_tid_), "r"(tls_) + : "rcx", "r11", "memory"); + return rc; +} + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_LINUX_MMAP_H_ */ diff --git a/libc/thread/thread.c b/libc/thread/thread.c new file mode 100644 index 000000000..3a44b8e65 --- /dev/null +++ b/libc/thread/thread.c @@ -0,0 +1,79 @@ +/*-*- 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 new file mode 100644 index 000000000..2d033201d --- /dev/null +++ b/libc/thread/thread.h @@ -0,0 +1,20 @@ +#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 new file mode 100644 index 000000000..9e9d5c9f4 --- /dev/null +++ b/libc/thread/thread.mk @@ -0,0 +1,51 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += LIBC_THREAD + +LIBC_THREAD_ARTIFACTS += LIBC_THREAD_A +LIBC_THREAD = $(LIBC_THREAD_A_DEPS) $(LIBC_THREAD_A) +LIBC_THREAD_A = o/$(MODE)/libc/thread/thread.a +LIBC_THREAD_A_FILES := $(wildcard libc/thread/*) +LIBC_THREAD_A_HDRS = $(filter %.h,$(LIBC_THREAD_A_FILES)) +LIBC_THREAD_A_SRCS_S = $(filter %.S,$(LIBC_THREAD_A_FILES)) +LIBC_THREAD_A_SRCS_C = $(filter %.c,$(LIBC_THREAD_A_FILES)) + +LIBC_THREAD_A_SRCS = \ + $(LIBC_THREAD_A_SRCS_S) \ + $(LIBC_THREAD_A_SRCS_C) + +LIBC_THREAD_A_OBJS = \ + $(LIBC_THREAD_A_SRCS_S:%.S=o/$(MODE)/%.o) \ + $(LIBC_THREAD_A_SRCS_C:%.c=o/$(MODE)/%.o) + +LIBC_THREAD_A_CHECKS = \ + $(LIBC_THREAD_A).pkg \ + $(LIBC_THREAD_A_HDRS:%=o/$(MODE)/%.ok) + +LIBC_THREAD_A_DIRECTDEPS = \ + LIBC_STUBS \ + LIBC_INTRIN \ + LIBC_BITS \ + LIBC_NEXGEN32E + +LIBC_THREAD_A_DEPS := \ + $(call uniq,$(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)))) + +$(LIBC_THREAD_A): libc/thread/ \ + $(LIBC_THREAD_A).pkg \ + $(LIBC_THREAD_A_OBJS) + +$(LIBC_THREAD_A).pkg: \ + $(LIBC_THREAD_A_OBJS) \ + $(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)_A).pkg) + +LIBC_THREAD_LIBS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x))) +LIBC_THREAD_SRCS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_SRCS)) +LIBC_THREAD_HDRS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_HDRS)) +LIBC_THREAD_CHECKS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_CHECKS)) +LIBC_THREAD_OBJS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_OBJS)) +$(LIBC_THREAD_OBJS): $(BUILD_FILES) libc/thread/thread.mk + +.PHONY: o/$(MODE)/libc/thread +o/$(MODE)/libc/thread: $(LIBC_THREAD_CHECKS) diff --git a/libc/thread/threadspawn.S b/libc/thread/threadspawn.S new file mode 100644 index 000000000..ffb359866 --- /dev/null +++ b/libc/thread/threadspawn.S @@ -0,0 +1,53 @@ +/*-*- 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│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ 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/macros.internal.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 -> +------------+ +// + +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