diff --git a/VKernel/Kernel/execrunner/elf.vp b/VKernel/Kernel/execrunner/elf.vp index b8f1c0b..95c8ea0 100644 --- a/VKernel/Kernel/execrunner/elf.vp +++ b/VKernel/Kernel/execrunner/elf.vp @@ -493,6 +493,9 @@ def spawn_elf(path: const str) -> int: p.exit() fat32.unlock() return -3 + # Ring3 → Ring0 落栈点按**线程**切换(见 sched._yield):主线程也必须登记进程内核栈, + # 否则同进程的额外用户线程会把它当成"没有内核栈的线程"而沿用上一个线程的栈。 + th.kern_stack = p.kern_stack tid: int = th.tid p.addThread(tid) slog: [80]char diff --git a/VKernel/Kernel/intr/syscall.vp b/VKernel/Kernel/intr/syscall.vp index 43cfb0e..ef93eb2 100644 --- a/VKernel/Kernel/intr/syscall.vp +++ b/VKernel/Kernel/intr/syscall.vp @@ -481,7 +481,9 @@ export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64 func_ptr: rawptr = rawptr(arg1) arg_ptr: rawptr = rawptr(arg2) cur_pid: int = process.current() - th: sched.Thread* = sched.Scheduler.create_thread(func_ptr, arg_ptr, cur_pid) + # app 传进来的是**用户地址**:必须用 create_user_thread(独立用户栈 + 独立内核栈、 + # 走 drop_to_user_mode 进 Ring 3),不能用 create_thread 在 Ring 0 直接 call。 + th: sched.Thread* = sched.Scheduler.create_user_thread(cur_pid, func_ptr, arg_ptr) if th is None: return uint64(0xFFFFFFFFFFFFFFFF) p: process.Process* = process.ProcessManager.get_process(cur_pid) if p: diff --git a/VKernel/Kernel/sched/process.vp b/VKernel/Kernel/sched/process.vp index 123baff..474b32e 100644 --- a/VKernel/Kernel/sched/process.vp +++ b/VKernel/Kernel/sched/process.vp @@ -18,6 +18,12 @@ define PROC_MAX_FDS = 16 define PROC_MAX_DIRS = 8 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: @@ -197,7 +203,7 @@ class ProcessManager: _proc_mgr: ProcessManager = ProcessManager() _proc_mgr_ptr: ProcessManager* = &_proc_mgr -export _saved_cr3: uint64 +export _saved_cr3: uint64 def current() -> int: m: ProcessManager* = _proc_mgr_ptr @@ -273,3 +279,70 @@ def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64): 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 + entry: rawptr + arg: rawptr + user_stack_top: uint64 + kern_stack: uint64 + +_user_ctx: [MAX_USER_THREADS]UserThreadCtx +_user_ctx_used: [MAX_USER_THREADS]int +_user_stack_next: int = 0 + +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 + 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) + _user_ctx_used[slot] = 1 + _user_ctx[slot].pid = pid + _user_ctx[slot].entry = entry + _user_ctx[slot].arg = arg + _user_ctx[slot].user_stack_top = vtop + _user_ctx[slot].kern_stack = uint64(ks) + uint64(KERN_STACK_SIZE) + return slot + +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] diff --git a/VKernel/Kernel/sched/sched.vp b/VKernel/Kernel/sched/sched.vp index f04d422..5ad5cae 100644 --- a/VKernel/Kernel/sched/sched.vp +++ b/VKernel/Kernel/sched/sched.vp @@ -1,6 +1,8 @@ import drivers.serial.uart.serial as serial import sched.process as proc import mm.mm as mm +import intr.gdt as gdt +import drivers.core.cpu.cpu as cpu import viperlib import asm @@ -25,6 +27,9 @@ class Thread: state: int tid: int pid: int + # Ring3 → Ring0 的落栈点(TSS.RSP0 / gs:[8]):用户线程才非 0,内核线程为 0。 + # 必须按线程切换 —— 同进程多用户线程共用进程级内核栈会互相压栈帧。 + kern_stack: uint64 def block(self): self.state = THREAD_BLOCKED @@ -74,6 +79,16 @@ class Scheduler: main_rsp_ptr: rawptr = &s.threads[0].rsp __asm__(f"""mov qword ptr [{asminp(main_rsp_ptr, ASM_DESCR.REG_ANY)}], rsp""", op=[ASM_DESCR.CLOBBER_MEMORY]) + # fail-fast:_thread_entry 的 asm 以硬编码偏移访问线程表(sizeof(Thread)=64、 + # current_tid=+1024=16*64)。增删 Thread 字段会让它们失配 ⇒ 线程入口会按错误的 + # 偏移取 func/arg 并 call 任意地址。这里直接核对,不符立即停机暴露。 + thr0_addr: uint64 = uint64(&s.threads[0]) + curt_addr: uint64 = uint64(&s.current_tid) + if Thread.__sizeof__() != 64 or (curt_addr - thr0_addr) != 1024: + serial.puts("[sched] FATAL: Thread 布局与 _thread_entry 硬编码偏移(64/1024)不一致\n") + while True: + asm.sti() + asm.hlt() @staticmethod def _yield(): @@ -126,6 +141,12 @@ class Scheduler: next_pid: int = s.threads[next_tid].pid _yield_cnt += 1 proc.switch_pid(next_pid) + # 按**线程**设置 Ring3→Ring0 的落栈点:switch_pid 只按进程设置(会把全进程的用户线程 + # 指到同一个内核栈上),两个线程都在 syscall 里 yield 时后者的栈帧会压掉前者的。 + next_ks: uint64 = s.threads[next_tid].kern_stack + if next_ks != 0: + gdt.set_tss_rsp0(next_ks) + cpu.set_kernel_rsp0(next_ks) _yield_lock = 0 _do_switch() if _if_saved: @@ -181,6 +202,7 @@ class Scheduler: th.pid = thread_pid th.func = func th.arg = arg + th.kern_stack = 0 stack_size: uint64 = THREAD_STACK_SIZE stack_ptr: rawptr = mm.malloc(stack_size) if not stack_ptr: @@ -207,6 +229,23 @@ class Scheduler: th.rsp = stack_top return th + @staticmethod + def create_user_thread(thread_pid: int, entry: rawptr, arg: rawptr) -> Thread*: + """创建**用户态**线程:入口在 Ring 3 执行(THREAD_CREATE 用)。 + + app 传来的函数指针是用户地址,绝不能用 create_thread 直接 call —— 那是在 Ring 0 + 执行用户代码,且与同进程其它线程共用一套内核栈。这里给线程配独立用户栈 + 独立 + 内核栈,由 _user_thread_entry 走 drop_to_user_mode 进 Ring 3。 + """ + slot: int = proc.alloc_user_thread(thread_pid, entry, arg) + if slot < 0: return None + ctx: proc.UserThreadCtx* = proc.user_thread_ctx(slot) + if ctx is None: return None + th: Thread* = Scheduler.create_thread(&_user_thread_entry, rawptr(uint64(slot)), thread_pid) + if th is None: return None + th.kern_stack = ctx.kern_stack + return th + _sched_storage: Scheduler = Scheduler() export _sched_ptr: Scheduler* = &_sched_storage @@ -238,20 +277,23 @@ def _do_switch(): ret""", op=[ASM_DESCR.CLOBBER_MEMORY]) +# Thread 布局:rsp0/stack8/stack_size16/func24/arg32/state40/tid44/pid48/kern_stack56 +# ⇒ sizeof(Thread) = 64,Scheduler.threads[16] 占 1024 字节 ⇒ current_tid 在 +1024。 +# 增删 Thread 字段时必须同步下面 asm 里的 1024 / 64(以及 +24/+32/+40 三个字段偏移)。 @__attribute__(naked) def _thread_entry(): __asm__(f"""sub rsp, 8 mov rax, qword ptr [rip + _sched_ptr] - mov ecx, dword ptr [rax + 896] + mov ecx, dword ptr [rax + 1024] movsxd rcx, ecx - imul rcx, rcx, 56 + imul rcx, rcx, 64 mov rdi, qword ptr [rax + rcx + 32] mov rax, qword ptr [rax + rcx + 24] call rax mov rax, qword ptr [rip + _sched_ptr] - mov ecx, dword ptr [rax + 896] + mov ecx, dword ptr [rax + 1024] movsxd rcx, ecx - imul rcx, rcx, 56 + imul rcx, rcx, 64 mov dword ptr [rax + rcx + 40], 3 add rsp, 8""", # `call rax` 破坏全部 caller-saved 寄存器(x64 SysV):除 RAX/RCX/RDX/ @@ -268,6 +310,21 @@ def _thread_entry(): asm.hlt() Scheduler._yield() +def _user_thread_entry(arg: rawptr) -> int: + """用户态线程的内核侧引导:配好 TSS/GS 落栈点后 iretq 进 Ring 3,永不返回。""" + slot: int = int(uint64(arg)) + ctx: proc.UserThreadCtx* = proc.user_thread_ctx(slot) + if ctx is None: return 0 + # 与 execrunner/elf.vp 的 _elf_entry_wrapper 同款:直到 iretq 之前必须关中断 + # (@pml4/TSS/GS 都是全局量,被定时器抢占时别的线程的 switch_pid 会把它们改掉); + # iretq 用用户态帧里的 RFLAGS.IF(0x202) 重新开中断。 + asm.cli() + proc.switch_pid(ctx.pid) + gdt.set_tss_rsp0(ctx.kern_stack) + cpu.set_kernel_rsp0(ctx.kern_stack) + proc.drop_to_user_mode(ctx.entry, ctx.user_stack_top, ctx.kern_stack) + return 0 + def thread_current() -> int: s: Scheduler* = _sched_ptr return s.current_tid