diff options
author | Mikhail Burakov <mburakov@mailbox.org> | 2023-09-08 05:37:30 +0200 |
---|---|---|
committer | Mikhail Burakov <mburakov@mailbox.org> | 2023-09-08 06:12:14 +0200 |
commit | 815f6b237437dbd9c143774134838a32997bb2dc (patch) | |
tree | ab9b08061d8da7561be6e874e1ef96757cd9fef2 | |
parent | 3e001b02461af36d7068fe1bffdfda7273d2f3b9 (diff) |
Add userspace threads implementation to toolbox
-rw-r--r-- | gthread.c | 241 | ||||
-rw-r--r-- | gthread.h | 38 |
2 files changed, 279 insertions, 0 deletions
diff --git a/gthread.c b/gthread.c new file mode 100644 index 0000000..024273c --- /dev/null +++ b/gthread.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2023 Mikhail Burakov. This file is part of toolbox. + * + * toolbox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * toolbox 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with toolbox. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "gthread.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +struct GThread { + struct GThread* prev; + + struct { + uint64_t rbx; // 0x08 + uint64_t rbp; // 0x10 + uint64_t rsp; // 0x18 + uint64_t r12; // 0x20 + uint64_t r13; // 0x28 + uint64_t r14; // 0x30 + uint64_t r15; // 0x38 + } registers; + + struct { + uint64_t tip; + size_t alloc; + void* data; + } stack; +}; + +static _Thread_local struct GThread g_master_thread; +static _Thread_local struct GThread* __attribute__((used)) g_current_thread; +static _Thread_local size_t g_threads_count; + +static struct GThread* GThreadAlloc(size_t stack_alloc) { + struct GThread* thread = malloc(sizeof(struct GThread)); + if (!thread) { + return NULL; + } + thread->stack.data = malloc(stack_alloc); + thread->stack.alloc = stack_alloc; + if (!thread->stack.data) { + free(thread); + return NULL; + } + return thread; +} + +static void __attribute__((naked)) Trampoline(void) { + __asm__( + "pop %rdi\n" + "ret\n"); +} + +static struct GThread* __attribute__((used)) +GThreadCreateImpl(void (*proc)(void*), void* user, uint64_t stack_tip) { + void* stack_data[] = {NULL, (void*)Trampoline, user, (void*)proc, NULL}; + struct GThread* thread = GThreadAlloc(sizeof(stack_data)); + if (!thread) { + return NULL; + } + if (!g_threads_count++) { + g_current_thread = &g_master_thread; + } + if (stack_tip > g_master_thread.stack.tip) { + g_master_thread.stack.tip = stack_tip; + } + thread->stack.tip = stack_tip; + thread->registers.rsp = stack_tip - sizeof(stack_data); + memcpy(thread->stack.data, stack_data, sizeof(stack_data)); + return thread; +} + +static bool __attribute__((used, naked)) +GThreadSaveRegisters(struct GThread* thread) { + __asm__( + "pop %rax\n" + "mov %rbx, 0x08(%rdi)\n" + "mov %rbp, 0x10(%rdi)\n" + "mov %rsp, 0x18(%rdi)\n" + "mov %r12, 0x20(%rdi)\n" + "mov %r13, 0x28(%rdi)\n" + "mov %r14, 0x30(%rdi)\n" + "mov %r15, 0x38(%rdi)\n" + "push %rax\n" + "ret\n"); +} + +static bool __attribute__((used)) GThreadSaveStack(struct GThread* thread) { + if (thread->registers.rsp >= thread->stack.tip) { + // mburakov: Stack tip is down the stack, no need to save anything. + return true; + } + size_t stack_size = thread->stack.tip - thread->registers.rsp; + if (thread->stack.alloc < stack_size) { + free(thread->stack.data); + thread->stack.data = malloc(stack_size); + thread->stack.alloc = stack_size; + if (!thread->stack.data) { + thread->stack.alloc = 0; + return false; + } + } + memcpy(thread->stack.data, (void*)thread->registers.rsp, stack_size); + return true; +} + +static void __attribute__((used, naked)) +GThreadRestoreRegisters(struct GThread* thread) { + __asm__( + "pop %rax\n" + "mov 0x08(%rdi), %rbx\n" + "mov 0x10(%rdi), %rbp\n" + "mov 0x18(%rdi), %rsp\n" + "mov 0x20(%rdi), %r12\n" + "mov 0x28(%rdi), %r13\n" + "mov 0x30(%rdi), %r14\n" + "mov 0x38(%rdi), %r15\n" + "push %rax\n" + "ret\n"); +} + +static void __attribute__((used)) GThreadRestoreStack(struct GThread* thread) { + if (thread->registers.rsp >= thread->stack.tip) { + // mburakov: Stack tip is down the stack, no need to restore anything. + return; + } + size_t stack_size = thread->stack.tip - thread->registers.rsp; + memcpy((void*)thread->registers.rsp, thread->stack.data, stack_size); +} + +static void GThreadFree(struct GThread* thread) { + free(thread->stack.data); + free(thread); +} + +struct GThread* __attribute__((naked)) +GThreadCreate(void (*proc)(void*), void* user) { + __asm__( + // mburakov: Save stack tip and pass it to the impl. Tip is measured from + // outside of spawn function (8 bytes offset). On threads switching, stack + // would be preserved starting from this point till stack pointer. + "lea 8(%rsp), %rdx\n" + "jmp GThreadCreateImpl\n"); +} + +bool __attribute__((naked)) GThreadWake(struct GThread* thread) { + __asm__( + // mburakov: Argument would be overwritten by upcoming function calls. + "push %rdi\n" + + // mburakov: Preserve registers of the current thread. + "mov %fs:g_current_thread@tpoff, %rdi\n" + "call GThreadSaveRegisters\n" + + // mburakov: Preserve stack of the current thread. + "call GThreadSaveStack\n" + "test %al, %al\n" + "je escape_wake\n" + + // mburakov: Recover registers of the new thread. + "mov (%rsp), %rdi\n" + "call GThreadRestoreRegisters\n" + + // mburakov: Update prev and current thread pointers. + "mov %fs:g_current_thread@tpoff, %rsi\n" + "mov %rdi, %fs:g_current_thread@tpoff\n" + "mov %rsi, (%rdi)\n" + + // mburakov: Recover stack of the new thread. + "call GThreadRestoreStack\n" + "mov $1, %eax\n" + + "escape_wake:\n" + "add $8, %rsp\n" + "ret\n"); +} + +bool __attribute__((naked)) GThreadYield(void) { + __asm__( + // mburakov: Yielding on master thread has no effect. + "mov %fs:g_current_thread@tpoff, %rdi\n" + "mov (%rdi), %rax\n" + "test %rax, %rax\n" + "jne proceed\n" + "ret\n" + "proceed:\n" + "push %rax\n" + + // mburakov: Preserve registers of the current thread. + "mov %fs:g_current_thread@tpoff, %rdi\n" + "call GThreadSaveRegisters\n" + + // mburakov: Preserve stack of the current thread. + "call GThreadSaveStack\n" + "test %al, %al\n" + "je escape_yield\n" + + // mburakov: Recover registers of the new thread. + "mov (%rsp), %rdi\n" + "call GThreadRestoreRegisters\n" + + // mburakov: Update current thread pointer. + "mov %rdi, %fs:g_current_thread@tpoff\n" + + // mburakov: Recover stack of the new thread. + "call GThreadRestoreStack\n" + "mov $1, %eax\n" + + "escape_yield:\n" + "add $8, %rsp\n" + "ret\n"); +} + +void GThreadDestroy(struct GThread* thread) { + if (thread == &g_master_thread || thread == g_current_thread) { + // mburakov: It is prohibited to cancel either master or current thread. + abort(); + } + GThreadFree(thread); + if (!--g_threads_count) { + // mburakov: No more non-master threads. Cleanup master thread stack data. + g_master_thread.stack.tip = 0; + free(g_master_thread.stack.data); + g_master_thread.stack.alloc = 0; + } +} diff --git a/gthread.h b/gthread.h new file mode 100644 index 0000000..3cacd0b --- /dev/null +++ b/gthread.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Mikhail Burakov. This file is part of toolbox. + * + * toolbox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * toolbox 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with toolbox. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef TOOLBOX_GTHREAD_H_ +#define TOOLBOX_GTHREAD_H_ + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct GThread; + +struct GThread* GThreadCreate(void (*proc)(void*), void* user); +bool GThreadWake(struct GThread* thread); +bool GThreadYield(void); +void GThreadDestroy(struct GThread* thread); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // TOOLBOX_GTHREAD_H_ |