Files

2175 lines
82 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 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")