#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,
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
// 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
SYSCALL_RECV = 64,
SYSCALL_SENDTO = 65,
SYSCALL_RECVFROM = 66,
+
+ SYSCALL_CLONE = 67,
+ SYSCALL_GETTID = 68,
};
#endif
}
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;
}
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;
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");
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;
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;
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;
}
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;
}
--- /dev/null
+#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
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 */
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));
--- /dev/null
+#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;
+}
return __syscall_ret(_syscall0(SYS_GETPGRP));
}
+int gettid(void) {
+ return _syscall0(SYS_GETTID);
+}
+
void* brk(void* addr) {
return (void*)_syscall1(SYS_BRK, (int)addr);
}