fix(vkernel): THREAD_CREATE 改用户态线程 - 独立用户栈与内核栈经 drop_to_user_mode 进 Ring3;调度器按线程切 TSS.RSP0/gs[8];同步 _thread_entry 硬编码偏移并加 fail-fast 自检

This commit is contained in:
TermiNexus committed 2026-10-15 16:45:09 +08:00
1 parent 89a319e1be
commit a7faeed21b
4 files changed
+141 -6

No files matched your search

+3
View File
@@ -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
+3 -1
View File
@@ -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:
+74 -1
View File
@@ -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]
+61 -4
View File
@@ -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