devfs:新增 /dev/fb0(framebuffer_device),提供 ioctl(FB_GET_INFO) 回填几何/格式/物理区间,并新增 devfs_file.mmap_info 虚方法(默认不可映射)把物理区间交给内核 mmap 用;main.vp 在 devfs_init 后登记它。输入设备(/dev/input/keyboard、/dev/input/mouse)此前已存在,本轮起从用户态可达。 用户态可达性:Process 增 fd_kind[](0=FAT32 fileobj*,1=devfs_fd+1),OPEN 对 "/dev/" 前缀改走 devfs,READ/WRITE/CLOSE 按 kind 分派;Process.exit 只对 kind=0 调 fat32.close(把 devfs fd 当 fileobj* 会跑飞)。fd 种类常量放在 process.vp 供 syscall.vp 引用,避免 process 反向 import devfs(devfs→keyboard/mouse→intr.idt→sched→process 会成环)。 新增 IOCTL syscall(Linux 号 16,本轮开始实现):只放给 devfs 设备,SDK 侧 vpsdk/fs.vp 加 ioctl() 助手。
448 lines
18 KiB
Plaintext
448 lines
18 KiB
Plaintext
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 = 8
|
||
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 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:
|
||
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
|
||
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
|
||
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
|
||
|
||
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:
|
||
fat32.close(fp)
|
||
self.fd_used[i] = 0
|
||
self.fd_table[i] = None
|
||
self.fd_kind[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
|
||
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
|
||
for j in range(PROC_MAX_DIRS):
|
||
p.dir_used[j] = 0
|
||
p.dir_table[j] = None
|
||
__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: 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
|
||
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
|
||
for j in range(PROC_MAX_DIRS):
|
||
p.dir_used[j] = 0
|
||
p.dir_table[j] = None
|
||
ks: rawptr = mm.malloc(KERN_STACK_SIZE)
|
||
if ks is None:
|
||
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):
|
||
# 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).
|
||
__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 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])
|
||
|
||
|
||
# ============================================================
|
||
# 用户态线程(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]
|