import mm import mm.mm as mm_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 viperlib import string import t, c PROC_READY: t.CDefine = 0 PROC_RUNNING: t.CDefine = 1 PROC_BLOCKED: t.CDefine = 2 PROC_DONE: t.CDefine = 3 MAX_PROCESSES: t.CDefine = 8 MAX_PROC_THREADS: t.CDefine = 8 PROC_MAX_FDS: t.CDefine = 16 PROC_MAX_DIRS: t.CDefine = 8 KERN_STACK_SIZE: t.CDefine = 8192 USER_STACK_SIZE: t.CDefine = 65536 @t.Object class Process: pid: t.CInt pml4_root: t.CUInt64T threads: t.CArray[t.CInt, 8] thread_count: t.CInt state: t.CInt entry: t.CVoid | t.CPtr elf_base: t.CUInt64T heap_start: t.CUInt64T heap_size: t.CUInt64T name: t.CArray[t.CChar, 32] fd_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_FDS] fd_used: t.CArray[t.CUInt8T, PROC_MAX_FDS] dir_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_DIRS] dir_used: t.CArray[t.CUInt8T, PROC_MAX_DIRS] dir_pool: t.CArray[fat32_types.dirobj, PROC_MAX_DIRS] kern_stack: t.CUInt64T user_stack: t.CUInt64T def addThread(self, tid: t.CInt) -> t.CInt: 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: t.CVoid | t.CPtr = self.fd_table[i] if fp: fat32.close(fp) self.fd_used[i] = 0 self.fd_table[i] = t.CVoid(0, t.CPtr) for i in range(PROC_MAX_DIRS): if self.dir_used[i]: dp: t.CVoid | t.CPtr = self.dir_table[i] if dp: fat32.closedir(dp) self.dir_used[i] = 0 self.dir_table[i] = t.CVoid(0, t.CPtr) m: ProcessManager | t.CPtr = _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: t.CUInt64T = m.processes[0].pml4_root if kernel_cr3 != 0: c.Asm(f"mov cr3, {c.AsmInp(kernel_cr3, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(kernel_cr3) @property def is_running(self) -> t.CInt: if self.state == PROC_RUNNING: return 1 return 0 @property def is_done(self) -> t.CInt: if self.state == PROC_DONE: return 1 return 0 @t.Object class ProcessManager: processes: t.CArray[Process, MAX_PROCESSES] current_pid: t.CInt count: t.CInt def __init__(): global _proc_mgr_ptr _proc_mgr_ptr = c.Addr(_proc_mgr) m: ProcessManager | t.CPtr = _proc_mgr_ptr m.current_pid = 0 m.count = 1 p: Process | t.CPtr = c.Addr(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: t.CInt = 0 for ch in name_str: if i >= 31: break p.name[i] = ch i += 1 p.name[i] = 0 j: t.CInt 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] = t.CVoid(0, t.CPtr) for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = t.CVoid(0, t.CPtr) c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""", op=[t.ASM_DESCR.CLOBBER_MEMORY]) @staticmethod def _switch(pid: t.CInt) -> t.CInt: global _saved_cr3 m: ProcessManager | t.CPtr = _proc_mgr_ptr if pid < 0 or pid >= m.count: return -1 p: Process | t.CPtr = c.Addr(m.processes[pid]) if p.state == PROC_DONE: return -1 old_pid: t.CInt = 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 c.Asm(f"""mov {c.AsmOut(_saved_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3 mov rax, {c.AsmInp(p.pml4_root, t.ASM_DESCR.REG_ANY)} mov cr3, rax""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) return 0 @staticmethod def current() -> Process | t.CPtr: m: ProcessManager | t.CPtr = _proc_mgr_ptr return c.Addr(m.processes[m.current_pid]) @staticmethod def get_process(pid: t.CInt) -> Process | t.CPtr: m: ProcessManager | t.CPtr = _proc_mgr_ptr if pid < 0 or pid >= m.count: return None return c.Addr(m.processes[pid]) @staticmethod def create_process(name: str, entry: t.CVoid | t.CPtr) -> Process | t.CPtr: m: ProcessManager | t.CPtr = _proc_mgr_ptr _dbp: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(_dbp), 80, "[process] create_process count=%d MAX=%d\n", m.count, MAX_PROCESSES) serial.puts(_dbp) if m.count >= MAX_PROCESSES: return None pid: t.CInt = m.count m.count += 1 p: Process | t.CPtr = c.Addr(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(c.Addr(p.name), 0, 32) i: t.CInt = 0 for ch in name: if i >= 31: break p.name[i] = ch i += 1 p.name[i] = 0 j: t.CInt 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] = t.CVoid(0, t.CPtr) for j in range(PROC_MAX_DIRS): p.dir_used[j] = 0 p.dir_table[j] = t.CVoid(0, t.CPtr) ks: t.CVoid | t.CPtr = mm_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: t.CUInt64T = KERN_STACK_SIZE p.kern_stack = t.CUInt64T(ks) + ks_size us: t.CVoid | t.CPtr = mm_mm.malloc(USER_STACK_SIZE) if us is None: serial.puts("[process] user stack alloc failed\n") mm_mm.free(ks) m.count = m.count - 1 return None us_size: t.CUInt64T = USER_STACK_SIZE p.user_stack = t.CUInt64T(us) + us_size c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX]) return p _proc_mgr: ProcessManager = ProcessManager() _proc_mgr_ptr: ProcessManager | t.CPtr = c.Addr(_proc_mgr) _saved_cr3: t.CUInt64T def current() -> t.CInt: m: ProcessManager | t.CPtr = _proc_mgr_ptr return m.current_pid def switch_pid(new_pid: t.CInt): m: ProcessManager | t.CPtr = _proc_mgr_ptr if m.current_pid == new_pid: return if m.current_pid >= 0 and m.current_pid < m.count: old_p: Process | t.CPtr = c.Addr(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 | t.CPtr = c.Addr(m.processes[new_pid]) new_p.state = PROC_RUNNING new_cr3: t.CUInt64T = new_p.pml4_root if new_cr3 != 0: c.Asm(f"mov cr3, {c.AsmInp(new_cr3, t.ASM_DESCR.REG_ANY)}", op=[t.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: t.CInt) -> t.CInt: m: ProcessManager | t.CPtr = _proc_mgr_ptr i: t.CInt for i in range(m.count): p: Process | t.CPtr = c.Addr(m.processes[i]) j: t.CInt for j in range(p.thread_count): if p.threads[j] == tid: return p.pid return -1 def drop_to_user_mode(entry: t.CVoid | t.CPtr, user_rsp: t.CUInt64T, kern_rsp: t.CUInt64T): # 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. c.Asm(f"""cli mov rsp, {c.AsmInp(kern_rsp, t.ASM_DESCR.REG_ANY)} mov rdx, {c.AsmInp(user_rsp, t.ASM_DESCR.REG_ANY)} mov rcx, {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)} mov r11, 0x202 push 0x23 push rdx push r11 push 0x1B push rcx iretq""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_RBP])