import mm.mm as mm import paging.paging as paging import drivers.serial.uart.serial as serial import drivers.fs.fat32.fat32 as fat32 import drivers.fs.fat32.fat32_types as fat32_types import drivers.core.cpu.cpu as cpu import intr.gdt as gdt import string define PROC_READY = 0 define PROC_RUNNING = 1 define PROC_BLOCKED = 2 define PROC_DONE = 3 define MAX_PROCESSES = 32 define MAX_PROC_THREADS = 8 define PROC_MAX_FDS = 16 # fd 种类(process.fd_kind[])。定义放这里、由 intr/syscall.vp 以 process.FD_KIND_* 引用, # 这样 process.vp 不必 import devfs —— devfs → keyboard/mouse → intr.idt → sched → process # 会形成环,不能让 process 反向依赖 devfs。 define FD_KIND_FAT32 = 0 define FD_KIND_DEVFS = 1 define FD_KIND_DIR = 2 define FD_KIND_PIPE = 3 define PROC_MAX_DIRS = 8 define MAX_VMAS = 64 define VMA_KIND_ANON = 1 define VMA_KIND_DEV = 2 define VMA_KIND_FILE = 3 define KERN_STACK_SIZE = 8192 define USER_STACK_SIZE = 65536 # 用户栈虚拟区:主线程栈顶 = USER_STACK_VTOP(见 execrunner/elf.vp 的映射), # 额外用户线程(THREAD_CREATE)从它下方按 USER_THREAD_STACK_GAP 逐个下移。 define USER_STACK_VTOP = 0x7FFFFFE00000 define USER_STACK_PAGES = 16 define USER_THREAD_STACK_GAP = 0x20000 define MAX_USER_THREADS = 16 class Process: pid: int pml4_root: uint64 threads: [8]int thread_count: int state: int entry: rawptr elf_base: uint64 heap_start: uint64 heap_size: uint64 name: [32]char cwd: [256]char fd_table: [PROC_MAX_FDS]rawptr fd_used: [PROC_MAX_FDS]uint8 # 0 = FAT32 文件(fd_table[i] 里是 fileobj*);1 = devfs 设备(fd_table[i] 里是 # devfs_fd+1,+1 只为避开"devfs fd 0"与空指针的歧义)。READ/WRITE/CLOSE/IOCTL 与 # Process.exit 都必须按它分派 —— 把 devfs fd 当 fileobj* 解引用会直接跑飞。 fd_kind: [PROC_MAX_FDS]uint8 fd_flags: [PROC_MAX_FDS]uint32 fd_shared: [PROC_MAX_FDS]uint8 dir_table: [PROC_MAX_DIRS]rawptr dir_used: [PROC_MAX_DIRS]uint8 dir_pool: [PROC_MAX_DIRS]fat32_types.dirobj kern_stack: uint64 user_stack: uint64 # Virtual memory areas backing mmap()/mprotect()/munmap()/mremap(). vma_base: [MAX_VMAS]uint64 vma_size: [MAX_VMAS]uint64 vma_prot: [MAX_VMAS]uint32 vma_kind: [MAX_VMAS]uint8 vma_flags: [MAX_VMAS]uint32 # brk() region (separate from the mmap bump allocator). brk_start: uint64 brk_end: uint64 # Linux process metadata. entry_rsp: uint64 parent_pid: int exit_code: int # Signals (per process; delivery happens on syscall return). sig_handler: [65]uint64 sig_actflags: [65]uint64 sig_restorer: [65]uint64 sig_mask: uint64 sig_pending: uint64 def addThread(self, tid: int) -> int: if self.thread_count >= MAX_PROC_THREADS: return -1 self.threads[self.thread_count] = tid self.thread_count += 1 return 0 def exit(self): self.state = PROC_DONE for i in range(PROC_MAX_FDS): if self.fd_used[i]: fp: rawptr = self.fd_table[i] # devfs 设备 fd 绝不能当 fileobj* 交给 fat32.close —— 那会把 devfs_fd+1 # 当指针解引用而跑飞。devfs 的 fd 表是独立的另一张;正规退出路径本来就不 # 回收 fd(见 intr/syscall.vp 的 EXIT 不调 Process.exit),这里只清槽位, # app 自己调 CLOSE 时才走 devfs_close。 if fp and self.fd_kind[i] == FD_KIND_FAT32 and self.fd_shared[i] == 0: fat32.close(fp) self.fd_used[i] = 0 self.fd_table[i] = None self.fd_kind[i] = 0 self.fd_shared[i] = 0 for i in range(PROC_MAX_DIRS): if self.dir_used[i]: dp: rawptr = self.dir_table[i] if dp: fat32.closedir(dp) self.dir_used[i] = 0 self.dir_table[i] = None m: ProcessManager* = _proc_mgr_ptr if m.current_pid == self.pid: m.current_pid = 0 m.processes[0].state = PROC_RUNNING if self.pml4_root != 0: kernel_cr3: uint64 = m.processes[0].pml4_root if kernel_cr3 != 0: __asm__(f"mov cr3, {asminp(kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(kernel_cr3) @property def is_running(self) -> bool: return self.state == PROC_RUNNING @property def is_done(self) -> bool: return self.state == PROC_DONE class ProcessManager: processes: [MAX_PROCESSES]Process current_pid: int count: int def __init__(self): global _proc_mgr_ptr _proc_mgr_ptr = &_proc_mgr m: ProcessManager* = _proc_mgr_ptr m.current_pid = 0 m.count = 1 p: Process* = &m.processes[0] p.pid = 0 p.state = PROC_RUNNING p.thread_count = 0 p.pml4_root = 0 p.entry = None p.elf_base = 0 p.heap_start = 0 p.heap_size = 0 name_str: str = "kernel" i: int = 0 for ch in name_str: if i >= 31: break p.name[i] = ch i += 1 p.name[i] = 0 string.strcpy(&p.cwd[0], "/") j: int for j in range(MAX_PROC_THREADS): p.threads[j] = -1 for j in range(PROC_MAX_FDS): p.fd_used[j] = 0 p.fd_table[j] = None p.fd_kind[j] = 0 p.fd_flags[j] = 0 p.fd_shared[j] = 0 for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = None for j in range(MAX_VMAS): p.vma_base[j] = 0 p.vma_size[j] = 0 p.vma_prot[j] = 0 p.vma_kind[j] = 0 p.vma_flags[j] = 0 p.brk_start = 0 p.brk_end = 0 p.entry_rsp = 0 p.parent_pid = 0 p.exit_code = 0 p.sig_mask = 0 p.sig_pending = 0 for j in range(65): p.sig_handler[j] = 0 p.sig_actflags[j] = 0 p.sig_restorer[j] = 0 __asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY]) @staticmethod def _switch(pid: int) -> int: global _saved_cr3 m: ProcessManager* = _proc_mgr_ptr if pid < 0 or pid >= m.count: return -1 p: Process* = &m.processes[pid] if p.state == PROC_DONE: return -1 old_pid: int = m.current_pid if old_pid == pid: return 0 if old_pid >= 0 and old_pid < m.count: m.processes[old_pid].state = PROC_READY p.state = PROC_RUNNING m.current_pid = pid __asm__(f"""mov {asmout(_saved_cr3, ASM_DESCR.OUTPUT_REG)}, cr3 mov rax, {asminp(p.pml4_root, ASM_DESCR.REG_ANY)} mov cr3, rax""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX]) return 0 @staticmethod def current() -> Process*: m: ProcessManager* = _proc_mgr_ptr return &m.processes[m.current_pid] @staticmethod def get_process(pid: int) -> Process*: m: ProcessManager* = _proc_mgr_ptr if pid < 0 or pid >= m.count: return None return &m.processes[pid] @staticmethod def create_process(name: str, entry: rawptr) -> Process*: m: ProcessManager* = _proc_mgr_ptr if m.count >= MAX_PROCESSES: # 启动时已有 1(内核)+7(app) 个槽;旧上限 8 导致 run/runb 永远失败。 serial.puts("[process] create_process: pid table full\n") return None pid: int = m.count m.count += 1 p: Process* = &m.processes[pid] p.pid = pid p.state = PROC_READY p.entry = entry p.elf_base = 0 p.thread_count = 0 p.heap_start = 0 p.heap_size = 0 string.memset(&p.name, 0, 32) i: int = 0 for ch in name: if i >= 31: break p.name[i] = ch i += 1 p.name[i] = 0 string.strcpy(&p.cwd[0], "/") for j in range(MAX_PROC_THREADS): p.threads[j] = -1 for j in range(PROC_MAX_FDS): p.fd_used[j] = 0 p.fd_table[j] = None p.fd_kind[j] = 0 p.fd_flags[j] = 0 p.fd_shared[j] = 0 for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = None for j in range(MAX_VMAS): p.vma_base[j] = 0 p.vma_size[j] = 0 p.vma_prot[j] = 0 p.vma_kind[j] = 0 p.vma_flags[j] = 0 p.brk_start = 0 p.brk_end = 0 p.entry_rsp = 0 p.parent_pid = 0 p.exit_code = 0 p.sig_mask = 0 p.sig_pending = 0 for j in range(65): p.sig_handler[j] = 0 p.sig_actflags[j] = 0 p.sig_restorer[j] = 0 ks: rawptr = mm.malloc(KERN_STACK_SIZE) if ks is None: serial.puts("[process] kernel stack alloc failed\n") m.count = m.count - 1 return None ks_size: uint64 = KERN_STACK_SIZE p.kern_stack = uint64(ks) + ks_size us: rawptr = mm.malloc(USER_STACK_SIZE) if us is None: serial.puts("[process] user stack alloc failed\n") mm.free(ks) m.count = m.count - 1 return None us_size: uint64 = USER_STACK_SIZE p.user_stack = uint64(us) + us_size __asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX]) return p _proc_mgr: ProcessManager = ProcessManager() _proc_mgr_ptr: ProcessManager* = &_proc_mgr export _saved_cr3: uint64 def current() -> int: m: ProcessManager* = _proc_mgr_ptr return m.current_pid def switch_pid(new_pid: int): m: ProcessManager* = _proc_mgr_ptr if m.current_pid == new_pid: return if m.current_pid >= 0 and m.current_pid < m.count: old_p: Process* = &m.processes[m.current_pid] if old_p.state != PROC_DONE: old_p.state = PROC_READY if new_pid >= 0 and new_pid < m.count: new_p: Process* = &m.processes[new_pid] new_p.state = PROC_RUNNING new_cr3: uint64 = new_p.pml4_root if new_cr3 != 0: __asm__(f"mov cr3, {asminp(new_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(new_cr3) # Update TSS RSP0 and GS kernel_rsp0 for the new process # This is needed so interrupts from Ring 3 use the correct kernel stack if new_p.kern_stack != 0: gdt.set_tss_rsp0(new_p.kern_stack) cpu.set_kernel_rsp0(new_p.kern_stack) m.current_pid = new_pid def get_pid(tid: int) -> int: m: ProcessManager* = _proc_mgr_ptr for i in range(m.count): p: Process* = &m.processes[i] for j in range(p.thread_count): if p.threads[j] == tid: return p.pid return -1 def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64, arg: rawptr = None): # NOTE: Do NOT swapgs here. This OS uses reverse GS convention: # GS_BASE = 0 (kernel & user), KERNEL_GS_BASE = &_per_cpu # syscall/ISR entry does swapgs to get GS_BASE = &_per_cpu # syscall/ISR return does swapgs to restore GS_BASE = 0 # GS state is restored in isrCommonhandler before _yield() if needed. # [FIX] Normalize GS to the user convention (GS_BASE = 0) before returning to ring 3. # This thread may have been switched in from ANOTHER thread's user-mode timer interrupt: # that path never runs the ISR exit swapgs, so GS_BASE is still the kernel view (pointing # at the per-cpu struct). Running user code in that state makes the app's first syscall # swapgs the wrong way; the kernel-stack lookup then reads garbage from linear address 8, # the stack pointer becomes garbage, and even the page-fault frame cannot be pushed # => triple fault with zero output (not even the ISR entry probe can fire). # Swapgs only when GS_BASE != 0 (idempotent; same idiom as the handler's GS restore). # 【修复】用户线程入口的第一个形参在 rdi —— 这条路径原来**从没设过 rdi**,于是 # `thread_create(fn, arg)` 的用户参数在用户侧永远是垃圾值。iretq 只恢复 # RIP/CS/RFLAGS/RSP/SS,其余通用寄存器原样保留,所以在这里设 rdi 即可传递。 arg_val: uint64 = 0 if arg is not None: arg_val = uint64(arg) __asm__(f"""cli mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)} mov ecx, 0xC0000101 rdmsr shl rdx, 32 or rax, rdx test rax, rax jz 1f swapgs 1: mov rdi, {asminp(arg_val, ASM_DESCR.REG_ANY)} mov rdx, {asminp(user_rsp, ASM_DESCR.REG_ANY)} mov rcx, {asminp(entry, ASM_DESCR.REG_ANY)} mov r11, 0x202 push 0x23 push rdx push r11 push 0x2B push rcx iretq""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_R11, ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_RBP]) # Resume context used by fork/clone children: they start user mode at the # parent's post-syscall RIP with a full register set and RAX=0. export _resume_ctx: [20]uint64 def set_resume(rip: uint64, rsp: uint64, flags: uint64, rax: uint64, regs: uint64*): _resume_ctx[0] = rip _resume_ctx[1] = rsp _resume_ctx[2] = flags _resume_ctx[3] = rax i: int = 0 while i < 12: _resume_ctx[4 + i] = regs[i] i += 1 def drop_to_user_mode_ctx(kern_rsp: uint64): __asm__(f"""cli mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)} mov ecx, 0xC0000101 rdmsr shl rdx, 32 or rax, rdx test rax, rax jz 1f swapgs 1: lea rbx, [rip + _resume_ctx] mov r15, [rbx+32] mov r14, [rbx+40] mov r13, [rbx+48] mov r12, [rbx+56] mov r10, [rbx+64] mov r9, [rbx+72] mov r8, [rbx+80] mov rbp, [rbx+88] mov rdi, [rbx+96] mov rsi, [rbx+104] mov rdx, [rbx+112] mov rcx, [rbx+0] mov r11, [rbx+16] mov rax, [rbx+24] push 0x23 push qword ptr [rbx+8] push r11 push 0x2B push rcx mov rbx, [rbx+120] iretq""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RBX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_RBP]) # ============================================================ # 用户态线程(THREAD_CREATE)上下文 # 每个用户线程需要两套栈,否则同进程多线程会互相踩: # · 用户栈(Ring 3 执行用):从 USER_STACK_VTOP 下方按固定间隔分配,映射 PTE_USER; # · 内核栈(Ring 3 → Ring 0 的 syscall/中断落栈点 = TSS.RSP0 / gs:[8]): # 必须按**线程**写入(由调度器切线程时设置),不能按进程 —— 否则一个线程在 # syscall 里 yield 时,另一个线程的 syscall 会压掉它的栈帧,返回即跑飞。 # ============================================================ class UserThreadCtx: pid: int tid: int entry: rawptr arg: rawptr # 入口 rsp(= 栈顶下一格;该格里放出口跳板的 VA,见 alloc_user_thread) user_stack_entry_rsp: uint64 # 用户栈映射上界(UnMap 用) user_stack_vtop: uint64 user_stack_phys: rawptr kern_stack: uint64 # 出口跳板物理页(映射在线程栈顶上方那一页,回收时一起交还) tramp: rawptr _user_ctx: [MAX_USER_THREADS]UserThreadCtx _user_ctx_used: [MAX_USER_THREADS]int _user_stack_next: int = 0 # 用户线程"自然 return"的出口跳板:一页只含下面 9 字节的用户态代码,映射到线程栈顶 # 上方那一页 [vtop, vtop+0x1000)(本线程栈与上一个线程栈之间的 64KB 间隙,栈向下增长 # 永远到不了)。栈顶下一格写它的 VA 当哨兵返回地址 ⇒ 线程函数 return 时直接落到 # `int 0x80(THREAD_EXIT)`,由内核干净回收本线程。 # # 为什么不能用原来的哨兵 0:return 会把 0 弹进 rip ⇒ 在 Ring3 取指 VA 0。VA 0 在内核 # identity map 里是**存在但 supervisor-only** 的页,于是 #PF 现场是 # rip=0x0 cr2=0x0 err=0x15(P=1 保护违例 | U=1 用户态 | I/D=1 取指)rsp=线程栈 vtop, # 日志以 "exc in tid=N, terminating" 收场 —— 一次正常的"线程结束"被报成异常崩溃 # (实测:Scene3D 渲染线程在窗口被关掉后 return 即此现场)。 define USER_THREAD_TRAMPOLINE_SIZE = 0x1000 define TRAMP_CODE_SIZE = 9 _tramp_code: [TRAMP_CODE_SIZE]uint8 = [ 0xB8, 0x22, 0x00, 0x00, 0x00, # mov eax, 34 = THREAD_EXIT(与 intr/syscall.vp 一致) 0xCD, 0x80, # int 0x80(DPL=3,硬件按 TSS.RSP0 落到本线程内核栈) 0xEB, 0xFE # jmp $(THREAD_EXIT 不该返回;Ring3 不能 hlt) ] def alloc_user_thread(pid: int, entry: rawptr, arg: rawptr) -> int: """登记一个用户线程:分配内核栈 + 用户栈 + 出口跳板页,并把后两者映射进该进程地址空间。 必须在调用者进程上下文里调用(MapPage 作用于当前 pml4 = THREAD_CREATE 调用者的)。 返回槽位号;失败返回 -1(不兜底,调用方显式处理失败)。 """ global _user_stack_next slot: int = -1 i: int for i in range(MAX_USER_THREADS): if _user_ctx_used[i] == 0: slot = i break if slot < 0: serial.puts("[proc] no free user-thread slot\n") return -1 ks: rawptr = mm.malloc(KERN_STACK_SIZE) if ks is None: serial.puts("[proc] user-thread kern stack alloc failed\n") return -1 us: rawptr = mm.malloc(USER_STACK_SIZE) if us is None: serial.puts("[proc] user-thread user stack alloc failed\n") mm.free(ks) return -1 tramp: rawptr = mm.malloc(uint64(USER_THREAD_TRAMPOLINE_SIZE)) if tramp is None: serial.puts("[proc] user-thread trampoline alloc failed\n") mm.free(us) mm.free(ks) return -1 us_size: uint64 = uint64(USER_STACK_SIZE) phys_bottom: uint64 = uint64(us) & ~uint64(0xFFF) vtop: uint64 = USER_STACK_VTOP - uint64(_user_stack_next + 1) * uint64(USER_THREAD_STACK_GAP) _user_stack_next = _user_stack_next + 1 si: int for si in range(USER_STACK_PAGES): vaddr: uint64 = (vtop - us_size) + uint64(si) * uint64(0x1000) paddr: uint64 = phys_bottom + uint64(si) * uint64(0x1000) paging.MapPage(vaddr, paddr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) # 出口跳板页:映射在 [vtop, vtop+0x1000) —— 位于本线程栈顶之上,栈向下增长永不到达。 # 字节拷贝沿用 apic.vp 的 _setup_trampoline 惯用法(uint8* 逐字节写)。 tramp_phys: uint64 = uint64(tramp) & ~uint64(0xFFF) dest: uint8* = uint8*(tramp_phys) src: uint8* = &_tramp_code for bi in range(TRAMP_CODE_SIZE): *(dest + uint64(bi)) = *(src + uint64(bi)) paging.MapPage(vtop, tramp_phys, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) # 入口 rsp 指向栈顶下一格,并在该格写出口跳板 VA 当哨兵返回地址:用户线程函数若 # return,ret 会跳进跳板执行 int 0x80(THREAD_EXIT) ⇒ 内核干净回收本线程。 entry_rsp: uint64 = vtop - uint64(8) ret_slot: uint64* = uint64*(entry_rsp) *ret_slot = vtop _user_ctx_used[slot] = 1 _user_ctx[slot].pid = pid _user_ctx[slot].tid = -1 _user_ctx[slot].entry = entry _user_ctx[slot].arg = arg _user_ctx[slot].user_stack_entry_rsp = entry_rsp _user_ctx[slot].user_stack_vtop = vtop _user_ctx[slot].user_stack_phys = us _user_ctx[slot].kern_stack = uint64(ks) + uint64(KERN_STACK_SIZE) _user_ctx[slot].tramp = tramp return slot def free_user_thread(slot: int): """回收用户线程资源(线程已不在用户态执行)。 · 解映射用户栈 VA 并交还物理页; · 解映射出口跳板页并交还物理页; · 清槽位标记(允许复用)。 内核栈**故意不在这里 free**:终止路径(异常 ISR / THREAD_EXIT)此刻正跑在该线程 自己的内核栈上,free 后它会被别的线程分配走并覆盖,直到 _do_switch 切走为止。 """ if slot < 0 or slot >= MAX_USER_THREADS: return if _user_ctx_used[slot] == 0: return vtop: uint64 = _user_ctx[slot].user_stack_vtop si: int for si in range(USER_STACK_PAGES): vaddr: uint64 = (vtop - uint64(USER_STACK_SIZE)) + uint64(si) * uint64(0x1000) paging.UnMapPage(vaddr) us: rawptr = _user_ctx[slot].user_stack_phys if us is not None: mm.free(us) _user_ctx[slot].user_stack_phys = None # 出口跳板页同样解映射并交还物理页(与用户栈同批回收) paging.UnMapPage(vtop) tramp: rawptr = _user_ctx[slot].tramp if tramp is not None: mm.free(tramp) _user_ctx[slot].tramp = None _user_ctx_used[slot] = 0 def find_user_slot_by_tid(tid: int) -> int: i: int for i in range(MAX_USER_THREADS): if _user_ctx_used[i] != 0 and _user_ctx[i].tid == tid: return i return -1 def user_thread_ctx(slot: int) -> UserThreadCtx*: if slot < 0 or slot >= MAX_USER_THREADS: return None if _user_ctx_used[slot] == 0: return None return &_user_ctx[slot]