fix(vkernel): THREAD_CREATE 改用户态线程 - 独立用户栈与内核栈经 drop_to_user_mode 进 Ring3;调度器按线程切 TSS.RSP0/gs[8];同步 _thread_entry 硬编码偏移并加 fail-fast 自检
This commit is contained in:
1 parent
89a319e1be
commit
a7faeed21b
4 files changed
+141
-6
No files matched your search
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user