diff --git a/VKernel/Kernel/execrunner/elf.vp b/VKernel/Kernel/execrunner/elf.vp index 78d446b..b366ceb 100644 --- a/VKernel/Kernel/execrunner/elf.vp +++ b/VKernel/Kernel/execrunner/elf.vp @@ -623,7 +623,13 @@ def spawn_elf(path: const str, argv: rawptr = None, envp: rawptr = None) -> int: isolate_va = 0x400000 # Clone kernel PML4 (identity mapping intact, no ELF mapping yet) - proc_pml4: uint64 = paging.clone_for_process(isolate_va) + # 必须以内核 PML4 为模板:运行时 spawn 的调用者是父进程,直接 clone 会把 + # 父进程的 heap/stack 页表(甚至共享 PT)带进子进程,子进程 MapPage 时会 + # 覆盖父进程的 PTE(现场:run hello.elf 后 Terminal ret 到 0、Explorer #DE)。 + old_pml4: uint64 = uint64(paging.GetCurrentPml4()) + paging.set_active_pml4(paging.get_kernel_pml4()) + proc_pml4: uint64 = paging.clone_for_process(isolate_va, USER_STACK_VTOP - uint64(0x1000)) + paging.set_active_pml4(old_pml4) slog1: [80]char viperlib.snprintf(&slog1, 80, "[elf] spawn: clone pml4=0x%lx va=0x%lx\n", proc_pml4, isolate_va) serial.puts(&slog1) diff --git a/VKernel/Kernel/paging/paging.vp b/VKernel/Kernel/paging/paging.vp index 89812bb..c8f5e57 100644 --- a/VKernel/Kernel/paging/paging.vp +++ b/VKernel/Kernel/paging/paging.vp @@ -43,13 +43,17 @@ pt_pool_limit: uint64 = 0 # pt_pool_start + pt_pool_pages*4096 AllocatedPages: uint64 = 0 pml4: _p* = None +# 内核启动时的 PML4;运行时 clone 新进程必须以它为模板,不能克隆调用者(父进程) +# 的用户页表,否则父子进程会共享 heap/stack 的 PT/PTE。 +_kernel_pml4: uint64 = 0 pdp: _p* pd: _p* pt: _p* def init(MemmapAddr: uint64, MemmapSize: uint64): - global pml4 + global pml4, _kernel_pml4 pml4 = GetCurrentPml4() + _kernel_pml4 = uint64(pml4) # bootloader/UEFI 的 CR0.WP 通常已是 1(原代码清 WP 正因此)。页表页 # (pml4/pdp/pd/pt,0xf8xxxx)默认只读,而内核运行时 MapPage/clone 需 # 修改页表。顺序:先清 WP(允许写只读页表页)→ MakePTWritable 放开 @@ -189,6 +193,56 @@ def _sanitize_identity_pd(pd: _p*, gb_base: uint64): j += 1 i += 1 +# 为 dst 进程克隆 vaddr 所在的 PML4/PDP/PD/PT 分支。已克隆过的层级直接复用, +# 因此可以对同一 1GiB 内的多个 2MiB 区域连续调用。 +# 关键用途:用户栈都映射在同一个 USER_STACK_VTOP;若不把该分支私有化, +# 新进程映射自己的栈会改写共享页表,把父进程的栈 PTE 覆盖掉 → ret 到 0。 +def _clone_vaddr(dst: _p*, vaddr: uint64): + pml4_i: uint64 = (vaddr >> 39) & 0x1FF + if not (pml4.entries[pml4_i] & PTE_PRESENT): return + src_pdp: _p* = _p*(pml4.entries[pml4_i] & ~uint64(0xFFF)) + dst_pdp_ent: uint64 = dst.entries[pml4_i] + if dst_pdp_ent == pml4.entries[pml4_i]: + new_pdp: _p* = AllocPage() + if not new_pdp: return + i: int = 0 + while i < 512: + new_pdp.entries[i] = src_pdp.entries[i] + if (new_pdp.entries[i] & PTE_PRESENT) != 0 and (new_pdp.entries[i] & PTE_PS) != 0: + new_pdp.entries[i] = new_pdp.entries[i] & ~PTE_USER + i += 1 + dst.entries[pml4_i] = (uint64(new_pdp) & ~uint64(0xFFF)) | (pml4.entries[pml4_i] & uint64(0xFFF) & ~PTE_USER) + dst_pdp_ent = dst.entries[pml4_i] + dst_pdp: _p* = _p*(dst_pdp_ent & ~uint64(0xFFF)) + pdp_i: uint64 = (vaddr >> 30) & 0x1FF + if not (src_pdp.entries[pdp_i] & PTE_PRESENT): return + if src_pdp.entries[pdp_i] & PTE_PS: + if _split_1gb_to_pd(uint64(src_pdp), pdp_i) == 0: return + src_pd: _p* = _p*(src_pdp.entries[pdp_i] & ~uint64(0xFFF)) + if dst_pdp.entries[pdp_i] == src_pdp.entries[pdp_i]: + new_pd: _p* = AllocPage() + if not new_pd: return + i = 0 + while i < 512: + new_pd.entries[i] = src_pd.entries[i] + if (new_pd.entries[i] & PTE_PRESENT) != 0 and (new_pd.entries[i] & PTE_PS) != 0: + new_pd.entries[i] = new_pd.entries[i] & ~PTE_USER + i += 1 + dst_pdp.entries[pdp_i] = (uint64(new_pd) & ~uint64(0xFFF)) | (src_pdp.entries[pdp_i] & uint64(0xFFF) & ~PTE_USER) + dst_pd: _p* = _p*(dst_pdp.entries[pdp_i] & ~uint64(0xFFF)) + pd_i: uint64 = (vaddr >> 21) & 0x1FF + if not (src_pd.entries[pd_i] & PTE_PRESENT): return + if src_pd.entries[pd_i] & PTE_PS: return + if dst_pd.entries[pd_i] == src_pd.entries[pd_i]: + src_pt: _p* = _p*(src_pd.entries[pd_i] & ~uint64(0xFFF)) + new_pt: _p* = AllocPage() + if not new_pt: return + i = 0 + while i < 512: + new_pt.entries[i] = src_pt.entries[i] & ~PTE_USER + i += 1 + dst_pd.entries[pd_i] = (uint64(new_pt) & ~uint64(0xFFF)) | (src_pd.entries[pd_i] & uint64(0xFFF) & ~PTE_USER) + def MapPage(VirtAddr: uint64, PhysAddr: uint64, flags: uint64): global pml4, pdp, pd, pt if VirtAddr == 0 or PhysAddr == 0: return @@ -575,7 +629,7 @@ def create_process_page_table() -> uint64: new_pml4.entries[i] = entry return uint64(new_pml4) -def clone_for_process(isolate_vaddr: uint64) -> uint64: +def clone_for_process(isolate_vaddr: uint64, stack_vaddr: uint64 = 0) -> uint64: global pml4 _dbg: [120]char viperlib.snprintf(&_dbg, 120, "[clone] pml4=0x%lx pool_used=%lu iso_va=0x%lx\n", uint64(pml4), pt_pool_used, isolate_vaddr) @@ -643,6 +697,12 @@ def clone_for_process(isolate_vaddr: uint64) -> uint64: for i in range(512): new_pt_page.entries[i] = kernel_pt.entries[i] & ~PTE_USER new_pd_page.entries[pd_idx] = (uint64(new_pt_page) & ~uint64(0xFFF)) | (kernel_pd.entries[pd_idx] & uint64(0xFFF) & ~PTE_USER) + # 用户栈(含 user thread 栈)都落在同一个高地址区;必须为每个进程私有化 + # 对应的 PML4/PDP/PD/PT 分支,否则新进程映射栈会覆盖父进程的栈 PTE。 + if stack_vaddr != 0: + _clone_vaddr(new_pml4_page, stack_vaddr) + _clone_vaddr(new_pml4_page, stack_vaddr - uint64(0x200000)) + _clone_vaddr(new_pml4_page, stack_vaddr - uint64(0x400000)) return uint64(new_pml4_page) # Deep-copy every USER mapping from src into dst (dst must already contain the @@ -786,3 +846,6 @@ def restore_pt_range(save_buf: uint64*, save_count: uint32): def set_active_pml4(cr3_val: uint64): global pml4 pml4 = _p*(cr3_val & ~uint64(0xFFF)) + +export def get_kernel_pml4() -> uint64: + return _kernel_pml4