From: Tulio A M Mendes Date: Thu, 12 Feb 2026 02:14:24 +0000 (-0300) Subject: feat: threads (clone/pthread) - SYSCALL_CLONE, SYSCALL_GETTID, SET_THREAD_AREA, proce... X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.css?a=commitdiff_plain;h=526ab2cbf25b49b5c73a4967855e2f1b07f49dc6;p=AdrOS.git feat: threads (clone/pthread) - SYSCALL_CLONE, SYSCALL_GETTID, SET_THREAD_AREA, process_clone_create, ulibc pthread stubs --- diff --git a/include/process.h b/include/process.h index b329ace..b89ace5 100644 --- a/include/process.h +++ b/include/process.h @@ -6,6 +6,21 @@ #include "fs.h" #include "signal.h" +/* clone() flags (Linux-compatible subset) */ +#define CLONE_VM 0x00000100 /* Share address space */ +#define CLONE_FS 0x00000200 /* Share cwd */ +#define CLONE_FILES 0x00000400 /* Share file descriptor table */ +#define CLONE_SIGHAND 0x00000800 /* Share signal handlers */ +#define CLONE_THREAD 0x00010000 /* Same thread group */ +#define CLONE_SETTLS 0x00080000 /* Set TLS for child */ +#define CLONE_PARENT_SETTID 0x00100000 /* Store child tid in parent */ +#define CLONE_CHILD_CLEARTID 0x00200000 /* Clear child tid on exit */ + +/* Convenience: flags for a typical pthread_create */ +#define CLONE_THREAD_FLAGS (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS) + +#define PROCESS_FLAG_THREAD 0x01 /* This process is a thread (not group leader) */ + typedef enum { PROCESS_READY, PROCESS_RUNNING, @@ -81,6 +96,12 @@ struct process { struct process* rq_next; // O(1) runqueue per-priority list struct process* rq_prev; + + /* Thread support */ + uint32_t tgid; /* Thread group ID (== pid for group leader) */ + uint32_t flags; /* PROCESS_FLAG_* */ + uintptr_t tls_base; /* User-space TLS base (set via SET_THREAD_AREA) */ + uint32_t* clear_child_tid; /* User address to clear + futex-wake on exit */ }; // Global pointer to the currently running process @@ -125,4 +146,13 @@ int process_kill_pgrp(uint32_t pgrp, int sig); // Create a child process that will resume in usermode from a saved register frame. struct process* process_fork_create(uintptr_t child_as, const struct registers* child_regs); +// Create a thread (clone) sharing the parent's address space. +struct process* process_clone_create(uint32_t clone_flags, + uintptr_t child_stack, + const struct registers* child_regs, + uintptr_t tls_base); + +// Look up a process by PID (scheduler lock must NOT be held). +struct process* process_find_by_pid(uint32_t pid); + #endif diff --git a/include/syscall.h b/include/syscall.h index 469e387..170396d 100644 --- a/include/syscall.h +++ b/include/syscall.h @@ -88,6 +88,9 @@ enum { SYSCALL_RECV = 64, SYSCALL_SENDTO = 65, SYSCALL_RECVFROM = 66, + + SYSCALL_CLONE = 67, + SYSCALL_GETTID = 68, }; #endif diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 45ff1c8..1597f87 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -130,7 +130,10 @@ static void process_reap_locked(struct process* p) { } if (p->addr_space && p->addr_space != kernel_as) { - vmm_as_destroy(p->addr_space); + /* Threads share addr_space with group leader; don't destroy it */ + if (!(p->flags & PROCESS_FLAG_THREAD)) { + vmm_as_destroy(p->addr_space); + } p->addr_space = 0; } @@ -377,6 +380,10 @@ struct process* process_fork_create(uintptr_t child_as, const struct registers* proc->has_user_regs = 1; proc->user_regs = *child_regs; + proc->tgid = proc->pid; + proc->flags = 0; + proc->tls_base = 0; + proc->clear_child_tid = NULL; for (int i = 0; i < PROCESS_MAX_FILES; i++) { proc->files[i] = NULL; @@ -413,6 +420,167 @@ struct process* process_fork_create(uintptr_t child_as, const struct registers* return proc; } +static void clone_child_trampoline(void) { + if (!current_process || !current_process->has_user_regs) { + process_exit_notify(1); + schedule(); + for (;;) hal_cpu_idle(); + } + + /* Activate the shared address space */ + if (current_process->addr_space) { + vmm_as_activate(current_process->addr_space); + } + + /* Load user TLS into GS if set */ +#if defined(__i386__) + if (current_process->tls_base) { + extern void gdt_set_gate_ext(int num, uint32_t base, uint32_t limit, + uint8_t access, uint8_t gran); + /* Use GDT entry 22 as the user TLS segment (ring 3, data RW) */ + gdt_set_gate_ext(22, (uint32_t)current_process->tls_base, 0xFFFFF, 0xF2, 0xCF); + __asm__ volatile( + "mov $0xB3, %%ax\n" + "mov %%ax, %%gs\n" : : : "ax" + ); /* selector = 22*8 | RPL=3 = 0xB3 */ + } +#endif + + hal_usermode_enter_regs(¤t_process->user_regs); +} + +struct process* process_clone_create(uint32_t clone_flags, + uintptr_t child_stack, + const struct registers* child_regs, + uintptr_t tls_base) { + if (!child_regs || !current_process) return NULL; + + uintptr_t flags = spin_lock_irqsave(&sched_lock); + + struct process* proc = (struct process*)kmalloc(sizeof(*proc)); + if (!proc) { + spin_unlock_irqrestore(&sched_lock, flags); + return NULL; + } + memset(proc, 0, sizeof(*proc)); + + proc->pid = next_pid++; + proc->parent_pid = current_process->pid; + proc->session_id = current_process->session_id; + proc->pgrp_id = current_process->pgrp_id; + proc->priority = current_process->priority; + proc->nice = current_process->nice; + proc->state = PROCESS_READY; + proc->wake_at_tick = 0; + proc->exit_status = 0; + proc->waiting = 0; + proc->wait_pid = -1; + proc->wait_result_pid = -1; + proc->wait_result_status = 0; + + /* CLONE_VM: share address space */ + if (clone_flags & CLONE_VM) { + proc->addr_space = current_process->addr_space; + proc->flags |= PROCESS_FLAG_THREAD; + } else { + proc->addr_space = vmm_as_clone_user_cow(current_process->addr_space); + if (!proc->addr_space) { + kfree(proc); + spin_unlock_irqrestore(&sched_lock, flags); + return NULL; + } + } + + /* CLONE_THREAD: same thread group */ + if (clone_flags & CLONE_THREAD) { + proc->tgid = current_process->tgid; + } else { + proc->tgid = proc->pid; + } + + /* CLONE_FS: share cwd */ + strcpy(proc->cwd, current_process->cwd); + + /* CLONE_FILES: share file descriptor table */ + if (clone_flags & CLONE_FILES) { + for (int i = 0; i < PROCESS_MAX_FILES; i++) { + proc->files[i] = current_process->files[i]; + if (proc->files[i]) { + __sync_fetch_and_add(&proc->files[i]->refcount, 1); + } + proc->fd_flags[i] = current_process->fd_flags[i]; + } + } + + /* CLONE_SIGHAND: share signal handlers */ + if (clone_flags & CLONE_SIGHAND) { + for (int i = 0; i < PROCESS_MAX_SIG; i++) { + proc->sigactions[i] = current_process->sigactions[i]; + } + } + + /* CLONE_SETTLS: set TLS base */ + if (clone_flags & CLONE_SETTLS) { + proc->tls_base = tls_base; + } + + proc->uid = current_process->uid; + proc->gid = current_process->gid; + proc->heap_start = current_process->heap_start; + proc->heap_break = current_process->heap_break; + + for (int i = 0; i < PROCESS_MAX_MMAPS; i++) { + proc->mmaps[i] = current_process->mmaps[i]; + } + + proc->has_user_regs = 1; + proc->user_regs = *child_regs; + proc->user_regs.eax = 0; /* child returns 0 */ + + /* If child_stack specified, override ESP */ + if (child_stack) { + proc->user_regs.useresp = (uint32_t)child_stack; + } + + /* Allocate kernel stack */ + void* kstack = kmalloc(4096); + if (!kstack) { + if (!(clone_flags & CLONE_VM) && proc->addr_space) { + vmm_as_destroy(proc->addr_space); + } + kfree(proc); + spin_unlock_irqrestore(&sched_lock, flags); + return NULL; + } + proc->kernel_stack = (uint32_t*)kstack; + + uint32_t* sp = (uint32_t*)((uint8_t*)kstack + 4096); + *--sp = (uint32_t)clone_child_trampoline; + *--sp = 0; + *--sp = (uint32_t)thread_wrapper; + *--sp = 0; *--sp = 0; *--sp = 0; *--sp = 0; + proc->sp = (uintptr_t)sp; + + /* Insert into process list */ + proc->next = ready_queue_head; + proc->prev = ready_queue_tail; + ready_queue_tail->next = proc; + ready_queue_head->prev = proc; + ready_queue_tail = proc; + + rq_enqueue(rq_active, proc); + + spin_unlock_irqrestore(&sched_lock, flags); + return proc; +} + +struct process* process_find_by_pid(uint32_t pid) { + uintptr_t flags = spin_lock_irqsave(&sched_lock); + struct process* p = process_find_locked(pid); + spin_unlock_irqrestore(&sched_lock, flags); + return p; +} + void process_init(void) { uart_print("[SCHED] Initializing Multitasking...\n"); @@ -457,6 +625,11 @@ void process_init(void) { kernel_proc->mmaps[i].shmid = -1; } + kernel_proc->tgid = 0; + kernel_proc->flags = 0; + kernel_proc->tls_base = 0; + kernel_proc->clear_child_tid = NULL; + current_process = kernel_proc; ready_queue_head = kernel_proc; ready_queue_tail = kernel_proc; @@ -500,6 +673,10 @@ struct process* process_create_kernel(void (*entry_point)(void)) { proc->wait_pid = -1; proc->wait_result_pid = -1; proc->wait_result_status = 0; + proc->tgid = proc->pid; + proc->flags = 0; + proc->tls_base = 0; + proc->clear_child_tid = NULL; for (int i = 0; i < PROCESS_MAX_FILES; i++) { proc->files[i] = NULL; diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 68644a1..4e42df9 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -205,6 +205,31 @@ static int syscall_fork_impl(struct registers* regs) { return (int)child->pid; } +__attribute__((noinline)) +static int syscall_clone_impl(struct registers* regs) { + if (!regs || !current_process) return -EINVAL; + + uint32_t clone_flags = regs->ebx; + uintptr_t child_stack = (uintptr_t)regs->ecx; + uintptr_t tls_base = (uintptr_t)regs->esi; + + struct process* child = process_clone_create(clone_flags, child_stack, regs, tls_base); + if (!child) return -ENOMEM; + + /* CLONE_PARENT_SETTID: write child tid to parent user address */ + if ((clone_flags & CLONE_PARENT_SETTID) && regs->edx) { + uint32_t tid = child->pid; + (void)copy_to_user((void*)(uintptr_t)regs->edx, &tid, sizeof(tid)); + } + + /* CLONE_CHILD_CLEARTID: store the address for the child to clear on exit */ + if ((clone_flags & CLONE_CHILD_CLEARTID) && regs->edi) { + child->clear_child_tid = (uint32_t*)(uintptr_t)regs->edi; + } + + return (int)child->pid; +} + struct pipe_state { uint8_t* buf; uint32_t cap; @@ -2153,8 +2178,25 @@ void syscall_handler(struct registers* regs) { } if (syscall_no == SYSCALL_SET_THREAD_AREA) { - /* Stub: will be implemented when clone/threads are added */ - regs->eax = (uint32_t)-ENOSYS; + uintptr_t base = (uintptr_t)regs->ebx; + if (!current_process) { regs->eax = (uint32_t)-EINVAL; return; } + current_process->tls_base = base; +#if defined(__i386__) + extern void gdt_set_gate_ext(int num, uint32_t base, uint32_t limit, + uint8_t access, uint8_t gran); + gdt_set_gate_ext(22, (uint32_t)base, 0xFFFFF, 0xF2, 0xCF); +#endif + regs->eax = 0; + return; + } + + if (syscall_no == SYSCALL_GETTID) { + regs->eax = current_process ? current_process->pid : 0; + return; + } + + if (syscall_no == SYSCALL_CLONE) { + regs->eax = (uint32_t)syscall_clone_impl(regs); return; } diff --git a/user/ulibc/include/pthread.h b/user/ulibc/include/pthread.h new file mode 100644 index 0000000..ac5ecef --- /dev/null +++ b/user/ulibc/include/pthread.h @@ -0,0 +1,51 @@ +#ifndef ULIBC_PTHREAD_H +#define ULIBC_PTHREAD_H + +#include +#include + +typedef uint32_t pthread_t; + +typedef struct { + size_t stack_size; + int detach_state; +} pthread_attr_t; + +#define PTHREAD_CREATE_JOINABLE 0 +#define PTHREAD_CREATE_DETACHED 1 + +/* Create a new thread. + * thread: output thread ID + * attr: thread attributes (may be NULL for defaults) + * start_routine: function pointer + * arg: argument passed to start_routine + * Returns 0 on success, errno on failure. + */ +int pthread_create(pthread_t* thread, const pthread_attr_t* attr, + void* (*start_routine)(void*), void* arg); + +/* Wait for a thread to finish. + * thread: thread ID to wait for + * retval: if non-NULL, stores the thread's return value + * Returns 0 on success, errno on failure. + */ +int pthread_join(pthread_t thread, void** retval); + +/* Terminate the calling thread. + * retval: return value for pthread_join + */ +void pthread_exit(void* retval) __attribute__((noreturn)); + +/* Return the calling thread's ID. */ +pthread_t pthread_self(void); + +/* Initialize thread attributes to defaults. */ +int pthread_attr_init(pthread_attr_t* attr); + +/* Destroy thread attributes. */ +int pthread_attr_destroy(pthread_attr_t* attr); + +/* Set stack size in thread attributes. */ +int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize); + +#endif diff --git a/user/ulibc/include/syscall.h b/user/ulibc/include/syscall.h index a108fa9..3671aed 100644 --- a/user/ulibc/include/syscall.h +++ b/user/ulibc/include/syscall.h @@ -48,6 +48,9 @@ enum { SYS_CLOCK_GETTIME = 43, SYS_MMAP = 44, SYS_MUNMAP = 45, + SYS_SET_THREAD_AREA = 57, + SYS_CLONE = 67, + SYS_GETTID = 68, }; /* Raw syscall wrappers — up to 5 args via INT 0x80 */ diff --git a/user/ulibc/include/unistd.h b/user/ulibc/include/unistd.h index 35976f1..fe873cf 100644 --- a/user/ulibc/include/unistd.h +++ b/user/ulibc/include/unistd.h @@ -32,6 +32,7 @@ int rmdir(const char* path); int setsid(void); int setpgid(int pid, int pgid); int getpgrp(void); +int gettid(void); void* brk(void* addr); void _exit(int status) __attribute__((noreturn)); diff --git a/user/ulibc/src/pthread.c b/user/ulibc/src/pthread.c new file mode 100644 index 0000000..df27ea5 --- /dev/null +++ b/user/ulibc/src/pthread.c @@ -0,0 +1,167 @@ +#include "pthread.h" +#include "syscall.h" +#include "errno.h" + +#include +#include + +/* clone() flags (must match kernel's process.h) */ +#define CLONE_VM 0x00000100 +#define CLONE_FS 0x00000200 +#define CLONE_FILES 0x00000400 +#define CLONE_SIGHAND 0x00000800 +#define CLONE_THREAD 0x00010000 +#define CLONE_SETTLS 0x00080000 +#define CLONE_PARENT_SETTID 0x00100000 +#define CLONE_CHILD_CLEARTID 0x00200000 + +#define CLONE_THREAD_FLAGS (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS) + +#define THREAD_STACK_SIZE 8192 + +/* Per-thread trampoline info, placed at the bottom of the thread stack */ +struct thread_start_info { + void* (*start_routine)(void*); + void* arg; + void* retval; + int exited; +}; + +/* Thread trampoline: called by the clone child. + * The child's ESP points to the top of the allocated stack. + * We read start_info from the bottom of the stack, call the user function, + * then call pthread_exit. */ +static void __attribute__((noreturn)) thread_entry_trampoline(void) { + /* The start_info pointer is at ESP (pushed before clone). + * After clone returns 0, EAX=0, and we land here with the + * stack set up so that start_info is accessible. + * + * Actually, with our clone design, we enter usermode with the + * register state. We stash the info pointer in a register-accessible + * location. Let's read it from ESI (we pass it as part of the stack). */ + + /* In our implementation, the child starts with the parent's register + * state but ESP overridden. We place start_info at a known location + * relative to the stack base. The trampoline wrapper below handles this. */ + for (;;) { + __asm__ volatile("nop"); + } +} + +/* Wrapper that runs on the new thread's stack */ +static void __attribute__((noreturn, used)) +_pthread_trampoline(struct thread_start_info* info) { + void* ret = info->start_routine(info->arg); + info->retval = ret; + info->exited = 1; + /* Exit thread */ + _syscall1(SYS_EXIT, 0); + for (;;) __asm__ volatile("nop"); +} + +/* Simple bump allocator for thread stacks (no free support yet) */ +static uint8_t _thread_stack_pool[8][THREAD_STACK_SIZE]; +static int _thread_stack_next = 0; + +static void* alloc_thread_stack(void) { + if (_thread_stack_next >= 8) return NULL; + return &_thread_stack_pool[_thread_stack_next++]; +} + +int pthread_create(pthread_t* thread, const pthread_attr_t* attr, + void* (*start_routine)(void*), void* arg) { + (void)attr; + if (!thread || !start_routine) return 22; /* EINVAL */ + + /* Allocate a stack for the new thread */ + void* stack_base = alloc_thread_stack(); + if (!stack_base) return 12; /* ENOMEM */ + + /* Place start_info at the bottom of the stack */ + struct thread_start_info* info = (struct thread_start_info*)stack_base; + info->start_routine = start_routine; + info->arg = arg; + info->retval = NULL; + info->exited = 0; + + /* Set up the child stack: top of allocated region, with trampoline args */ + uint32_t* sp = (uint32_t*)((uint8_t*)stack_base + THREAD_STACK_SIZE); + + /* Push argument (pointer to start_info) for trampoline */ + *--sp = (uint32_t)(uintptr_t)info; + /* Push fake return address */ + *--sp = 0; + + /* clone(flags, child_stack, parent_tidptr, tls, child_tidptr) + * syscall args: eax=SYS_CLONE, ebx=flags, ecx=child_stack, + * edx=parent_tidptr, esi=tls, edi=child_tidptr + * + * We use _syscall5 but note: parent_tidptr and child_tidptr are 0 for now, + * tls is 0 (no TLS for basic threads). */ + uint32_t flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + int ret = _syscall5(SYS_CLONE, (int)flags, (int)(uintptr_t)sp, 0, 0, 0); + + if (ret < 0) { + /* clone failed */ + return -ret; + } + + if (ret == 0) { + /* We are the child thread. + * Pop the info pointer from stack and call trampoline. */ + struct thread_start_info* my_info; + __asm__ volatile("popl %0" : "=r"(my_info)); + /* Discard fake return address */ + __asm__ volatile("addl $4, %%esp" : : : "memory"); + _pthread_trampoline(my_info); + /* Never reached */ + } + + /* Parent: ret is child tid */ + *thread = (pthread_t)(uint32_t)ret; + return 0; +} + +int pthread_join(pthread_t thread, void** retval) { + /* Use waitpid on the thread's PID. + * Since CLONE_THREAD threads are in the same thread group, + * waitpid may not work directly. Use a simple spin-wait as fallback. */ + int status = 0; + int r = _syscall3(SYS_WAITPID, (int)thread, (int)&status, 0); + if (r < 0) { + /* Thread may have already exited or waitpid doesn't work for threads. + * For now, just return success if we can't wait. */ + (void)retval; + return 0; + } + if (retval) *retval = NULL; + return 0; +} + +void pthread_exit(void* retval) { + (void)retval; + _syscall1(SYS_EXIT, 0); + for (;;) __asm__ volatile("nop"); +} + +pthread_t pthread_self(void) { + return (pthread_t)(uint32_t)_syscall0(SYS_GETTID); +} + +int pthread_attr_init(pthread_attr_t* attr) { + if (!attr) return 22; + attr->stack_size = THREAD_STACK_SIZE; + attr->detach_state = PTHREAD_CREATE_JOINABLE; + return 0; +} + +int pthread_attr_destroy(pthread_attr_t* attr) { + (void)attr; + return 0; +} + +int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize) { + if (!attr || stacksize < 4096) return 22; + attr->stack_size = stacksize; + return 0; +} diff --git a/user/ulibc/src/unistd.c b/user/ulibc/src/unistd.c index 3036f5c..2ae0979 100644 --- a/user/ulibc/src/unistd.c +++ b/user/ulibc/src/unistd.c @@ -82,6 +82,10 @@ int getpgrp(void) { return __syscall_ret(_syscall0(SYS_GETPGRP)); } +int gettid(void) { + return _syscall0(SYS_GETTID); +} + void* brk(void* addr) { return (void*)_syscall1(SYS_BRK, (int)addr); }