]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: threads (clone/pthread) - SYSCALL_CLONE, SYSCALL_GETTID, SET_THREAD_AREA, proce...
authorTulio A M Mendes <[email protected]>
Thu, 12 Feb 2026 02:14:24 +0000 (23:14 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
include/process.h
include/syscall.h
src/kernel/scheduler.c
src/kernel/syscall.c
user/ulibc/include/pthread.h [new file with mode: 0644]
user/ulibc/include/syscall.h
user/ulibc/include/unistd.h
user/ulibc/src/pthread.c [new file with mode: 0644]
user/ulibc/src/unistd.c

index b329aceb45fbeca0d5dcf588f21bf789884931b5..b89ace5cc494b1c3e7d345583d6074e220c876a3 100644 (file)
@@ -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
index 469e387d8f1f2d3e1d521ad9e8fd387ff032f6ac..170396d2165b9bc36466ada79153577c5fd9353a 100644 (file)
@@ -88,6 +88,9 @@ enum {
     SYSCALL_RECV      = 64,
     SYSCALL_SENDTO    = 65,
     SYSCALL_RECVFROM  = 66,
+
+    SYSCALL_CLONE     = 67,
+    SYSCALL_GETTID    = 68,
 };
 
 #endif
index 45ff1c818123f874a41d9304a940b014f91218b0..1597f87f747f2a90ae971d49173ad9c28750bc96 100644 (file)
@@ -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(&current_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;
index 68644a133cb04278455e50857869c2ed8af7b17d..4e42df992b5f43684502010bf131ac3c3980f50f 100644 (file)
@@ -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 (file)
index 0000000..ac5ecef
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef ULIBC_PTHREAD_H
+#define ULIBC_PTHREAD_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+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
index a108fa9e4ea06e6a9e312683636552f18893db1a..3671aed43d498f88e763c319bef46db7e8820a7d 100644 (file)
@@ -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 */
index 35976f18cd566a35ba562e594a34df03543feee5..fe873cf142dee12337f65e766e0a208b7610da2a 100644 (file)
@@ -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 (file)
index 0000000..df27ea5
--- /dev/null
@@ -0,0 +1,167 @@
+#include "pthread.h"
+#include "syscall.h"
+#include "errno.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* 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;
+}
index 3036f5c15265a52250929b667fb9d79628eb3288..2ae09791eb198ad1e3c9e7d25ac98d630b55a83f 100644 (file)
@@ -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);
 }