fix(paging): clone from kernel PML4 and isolate user stack tables for runtime spawn
This commit is contained in:
1 parent
51287eb1a0
commit
65ad527eeb
2 files changed
+72
-3
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user