From e90b72ec3082588b296956730bd2930cf8d5d671 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 10 Jul 2026 11:31:46 +0800 Subject: refactor: promote new architecture to repository root Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior. --- na/kernel/task.cc | 780 ------------------------------------------------------ 1 file changed, 780 deletions(-) delete mode 100644 na/kernel/task.cc (limited to 'na/kernel/task.cc') diff --git a/na/kernel/task.cc b/na/kernel/task.cc deleted file mode 100644 index 9f9b043..0000000 --- a/na/kernel/task.cc +++ /dev/null @@ -1,780 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Task Management — create, exit, destroy, kthread - * - * Implements task lifecycle management: kernel thread creation, - * task exit (zombie transition), and resource reclamation. - * - * Reference: docs/specs/task.md §4-§6 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ====================================================================== - * Forward declarations - * ====================================================================== */ - -extern "C" void kthread_entry_trampoline(); -extern "C" void ret_from_fork(); - -/* ====================================================================== - * Global task list - * ====================================================================== */ - -list_node g_all_tasks; -Spinlock g_task_list_lock; -int32 g_nr_tasks = 0; - -/* Special tasks */ -TaskControlBlock* g_kernel_task = nullptr; /* PID 0 container */ -TaskControlBlock* g_init_task = nullptr; /* PID 1 (future) */ - -/* ====================================================================== - * task_init() — called once from kernel_main before sched_init - * ====================================================================== */ - -void task_init() -{ - g_all_tasks.init(); - g_task_list_lock.init("task_list"); - g_pid_alloc.init(); - - printk(KERN_INFO "init: task_init done (pid_alloc ready)\n"); -} - -/* ====================================================================== - * alloc_task — allocate TCB + kernel stack - * - * This is the SINGLE canonical TCB initializer. Both kthread_create - * (task.cc) and sched_init (sched_core.cc) must use this function. - * NEVER duplicate this logic — adding fields to TaskControlBlock - * requires updating only this function. - * ====================================================================== */ - -TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags, uint8 cpu) -{ - auto* task = new TaskControlBlock; - if (!task) return nullptr; - - task->context = {}; - - /* Allocate 8KB kernel stack (order-1 = 2 pages) */ - Page* stack_page = pmm_alloc_pages(1); - if (!stack_page) { - delete task; - return nullptr; - } - task->kstack_pa = page_to_pa(stack_page).raw; - task->kstack_va = pa_to_va(phys_addr(task->kstack_pa)).raw; - - /* Write stack canary at bottom */ - *reinterpret_cast(task->kstack_va) = kStackCanary; - - /* Place TrapFrame at the top of the kernel stack (like Linux pt_regs). - * ALL tasks — kernel threads and user processes — have a TrapFrame - * here. kthread_create sets the initial SP below the TrapFrame so - * the kthread function never touches this reserved area. - * kernel_execve / do_execve can later fill it in for user-mode return. */ - uint64 tf_addr = task->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - task->trapframe = reinterpret_cast(tf_addr); - __builtin_memset(task->trapframe, 0, sizeof(arch::TrapFrame)); - - /* Basic fields */ - task->name = name; - task->priority = priority; - task->time_slice = priority_to_timeslice(priority); - task->state = TaskState::Runnable; - task->cpu_id = cpu; - task->flags = flags; - task->exit_code = 0; - task->exit_signal = 0; - task->preempt_count = 0; - task->clear_child_tid = 0; - task->stopped_code = 0; - task->notify_continued = false; - task->parent = nullptr; - task->mm = nullptr; - task->fpstate = {}; - task->files = nullptr; - task->fs = nullptr; - task->pending.init(); - task->blocked.clear(); - task->sighand = nullptr; - task->signal = nullptr; - - /* Init all list nodes */ - task->run_link.init(); - task->all_link.init(); - task->children.init(); - task->sibling.init(); - task->lock.init("task"); - task->wait_chld.init("wait_chld"); - - /* Default sched class */ - extern const SchedClass g_sched_class_o1; - task->sched_class = &g_sched_class_o1; - task->sched_entity = &task->run_link; - - return task; -} - -/* Add task to global task list */ -static void task_list_add(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - list_node* head = &g_all_tasks; - task->all_link.prev = head->prev; - task->all_link.next = head; - head->prev->next = &task->all_link; - head->prev = &task->all_link; - - g_nr_tasks++; -} - -/* Remove task from global task list */ -static void task_list_remove(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - task->all_link.prev->next = task->all_link.next; - task->all_link.next->prev = task->all_link.prev; - task->all_link.init(); - - if (g_nr_tasks > 0) - g_nr_tasks--; -} - -/* ====================================================================== - * kthread_create — create a kernel thread - * ====================================================================== */ - -TaskControlBlock* kthread_create(const char* name, - void (*entry)(void*), - void* arg, - uint8 priority) -{ - TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD, - static_cast(arch::cpu::id())); - if (!task) return nullptr; - - /* Allocate PID */ - task->pid = g_pid_alloc.alloc(); - if (task->pid < 0) { - /* PID exhausted — clean up */ - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - delete task; - return nullptr; - } - task->tgid = task->pid; - - /* Kernel threads: exit_signal = -1 → auto-reap on exit. - * Linux: kernel_thread() passes (flags & ~CSIGNAL) → exit_signal = 0, - * but kthreads' parent (kthreadd) reaps them. We have no kthreadd, - * so we use -1 which makes exit_notify() call release_task() directly, - * identical to Linux's CLONE_THREAD autoreap path. */ - task->exit_signal = -1; - - /* Set up context for kthread_entry_trampoline. - * SP starts BELOW the TrapFrame reserved at the stack top. - * This ensures the kthread function never clobbers the TrapFrame area. */ - uint64 stack_top = task->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - task->context = {}; - task->context.ra = reinterpret_cast(kthread_entry_trampoline); - task->context.sp = stack_top; - task->context.s[0] = reinterpret_cast(entry); - task->context.s[1] = reinterpret_cast(arg); - - /* Parent is kernel_task (PID 0) or current caller */ - TaskControlBlock* parent = g_kernel_task ? g_kernel_task : current_task(); - task->parent = parent; - if (parent) { - LockGuard guard(parent->lock); - list_node* head = &parent->children; - task->sibling.prev = head->prev; - task->sibling.next = head; - head->prev->next = &task->sibling; - head->prev = &task->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(task); - sched_enqueue(task); - - return task; -} - -/* ====================================================================== - * task_exit — transition current task to Zombie - * ====================================================================== */ - -void task_exit(int code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_exit: no current task"); - - /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */ - curr->exit_code = (code & 0xFF) << 8; - - /* CLONE_CHILD_CLEARTID / set_tid_address: write 0 to user tidptr. - * This is how pthread_join detects thread termination. - * TODO: futex_wake(clear_child_tid, 1) after writing 0. */ - if (curr->clear_child_tid != 0 && curr->mm != nullptr) { - int32 zero = 0; - copy_to_user(curr->clear_child_tid, &zero, sizeof(zero)); - curr->clear_child_tid = 0; - } - - /* Release user address space (refcount-safe). - * MUST switch to kernel page table FIRST — otherwise we're running - * on a freed page table after put() deallocates it. */ - if (curr->mm != nullptr) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children to init (or kernel_task if no init yet) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry(child_node); - child->parent = foster; - - /* Move sibling link to foster's children */ - child_node->prev->next = child_node->next; - child_node->next->prev = child_node->prev; - - list_node* fhead = &foster->children; - child_node->prev = fhead->prev; - child_node->next = fhead; - fhead->prev->next = child_node; - fhead->prev = child_node; - - child_node = next_sib; - } - curr->children.init(); - } - - - /* ---- exit_notify: Linux kernel/exit.c:exit_notify() ---- - * Determine whether this task should be auto-reaped or become zombie. - * - * Linux logic (simplified): - * - exit_signal == -1 (kthreads, sub-threads): autoreap = true - * - exit_signal >= 1 (user processes): notify parent, become zombie - * - * If autoreap, we call release_task() equivalent inline: - * free PID, remove from task list, then deschedule to Dead. */ - bool autoreap = (curr->exit_signal <= 0); - - if (autoreap) { - /* Auto-reap: release resources immediately (no zombie state). - * Linux: exit_notify() → tsk->exit_state = EXIT_DEAD → release_task() */ - task_list_remove(curr); - g_pid_alloc.free(curr->pid); - - /* Remove from parent's children list */ - curr->sibling.prev->next = curr->sibling.next; - curr->sibling.next->prev = curr->sibling.prev; - curr->sibling.init(); - - /* Drop signal_struct ref: last task in the thread group also - * detaches us from the pgrp hash. */ - if (curr->signal) { - put_signal_struct(curr->signal); - curr->signal = nullptr; - } - if (curr->sighand) { - put_sighand(curr->sighand); - curr->sighand = nullptr; - } - - curr->state = TaskState::Dead; - schedule(); - __builtin_unreachable(); - } - - /* User process path: become Zombie for parent wait() */ - curr->state = TaskState::Zombie; - - /* Wake parent->wait_chld queue */ - if (curr->parent) { - curr->parent->wait_chld.wake_all(); - } - - /* Yield CPU — never returns */ - schedule(); - - __builtin_unreachable(); -} - -void task_kill_current(int sig) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_kill_current: no current task"); - - /* Linux ABI: signal-killed encodes status as (signo & 0x7F) */ - curr->exit_code = sig & 0x7F; - - /* Release user address space (refcount-safe). - * Switch to kernel page table first (same as task_exit). */ - if (curr->mm) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children (same as task_exit) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry(child_node); - child->parent = foster; - - child_node->prev->next = child_node->next; - child_node->next->prev = child_node->prev; - - list_node* fhead = &foster->children; - child_node->prev = fhead->prev; - child_node->next = fhead; - fhead->prev->next = child_node; - fhead->prev = child_node; - - child_node = next_sib; - } - curr->children.init(); - } - - curr->state = TaskState::Zombie; - - if (curr->parent) { - curr->parent->wait_chld.wake_all(); - } - - schedule(); - __builtin_unreachable(); -} - -/* ====================================================================== - * task_destroy — fully reclaim a Zombie task (called by waitpid) - * ====================================================================== */ - -void task_destroy(TaskControlBlock* task) -{ - /* Remove from global list */ - task_list_remove(task); - - /* Remove from parent's children */ - task->sibling.prev->next = task->sibling.next; - task->sibling.next->prev = task->sibling.prev; - task->sibling.init(); - - /* Free PID */ - g_pid_alloc.free(task->pid); - - /* Drop signal_struct + sighand refs. For zombies these are delayed - * until reap (here) so a waitpid() walker can still inspect - * pgid/sid; Linux defers __exit_signal/__cleanup_sighand to - * release_task() for the same reason. */ - if (task->signal) { - put_signal_struct(task->signal); - task->signal = nullptr; - } - if (task->sighand) { - put_sighand(task->sighand); - task->sighand = nullptr; - } - - /* Free kernel stack */ - if (task->kstack_pa != 0) { - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - } - - /* Free TCB */ - delete task; -} - -/* ====================================================================== - * do_wait4 — wait for a child to exit (Linux-compatible) - * - * @wait_pid: >0 = specific child PID - * -1 = any child - * 0 = any child in same process group (TODO) - * <-1 = any child in specific process group (TODO) - * @options: WNOHANG = return immediately if no zombie - * @exit_code: output parameter (already in Linux wstatus format) - * - * Returns: PID of exited child, -ECHILD, or -EAGAIN (WNOHANG). - * ====================================================================== */ - -int32 do_wait4(int32 wait_pid, int options, int32* exit_code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - while (true) { - TaskControlBlock* zombie = nullptr; - TaskControlBlock* report = nullptr; /* Stopped/Continued report */ - int32 report_status = 0; - bool has_children = false; - - /* - * Two-phase wait: put ourselves on the wait queue BEFORE - * checking the condition. This closes the race window where - * a child exits between our check and schedule(). - * - * If a child exits and calls wake_all() while we're checking, - * our state will be flipped back to Runnable, and schedule() - * will simply return immediately. - */ - curr->wait_chld.prepare_to_wait(curr, TaskState::Sleeping); - - /* Scan children */ - curr->lock.acquire(); - - if (curr->children.is_empty()) { - curr->lock.release(); - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - has_children = true; - - list_node* node = curr->children.next; - while (node != &curr->children) { - auto* child = list_entry(node); - bool matches = (wait_pid == -1) || (wait_pid == child->pid); - if (!matches) { - node = node->next; - continue; - } - - /* Zombie reaping has priority — it frees resources. */ - if (child->state == TaskState::Zombie) { - zombie = child; - break; - } - - /* WUNTRACED: report Stopped child once per stop event. */ - if ((options & WUNTRACED) && child->stopped_code != 0) { - report = child; - report_status = child->stopped_code; - break; - } - - /* WCONTINUED: report a one-shot SIGCONT resume. */ - if ((options & WCONTINUED) && child->notify_continued) { - report = child; - report_status = W_CONTINUED(); - break; - } - - node = node->next; - } - curr->lock.release(); - - if (zombie) { - curr->wait_chld.finish_wait(curr); - int32 pid = zombie->pid; - if (exit_code) *exit_code = zombie->exit_code; - task_destroy(zombie); - return pid; - } - - if (report) { - /* Status-only report: clear the one-shot flag, leave the - * child alive. WNOWAIT (peek) would skip the clear, but - * we don't yet honour it. */ - curr->wait_chld.finish_wait(curr); - int32 pid = report->pid; - if (exit_code) *exit_code = report_status; - if (W_CONTINUED() == report_status) { - report->notify_continued = false; - } else { - report->stopped_code = 0; - } - return pid; - } - - /* No event to report */ - if (!has_children) { - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - if (options & WNOHANG) { - curr->wait_chld.finish_wait(curr); - return 0; - } - - /* Condition still false — deschedule. - * We are already on the wait queue and marked Sleeping. */ - schedule(); - - /* Woken up — finish_wait removes us from queue, restores Runnable */ - curr->wait_chld.finish_wait(curr); - - /* Loop back to re-check the condition */ - } -} - -/* Legacy wrapper for internal callers */ -int32 task_wait(int32* exit_code) -{ - return do_wait4(-1, 0, exit_code); -} - -/* ====================================================================== - * task_clone — clone current task with Linux-compatible flags - * - * @tf: trap frame to copy to child - * @clone_flags: CLONE_VM | CLONE_THREAD | CSIGNAL etc. - * @child_stack: if non-zero, set as child's user SP (for pthread) - * @parent_tidptr: write child tid here in parent (CLONE_PARENT_SETTID) - * @child_tidptr: write child tid here in child (CLONE_CHILD_SETTID) - * @tls: TLS pointer (CLONE_SETTLS → tp register) - * - * Returns: child PID in parent, 0 in child, or negative errno. - * ====================================================================== */ - -int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, - uint64 child_stack, uint64 parent_tidptr, - uint64 child_tidptr, uint64 tls) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - TaskControlBlock* child = alloc_task("cloned", curr->priority, 0, - static_cast(arch::cpu::id())); - if (!child) return -ENOMEM; - - /* Allocate PID */ - child->pid = g_pid_alloc.alloc(); - if (child->pid < 0) { - pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); - delete child; - return -ENOMEM; - } - - /* exit_signal + tgid: from clone_flags. - * Linux: fork.c line 2418/2424 */ - if (clone_flags & CLONE_THREAD) { - child->tgid = curr->tgid; - child->exit_signal = -1; /* Linux: sub-threads auto-reap */ - } else { - child->tgid = child->pid; - child->exit_signal = static_cast(clone_flags & 0xFF); /* CSIGNAL */ - if (child->exit_signal == 0) { - child->exit_signal = SIGCHLD; /* default for fork() */ - } - } - - /* Address space: CLONE_VM shares, otherwise deep copy */ - if (clone_flags & CLONE_VM) { - if (curr->mm) { - curr->mm->incref(); - child->mm = curr->mm; - } - } else { - if (curr->mm) { - child->mm = vmm_clone(curr->mm); - if (!child->mm) { - g_pid_alloc.free(child->pid); - pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); - delete child; - return -ENOMEM; - } - } - } - - /* Copy TrapFrame to the child's kernel stack */ - uint64 tf_addr = child->kstack_va + kKernelStackSize - sizeof(arch::TrapFrame); - child->trapframe = reinterpret_cast(tf_addr); - *child->trapframe = *tf; - - /* Set Kernel SP */ - child->trapframe->kernel_sp = reinterpret_cast(child->trapframe); - child->trapframe->kernel_cpu_id = child->cpu_id; - - /* Child returns 0 from fork/clone */ - child->trapframe->a[0] = 0; - - /* If caller specified a child stack, use it (pthread_create) */ - if (child_stack != 0) { - child->trapframe->sp = child_stack; - } - - /* CLONE_SETTLS: set thread pointer register */ - if (clone_flags & CLONE_SETTLS) { - child->trapframe->tp = tls; - } - - /* Set up context for ret_from_fork */ - uint64 stack_top = tf_addr; - child->context = {}; - child->context.ra = reinterpret_cast(ret_from_fork); - child->context.sp = stack_top; - child->context.s[0] = tf_addr; - - /* ---- Copy fs_struct (Linux: copy_fs in fork.c) ---- - * CLONE_FS: share the same fs_struct (increment refcount). - * Otherwise: allocate a new fs_struct copying root/pwd. */ - if (curr->fs != nullptr) { - if (clone_flags & CLONE_FS) { - curr->fs->count.fetch_add(1, __ATOMIC_RELAXED); - child->fs = curr->fs; - } else { - child->fs = copy_fs_struct(curr->fs); - } - } - - /* ---- Copy files_struct (Linux: copy_files in fork.c) ---- - * CLONE_FILES: share the same fd table. - * Otherwise: duplicate the fd table. */ - if (curr->files != nullptr) { - if (clone_flags & CLONE_FILES) { - curr->files->count.fetch_add(1, __ATOMIC_RELAXED); - child->files = curr->files; - } else { - child->files = dup_fd(curr->files); - } - } - - /* ---- Copy sighand (Linux kernel/fork.c:copy_sighand) ---- - * CLONE_SIGHAND: share signal handlers (incref parent's). - * Otherwise: deep copy of the action table; child gets its own - * sighand_struct (and its own siglock). Both branches MUST yield - * a non-null sighand — every user task needs one so signal paths - * can take the siglock without lazy-allocation races. */ - if (clone_flags & CLONE_SIGHAND) { - if (!curr->sighand) { - /* CLONE_SIGHAND requires the parent to have a sighand. */ - return -EINVAL; - } - get_sighand(curr->sighand); - child->sighand = curr->sighand; - } else { - child->sighand = copy_sighand(curr->sighand); - if (!child->sighand) { - /* OOM — same best-effort policy as fs/files/signal above. - * A separate cleanup-on-OOM pass is on the to-do list. */ - } - } - - /* ---- Copy signal_struct (Linux: copy_signal in fork.c) ---- - * CLONE_THREAD shares the parent's signal_struct (incref). - * Otherwise allocate a new one that inherits pgid/sid/tty from the - * parent so the child stays in the same process group and session — - * this is what makes fork() participate in the existing job-control - * identity without explicit setpgid() calls. */ - if (curr->signal != nullptr) { - if (clone_flags & CLONE_THREAD) { - get_signal_struct(curr->signal); - child->signal = curr->signal; - } else { - child->signal = fork_signal_struct(curr->signal, child); - } - } - - /* Add to task tree */ - child->parent = curr; - { - LockGuard guard(curr->lock); - list_node* head = &curr->children; - child->sibling.prev = head->prev; - child->sibling.next = head; - head->prev->next = &child->sibling; - head->prev = &child->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(child); - sched_enqueue(child); - - /* CLONE_PARENT_SETTID: write child tid to parent's user address */ - if ((clone_flags & CLONE_PARENT_SETTID) && parent_tidptr != 0) { - copy_to_user(parent_tidptr, &child->pid, sizeof(child->pid)); - } - - /* CLONE_CHILD_SETTID: write child tid into child's user address. - * Note: this runs in parent context, but the address is in the - * child's mm. For CLONE_VM (threads), parent and child share mm - * so this works directly. For fork, the page was just CoW-copied. */ - if ((clone_flags & CLONE_CHILD_SETTID) && child_tidptr != 0) { - if (clone_flags & CLONE_VM) { - /* Same address space — write directly */ - copy_to_user(child_tidptr, &child->pid, sizeof(child->pid)); - } - /* For non-CLONE_VM fork, the child's mm is separate. - * The write would need to go into the child's pagetable. - * Linux handles this in the child's first schedule via - * a special flag. For now, threads (CLONE_VM) are the - * primary consumer. */ - } - - /* CLONE_CHILD_CLEARTID: kernel writes 0 to child_tidptr on exit */ - if (clone_flags & CLONE_CHILD_CLEARTID) { - child->clear_child_tid = child_tidptr; - } - - return child->pid; -} - -/* Legacy wrapper — plain fork() semantics */ -int32 task_fork(arch::TrapFrame* tf) -{ - return task_clone(tf, SIGCHLD, 0, 0, 0, 0); -} - - -/* ====================================================================== - * task_check_stack_canary - * ====================================================================== */ - -void task_check_stack_canary(TaskControlBlock* task) -{ - if (!task || task->kstack_va == 0) return; - - auto* canary = reinterpret_cast(task->kstack_va); - if (*canary != kStackCanary) { - kernel_panic("kernel stack overflow detected"); - } -} -- cgit v1.2.3