Files

581 lines
23 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import mm.mm as mm
import paging.paging as paging
import drivers.serial.uart.serial as serial
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import drivers.core.cpu.cpu as cpu
import intr.gdt as gdt
import string
define PROC_READY = 0
define PROC_RUNNING = 1
define PROC_BLOCKED = 2
define PROC_DONE = 3
define MAX_PROCESSES = 32
define MAX_PROC_THREADS = 8
define PROC_MAX_FDS = 16
# fd 种类(process.fd_kind[])。定义放这里、由 intr/syscall.vp 以 process.FD_KIND_* 引用,
# 这样 process.vp 不必 import devfs —— devfs → keyboard/mouse → intr.idt → sched → process
# 会形成环,不能让 process 反向依赖 devfs。
define FD_KIND_FAT32 = 0
define FD_KIND_DEVFS = 1
define FD_KIND_DIR = 2
define FD_KIND_PIPE = 3
define PROC_MAX_DIRS = 8
define MAX_VMAS = 64
define VMA_KIND_ANON = 1
define VMA_KIND_DEV = 2
define VMA_KIND_FILE = 3
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:
pid: int
pml4_root: uint64
threads: [8]int
thread_count: int
state: int
entry: rawptr
elf_base: uint64
heap_start: uint64
heap_size: uint64
name: [32]char
cwd: [256]char
fd_table: [PROC_MAX_FDS]rawptr
fd_used: [PROC_MAX_FDS]uint8
# 0 = FAT32 文件(fd_table[i] 里是 fileobj*);1 = devfs 设备(fd_table[i] 里是
# devfs_fd+1,+1 只为避开"devfs fd 0"与空指针的歧义)。READ/WRITE/CLOSE/IOCTL 与
# Process.exit 都必须按它分派 —— 把 devfs fd 当 fileobj* 解引用会直接跑飞。
fd_kind: [PROC_MAX_FDS]uint8
fd_flags: [PROC_MAX_FDS]uint32
fd_shared: [PROC_MAX_FDS]uint8
dir_table: [PROC_MAX_DIRS]rawptr
dir_used: [PROC_MAX_DIRS]uint8
dir_pool: [PROC_MAX_DIRS]fat32_types.dirobj
kern_stack: uint64
user_stack: uint64
# Virtual memory areas backing mmap()/mprotect()/munmap()/mremap().
vma_base: [MAX_VMAS]uint64
vma_size: [MAX_VMAS]uint64
vma_prot: [MAX_VMAS]uint32
vma_kind: [MAX_VMAS]uint8
vma_flags: [MAX_VMAS]uint32
# brk() region (separate from the mmap bump allocator).
brk_start: uint64
brk_end: uint64
# Linux process metadata.
entry_rsp: uint64
parent_pid: int
exit_code: int
# Signals (per process; delivery happens on syscall return).
sig_handler: [65]uint64
sig_actflags: [65]uint64
sig_restorer: [65]uint64
sig_mask: uint64
sig_pending: uint64
def addThread(self, tid: int) -> int:
if self.thread_count >= MAX_PROC_THREADS: return -1
self.threads[self.thread_count] = tid
self.thread_count += 1
return 0
def exit(self):
self.state = PROC_DONE
for i in range(PROC_MAX_FDS):
if self.fd_used[i]:
fp: rawptr = self.fd_table[i]
# devfs 设备 fd 绝不能当 fileobj* 交给 fat32.close —— 那会把 devfs_fd+1
# 当指针解引用而跑飞。devfs 的 fd 表是独立的另一张;正规退出路径本来就不
# 回收 fd(见 intr/syscall.vp 的 EXIT 不调 Process.exit),这里只清槽位,
# app 自己调 CLOSE 时才走 devfs_close。
if fp and self.fd_kind[i] == FD_KIND_FAT32 and self.fd_shared[i] == 0:
fat32.close(fp)
self.fd_used[i] = 0
self.fd_table[i] = None
self.fd_kind[i] = 0
self.fd_shared[i] = 0
for i in range(PROC_MAX_DIRS):
if self.dir_used[i]:
dp: rawptr = self.dir_table[i]
if dp:
fat32.closedir(dp)
self.dir_used[i] = 0
self.dir_table[i] = None
m: ProcessManager* = _proc_mgr_ptr
if m.current_pid == self.pid:
m.current_pid = 0
m.processes[0].state = PROC_RUNNING
if self.pml4_root != 0:
kernel_cr3: uint64 = m.processes[0].pml4_root
if kernel_cr3 != 0:
__asm__(f"mov cr3, {asminp(kernel_cr3, ASM_DESCR.REG_ANY)}",
op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(kernel_cr3)
@property
def is_running(self) -> bool: return self.state == PROC_RUNNING
@property
def is_done(self) -> bool: return self.state == PROC_DONE
class ProcessManager:
processes: [MAX_PROCESSES]Process
current_pid: int
count: int
def __init__(self):
global _proc_mgr_ptr
_proc_mgr_ptr = &_proc_mgr
m: ProcessManager* = _proc_mgr_ptr
m.current_pid = 0
m.count = 1
p: Process* = &m.processes[0]
p.pid = 0
p.state = PROC_RUNNING
p.thread_count = 0
p.pml4_root = 0
p.entry = None
p.elf_base = 0
p.heap_start = 0
p.heap_size = 0
name_str: str = "kernel"
i: int = 0
for ch in name_str:
if i >= 31: break
p.name[i] = ch
i += 1
p.name[i] = 0
string.strcpy(&p.cwd[0], "/")
j: int
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
for j in range(PROC_MAX_FDS):
p.fd_used[j] = 0
p.fd_table[j] = None
p.fd_kind[j] = 0
p.fd_flags[j] = 0
p.fd_shared[j] = 0
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = None
for j in range(MAX_VMAS):
p.vma_base[j] = 0
p.vma_size[j] = 0
p.vma_prot[j] = 0
p.vma_kind[j] = 0
p.vma_flags[j] = 0
p.brk_start = 0
p.brk_end = 0
p.entry_rsp = 0
p.parent_pid = 0
p.exit_code = 0
p.sig_mask = 0
p.sig_pending = 0
for j in range(65):
p.sig_handler[j] = 0
p.sig_actflags[j] = 0
p.sig_restorer[j] = 0
__asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""",
op=[ASM_DESCR.CLOBBER_MEMORY])
@staticmethod
def _switch(pid: int) -> int:
global _saved_cr3
m: ProcessManager* = _proc_mgr_ptr
if pid < 0 or pid >= m.count: return -1
p: Process* = &m.processes[pid]
if p.state == PROC_DONE: return -1
old_pid: int = m.current_pid
if old_pid == pid: return 0
if old_pid >= 0 and old_pid < m.count:
m.processes[old_pid].state = PROC_READY
p.state = PROC_RUNNING
m.current_pid = pid
__asm__(f"""mov {asmout(_saved_cr3, ASM_DESCR.OUTPUT_REG)}, cr3
mov rax, {asminp(p.pml4_root, ASM_DESCR.REG_ANY)}
mov cr3, rax""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX])
return 0
@staticmethod
def current() -> Process*:
m: ProcessManager* = _proc_mgr_ptr
return &m.processes[m.current_pid]
@staticmethod
def get_process(pid: int) -> Process*:
m: ProcessManager* = _proc_mgr_ptr
if pid < 0 or pid >= m.count: return None
return &m.processes[pid]
@staticmethod
def create_process(name: str, entry: rawptr) -> Process*:
m: ProcessManager* = _proc_mgr_ptr
if m.count >= MAX_PROCESSES:
# 启动时已有 1(内核)+7(app) 个槽;旧上限 8 导致 run/runb 永远失败。
serial.puts("[process] create_process: pid table full\n")
return None
pid: int = m.count
m.count += 1
p: Process* = &m.processes[pid]
p.pid = pid
p.state = PROC_READY
p.entry = entry
p.elf_base = 0
p.thread_count = 0
p.heap_start = 0
p.heap_size = 0
string.memset(&p.name, 0, 32)
i: int = 0
for ch in name:
if i >= 31: break
p.name[i] = ch
i += 1
p.name[i] = 0
string.strcpy(&p.cwd[0], "/")
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
for j in range(PROC_MAX_FDS):
p.fd_used[j] = 0
p.fd_table[j] = None
p.fd_kind[j] = 0
p.fd_flags[j] = 0
p.fd_shared[j] = 0
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = None
for j in range(MAX_VMAS):
p.vma_base[j] = 0
p.vma_size[j] = 0
p.vma_prot[j] = 0
p.vma_kind[j] = 0
p.vma_flags[j] = 0
p.brk_start = 0
p.brk_end = 0
p.entry_rsp = 0
p.parent_pid = 0
p.exit_code = 0
p.sig_mask = 0
p.sig_pending = 0
for j in range(65):
p.sig_handler[j] = 0
p.sig_actflags[j] = 0
p.sig_restorer[j] = 0
ks: rawptr = mm.malloc(KERN_STACK_SIZE)
if ks is None:
serial.puts("[process] kernel stack alloc failed\n")
m.count = m.count - 1
return None
ks_size: uint64 = KERN_STACK_SIZE
p.kern_stack = uint64(ks) + ks_size
us: rawptr = mm.malloc(USER_STACK_SIZE)
if us is None:
serial.puts("[process] user stack alloc failed\n")
mm.free(ks)
m.count = m.count - 1
return None
us_size: uint64 = USER_STACK_SIZE
p.user_stack = uint64(us) + us_size
__asm__(f"""mov {asmout(p.pml4_root, ASM_DESCR.OUTPUT_REG)}, cr3""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX])
return p
_proc_mgr: ProcessManager = ProcessManager()
_proc_mgr_ptr: ProcessManager* = &_proc_mgr
export _saved_cr3: uint64
def current() -> int:
m: ProcessManager* = _proc_mgr_ptr
return m.current_pid
def switch_pid(new_pid: int):
m: ProcessManager* = _proc_mgr_ptr
if m.current_pid == new_pid: return
if m.current_pid >= 0 and m.current_pid < m.count:
old_p: Process* = &m.processes[m.current_pid]
if old_p.state != PROC_DONE:
old_p.state = PROC_READY
if new_pid >= 0 and new_pid < m.count:
new_p: Process* = &m.processes[new_pid]
new_p.state = PROC_RUNNING
new_cr3: uint64 = new_p.pml4_root
if new_cr3 != 0:
__asm__(f"mov cr3, {asminp(new_cr3, ASM_DESCR.REG_ANY)}",
op=[ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(new_cr3)
# Update TSS RSP0 and GS kernel_rsp0 for the new process
# This is needed so interrupts from Ring 3 use the correct kernel stack
if new_p.kern_stack != 0:
gdt.set_tss_rsp0(new_p.kern_stack)
cpu.set_kernel_rsp0(new_p.kern_stack)
m.current_pid = new_pid
def get_pid(tid: int) -> int:
m: ProcessManager* = _proc_mgr_ptr
for i in range(m.count):
p: Process* = &m.processes[i]
for j in range(p.thread_count):
if p.threads[j] == tid:
return p.pid
return -1
def drop_to_user_mode(entry: rawptr, user_rsp: uint64, kern_rsp: uint64, arg: rawptr = None):
# NOTE: Do NOT swapgs here. This OS uses reverse GS convention:
# GS_BASE = 0 (kernel & user), KERNEL_GS_BASE = &_per_cpu
# syscall/ISR entry does swapgs to get GS_BASE = &_per_cpu
# syscall/ISR return does swapgs to restore GS_BASE = 0
# GS state is restored in isrCommonhandler before _yield() if needed.
# [FIX] Normalize GS to the user convention (GS_BASE = 0) before returning to ring 3.
# This thread may have been switched in from ANOTHER thread's user-mode timer interrupt:
# that path never runs the ISR exit swapgs, so GS_BASE is still the kernel view (pointing
# at the per-cpu struct). Running user code in that state makes the app's first syscall
# swapgs the wrong way; the kernel-stack lookup then reads garbage from linear address 8,
# the stack pointer becomes garbage, and even the page-fault frame cannot be pushed
# => triple fault with zero output (not even the ISR entry probe can fire).
# Swapgs only when GS_BASE != 0 (idempotent; same idiom as the handler's GS restore).
# 【修复】用户线程入口的第一个形参在 rdi —— 这条路径原来**从没设过 rdi**,于是
# `thread_create(fn, arg)` 的用户参数在用户侧永远是垃圾值。iretq 只恢复
# RIP/CS/RFLAGS/RSP/SS,其余通用寄存器原样保留,所以在这里设 rdi 即可传递。
arg_val: uint64 = 0
if arg is not None: arg_val = uint64(arg)
__asm__(f"""cli
mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)}
mov ecx, 0xC0000101
rdmsr
shl rdx, 32
or rax, rdx
test rax, rax
jz 1f
swapgs
1:
mov rdi, {asminp(arg_val, ASM_DESCR.REG_ANY)}
mov rdx, {asminp(user_rsp, ASM_DESCR.REG_ANY)}
mov rcx, {asminp(entry, ASM_DESCR.REG_ANY)}
mov r11, 0x202
push 0x23
push rdx
push r11
push 0x2B
push rcx
iretq""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_R11, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8,
ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10,
ASM_DESCR.CLOBBER_RBP])
# Resume context used by fork/clone children: they start user mode at the
# parent's post-syscall RIP with a full register set and RAX=0.
export _resume_ctx: [20]uint64
def set_resume(rip: uint64, rsp: uint64, flags: uint64, rax: uint64, regs: uint64*):
_resume_ctx[0] = rip
_resume_ctx[1] = rsp
_resume_ctx[2] = flags
_resume_ctx[3] = rax
i: int = 0
while i < 12:
_resume_ctx[4 + i] = regs[i]
i += 1
def drop_to_user_mode_ctx(kern_rsp: uint64):
__asm__(f"""cli
mov rsp, {asminp(kern_rsp, ASM_DESCR.REG_ANY)}
mov ecx, 0xC0000101
rdmsr
shl rdx, 32
or rax, rdx
test rax, rax
jz 1f
swapgs
1:
lea rbx, [rip + _resume_ctx]
mov r15, [rbx+32]
mov r14, [rbx+40]
mov r13, [rbx+48]
mov r12, [rbx+56]
mov r10, [rbx+64]
mov r9, [rbx+72]
mov r8, [rbx+80]
mov rbp, [rbx+88]
mov rdi, [rbx+96]
mov rsi, [rbx+104]
mov rdx, [rbx+112]
mov rcx, [rbx+0]
mov r11, [rbx+16]
mov rax, [rbx+24]
push 0x23
push qword ptr [rbx+8]
push r11
push 0x2B
push rcx
mov rbx, [rbx+120]
iretq""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RBX, ASM_DESCR.CLOBBER_RCX,
ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI,
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
tid: int
entry: rawptr
arg: rawptr
# 入口 rsp(= 栈顶下一格;该格里放出口跳板的 VA,见 alloc_user_thread)
user_stack_entry_rsp: uint64
# 用户栈映射上界(UnMap 用)
user_stack_vtop: uint64
user_stack_phys: rawptr
kern_stack: uint64
# 出口跳板物理页(映射在线程栈顶上方那一页,回收时一起交还)
tramp: rawptr
_user_ctx: [MAX_USER_THREADS]UserThreadCtx
_user_ctx_used: [MAX_USER_THREADS]int
_user_stack_next: int = 0
# 用户线程"自然 return"的出口跳板:一页只含下面 9 字节的用户态代码,映射到线程栈顶
# 上方那一页 [vtop, vtop+0x1000)(本线程栈与上一个线程栈之间的 64KB 间隙,栈向下增长
# 永远到不了)。栈顶下一格写它的 VA 当哨兵返回地址 ⇒ 线程函数 return 时直接落到
# `int 0x80(THREAD_EXIT)`,由内核干净回收本线程。
#
# 为什么不能用原来的哨兵 0:return 会把 0 弹进 rip ⇒ 在 Ring3 取指 VA 0。VA 0 在内核
# identity map 里是**存在但 supervisor-only** 的页,于是 #PF 现场是
# rip=0x0 cr2=0x0 err=0x15(P=1 保护违例 | U=1 用户态 | I/D=1 取指)rsp=线程栈 vtop,
# 日志以 "exc in tid=N, terminating" 收场 —— 一次正常的"线程结束"被报成异常崩溃
# (实测:Scene3D 渲染线程在窗口被关掉后 return 即此现场)。
define USER_THREAD_TRAMPOLINE_SIZE = 0x1000
define TRAMP_CODE_SIZE = 9
_tramp_code: [TRAMP_CODE_SIZE]uint8 = [
0xB8, 0x22, 0x00, 0x00, 0x00, # mov eax, 34 = THREAD_EXIT(与 intr/syscall.vp 一致)
0xCD, 0x80, # int 0x80(DPL=3,硬件按 TSS.RSP0 落到本线程内核栈)
0xEB, 0xFE # jmp $(THREAD_EXIT 不该返回;Ring3 不能 hlt)
]
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
tramp: rawptr = mm.malloc(uint64(USER_THREAD_TRAMPOLINE_SIZE))
if tramp is None:
serial.puts("[proc] user-thread trampoline alloc failed\n")
mm.free(us)
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)
# 出口跳板页:映射在 [vtop, vtop+0x1000) —— 位于本线程栈顶之上,栈向下增长永不到达。
# 字节拷贝沿用 apic.vp 的 _setup_trampoline 惯用法(uint8* 逐字节写)。
tramp_phys: uint64 = uint64(tramp) & ~uint64(0xFFF)
dest: uint8* = uint8*(tramp_phys)
src: uint8* = &_tramp_code
for bi in range(TRAMP_CODE_SIZE):
*(dest + uint64(bi)) = *(src + uint64(bi))
paging.MapPage(vtop, tramp_phys, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
# 入口 rsp 指向栈顶下一格,并在该格写出口跳板 VA 当哨兵返回地址:用户线程函数若
# return,ret 会跳进跳板执行 int 0x80(THREAD_EXIT) ⇒ 内核干净回收本线程。
entry_rsp: uint64 = vtop - uint64(8)
ret_slot: uint64* = uint64*(entry_rsp)
*ret_slot = vtop
_user_ctx_used[slot] = 1
_user_ctx[slot].pid = pid
_user_ctx[slot].tid = -1
_user_ctx[slot].entry = entry
_user_ctx[slot].arg = arg
_user_ctx[slot].user_stack_entry_rsp = entry_rsp
_user_ctx[slot].user_stack_vtop = vtop
_user_ctx[slot].user_stack_phys = us
_user_ctx[slot].kern_stack = uint64(ks) + uint64(KERN_STACK_SIZE)
_user_ctx[slot].tramp = tramp
return slot
def free_user_thread(slot: int):
"""回收用户线程资源(线程已不在用户态执行)。
· 解映射用户栈 VA 并交还物理页;
· 解映射出口跳板页并交还物理页;
· 清槽位标记(允许复用)。
内核栈**故意不在这里 free**:终止路径(异常 ISR / THREAD_EXIT)此刻正跑在该线程
自己的内核栈上,free 后它会被别的线程分配走并覆盖,直到 _do_switch 切走为止。
"""
if slot < 0 or slot >= MAX_USER_THREADS: return
if _user_ctx_used[slot] == 0: return
vtop: uint64 = _user_ctx[slot].user_stack_vtop
si: int
for si in range(USER_STACK_PAGES):
vaddr: uint64 = (vtop - uint64(USER_STACK_SIZE)) + uint64(si) * uint64(0x1000)
paging.UnMapPage(vaddr)
us: rawptr = _user_ctx[slot].user_stack_phys
if us is not None:
mm.free(us)
_user_ctx[slot].user_stack_phys = None
# 出口跳板页同样解映射并交还物理页(与用户栈同批回收)
paging.UnMapPage(vtop)
tramp: rawptr = _user_ctx[slot].tramp
if tramp is not None:
mm.free(tramp)
_user_ctx[slot].tramp = None
_user_ctx_used[slot] = 0
def find_user_slot_by_tid(tid: int) -> int:
i: int
for i in range(MAX_USER_THREADS):
if _user_ctx_used[i] != 0 and _user_ctx[i].tid == tid:
return i
return -1
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]