import drivers.serial.uart.serial as serial import drivers.fs.fat32.fat32 as fat32 import drivers.fs.fat32.fat32_types as fat32_types import sched.sched as sched import sched.process as process import viperlib import string import mm.mm as mm import paging.paging as paging import execrunner.elf as elf import drivers.devfs.devfs as devfs import platform.acpi.acpi as acpi import platform.pch.timer as timer import platform.pch.rtc as rtc import intr.gdt as gdt import drivers.core.cpu.cpu as cpu import abi.linux as abi import asm # ---------------------------------------------------------------------------- # Syscall entry frame (set up by the compiler-provided syscall_entry): # rbp+0x00 r15 +0x08 r14 +0x10 r13 +0x18 r12 # +0x20 r10 +0x28 r9 +0x30 r8 +0x38 rbp # +0x40 rdi +0x48 rsi +0x50 rdx +0x58 rbx # +0x60 r11(user flags) +0x68 rcx(user rip) # +0x70 from-user flag +0x78 user rsp # The handler itself never touches rbp, so a naked read is stable. # ---------------------------------------------------------------------------- define FR_R15 = 0 define FR_R14 = 8 define FR_R13 = 16 define FR_R12 = 24 define FR_R10 = 32 define FR_R9 = 40 define FR_R8 = 48 define FR_RBP = 56 define FR_RDI = 64 define FR_RSI = 72 define FR_RDX = 80 define FR_RBX = 88 define FR_FLAGS = 96 define FR_RIP = 104 define FR_FROMU = 112 define FR_RSP = 120 # Entry trampoline state (naked syscall_handler stores the entry rbp here). export _entry_frame: uint64 = 0 export _handler_ptr: rawptr = None # ============================================================================ # 系统调用号表(由 Scripts/syscall_table.py 生成,禁止手改)。 # 生成源:Scripts/syscall_table.py;build.py 在编译前自动同步。 # ============================================================================ # BEGIN GENERATED SYSCALL TABLE enum Syscall: # ---- Linux 对齐段(号 = Linux x86-64 的号) ---- READ = 0 # read(fd, buf, count) WRITE = 1 # write(fd, buf, count) OPEN = 2 # open(path, flags) - Linux O_* CLOSE = 3 # close(fd) STAT = 4 # stat(path, fileinfo*) - legacy struct FSTAT = 5 # fstat(fd, struct stat*) POLL = 7 # poll(fds, nfds, timeout) - stub 0 SEEK = 8 # lseek(fd, offset, whence) -> new offset MMAP = 9 # mmap(addr, len, prot, flags, fd, off) MPROTECT = 10 # mprotect(addr, len, prot) MUNMAP = 11 # munmap(addr, len) BRK = 12 # brk(addr) -> new break RT_SIGACTION = 13 # rt_sigaction() - bookkeeping RT_SIGPROCMASK = 14 # rt_sigprocmask() - bookkeeping RT_SIGRETURN = 15 # rt_sigreturn() IOCTL = 16 # ioctl(fd, request, arg) PREAD64 = 17 # pread64(fd, buf, count, offset) PWRITE64 = 18 # pwrite64(fd, buf, count, offset) READV = 19 # readv(fd, iov, iovcnt) WRITEV = 20 # writev(fd, iov, iovcnt) ACCESS = 21 # access(path, mode) PIPE = 22 # pipe(int[2]) YIELD = 24 # sched_yield() MREMAP = 25 # mremap(old, oldsz, newsz, flags, newaddr) MADVISE = 28 # madvise(addr, len, advice) DUP = 32 # dup(fd) DUP2 = 33 # dup2(oldfd, newfd) NANOSLEEP = 35 # nanosleep(req, rem) GETPID = 39 # getpid() CLONE = 56 # clone(flags, stack, ptid, ctid, tls) FORK = 57 # fork() - copy address space SPAWN = 59 # execve(path, argv, envp) THREAD_EXIT = 60 # exit(): retire current thread WAIT4 = 61 # wait4(pid, status, options, rusage) KILL = 62 # kill(pid, sig) - bookkeeping UNAME = 63 # uname(utsname*) FCNTL = 72 # fcntl(fd, cmd, arg) GETCWD = 79 # getcwd(buf, size) CHDIR = 80 # chdir(path) RENAME = 82 # rename(old, new) MKDIR = 83 # mkdir(path) RMDIR = 84 # rmdir(path) REMOVE = 87 # unlink(path) READLINK = 89 # readlink(path, buf, size) - no symlinks GETUID = 102 # getuid() GETGID = 104 # getgid() GETEUID = 107 # geteuid() GETEGID = 108 # getegid() GETTIMEOFDAY = 96 # gettimeofday(tv, tz) GETPPID = 110 # getppid() ARCH_PRCTL = 158 # arch_prctl(code, addr) REBOOT_LINUX = 169 # reboot(magic1, magic2, cmd, arg) GETTID = 186 # gettid() FUTEX = 202 # futex(uaddr, op, val, timeout) GETDENTS64 = 217 # getdents64(fd, buf, count) SET_TID_ADDRESS = 218 # set_tid_address(tidptr) CLOCK_GETTIME = 228 # clock_gettime(clockid, timespec*) CLOCK_GETRES = 229 # clock_getres(clockid, timespec*) EXIT = 231 # exit_group(): terminate all threads OPENAT = 257 # openat(dirfd, path, flags, mode) NEWFSTATAT = 262 # newfstatat(dirfd, path, stat*, flags) UNLINKAT = 263 # unlinkat(dirfd, path, flags) READLINKAT = 267 # readlinkat(dirfd, path, buf, size) FACCESSAT = 269 # faccessat(dirfd, path, mode, flags) SET_ROBUST_LIST = 273 # set_robust_list(head, len) DUP3 = 292 # dup3(oldfd, newfd, flags) PIPE2 = 293 # pipe2(int[2], flags) PRLIMIT64 = 302 # prlimit64(pid, resource, new, old) GETRANDOM = 318 # getrandom(buf, len, flags) STATX = 332 # statx(...) - -ENOSYS, glibc falls back RSEQ = 334 # rseq(rseq, len, flags) - -ENOSYS # ---- 临时私有段(0x1000+):Linux 无对应,逐条迁出 ---- LOAD_SO = 0x1006 # TODO(userspace): dynamic library loading SO_CALL1 = 0x1007 # TODO(userspace): dynamic library call REBOOT = 0x1008 # TODO(linux): reboot(169) SHUTDOWN = 0x1009 # TODO(linux): reboot(RB_POWER_OFF) SPAWN_PROC = 0x100A # TODO(userspace): Terminal run helper THREAD_CREATE_OLD = 0x100B # TODO(userspace): ViperOS thread_create # END GENERATED SYSCALL TABLE define IA32_EFER = 0xC0000080 define IA32_STAR = 0xC0000081 define IA32_LSTAR = 0xC0000082 define IA32_FMASK = 0xC0000084 # Reset via the 8042 controller; PCI reset and halt are fallbacks. def power_reboot(): serial.puts("[power] reboot\n") asm.cli() for _ in range(1000): if (asm.inb(0x64) & 0x02) == 0: break asm.outb(0x64, 0xFE) asm.outb(0xCF9, 0x02) asm.io_wait() asm.outb(0xCF9, 0x06) while True: asm.cli() asm.hlt() # ACPI S5 power-off. def power_shutdown(): serial.puts("[power] shutdown\n") asm.cli() acpi.poweroff() while True: asm.cli() asm.hlt() def wrmsr(msr: uint32, val: uint64): lo: uint32 = uint32(val & 0xFFFFFFFF) hi: uint32 = uint32(val >> 32) __asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)} mov eax, {asminp(lo, ASM_DESCR.REG_ANY)} mov edx, {asminp(hi, ASM_DESCR.REG_ANY)} wrmsr""", op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX]) def rdmsr(msr: uint32) -> uint64: lo: uint32 hi: uint32 # rdmsr 固定写 EDX:EAX;out 绑定 + CLOBBER_RAX/RDX 冲突会导致 lo/hi # 分配到非 EAX/EDX 读到垃圾。显式 mov 到输出寄存器(与 asm.inb 修复同模式)。 __asm__(f"""mov ecx, {asminp(msr, ASM_DESCR.REG_ANY)} rdmsr mov {asmout(lo, ASM_DESCR.OUTPUT_REG)}, eax mov {asmout(hi, ASM_DESCR.OUTPUT_REG)}, edx""", out=[asmout(lo, ASM_DESCR.OUTPUT_REG), asmout(hi, ASM_DESCR.OUTPUT_REG)], op=[ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RDX]) return uint64(lo) | (uint64(hi) << 32) def _current_proc() -> process.Process*: return process.ProcessManager.current() def _fd_alloc(p: process.Process*) -> int: for i in range(process.PROC_MAX_FDS): if not p.fd_used[i]: p.fd_used[i] = 1 p.fd_flags[i] = 0 p.fd_shared[i] = 0 return i return -1 def _fd_free(p: process.Process*, fd: int): if fd >= 0 and fd < process.PROC_MAX_FDS: p.fd_used[fd] = None p.fd_table[fd] = None p.fd_kind[fd] = 0 p.fd_flags[fd] = 0 p.fd_shared[fd] = 0 # dup()/dup2() share the same underlying object; the last close wins. def _fd_other_ref(p: process.Process*, fd: int) -> int: fp: uint64 = uint64(p.fd_table[fd]) k: uint8 = p.fd_kind[fd] for i in range(process.PROC_MAX_FDS): if i == fd: continue if not p.fd_used[i]: continue if p.fd_kind[i] != k: continue if uint64(p.fd_table[i]) == fp: return 1 return 0 def _dir_alloc(p: process.Process*) -> int: for i in range(process.PROC_MAX_DIRS): if not p.dir_used[i]: p.dir_used[i] = 1 return i return -1 def _dir_free(p: process.Process*, dd: int): if dd >= 0 and dd < process.PROC_MAX_DIRS: p.dir_used[dd] = None p.dir_table[dd] = None # ============================================================================ # Linux ABI helpers # ============================================================================ # All handlers return int: non-negative on success, -errno on failure. def _err(e: int) -> int: return 0 - e def _fr_errno(r: int) -> int: """Translate a FAT32 FRESULT into -errno (0 stays 0).""" if r == 0: return 0 if r == int(fat32_types.FRESULT.FR_NO_FILE) or r == int(fat32_types.FRESULT.FR_NO_PATH): return _err(abi.ENOENT) if r == int(fat32_types.FRESULT.FR_INVALID_NAME): return _err(abi.EINVAL) if r == int(fat32_types.FRESULT.FR_DENIED): return _err(abi.EACCES) if r == int(fat32_types.FRESULT.FR_EXIST): return _err(abi.EEXIST) if r == int(fat32_types.FRESULT.FR_INVALID_OBJECT): return _err(abi.EBADF) if r == int(fat32_types.FRESULT.FR_WRITE_PROTECTED): return _err(abi.EROFS) if r == int(fat32_types.FRESULT.FR_NOT_ENOUGH_CORE): return _err(abi.ENOMEM) if r == int(fat32_types.FRESULT.FR_TOO_MANY_OPEN_FILES): return _err(abi.EMFILE) if r == int(fat32_types.FRESULT.FR_INVALID_PARAMETER): return _err(abi.EINVAL) if r == int(fat32_types.FRESULT.FR_TIMEOUT): return _err(abi.EIO) if r == int(fat32_types.FRESULT.FR_LOCKED): return _err(abi.EBUSY) return _err(abi.EIO) def _open_mode(flags: uint32) -> uint8: """Linux O_* -> FatFs FA_* access/create mode.""" m: int = 0 acc: uint32 = flags & uint32(abi.O_ACCMODE) if acc == uint32(abi.O_RDONLY): m = int(fat32_types.FA_READ) elif acc == uint32(abi.O_WRONLY): m = int(fat32_types.FA_WRITE) else: m = int(fat32_types.FA_READ) | int(fat32_types.FA_WRITE) if (flags & uint32(abi.O_EXCL)) != 0 and (flags & uint32(abi.O_CREAT)) != 0: m = m | int(fat32_types.FA_CREATE_NEW) elif (flags & uint32(abi.O_TRUNC)) != 0: m = m | int(fat32_types.FA_CREATE_ALWAYS) elif (flags & uint32(abi.O_CREAT)) != 0: m = m | int(fat32_types.FA_OPEN_ALWAYS) elif (flags & uint32(abi.O_APPEND)) != 0: m = m | int(fat32_types.FA_OPEN_APPEND) return uint8(m) # Resolve `path` against `cwd` (dirfd == AT_FDCWD) or require an absolute path. # Directory-relative lookups from a dir fd are added with openat (P1). def _resolve_path(dirfd: int, path: const str, out: char*) -> int: if path is None: return _err(abi.EFAULT) n: int = 0 if path[0] == 47: while path[n] != 0: if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) out[n] = path[n] n += 1 out[n] = 0 return 0 if dirfd != abi.AT_FDCWD: return _err(abi.ENOTSUP) p: process.Process* = _current_proc() c: int = 0 while p.cwd[c] != 0: if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) out[n] = p.cwd[c] n += 1 c += 1 if n > 0 and out[n - 1] != 47: out[n] = 47 n += 1 i: int = 0 while path[i] != 0: if n >= abi.PATH_MAX - 1: return _err(abi.ENAMETOOLONG) out[n] = path[i] n += 1 i += 1 out[n] = 0 return 0 # Fill the common fields of a Linux struct stat. Times stay 0: FAT32 stores # DOS timestamps, which are not converted yet (P1 with newfstatat). def _fill_stat(st: abi.linux_stat*, size: uint64, mode: uint32, ino: uint64): string.memset(rawptr(st), 0, uint64(abi.SIZEOF_LINUX_STAT)) st.st_ino = ino st.st_nlink = 1 st.st_mode = mode st.st_size = size st.st_blksize = 512 st.st_blocks = (size + 511) / 512 # Software DRNG for getrandom(): no hardware entropy requirement, only used # for stack canaries and similar non-cryptographic purposes (see docs). _rng_state: uint64 = 0x9E3779B97F4A7C15 def _rng_next() -> uint64: global _rng_state # 64-bit LCG; the high bits are returned because low LCG bits are weak. _rng_state = _rng_state * uint64(6364136223846793005) + uint64(1442695040888963407) return _rng_state >> 16 def EXIT(code: int) -> int: serial.puts("[syscall] exit called\n") p: process.Process* = _current_proc() p.exit_code = code # S3e:不再在这里拆窗口 —— 窗口/输入已整体交给用户态图形壳 explorer(/dev/desk 共享区), # 内核不再持有任何窗口语义。 # Linux exit_group:退役本进程的**所有**线程,绝不返回用户态。 # (THREAD_EXIT=60 只退当前线程。) # 只 destroy 窗口不够:app 的 _start 在 process.exit(0) 之后还有 `return 0`,而内核是以 # rsp = USER_STACK_VTOP 进入 _start 的(elf.vp),一旦收尾 `ret` 就会去读 [VTOP]: # #PF cr2=rsp=0x7fffffe00000,rip 在 _start 内,rbp=0,rax=退出码(实测现场)。 sched.Scheduler.exit_process(p.pid) # 【硬保护】exit_process 不返回;这里仅作兜底。 while True: sched.Scheduler._yield() return 0 _loaded_so_base: rawptr = None _loaded_so_path: [64]char def LOAD_SO(path_ptr: str) -> uint64: global _loaded_so_base path: const str = path_ptr serial.puts("[so] loading: ") serial.puts(path) serial.puts("\n") base: rawptr = elf.load_so(path) if base is not None: _loaded_so_base = base for i in range(63): ch: char = path[i] _loaded_so_path[i] = ch if ch == 0: break _loaded_so_path[63] = 0 return uint64(base) return 0 def SO_CALL1(sym_name_ptr: str, arg1: rawptr) -> int: if _loaded_so_base is None: return -1 sym_name: const str = sym_name_ptr fn: rawptr = elf.get_so_symbol(_loaded_so_base, sym_name, &_loaded_so_path[0]) if fn is None: serial.puts("[so] symbol not found\n") return -2 __asm__(f"""mov rdi, {asminp(arg1, ASM_DESCR.REG_ANY)} call {asminp(fn, ASM_DESCR.REG_ANY)}""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) serial.puts("[so] call returned\n") return 0 def GETPID() -> uint64: """getpid():当前进程 pid。/dev/desk 的事件按 pid 过滤,app 需要它。""" return uint64(process.current()) def _is_dev_path(path: const str) -> int: """路径是否以 "/dev/" 开头(走 devfs 设备节点而不是 FAT32)。逐字符比较,不引 str 库。""" if path[0] != 47: return 0 # '/' if path[1] != 100: return 0 # 'd' if path[2] != 101: return 0 # 'e' if path[3] != 118: return 0 # 'v' if path[4] != 47: return 0 # '/' return 1 def OPEN(path_ptr: str, mode: uint32) -> int: p: process.Process* = _current_proc() path: const str = path_ptr # /dev/* 走 devfs 设备节点(serial / 输入 / fb…),其余仍走 FAT32 文件系统。 # 判据用逐字符比较(不引 str 库函数):"/dev/" = 47 '/'、100 'd'、101 'e'、118 'v'、47 '/'。 if _is_dev_path(path): dfd: int = devfs.devfs_open(path, int(mode)) if dfd < 0: return _err(abi.ENOENT) fd: int = _fd_alloc(p) if fd < 0: devfs.devfs_close(dfd) return _err(abi.EMFILE) # 存 devfs_fd+1:devfs fd 0 是合法值,直接用会与"空槽位"混淆 p.fd_table[fd] = rawptr(uint64(dfd + 1)) p.fd_kind[fd] = process.FD_KIND_DEVFS p.fd_flags[fd] = mode return fd fat32.lock() fp: rawptr = fat32.open(path, _open_mode(mode)) if not fp: fat32.unlock() return _err(abi.ENOENT) fd: int = _fd_alloc(p) if fd < 0: fat32.close(fp) fat32.unlock() return _err(abi.EMFILE) p.fd_table[fd] = fp p.fd_kind[fd] = process.FD_KIND_FAT32 p.fd_flags[fd] = mode fat32.unlock() return fd def CLOSE(fd: int) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: return _err(abi.EBADF) # Pipes carry their own reference counts: every inherited/dup'd end was # counted, so always decrement (fork's shared flag must not skip this). if p.fd_kind[fd] == process.FD_KIND_PIPE: res_pipe: int = _pipe_close(fp) _fd_free(p, fd) return res_pipe # fork() copies the fd table; shared entries must not close the # underlying object (the parent still owns it). if p.fd_shared[fd] != 0: _fd_free(p, fd) return 0 # dup()/dup2() aliases: only the last close releases the object. if _fd_other_ref(p, fd) != 0: _fd_free(p, fd) return 0 if p.fd_kind[fd] == process.FD_KIND_DEVFS: res_dev: int = devfs.devfs_close(int(uint64(fp)) - 1) _fd_free(p, fd) if res_dev < 0: return _err(abi.EIO) return 0 if p.fd_kind[fd] == process.FD_KIND_DIR: dd: int = int(uint64(fp)) - 1 if dd >= 0 and dd < process.PROC_MAX_DIRS: fat32.lock() fat32.closedir(&p.dir_pool[dd]) fat32.unlock() _dir_free(p, dd) _fd_free(p, fd) return 0 fat32.lock() res: int = int(fat32.close(fp)) fat32.unlock() _fd_free(p, fd) return _fr_errno(res) def READ(fd: int, buf_ptr: rawptr, count: uint32) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: return _err(abi.EBADF) if p.fd_kind[fd] == process.FD_KIND_DIR: return _err(abi.EISDIR) if p.fd_kind[fd] == process.FD_KIND_PIPE: return _pipe_read(fp, buf_ptr, count) if p.fd_kind[fd] == process.FD_KIND_DEVFS: r: int = devfs.devfs_read(int(uint64(fp)) - 1, buf_ptr, count) if r < 0: return _err(abi.EIO) return r fat32.lock() br: uint32 = fat32.read(fp, buf_ptr, count) fat32.unlock() return int(br) def WRITE(fd: int, buf_ptr: rawptr, count: uint32) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: return _err(abi.EBADF) if p.fd_kind[fd] == process.FD_KIND_DIR: return _err(abi.EISDIR) if p.fd_kind[fd] == process.FD_KIND_PIPE: return _pipe_write(fp, buf_ptr, count) if p.fd_kind[fd] == process.FD_KIND_DEVFS: r: int = devfs.devfs_write(int(uint64(fp)) - 1, buf_ptr, count) if r < 0: return _err(abi.EIO) return r fat32.lock() bw: uint32 = fat32.write(fp, buf_ptr, count) fat32.unlock() return int(bw) def IOCTL(fd: int, request: uint32, arg: rawptr) -> int: """设备控制(Linux ioctl 的子集):当前只有 devfs 设备支持,用于 /dev/fb0 查几何。""" p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) if p.fd_kind[fd] != process.FD_KIND_DEVFS: return _err(abi.ENOTTY) r: int = devfs.devfs_ioctl(int(uint64(p.fd_table[fd])) - 1, request, arg) if r < 0: return _err(abi.EINVAL) return r def SEEK(fd: int, offset: uint64, whence: uint32) -> int: """lseek(fd, offset, whence) -> new offset, or -errno. offset is 64-bit on the ABI side; FAT32 caps files at 4 GiB so anything beyond that range is rejected with -EINVAL. Whence: 0=SET 1=CUR 2=END. """ p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) fp: rawptr = p.fd_table[fd] if not fp: return _err(abi.EBADF) if p.fd_kind[fd] == process.FD_KIND_DIR: return _err(abi.EINVAL) if p.fd_kind[fd] == process.FD_KIND_PIPE: return _err(abi.ESPIPE) if p.fd_kind[fd] == process.FD_KIND_DEVFS: return _err(abi.ESPIPE) fat32.lock() base: int = 0 if whence == uint32(abi.SEEK_CUR): base = int(fat32.tell(fp)) elif whence == uint32(abi.SEEK_END): base = int(fat32.size(fp)) elif whence != uint32(abi.SEEK_SET): fat32.unlock() return _err(abi.EINVAL) tgt: int = base + int(offset) if tgt < 0 or tgt > 0x7FFFFFFF: fat32.unlock() return _err(abi.EINVAL) r: int = int(fat32.seek(fp, uint32(tgt))) fat32.unlock() if r != 0: return _fr_errno(r) return tgt def MKDIR(path_ptr: str) -> int: path: const str = path_ptr fat32.lock() r: int = int(fat32.mkdir(path)) fat32.unlock() return _fr_errno(r) def REMOVE(path_ptr: str) -> int: path: const str = path_ptr fat32.lock() r: int = int(fat32.remove(path)) fat32.unlock() return _fr_errno(r) # ---------------- P1: file system ABI ---------------- def _open_dir(path: const str) -> int: p: process.Process* = _current_proc() dd: int = _dir_alloc(p) if dd < 0: return _err(abi.EMFILE) dp: fat32_types.dirobj* = &p.dir_pool[dd] fat32.lock() res: int = int(fat32.opendir(path, dp)) fat32.unlock() if res != 0: _dir_free(p, dd) return _fr_errno(res) p.dir_table[dd] = rawptr(uint64(dp)) fd: int = _fd_alloc(p) if fd < 0: fat32.lock() fat32.closedir(dp) fat32.unlock() _dir_free(p, dd) return _err(abi.EMFILE) p.fd_table[fd] = rawptr(uint64(dd + 1)) p.fd_kind[fd] = process.FD_KIND_DIR return fd def OPENAT(dirfd: int, path_ptr: str, flags: uint32, mode: uint32) -> int: path: const str = path_ptr buf: [256]char r: int = _resolve_path(dirfd, path, &buf[0]) if r != 0: return r if (flags & uint32(abi.O_DIRECTORY)) != 0: return _open_dir(&buf[0]) return OPEN(&buf[0], flags) def GETDENTS64(fd: int, buf_ptr: rawptr, count: uint64) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) if p.fd_kind[fd] != process.FD_KIND_DIR: return _err(abi.ENOTDIR) dd: int = int(uint64(p.fd_table[fd])) - 1 if dd < 0 or dd >= process.PROC_MAX_DIRS: return _err(abi.EBADF) dp: fat32_types.dirobj* = &p.dir_pool[dd] info: fat32_types.fileinfo fat32.lock() r: int = int(fat32.readdir(dp, &info)) fat32.unlock() if r != 0: if r == int(fat32_types.FRESULT.FR_NO_FILE): return 0 return _fr_errno(r) name: str = &info.fname[0] n: int = int(string.strlen(name)) reclen: int = (19 + n + 1 + 7) / 8 * 8 if uint64(reclen) > count: return _err(abi.EINVAL) d: abi.linux_dirent64* = buf_ptr d.d_ino = uint64(1) d.d_off = uint64(0) d.d_reclen = uint16(reclen) if (int(info.attr) & 0x10) != 0: d.d_type = uint8(abi.DT_DIR) else: d.d_type = uint8(abi.DT_REG) dst: char* = char*(uint64(buf_ptr) + 19) i: int = 0 while i < n: dst[i] = name[i] i += 1 dst[n] = 0 i = n + 1 while i < reclen - 19: dst[i] = 0 i += 1 return reclen def FSTAT(fd: int, st_ptr: rawptr) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) if st_ptr is None: return _err(abi.EFAULT) st: abi.linux_stat* = st_ptr if p.fd_kind[fd] == process.FD_KIND_DEVFS: _fill_stat(st, uint64(0), uint32(abi.MODE_CHAR), uint64(fd + 1)) elif p.fd_kind[fd] == process.FD_KIND_DIR: _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(fd + 1)) elif p.fd_kind[fd] == process.FD_KIND_PIPE: _fill_stat(st, uint64(0), uint32(abi.MODE_FIFO), uint64(fd + 1)) else: fp: rawptr = p.fd_table[fd] size: uint64 = uint64(fat32.size(fp)) _fill_stat(st, size, uint32(abi.MODE_FILE), uint64(fd + 1)) return 0 def NEWFSTATAT(dirfd: int, path_ptr: str, st_ptr: rawptr, flags: uint32) -> int: if st_ptr is None: return _err(abi.EFAULT) path: const str = path_ptr if path is None: return _err(abi.EFAULT) if path[0] == 0: if (flags & uint32(abi.AT_EMPTY_PATH)) != 0: return FSTAT(dirfd, st_ptr) return _err(abi.ENOENT) st: abi.linux_stat* = st_ptr buf: [256]char r: int = _resolve_path(dirfd, path, &buf[0]) if r != 0: return r if _is_dev_path(&buf[0]): _fill_stat(st, uint64(0), uint32(abi.MODE_CHAR), uint64(1)) return 0 if buf[0] == 47 and buf[1] == 0: _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(1)) return 0 info: fat32_types.fileinfo fat32.lock() res: int = int(fat32.stat(&buf[0], &info)) fat32.unlock() if res != 0: return _fr_errno(res) if (int(info.attr) & 0x10) != 0: _fill_stat(st, uint64(0), uint32(abi.MODE_DIR), uint64(1)) else: _fill_stat(st, uint64(info.file_size), uint32(abi.MODE_FILE), uint64(1)) return 0 def DUP(fd: int) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) nfd: int = _fd_alloc(p) if nfd < 0: return _err(abi.EMFILE) p.fd_table[nfd] = p.fd_table[fd] p.fd_kind[nfd] = p.fd_kind[fd] p.fd_flags[nfd] = p.fd_flags[fd] p.fd_shared[nfd] = p.fd_shared[fd] if p.fd_kind[nfd] == process.FD_KIND_PIPE: _pipe_add_ref(p.fd_table[nfd]) return nfd def DUP2(oldfd: int, newfd: int) -> int: p: process.Process* = _current_proc() if oldfd < 0 or oldfd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[oldfd]: return _err(abi.EBADF) if newfd < 0 or newfd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if oldfd == newfd: return newfd if p.fd_used[newfd]: r: int = CLOSE(newfd) if r != 0: return r p.fd_used[newfd] = 1 p.fd_table[newfd] = p.fd_table[oldfd] p.fd_kind[newfd] = p.fd_kind[oldfd] p.fd_flags[newfd] = p.fd_flags[oldfd] p.fd_shared[newfd] = p.fd_shared[oldfd] if p.fd_kind[newfd] == process.FD_KIND_PIPE: _pipe_add_ref(p.fd_table[newfd]) return newfd def DUP3(oldfd: int, newfd: int, flags: uint32) -> int: return DUP2(oldfd, newfd) def FCNTL(fd: int, cmd: uint32, arg: uint64) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) c: int = int(cmd) if c == abi.F_DUPFD or c == abi.F_DUPFD_CLOEXEC: return DUP(fd) if c == abi.F_GETFD: return 0 if c == abi.F_SETFD: return 0 if c == abi.F_GETFL: return int(p.fd_flags[fd]) if c == abi.F_SETFL: p.fd_flags[fd] = uint32(arg) return 0 return _err(abi.EINVAL) def PREAD64(fd: int, buf_ptr: rawptr, count: uint32, offset: uint64) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) if p.fd_kind[fd] != process.FD_KIND_FAT32: return _err(abi.ESPIPE) cur: int = SEEK(fd, uint64(0), uint32(abi.SEEK_CUR)) if cur < 0: return cur r: int = SEEK(fd, offset, uint32(abi.SEEK_SET)) if r < 0: return r n: int = READ(fd, buf_ptr, count) SEEK(fd, uint64(cur), uint32(abi.SEEK_SET)) return n def PWRITE64(fd: int, buf_ptr: rawptr, count: uint32, offset: uint64) -> int: p: process.Process* = _current_proc() if fd < 0 or fd >= process.PROC_MAX_FDS: return _err(abi.EBADF) if not p.fd_used[fd]: return _err(abi.EBADF) if p.fd_kind[fd] != process.FD_KIND_FAT32: return _err(abi.ESPIPE) cur: int = SEEK(fd, uint64(0), uint32(abi.SEEK_CUR)) if cur < 0: return cur r: int = SEEK(fd, offset, uint32(abi.SEEK_SET)) if r < 0: return r n: int = WRITE(fd, buf_ptr, count) SEEK(fd, uint64(cur), uint32(abi.SEEK_SET)) return n def RENAME(old_ptr: str, new_ptr: str) -> int: oldp: const str = old_ptr newp: const str = new_ptr fat32.lock() r: int = int(fat32.rename(oldp, newp)) fat32.unlock() return _fr_errno(r) def RMDIR(path_ptr: str) -> int: path: const str = path_ptr fat32.lock() r: int = int(fat32.rmdir(path)) fat32.unlock() return _fr_errno(r) def UNLINKAT(dirfd: int, path_ptr: str, flags: uint32) -> int: path: const str = path_ptr buf: [256]char r: int = _resolve_path(dirfd, path, &buf[0]) if r != 0: return r if (flags & uint32(abi.AT_REMOVEDIR)) != 0: return RMDIR(&buf[0]) return REMOVE(&buf[0]) def STAT(path_ptr: str, info_ptr: rawptr) -> int: path: const str = path_ptr info: fat32_types.fileinfo* = info_ptr fat32.lock() r: int = int(fat32.stat(path, info)) fat32.unlock() return _fr_errno(r) # Private launcher kept for Terminal's `run` command (a new child, caller stays alive). def SPAWN_PROC(path_ptr: str, blocking: int) -> int: path: const str = path_ptr pid: int = elf.spawn_elf(path) if pid < 0: return _err(abi.ENOENT) if blocking: p: process.Process* = process.ProcessManager.get_process(pid) if p: while p.state != process.PROC_DONE: sched.Scheduler.try_reschedule() return pid def WAIT4(pid: int, status_ptr: rawptr, options: uint32, rusage: rawptr) -> int: cur: int = process.current() while True: found_child: int = 0 i: int = 0 while i < process.MAX_PROCESSES: p: process.Process* = process.ProcessManager.get_process(i) if p is not None: matches: int = 0 if pid <= 0: matches = 1 # any child (pid 0: same group, approximated) elif p.pid == pid: matches = 1 if matches != 0 and p.pid != cur and p.parent_pid == cur: found_child = 1 if p.state == process.PROC_DONE: if status_ptr is not None: st: int* = int*(status_ptr) st[0] = (p.exit_code & 0xFF) << 8 p.parent_pid = -1 return p.pid i += 1 if found_child == 0: return _err(abi.ECHILD) if (options & uint32(1)) != 0: # WNOHANG return 0 sched.Scheduler.yield_now() # mmap bump allocator and brk base must not overlap (both live below the stack). define USER_HEAP_BASE = 0x20000000 define USER_BRK_BASE = 0x40000000 define USER_VA_LIMIT = 0x7FFFFF000000 # fd = -1(Linux 匿名映射约定) define MMAP_FD_ANON: uint64 = 0xFFFFFFFFFFFFFFFF # ---- VMA bookkeeping ------------------------------------------------------- def _vma_find(p: process.Process*, addr: uint64) -> int: for i in range(process.MAX_VMAS): if p.vma_kind[i] == 0: continue b: uint64 = p.vma_base[i] if addr >= b and addr < b + p.vma_size[i]: return i return -1 def _vma_overlap(p: process.Process*, start: uint64, size: uint64) -> int: if size == 0: return 0 end: uint64 = start + size for i in range(process.MAX_VMAS): if p.vma_kind[i] == 0: continue b: uint64 = p.vma_base[i] e: uint64 = b + p.vma_size[i] if start < e and b < end: return 1 return 0 def _vma_add(p: process.Process*, base: uint64, size: uint64, prot: uint32, kind: int, flags: uint32) -> int: for i in range(process.MAX_VMAS): if p.vma_kind[i] == 0: p.vma_base[i] = base p.vma_size[i] = size p.vma_prot[i] = prot p.vma_kind[i] = uint8(kind) p.vma_flags[i] = flags return 0 return _err(abi.ENOMEM) def _vma_del(p: process.Process*, i: int): p.vma_base[i] = 0 p.vma_size[i] = 0 p.vma_prot[i] = 0 p.vma_kind[i] = 0 p.vma_flags[i] = 0 def _unmap_pages(p: process.Process*, start: uint64, end: uint64, free_pages: int): va: uint64 = start while va < end: if free_pages != 0: phys: uint64 = paging.GetPagePhys(va) if phys != 0: mm.free_page(rawptr(phys)) paging.UnMapPage(va) va = va + uint64(4096) # Remove [addr, addr+length) from the VMA table, splitting partially covered # areas, and release the backing frames of anon/file mappings. def _vma_unmap_range(p: process.Process*, addr: uint64, length: uint64) -> int: if length == 0: return 0 end: uint64 = addr + length i: int = 0 while i < process.MAX_VMAS: if p.vma_kind[i] != 0: b: uint64 = p.vma_base[i] e: uint64 = b + p.vma_size[i] if b < end and addr < e: s: uint64 = addr if b > s: s = b t: uint64 = end if e < t: t = e free_pages: int = 1 if p.vma_kind[i] == uint8(process.VMA_KIND_DEV): free_pages = 0 _unmap_pages(p, s, t, free_pages) if s <= b and t >= e: _vma_del(p, i) elif s <= b: p.vma_base[i] = t p.vma_size[i] = e - t elif t >= e: p.vma_size[i] = s - b else: r: int = _vma_add(p, t, e - t, uint32(p.vma_prot[i]), int(p.vma_kind[i]), uint32(p.vma_flags[i])) if r != 0: return r p.vma_size[i] = s - b i += 1 return 0 def MMAP(addr: uint64, length: uint64, prot: uint64, flags: uint64, fd: uint64, offset: uint64) -> uint64: """mmap(addr, length, prot, flags, fd, offset) -> base, or MAP_FAILED (-1). · MAP_ANONYMOUS: fresh zeroed pages; · FAT32 fd: file contents copied into fresh pages (MAP_PRIVATE semantics); · devfs fd: the device physical range is mapped directly (no frame owns it). """ p: process.Process* = _current_proc() if length == 0: return abi.MAP_FAILED aligned: uint64 = (length + uint64(0xFFF)) & ~uint64(0xFFF) if aligned == 0: return abi.MAP_FAILED fixed: int = 0 if (flags & uint64(abi.MAP_FIXED)) != 0: fixed = 1 base: uint64 = 0 if fixed != 0: if (addr & uint64(0xFFF)) != 0: return abi.MAP_FAILED base = addr if base < uint64(0x10000) or base + aligned > uint64(USER_VA_LIMIT): return abi.MAP_FAILED if p.brk_end > p.brk_start and base < p.brk_end and p.brk_start < base + aligned: return abi.MAP_FAILED else: if p.heap_start == 0: p.heap_start = USER_HEAP_BASE base = p.heap_start + p.heap_size while True: brk_hit: int = 0 if p.brk_end > p.brk_start and base < p.brk_end and p.brk_start < base + aligned: brk_hit = 1 if _vma_overlap(p, base, aligned) == 0 and brk_hit == 0: break p.heap_size = p.heap_size + aligned base = p.heap_start + p.heap_size if base + aligned > uint64(USER_VA_LIMIT): return abi.MAP_FAILED if base + aligned > uint64(USER_VA_LIMIT): return abi.MAP_FAILED # classify the mapping source kind: int = process.VMA_KIND_ANON uncached: int = 0 dev_phys: uint64 = 0 dev_size: uint64 = 0 fp: rawptr = None if (flags & uint64(abi.MAP_ANONYMOUS)) != 0: kind = process.VMA_KIND_ANON elif fd == MMAP_FD_ANON: return abi.MAP_FAILED else: fdi: int = int(fd) if fdi < 0 or fdi >= process.PROC_MAX_FDS: return abi.MAP_FAILED if not p.fd_used[fdi]: return abi.MAP_FAILED if p.fd_kind[fdi] == process.FD_KIND_DEVFS: kind = process.VMA_KIND_DEV if (offset & uint64(0xFFF)) != 0: return abi.MAP_FAILED if devfs.devfs_get_mmap_info(int(uint64(p.fd_table[fdi])) - 1, &dev_phys, &dev_size) != 0: return abi.MAP_FAILED if devfs.devfs_mmap_uncached(int(uint64(p.fd_table[fdi])) - 1) != 0: uncached = 1 if offset + aligned > dev_size: return abi.MAP_FAILED elif p.fd_kind[fdi] == process.FD_KIND_FAT32: kind = process.VMA_KIND_FILE if (offset & uint64(0xFFF)) != 0: return abi.MAP_FAILED if offset > uint64(0xFFFFFFFF): return abi.MAP_FAILED fp = p.fd_table[fdi] else: return abi.MAP_FAILED if fixed != 0: ur: int = _vma_unmap_range(p, base, aligned) if ur != 0: return abi.MAP_FAILED pte: uint64 = paging.PTE_PRESENT | paging.PTE_USER if (prot & uint64(abi.PROT_WRITE)) != 0: pte = pte | paging.PTE_WRITABLE if uncached != 0: # MMIO (framebuffer). Use PAT write-combining when the CPU exposes it; # otherwise leave the mapping write-back (fast sequential blits, and the # compositor does not rely on UC for visibility). UC (PTE_PCD) made # dragging noticeably slower on VMware. if paging.has_pat_wc() != 0: pte = pte | paging.PTE_PAT # 这一段会连续调用 MapPage,而 MapPage 依赖**全局 paging.pml4**。若中途被 # 定时器抢占并切换进程,paging.pml4 会被 switch_pid 改成别的进程,后续页就 # 映射进错误的地址空间(本进程留下未映射空洞;用户写空洞时若命中 identity # 大页就会直接改物理内存 → BAD blk / 画面花屏)。整个映射循环必须原子。 asm.cli() va: uint64 = base if kind == process.VMA_KIND_FILE: fat32.lock() oldpos: uint32 = fat32.tell(fp) fat32.seek(fp, uint32(offset)) fat32.unlock() done: uint64 = 0 while va < base + aligned: fphys: rawptr = mm.alloc_page() if fphys is None: fat32.lock() fat32.seek(fp, oldpos) fat32.unlock() _unmap_pages(p, base, va, 1) asm.sti() return abi.MAP_FAILED string.memset(fphys, 0, 4096) chunk: uint32 = 4096 if length - done < uint64(4096): chunk = uint32(length - done) if chunk > 0: fat32.lock() got: int = fat32.read(fp, fphys, chunk) fat32.unlock() if got > 0: done = done + uint64(got) paging.MapPage(va, uint64(fphys), pte) va = va + uint64(4096) fat32.lock() fat32.seek(fp, oldpos) fat32.unlock() else: while va < base + aligned: if kind == process.VMA_KIND_DEV: paging.MapPage(va, dev_phys + offset + (va - base), pte) else: aphys: rawptr = mm.alloc_page() if aphys is None: _mf: [128]char viperlib.snprintf(&_mf, 128, "[mmap] FAIL pid=%d va=0x%lx len=0x%lx\n", p.pid, va, aligned) serial.puts(&_mf) _unmap_pages(p, base, va, 1) asm.sti() return abi.MAP_FAILED string.memset(aphys, 0, 4096) paging.MapPage(va, uint64(aphys), pte) va = va + uint64(4096) r: int = _vma_add(p, base, aligned, uint32(prot), kind, uint32(flags)) if r != 0: if kind == process.VMA_KIND_DEV: _unmap_pages(p, base, base + aligned, 0) else: _unmap_pages(p, base, base + aligned, 1) asm.sti() return abi.MAP_FAILED if fixed == 0: p.heap_size = p.heap_size + aligned asm.sti() return base def MUNMAP(ptr: uint64, size: uint64) -> int: if size == 0: return _err(abi.EINVAL) start: uint64 = ptr & ~uint64(0xFFF) end: uint64 = (ptr + size + uint64(0xFFF)) & ~uint64(0xFFF) if end <= start: return _err(abi.EINVAL) if start < uint64(0x10000) or end > uint64(USER_VA_LIMIT): return _err(abi.EINVAL) asm.cli() _mr: int = _vma_unmap_range(_current_proc(), start, end - start) asm.sti() return _mr def MPROTECT(addr: uint64, length: uint64, prot: uint64) -> int: p: process.Process* = _current_proc() if length == 0: return 0 start: uint64 = addr & ~uint64(0xFFF) end: uint64 = (addr + length + uint64(0xFFF)) & ~uint64(0xFFF) if end <= start: return _err(abi.EINVAL) if start < uint64(0x10000) or end > uint64(USER_VA_LIMIT): return _err(abi.EINVAL) va: uint64 = start while va < end: # Kernel-loaded ELF segments are not in the VMA table; accept any # present 4KB page so RELRO mprotect() on the main image still works. if _vma_find(p, va) < 0 and paging.GetPagePhys(va) == 0: return _err(abi.ENOMEM) va = va + uint64(4096) # Snapshot the VMAs that overlap: splitting below adds new entries. idx: [64]int n: int = 0 for i in range(process.MAX_VMAS): if p.vma_kind[i] == 0: continue b: uint64 = p.vma_base[i] e: uint64 = b + p.vma_size[i] if b < end and start < e: idx[n] = i n += 1 j: int = 0 while j < n: i: int = idx[j] b: uint64 = p.vma_base[i] e: uint64 = b + p.vma_size[i] s: uint64 = start if b > s: s = b t: uint64 = end if e < t: t = e old_prot: uint32 = uint32(p.vma_prot[i]) kind: int = int(p.vma_kind[i]) vflags: uint32 = uint32(p.vma_flags[i]) if s <= b and t >= e: p.vma_prot[i] = uint32(prot) elif s <= b: r: int = _vma_add(p, t, e - t, old_prot, kind, vflags) if r != 0: return r p.vma_size[i] = t - b p.vma_prot[i] = uint32(prot) elif t >= e: r = _vma_add(p, s, e - s, uint32(prot), kind, vflags) if r != 0: return r p.vma_size[i] = s - b else: r = _vma_add(p, s, t - s, uint32(prot), kind, vflags) if r != 0: return r r = _vma_add(p, t, e - t, old_prot, kind, vflags) if r != 0: return r p.vma_size[i] = s - b j += 1 pte: uint64 = paging.PTE_PRESENT | paging.PTE_USER if (prot & uint64(abi.PROT_WRITE)) != 0: pte = pte | paging.PTE_WRITABLE va = start while va < end: if paging.SetPageFlags(va, pte) == 0: return _err(abi.ENOMEM) va = va + uint64(4096) return 0 def BRK(addr: uint64) -> uint64: p: process.Process* = _current_proc() if p.brk_start == 0: p.brk_start = USER_BRK_BASE p.brk_end = USER_BRK_BASE if addr == 0: return p.brk_end if addr < p.brk_start or addr > uint64(USER_VA_LIMIT): return p.brk_end old_map: uint64 = (p.brk_end + uint64(0xFFF)) & ~uint64(0xFFF) new_map: uint64 = (addr + uint64(0xFFF)) & ~uint64(0xFFF) if new_map > old_map: asm.cli() va: uint64 = old_map while va < new_map: if _vma_overlap(p, va, uint64(4096)) != 0: _unmap_pages(p, old_map, va, 1) asm.sti() return p.brk_end bphys: rawptr = mm.alloc_page() if bphys is None: _unmap_pages(p, old_map, va, 1) asm.sti() return p.brk_end string.memset(bphys, 0, 4096) paging.MapPage(va, uint64(bphys), paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) va = va + uint64(4096) asm.sti() elif new_map < old_map: _unmap_pages(p, new_map, old_map, 1) p.brk_end = addr return p.brk_end def MREMAP(old_addr: uint64, old_size: uint64, new_size: uint64, flags: uint64, new_addr: uint64) -> uint64: p: process.Process* = _current_proc() if old_size == 0 or new_size == 0: return abi.MAP_FAILED if (old_addr & uint64(0xFFF)) != 0: return abi.MAP_FAILED old_len: uint64 = (old_size + uint64(0xFFF)) & ~uint64(0xFFF) new_len: uint64 = (new_size + uint64(0xFFF)) & ~uint64(0xFFF) if old_addr < uint64(0x10000) or old_addr + old_len > uint64(USER_VA_LIMIT): return abi.MAP_FAILED if _vma_find(p, old_addr) < 0: return abi.MAP_FAILED if new_len <= old_len: if new_len < old_len: _vma_unmap_range(p, old_addr + new_len, old_len - new_len) return old_addr if (flags & uint64(abi.MREMAP_MAYMOVE)) == 0: return abi.MAP_FAILED mflags: uint64 = uint64(abi.MAP_PRIVATE | abi.MAP_ANONYMOUS) hint: uint64 = 0 if (flags & uint64(abi.MREMAP_FIXED)) != 0: if new_addr < old_addr + old_len and old_addr < new_addr + new_len: return abi.MAP_FAILED mflags = mflags | uint64(abi.MAP_FIXED) hint = new_addr nv: uint64 = MMAP(hint, new_len, uint64(abi.PROT_READ | abi.PROT_WRITE), mflags, MMAP_FD_ANON, 0) if nv == abi.MAP_FAILED: return abi.MAP_FAILED string.memcpy(rawptr(nv), rawptr(old_addr), old_len) _vma_unmap_range(p, old_addr, old_len) return nv def MADVISE(addr: uint64, length: uint64, advice: uint32) -> int: # No paging/swapping policy yet; accept the hint. return 0 # ============================================================================ # P4/P5/P6: TLS, futex, time, signals, iovec, pipes, reboot # ============================================================================ # ---- arch_prctl ---- def ARCH_PRCTL(code: int, addr: uint64) -> int: if code == abi.ARCH_SET_FS: sched.set_fs_base(addr) return 0 if code == abi.ARCH_GET_FS: if addr == 0: return _err(abi.EFAULT) dst: uint64* = uint64*(addr) dst[0] = sched.get_fs_base() return 0 return _err(abi.ENOSYS) # ---- futex: polling implementation (no sleep queue yet) ---- def FUTEX(uaddr: uint64, op: uint32, val: uint32, timeout: rawptr) -> int: if uaddr == 0: return _err(abi.EFAULT) cmd: int = int(op) & 0x7F u: uint32* = uint32*(uaddr) if cmd == 0 or cmd == 9: if u[0] != val: return _err(abi.EAGAIN) while u[0] == val: sched.Scheduler.yield_now() return 0 if cmd == 1 or cmd == 10: return 0 return _err(abi.ENOSYS) # ---- time ---- def _rtc_epoch() -> uint64: y: int = int(rtc.rtc_get_year()) mo: int = int(rtc.rtc_get_month()) d: int = int(rtc.rtc_get_day()) h: int = int(rtc.rtc_get_hours()) mi: int = int(rtc.rtc_get_minutes()) s: int = int(rtc.rtc_get_seconds()) yy: int = y if mo <= 2: yy = yy - 1 era: int = yy / 400 yoe: int = yy - era * 400 mp: int = (mo + 9) % 12 doy: int = (153 * mp + 2) / 5 + d - 1 doe: int = yoe * 365 + yoe / 4 - yoe / 100 + doy days: int = era * 146097 + doe - 719468 return uint64(days * 86400 + h * 3600 + mi * 60 + s) def CLOCK_GETTIME(clockid: int, ts_ptr: rawptr) -> int: if ts_ptr is None: return _err(abi.EFAULT) ts: abi.linux_timespec* = ts_ptr if clockid == 0: ts.tv_sec = _rtc_epoch() ts.tv_nsec = uint64(0) else: t: uint64 = timer.timer_get_ticks() ts.tv_sec = t / uint64(1000) ts.tv_nsec = (t % uint64(1000)) * uint64(1000000) return 0 def CLOCK_GETRES(clockid: int, ts_ptr: rawptr) -> int: if ts_ptr is None: return _err(abi.EFAULT) ts: abi.linux_timespec* = ts_ptr ts.tv_sec = uint64(0) ts.tv_nsec = uint64(1000000) return 0 def GETTIMEOFDAY(tv_ptr: rawptr, tz_ptr: rawptr) -> int: if tv_ptr is not None: tv: uint64* = uint64*(tv_ptr) tv[0] = _rtc_epoch() tv[1] = uint64(0) if tz_ptr is not None: string.memset(tz_ptr, 0, 8) return 0 def NANOSLEEP(req_ptr: rawptr, rem_ptr: rawptr) -> int: if req_ptr is None: return _err(abi.EFAULT) ts: abi.linux_timespec* = req_ptr ms: uint64 = ts.tv_sec * uint64(1000) + ts.tv_nsec / uint64(1000000) if ms > uint64(0x7FFFFFFF): ms = uint64(0x7FFFFFFF) if ms > 0: timer.timer_msleep(uint32(ms)) return 0 # ---- signals: registration + delivery on syscall return ---- define SIG_DFL = 0 define SIG_IGN = 1 define SA_NODEFER = 0x40000000 def RT_SIGACTION(sig: int, act_ptr: rawptr, old_ptr: rawptr, sigsetsize: uint32) -> int: if sig < 1 or sig > 64: return _err(abi.EINVAL) p: process.Process* = _current_proc() if old_ptr is not None: o: uint64* = uint64*(old_ptr) o[0] = p.sig_handler[sig] o[1] = p.sig_actflags[sig] o[2] = p.sig_restorer[sig] o[3] = uint64(0) if act_ptr is not None: a: uint64* = uint64*(act_ptr) p.sig_handler[sig] = a[0] p.sig_actflags[sig] = a[1] p.sig_restorer[sig] = a[2] return 0 def RT_SIGPROCMASK(how: int, set_ptr: rawptr, old_ptr: rawptr, sigsetsize: uint32) -> int: p: process.Process* = _current_proc() if old_ptr is not None: o: uint64* = uint64*(old_ptr) o[0] = p.sig_mask if set_ptr is not None: s: uint64* = uint64*(set_ptr) m: uint64 = s[0] if how == 0: p.sig_mask = p.sig_mask | m elif how == 1: p.sig_mask = p.sig_mask & ~m elif how == 2: p.sig_mask = m else: return _err(abi.EINVAL) return 0 def KILL(pid: int, sig: int) -> int: if sig < 0 or sig > 64: return _err(abi.EINVAL) if sig == 0: return 0 target: process.Process* = None if pid == 0 or pid == process.current(): target = _current_proc() elif pid > 0: target = process.ProcessManager.get_process(pid) if target is None: return _err(abi.ESRCH) target.sig_pending = target.sig_pending | (uint64(1) << uint64(sig - 1)) return 0 def RT_SIGRETURN() -> int: f: uint64* = uint64*(sched.get_syscall_frame()) sf: uint64 = f[15] - uint64(8) w: uint64* = uint64*(sf) i: int = 0 while i < 12: f[i] = w[1 + i] i += 1 f[13] = w[13] f[12] = w[14] f[15] = w[15] p: process.Process* = _current_proc() p.sig_mask = w[18] return int(w[16]) # Deliver one pending signal by rewriting the syscall return frame. # Returns 1 if a handler was entered (the syscall must return 0 in RAX). def _deliver_signals(ret: uint64) -> int: p: process.Process* = _current_proc() avail: uint64 = p.sig_pending & ~p.sig_mask # SIGKILL(9) / SIGSTOP(19) cannot be blocked avail = avail | (p.sig_pending & (uint64(1) << uint64(8))) avail = avail | (p.sig_pending & (uint64(1) << uint64(18))) if avail == 0: return 0 sig: int = 1 while sig <= 64: if (avail & (uint64(1) << uint64(sig - 1))) != 0: break sig += 1 if sig > 64: return 0 bit: uint64 = uint64(1) << uint64(sig - 1) p.sig_pending = p.sig_pending & ~bit h: uint64 = p.sig_handler[sig] if h == uint64(SIG_IGN): return _deliver_signals(ret) if h == uint64(SIG_DFL): if sig == 1 or sig == 2 or sig == 3 or sig == 4 or sig == 5 or sig == 6 or sig == 7 or sig == 8 or sig == 9 or sig == 11 or sig == 13 or sig == 14 or sig == 15: p.exit_code = 128 + sig sched.Scheduler.exit_process(p.pid) return _deliver_signals(ret) rest: uint64 = p.sig_restorer[sig] if rest == 0: return _deliver_signals(ret) f: uint64* = uint64*(sched.get_syscall_frame()) user_rsp: uint64 = f[15] user_rip: uint64 = f[13] user_flags: uint64 = f[12] # The handler is entered with RSP == sf and [sf] == restorer, exactly like a # normal call: rsp % 16 == 8 at function entry. rt_sigreturn then runs with # rsp == sf + 8 and recovers sf as (user_rsp - 8). sf: uint64 = ((user_rsp - uint64(320)) & ~uint64(0xF)) - uint64(8) w: uint64* = uint64*(sf) w[0] = rest i: int = 0 while i < 12: w[1 + i] = f[i] i += 1 w[13] = user_rip w[14] = user_flags w[15] = user_rsp w[16] = ret w[17] = uint64(sig) w[18] = p.sig_mask si: uint64 = sf + uint64(152) string.memset(rawptr(si), 0, 128) si32: uint32* = uint32*(si) si32[0] = uint32(sig) # si_signo si32[1] = uint32(0) # si_errno si32[2] = uint32(0) # si_code # handler(sig, &siginfo, &ucontext) f[8] = uint64(sig) f[9] = si f[10] = sf + uint64(8) f[13] = h # Enter the handler with [sf] = restorer (normal call-frame shape). f[15] = sf if (p.sig_actflags[sig] & uint64(SA_NODEFER)) == 0: p.sig_mask = p.sig_mask | bit return 1 # ---- iovec ---- define IOV_MAX = 1024 def READV(fd: int, iov_ptr: rawptr, iovcnt: int) -> int: if iov_ptr is None or iovcnt < 0 or iovcnt > IOV_MAX: return _err(abi.EINVAL) iov: abi.iovec* = iov_ptr total: int = 0 i: int = 0 while i < iovcnt: want: uint32 = 0xFFFFFFFF if iov[i].len < uint64(0xFFFFFFFF): want = uint32(iov[i].len) n: int = READ(fd, iov[i].base, want) if n < 0: if total > 0: return total return n total = total + n if uint64(n) < iov[i].len: break i += 1 return total def WRITEV(fd: int, iov_ptr: rawptr, iovcnt: int) -> int: if iov_ptr is None or iovcnt < 0 or iovcnt > IOV_MAX: return _err(abi.EINVAL) iov: abi.iovec* = iov_ptr total: int = 0 i: int = 0 while i < iovcnt: want: uint32 = 0xFFFFFFFF if iov[i].len < uint64(0xFFFFFFFF): want = uint32(iov[i].len) n: int = WRITE(fd, iov[i].base, want) if n < 0: if total > 0: return total return n total = total + n if uint64(n) < iov[i].len: break i += 1 return total def POLL(fds: rawptr, nfds: uint64, timeout: int) -> int: return 0 # ---- pipes ---- define MAX_PIPES = 8 define PIPE_CAP = 4096 _pipe_buf: [MAX_PIPES]rawptr _pipe_head: [MAX_PIPES]uint32 _pipe_tail: [MAX_PIPES]uint32 _pipe_cnt: [MAX_PIPES]uint32 _pipe_rd: [MAX_PIPES]int _pipe_wr: [MAX_PIPES]int _pipe_used: [MAX_PIPES]uint8 def _pipe_alloc() -> int: for i in range(MAX_PIPES): if _pipe_used[i] == 0: b: rawptr = mm.malloc(uint64(PIPE_CAP)) if b is None: return -1 _pipe_buf[i] = b _pipe_head[i] = 0 _pipe_tail[i] = 0 _pipe_cnt[i] = 0 _pipe_rd[i] = 0 _pipe_wr[i] = 0 _pipe_used[i] = 1 return i return -1 def PIPE2(fds_ptr: rawptr, flags: uint32) -> int: if fds_ptr is None: return _err(abi.EFAULT) p: process.Process* = _current_proc() id: int = _pipe_alloc() if id < 0: return _err(abi.EMFILE) rfd: int = _fd_alloc(p) if rfd < 0: mm.free(_pipe_buf[id]) _pipe_used[id] = 0 return _err(abi.EMFILE) wfd: int = _fd_alloc(p) if wfd < 0: _fd_free(p, rfd) mm.free(_pipe_buf[id]) _pipe_used[id] = 0 return _err(abi.EMFILE) p.fd_table[rfd] = rawptr(uint64(id * 2 + 1)) p.fd_kind[rfd] = process.FD_KIND_PIPE p.fd_flags[rfd] = flags p.fd_table[wfd] = rawptr(uint64(id * 2 + 2)) p.fd_kind[wfd] = process.FD_KIND_PIPE p.fd_flags[wfd] = flags _pipe_rd[id] = 1 _pipe_wr[id] = 1 fds: int* = int*(fds_ptr) fds[0] = rfd fds[1] = wfd return 0 def PIPE(fds_ptr: rawptr) -> int: return PIPE2(fds_ptr, uint32(0)) def _pipe_read(fp: rawptr, buf_ptr: rawptr, count: uint32) -> int: id: int = (int(uint64(fp)) - 1) / 2 if id < 0 or id >= MAX_PIPES or _pipe_used[id] == 0: return _err(abi.EBADF) while _pipe_cnt[id] == 0: if _pipe_wr[id] == 0: return 0 sched.Scheduler.yield_now() n: uint32 = count if _pipe_cnt[id] < n: n = _pipe_cnt[id] dst: uint8* = uint8*(buf_ptr) i: uint32 = 0 while i < n: src: uint8* = uint8*(uint64(_pipe_buf[id]) + uint64(_pipe_head[id])) dst[i] = src[0] _pipe_head[id] = (_pipe_head[id] + uint32(1)) % uint32(PIPE_CAP) i += 1 _pipe_cnt[id] = _pipe_cnt[id] - n return int(n) def _pipe_write(fp: rawptr, buf_ptr: rawptr, count: uint32) -> int: id: int = (int(uint64(fp)) - 1) / 2 if id < 0 or id >= MAX_PIPES or _pipe_used[id] == 0: return _err(abi.EBADF) if _pipe_rd[id] == 0: return _err(abi.EPIPE) while _pipe_cnt[id] >= uint32(PIPE_CAP): if _pipe_rd[id] == 0: return _err(abi.EPIPE) sched.Scheduler.yield_now() n: uint32 = count room: uint32 = uint32(PIPE_CAP) - _pipe_cnt[id] if room < n: n = room src: uint8* = uint8*(buf_ptr) i: uint32 = 0 while i < n: dst: uint8* = uint8*(uint64(_pipe_buf[id]) + uint64(_pipe_tail[id])) dst[0] = src[i] _pipe_tail[id] = (_pipe_tail[id] + uint32(1)) % uint32(PIPE_CAP) i += 1 _pipe_cnt[id] = _pipe_cnt[id] + n return int(n) # Count one more reference to a pipe end (dup/dup2/fork inherit). def _pipe_add_ref(fp: rawptr): v: int = int(uint64(fp)) - 1 id: int = v / 2 end: int = v % 2 if id < 0 or id >= MAX_PIPES: return if end == 0: _pipe_rd[id] = _pipe_rd[id] + 1 else: _pipe_wr[id] = _pipe_wr[id] + 1 def _pipe_close(fp: rawptr) -> int: v: int = int(uint64(fp)) - 1 id: int = v / 2 end: int = v % 2 if id < 0 or id >= MAX_PIPES: return _err(abi.EBADF) if end == 0: if _pipe_rd[id] > 0: _pipe_rd[id] = _pipe_rd[id] - 1 else: if _pipe_wr[id] > 0: _pipe_wr[id] = _pipe_wr[id] - 1 if _pipe_rd[id] == 0 and _pipe_wr[id] == 0: if _pipe_buf[id] is not None: mm.free(_pipe_buf[id]) _pipe_buf[id] = None _pipe_used[id] = 0 return 0 # ---- Linux reboot(169) ---- define LINUX_REBOOT_MAGIC1 = 0xFEE1DEAD define LINUX_REBOOT_MAGIC2 = 672274793 define LINUX_REBOOT_CMD_RESTART = 0x01234567 define LINUX_REBOOT_CMD_POWER_OFF = 0x4321FEDC def REBOOT_LINUX(magic1: uint32, magic2: uint32, cmd: uint32, arg: rawptr) -> int: if magic1 != uint32(LINUX_REBOOT_MAGIC1) or magic2 != uint32(LINUX_REBOOT_MAGIC2): return _err(abi.EINVAL) if cmd == uint32(LINUX_REBOOT_CMD_RESTART): power_reboot() return 0 if cmd == uint32(LINUX_REBOOT_CMD_POWER_OFF): power_shutdown() return 0 return _err(abi.EINVAL) # ============================================================================ # Linux clone()/fork(): resume the child at the caller's post-syscall RIP # ============================================================================ define CLONE_VM = 0x00000100 define CLONE_FS = 0x00000200 define CLONE_FILES = 0x00000400 define CLONE_SIGHAND = 0x00000800 define CLONE_THREAD = 0x00010000 define CLONE_SETTLS = 0x00080000 define CLONE_PARENT_SETTID = 0x00100000 define CLONE_CHILD_CLEARTID = 0x00200000 define CLONE_CHILD_SETTID = 0x01000000 class ForkCtx: pid: int rip: uint64 rsp: uint64 flags: uint64 fs: uint64 kern_stack: uint64 regs: [12]uint64 def _clone_context(flags: uint64, stack: uint64, tls: uint64) -> ForkCtx*: fp: uint64 = sched.get_syscall_frame() if fp == 0: return None f: uint64* = uint64*(fp) ctx: ForkCtx* = ForkCtx*(mm.malloc(uint64(ForkCtx.__sizeof__()))) if ctx is None: return None string.memset(rawptr(ctx), 0, uint64(ForkCtx.__sizeof__())) ctx.rip = f[13] ctx.flags = f[12] ctx.rsp = f[15] if stack != 0: ctx.rsp = stack ctx.fs = sched.get_fs_base() if (flags & uint64(CLONE_SETTLS)) != 0 and tls != 0: ctx.fs = tls i: int = 0 while i < 12: ctx.regs[i] = f[i] i += 1 return ctx def _fork_child_entry(arg: rawptr) -> int: ctx: ForkCtx* = arg asm.cli() proc.switch_pid(ctx.pid) ks: uint64 = ctx.kern_stack gdt.set_tss_rsp0(ks) cpu.set_kernel_rsp0(ks) proc.set_resume(ctx.rip, ctx.rsp, ctx.flags, uint64(0), &ctx.regs[0]) mm.free(rawptr(ctx)) proc.drop_to_user_mode_ctx(ks) return 0 def _clone_thread(flags: uint64, stack: uint64, ptid: uint64, ctid: uint64, tls: uint64) -> int: cur: process.Process* = _current_proc() ctx: ForkCtx* = _clone_context(flags, stack, tls) if ctx is None: return _err(abi.EAGAIN) ctx.pid = cur.pid th: sched.Thread* = sched.Scheduler.create_thread(&_fork_child_entry, rawptr(ctx), cur.pid) if th is None: mm.free(rawptr(ctx)) return _err(abi.EAGAIN) th.kern_stack = uint64(th.stack) + th.stack_size ctx.kern_stack = th.kern_stack if cur.addThread(th.tid) != 0: th.state = sched.THREAD_DONE mm.free(rawptr(ctx)) return _err(abi.EAGAIN) sched.set_thread_fs_base(th.tid, ctx.fs) if (flags & uint64(CLONE_PARENT_SETTID)) != 0 and ptid != 0: pt: int* = int*(ptid) pt[0] = th.tid if (flags & uint64(CLONE_CHILD_SETTID)) != 0 and ctid != 0: ct: int* = int*(ctid) ct[0] = th.tid if (flags & uint64(CLONE_CHILD_CLEARTID)) != 0 and ctid != 0: sched.set_clear_child_tid(th.tid, ctid) return th.tid def _alloc_page4k() -> rawptr: return mm.alloc_page() # Fork/exec inherit the fd table as shared references: neither side may release # an object the other still uses. Non-pipe objects have no cross-process # refcount yet, so mark BOTH sides shared (they are leaked on close instead of # being freed under a live user). Pipes do have counts and stay reclaimable. def _copy_fds(dst: process.Process*, src: process.Process*): i: int = 0 while i < process.PROC_MAX_FDS: if src.fd_used[i]: dst.fd_used[i] = 1 dst.fd_table[i] = src.fd_table[i] dst.fd_kind[i] = src.fd_kind[i] dst.fd_flags[i] = src.fd_flags[i] if src.fd_kind[i] == process.FD_KIND_PIPE: dst.fd_shared[i] = 0 _pipe_add_ref(src.fd_table[i]) else: dst.fd_shared[i] = 1 src.fd_shared[i] = 1 i += 1 # Linux execve (placed after the pipe/fork helpers so it can inherit fds). def EXECVE(path_ptr: str, argv: rawptr, envp: rawptr) -> int: cur: process.Process* = _current_proc() old_parent: int = cur.parent_pid path: const str = path_ptr pid: int = elf.spawn_elf(path, argv, envp) if pid < 0: return _err(abi.ENOENT) np: process.Process* = process.ProcessManager.get_process(pid) if np is not None: np.parent_pid = old_parent string.strcpy(&np.cwd[0], &cur.cwd[0]) np.sig_mask = cur.sig_mask i: int = 0 while i < 65: np.sig_handler[i] = 0 np.sig_actflags[i] = 0 np.sig_restorer[i] = 0 i += 1 np.sig_pending = 0 _copy_fds(np, cur) cur.exit_code = 0 sched.Scheduler.exit_process(cur.pid) return 0 def _fork_process(stack: uint64) -> int: cur: process.Process* = _current_proc() ctx: ForkCtx* = _clone_context(uint64(0), stack, uint64(0)) if ctx is None: return _err(abi.EAGAIN) np: process.Process* = process.ProcessManager.create_process(&cur.name[0], None) if np is None: mm.free(rawptr(ctx)) return _err(abi.EAGAIN) new_pml4: uint64 = paging.create_process_page_table() if new_pml4 == 0: mm.free(rawptr(ctx)) np.exit() return _err(abi.ENOMEM) if paging.CopyUserSpace(cur.pml4_root, new_pml4, rawptr(uint64(&_alloc_page4k))) != 0: mm.free(rawptr(ctx)) np.exit() return _err(abi.ENOMEM) np.pml4_root = new_pml4 np.elf_base = cur.elf_base np.heap_start = cur.heap_start np.heap_size = cur.heap_size np.brk_start = cur.brk_start np.brk_end = cur.brk_end np.entry = cur.entry np.parent_pid = cur.pid string.strcpy(&np.cwd[0], &cur.cwd[0]) i: int = 0 while i < process.MAX_VMAS: np.vma_base[i] = cur.vma_base[i] np.vma_size[i] = cur.vma_size[i] np.vma_prot[i] = cur.vma_prot[i] np.vma_kind[i] = cur.vma_kind[i] np.vma_flags[i] = cur.vma_flags[i] i += 1 i = 0 while i < 65: np.sig_handler[i] = cur.sig_handler[i] np.sig_actflags[i] = cur.sig_actflags[i] np.sig_restorer[i] = cur.sig_restorer[i] i += 1 np.sig_mask = cur.sig_mask np.sig_pending = 0 # shared fd table (the child must not close the parent's objects) _copy_fds(np, cur) # create_process allocated a private 64KB stack; the child uses the copy. if np.user_stack != 0: mm.free(rawptr(np.user_stack - uint64(process.USER_STACK_SIZE))) np.user_stack = 0 ctx.pid = np.pid th: sched.Thread* = sched.Scheduler.create_thread(&_fork_child_entry, rawptr(ctx), np.pid) if th is None: mm.free(rawptr(ctx)) np.exit() return _err(abi.EAGAIN) th.kern_stack = np.kern_stack ctx.kern_stack = np.kern_stack np.addThread(th.tid) sched.set_thread_fs_base(th.tid, ctx.fs) return np.pid def CLONE(flags: uint64, stack: uint64, ptid: uint64, ctid: uint64, tls: uint64) -> int: if (flags & uint64(CLONE_VM)) != 0: return _clone_thread(flags, stack, ptid, ctid, tls) return _fork_process(stack) def FORK() -> int: return _fork_process(uint64(0)) # ============================================================================ # P0: identity / environment / random # ============================================================================ def GETUID() -> uint64: return uint64(0) def GETEUID() -> uint64: return uint64(0) def GETGID() -> uint64: return uint64(0) def GETEGID() -> uint64: return uint64(0) def GETPPID() -> uint64: return uint64(_current_proc().parent_pid) def GETTID() -> uint64: return uint64(sched.thread_current()) def SET_TID_ADDRESS(tid_ptr: rawptr) -> uint64: # Single-address-space threads: the scheduler tid is the Linux tid. tid: int = sched.thread_current() if tid_ptr is not None: sched.set_clear_child_tid(tid, uint64(tid_ptr)) return uint64(tid) def SET_ROBUST_LIST(head: rawptr, len: uint64) -> int: # Recorded nowhere yet; futex-based thread exit lands with P4. return 0 def RSEQ(rseq_ptr: rawptr, len: uint32, flags: uint32) -> int: # glibc disables rseq when this returns -ENOSYS. return _err(abi.ENOSYS) def UNAME(buf_ptr: rawptr) -> int: if buf_ptr is None: return _err(abi.EFAULT) u: abi.linux_utsname* = buf_ptr string.memset(rawptr(u), 0, uint64(abi.SIZEOF_UTSNAME)) # Report "Linux" so scripts and build systems do not special-case us; # the ViperOS identity stays in the version string. string.strcpy(&u.sysname[0], "Linux") string.strcpy(&u.nodename[0], "viperos") string.strcpy(&u.release[0], "6.1.0-viperos") string.strcpy(&u.version[0], "#1 SMP ViperOS") string.strcpy(&u.machine[0], "x86_64") string.strcpy(&u.domainname[0], "(none)") return 0 def GETCWD(buf_ptr: rawptr, size: uint64) -> int: if buf_ptr is None: return _err(abi.EFAULT) p: process.Process* = _current_proc() n: int = int(string.strlen(&p.cwd[0])) if size < uint64(n + 1): return _err(abi.ERANGE) dst: char* = char*(buf_ptr) string.strcpy(dst, &p.cwd[0]) return n + 1 def CHDIR(path_ptr: str) -> int: path: const str = path_ptr buf: [256]char r: int = _resolve_path(abi.AT_FDCWD, path, &buf[0]) if r != 0: return r p: process.Process* = _current_proc() if buf[0] == 47 and buf[1] == 0: string.strcpy(&p.cwd[0], "/") return 0 info: fat32_types.fileinfo fat32.lock() res: int = int(fat32.stat(&buf[0], &info)) fat32.unlock() if res != 0: return _fr_errno(res) if (int(info.attr) & 0x10) == 0: return _err(abi.ENOTDIR) string.strcpy(&p.cwd[0], &buf[0]) return 0 def FACCESSAT(dirfd: int, path_ptr: str, mode: uint32, flags: uint32) -> int: path: const str = path_ptr buf: [256]char r: int = _resolve_path(dirfd, path, &buf[0]) if r != 0: return r if buf[0] == 47 and buf[1] == 0: return 0 info: fat32_types.fileinfo fat32.lock() res: int = int(fat32.stat(&buf[0], &info)) fat32.unlock() if res != 0: return _fr_errno(res) return 0 def ACCESS(path_ptr: str, mode: uint32) -> int: return FACCESSAT(abi.AT_FDCWD, path_ptr, mode, uint32(0)) def READLINKAT(dirfd: int, path_ptr: str, buf_ptr: rawptr, bufsiz: uint64) -> int: path: const str = path_ptr tmp: [256]char r: int = _resolve_path(dirfd, path, &tmp[0]) if r != 0: return r info: fat32_types.fileinfo fat32.lock() res: int = int(fat32.stat(&tmp[0], &info)) fat32.unlock() if res != 0: return _fr_errno(res) # FAT32 has no symbolic links; an existing name is simply not a link. return _err(abi.EINVAL) def READLINK(path_ptr: str, buf_ptr: rawptr, bufsiz: uint64) -> int: return READLINKAT(abi.AT_FDCWD, path_ptr, buf_ptr, bufsiz) def PRLIMIT64(pid: int, resource: uint32, new_ptr: rawptr, old_ptr: rawptr) -> int: if pid != 0 and pid != process.current(): return _err(abi.ESRCH) # Setting limits is accepted but not enforced yet. if old_ptr is not None: rl: abi.linux_rlimit* = old_ptr cur: uint64 = abi.RLIM_INFINITY mx: uint64 = abi.RLIM_INFINITY if resource == uint32(abi.RLIMIT_STACK): cur = uint64(process.USER_STACK_SIZE) mx = uint64(process.USER_STACK_SIZE) elif resource == uint32(abi.RLIMIT_NOFILE): cur = uint64(process.PROC_MAX_FDS) mx = uint64(process.PROC_MAX_FDS) rl.cur = cur rl.max = mx return 0 def GETRANDOM(buf_ptr: rawptr, buflen: uint64, flags: uint32) -> int: if buf_ptr is None: return _err(abi.EFAULT) if buflen > uint64(0x7FFFFFFF): return _err(abi.EINVAL) p: uint8* = uint8*(buf_ptr) i: uint64 = 0 while i < buflen: v: uint64 = _rng_next() j: int = 0 while j < 8 and i < buflen: p[i] = uint8(v & uint64(0xFF)) v = v >> 8 i += 1 j += 1 return int(buflen) extern def syscall_entry() def syscall_dispatch(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: n: int = int(num) if n == int(Syscall.EXIT): return uint64(EXIT(int(arg1))) elif n == int(Syscall.OPEN): path_ptr: str = str(arg1) return uint64(OPEN(path_ptr, uint32(arg2))) elif n == int(Syscall.CLOSE): return uint64(CLOSE(int(arg1))) elif n == int(Syscall.READ): buf_ptr: rawptr = rawptr(arg2) return uint64(READ(int(arg1), buf_ptr, uint32(arg3))) elif n == int(Syscall.WRITE): buf_ptr: rawptr = rawptr(arg2) return uint64(WRITE(int(arg1), buf_ptr, uint32(arg3))) elif n == int(Syscall.IOCTL): return uint64(IOCTL(int(arg1), uint32(arg2), rawptr(arg3))) elif n == int(Syscall.SEEK): return uint64(SEEK(int(arg1), arg2, uint32(arg3))) elif n == int(Syscall.MKDIR): path_ptr: str = str(arg1) return uint64(MKDIR(path_ptr)) elif n == int(Syscall.REMOVE): path_ptr: str = str(arg1) return uint64(REMOVE(path_ptr)) elif n == int(Syscall.STAT): path_ptr: str = str(arg1) info_ptr: rawptr = rawptr(arg2) return uint64(STAT(path_ptr, info_ptr)) elif n == int(Syscall.REBOOT): power_reboot() return uint64(0) elif n == int(Syscall.SHUTDOWN): power_shutdown() return uint64(0) elif n == int(Syscall.LOAD_SO): path_ptr_so: rawptr = rawptr(arg1) return LOAD_SO(path_ptr_so) elif n == int(Syscall.SO_CALL1): sym_ptr: rawptr = rawptr(arg1) return uint64(SO_CALL1(sym_ptr, rawptr(arg2))) elif n == int(Syscall.GETPID): return GETPID() elif n == int(Syscall.YIELD): # sched_yield 是"主动放弃剩余时间片",必须无条件切换;try_reschedule() 只在 # 时间片到期时才切,作为 syscall 会让等待外设的进程白等(见 Scheduler.yield_now 注释)。 sched.Scheduler.yield_now() return 0 elif n == int(Syscall.SPAWN): return uint64(EXECVE(str(arg1), rawptr(arg2), rawptr(arg3))) elif n == int(Syscall.SPAWN_PROC): path_ptr_sp2: rawptr = rawptr(arg1) return uint64(SPAWN_PROC(path_ptr_sp2, int(arg2))) elif n == int(Syscall.WAIT4): return uint64(WAIT4(int(arg1), rawptr(arg2), uint32(arg3), rawptr(arg4))) elif n == int(Syscall.MMAP): return MMAP(arg1, arg2, arg3, arg4, arg5, arg6) elif n == int(Syscall.MUNMAP): return uint64(MUNMAP(arg1, arg2)) elif n == int(Syscall.MPROTECT): return uint64(MPROTECT(arg1, arg2, arg3)) elif n == int(Syscall.BRK): return BRK(arg1) elif n == int(Syscall.MREMAP): return MREMAP(arg1, arg2, arg3, arg4, arg5) elif n == int(Syscall.MADVISE): return uint64(MADVISE(arg1, arg2, uint32(arg3))) elif n == int(Syscall.CLONE): return uint64(CLONE(arg1, arg2, arg3, arg4, arg5)) elif n == int(Syscall.FORK): return uint64(FORK()) elif n == int(Syscall.THREAD_CREATE_OLD): func_ptr: rawptr = rawptr(arg1) arg_ptr: rawptr = rawptr(arg2) cur_pid: int = process.current() # 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(_err(abi.EAGAIN)) p: process.Process* = process.ProcessManager.get_process(cur_pid) if p: p.addThread(th.tid) return uint64(th.tid) elif n == int(Syscall.THREAD_EXIT): # 用户线程显式退出(回收用户栈等资源后让出,永不返回)。 sched.Scheduler.exit_current_thread() return 0 elif n == int(Syscall.UNAME): return uint64(UNAME(rawptr(arg1))) elif n == int(Syscall.GETUID): return GETUID() elif n == int(Syscall.GETEUID): return GETEUID() elif n == int(Syscall.GETGID): return GETGID() elif n == int(Syscall.GETEGID): return GETEGID() elif n == int(Syscall.GETPPID): return GETPPID() elif n == int(Syscall.GETTID): return GETTID() elif n == int(Syscall.GETCWD): return uint64(GETCWD(rawptr(arg1), arg2)) elif n == int(Syscall.CHDIR): return uint64(CHDIR(str(arg1))) elif n == int(Syscall.ACCESS): return uint64(ACCESS(str(arg1), uint32(arg2))) elif n == int(Syscall.FACCESSAT): return uint64(FACCESSAT(int(arg1), str(arg2), uint32(arg3), uint32(arg4))) elif n == int(Syscall.READLINK): return uint64(READLINK(str(arg1), rawptr(arg2), arg3)) elif n == int(Syscall.READLINKAT): return uint64(READLINKAT(int(arg1), str(arg2), rawptr(arg3), arg4)) elif n == int(Syscall.SET_TID_ADDRESS): return SET_TID_ADDRESS(rawptr(arg1)) elif n == int(Syscall.SET_ROBUST_LIST): return uint64(SET_ROBUST_LIST(rawptr(arg1), arg2)) elif n == int(Syscall.RSEQ): return uint64(RSEQ(rawptr(arg1), uint32(arg2), uint32(arg3))) elif n == int(Syscall.PRLIMIT64): return uint64(PRLIMIT64(int(arg1), uint32(arg2), rawptr(arg3), rawptr(arg4))) elif n == int(Syscall.GETRANDOM): return uint64(GETRANDOM(rawptr(arg1), arg2, uint32(arg3))) elif n == int(Syscall.FSTAT): return uint64(FSTAT(int(arg1), rawptr(arg2))) elif n == int(Syscall.NEWFSTATAT): return uint64(NEWFSTATAT(int(arg1), str(arg2), rawptr(arg3), uint32(arg4))) elif n == int(Syscall.OPENAT): return uint64(OPENAT(int(arg1), str(arg2), uint32(arg3), uint32(arg4))) elif n == int(Syscall.GETDENTS64): return uint64(GETDENTS64(int(arg1), rawptr(arg2), arg3)) elif n == int(Syscall.DUP): return uint64(DUP(int(arg1))) elif n == int(Syscall.DUP2): return uint64(DUP2(int(arg1), int(arg2))) elif n == int(Syscall.DUP3): return uint64(DUP3(int(arg1), int(arg2), uint32(arg3))) elif n == int(Syscall.FCNTL): return uint64(FCNTL(int(arg1), uint32(arg2), arg3)) elif n == int(Syscall.PREAD64): return uint64(PREAD64(int(arg1), rawptr(arg2), uint32(arg3), arg4)) elif n == int(Syscall.PWRITE64): return uint64(PWRITE64(int(arg1), rawptr(arg2), uint32(arg3), arg4)) elif n == int(Syscall.RENAME): return uint64(RENAME(str(arg1), str(arg2))) elif n == int(Syscall.RMDIR): return uint64(RMDIR(str(arg1))) elif n == int(Syscall.UNLINKAT): return uint64(UNLINKAT(int(arg1), str(arg2), uint32(arg3))) elif n == int(Syscall.ARCH_PRCTL): return uint64(ARCH_PRCTL(int(arg1), arg2)) elif n == int(Syscall.FUTEX): return uint64(FUTEX(arg1, uint32(arg2), uint32(arg3), rawptr(arg4))) elif n == int(Syscall.CLOCK_GETTIME): return uint64(CLOCK_GETTIME(int(arg1), rawptr(arg2))) elif n == int(Syscall.CLOCK_GETRES): return uint64(CLOCK_GETRES(int(arg1), rawptr(arg2))) elif n == int(Syscall.GETTIMEOFDAY): return uint64(GETTIMEOFDAY(rawptr(arg1), rawptr(arg2))) elif n == int(Syscall.NANOSLEEP): return uint64(NANOSLEEP(rawptr(arg1), rawptr(arg2))) elif n == int(Syscall.RT_SIGACTION): return uint64(RT_SIGACTION(int(arg1), rawptr(arg2), rawptr(arg3), uint32(arg4))) elif n == int(Syscall.RT_SIGPROCMASK): return uint64(RT_SIGPROCMASK(int(arg1), rawptr(arg2), rawptr(arg3), uint32(arg4))) elif n == int(Syscall.RT_SIGRETURN): return uint64(RT_SIGRETURN()) elif n == int(Syscall.KILL): return uint64(KILL(int(arg1), int(arg2))) elif n == int(Syscall.READV): return uint64(READV(int(arg1), rawptr(arg2), int(arg3))) elif n == int(Syscall.WRITEV): return uint64(WRITEV(int(arg1), rawptr(arg2), int(arg3))) elif n == int(Syscall.POLL): return uint64(POLL(rawptr(arg1), arg2, int(arg3))) elif n == int(Syscall.PIPE): return uint64(PIPE(rawptr(arg1))) elif n == int(Syscall.PIPE2): return uint64(PIPE2(rawptr(arg1), uint32(arg2))) elif n == int(Syscall.REBOOT_LINUX): return uint64(REBOOT_LINUX(uint32(arg1), uint32(arg2), uint32(arg3), rawptr(arg4))) # Unknown/unimplemented Linux numbers return -ENOSYS (never success). return uint64(_err(abi.ENOSYS)) # Normal C entry: dispatch, then deliver at most one pending signal by # rewriting the saved user context. Returning 0 makes the signal handler's # entry RAX = 0; the original syscall result is saved in the frame. def _syscall_invoke(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: sched.set_syscall_frame(_entry_frame) r: uint64 = syscall_dispatch(num, arg1, arg2, arg3, arg4, arg5, arg6) if _deliver_signals(r) != 0: return uint64(0) return r # The compiler-provided syscall_entry calls this symbol. Naked: save the # entry frame pointer and tail-jump into _syscall_invoke (its ret returns to # the entry's call site). @__attribute__(naked) export def syscall_handler(num: uint64, arg1: uint64, arg2: uint64, arg3: uint64, arg4: uint64, arg5: uint64, arg6: uint64) -> uint64: __asm__("mov qword ptr [rip + _entry_frame], rbp\n jmp qword ptr [rip + _handler_ptr]") return 0 def init(): global _handler_ptr _handler_ptr = rawptr(uint64(&_syscall_invoke)) efer: uint64 = rdmsr(IA32_EFER) efer = efer | 1 wrmsr(IA32_EFER, efer) star_val: uint64 = (uint64(0x08) << 32) | (uint64(0x1B) << 48) wrmsr(IA32_STAR, star_val) wrmsr(IA32_LSTAR, uint64(syscall_entry)) wrmsr(IA32_FMASK, 0x200) serial.puts("[syscall] MSR init done\n")