Files
ViperOS/docs/syscall.md
T

18 KiB
Raw Blame History

ViperOS syscall 与 Linux x86-64 ABI 对齐计划

状态:P0–P6 完成(含明确受限项;内核/应用构建通过,fork、wait4、信号投递已在 QEMU 冒烟验证) 最后更新:2026-10-28

目标:让 ViperOS 的 syscall 面逐步收敛到 Linux x86-64 ABI,最终能够直接运行静态/动态链接的 glibc 程序。

非目标(至少本计划内不做):

  • 网络协议栈相关 syscall(socket/connect/…)
  • ptrace / seccomp / cgroup / namespace
  • 完整 VFS(符号链接、硬链接、inode 语义)
  • 与 Linux 内核模块、io_uring、epoll 的兼容

1. 现状

1.1 入口与调用约定

项目 现状
指令 syscall(MSR STAR/LSTAR/FMASK),另注册了 DPL=3 的 int 0x80 遗留门
调用号 rax
参数 rdi, rsi, rdx, r10, r8, r9(与 Linux 一致)
返回 rax;目前正负混用
错误 无 errno、无 TLS;部分返回 -1,部分返回 FAT32 FRESULT 正数,未知调用号返回 0
表 VKernel/Kernel/intr/syscall.vp 与 vpsdk/syscall.vp 两份手工镜像

1.2 已实现的调用

权威列表见 Scripts/syscall_table.py(build.py 构建前自动同步内核与 SDK)。 截至 P6,Linux 号段已覆盖:

文件   open/openat/close/read/write/pread/pwrite/lseek/stat/fstat/newfstatat/
       getdents64/dup/dup2/dup3/fcntl/mkdir/unlink/rename/rmdir/getcwd/chdir/
       access/faccessat/readlink/readlinkat
内存   brk/mmap/munmap/mprotect/mremap/madvise
进程   getpid/getppid/gettid/exit/exit_group/wait4/clone/fork/execve
线程   arch_prctl(FS)/set_tid_address/set_robust_list/futex/sched_yield
时间   clock_gettime/clock_getres/gettimeofday/nanosleep
信号   rt_sigaction/rt_sigprocmask/rt_sigreturn/kill
IPC    pipe/pipe2/readv/writev/poll(超时桩)
系统   uname/getuid/geteuid/getgid/getegid/prlimit64/getrandom/reboot

私有段仅剩兼容别名(LOAD_SO/SO_CALL1/SPAWN_PROC/THREAD_CREATE_OLD/ REBOOT/SHUTDOWN)。

1.3 已知偏差

  • execve(59) 通过“加载新进程 + 退出调用者”模拟,PID 会改变
  • fork(57) / clone(56) 会复制/共享地址空间;CLONE_VM 无 CLONE_THREAD 的 vfork 场景按同进程线程近似
  • 信号只在 syscall 返回 时投递(没有中断返回钩子);ucontext_t 为自定义 紧凑布局,siglongjmp 不可用
  • futex 为“查值 + yield”轮询;poll 恒返回 0(仅超时语义)
  • exit_group(231) 终止进程内所有线程;进程退出不回收管道/文件引用计数
  • fork/exec 继承的非管道 fd 标记为两侧共享:close 只清槽位不释放对象(避免 交叉释放;代价是泄漏)
  • 资源上限:8 进程 / 每进程 8 线程 / 16 fd / 8 目录句柄 / 64 VMA / 8 管道
  • P1 已补:openat / fstat / newfstatat / getdents64 / dup / fcntl, 私有目录调用已删除
  • P2 已补:brk / mprotect / munmap / mremap / madvise,文件映射
  • P3–P6 已补:启动栈/auxv、PT_INTERP/ET_DYN、execve、wait4、Linux clone/fork、TLS、时间、管道、信号投递、reboot(169)

1.4 维护风险

两份 enum Syscall 靠人工同步。P0 起改为生成:

Scripts/syscall_table.py   ← 唯一权威表
        │
        ├─ VKernel/Kernel/intr/syscall.vp   [BEGIN/END GENERATED]
        └─ vpsdk/syscall.vp                 [BEGIN/END GENERATED]

build.py 在编译前运行生成器;两边不一致会直接覆盖,不再依赖记忆。


2. 目标 ABI 契约

2.1 调用与返回

  • rax = 调用号,参数 rdi/rsi/rdx/r10/r8/r9
  • 成功:返回非负值(字节数、fd、地址、0…)
  • 失败:返回 -errno(-ENOENT、-EINVAL…),不再返回 FRESULT 正数
  • 未实现:-ENOSYS
  • 指针参数越界/为空:-EFAULT
  • 用户态 errno 由 glibc 从返回值设置,内核不维护 errno

2.2 数据结构

内核新增 abi/linux.vp(内核工程 include 增加仓库根目录),字段布局按 x86-64 Linux; SDK 侧需要结构体时可直接 import abi.linux。两者共用同一份定义,避免布局漂移。

类型 大小 用途
abi.linux_stat 144 stat/fstat/newfstatat
abi.linux_dirent64 19+name getdents64
abi.linux_timespec 16 时间
abi.linux_utsname 390 uname
abi.linux_rlimit 16 prlimit64
abi.iovec 16 readv/writev

2.3 启动栈(P3)

glibc 不读 _start(argc, argv),它要求内核按 Linux 方式布置用户栈:

[argc][argv0..argvN][NULL][envp...][NULL][auxv pairs][AT_NULL]

必须提供的 auxv:AT_PHDR、AT_PHENT、AT_PHNUM、AT_ENTRY、AT_BASE、 AT_PAGESZ、AT_RANDOM、AT_UID/EUID/GID/EGID、AT_SECURE=0、 AT_EXECFN、AT_SYSINFO_EHDR(可选,无 vDSO 时省略)。

动态链接:PT_INTERP 指向的解释器按 ET_DYN 基址(0x70000000)加载, 主程序 PIE 基址 0x60000000,AT_BASE 指向解释器,AT_PHDR/AT_ENTRY 仍指向主程序。系统镜像中需要实际存在该解释器与 libc。

2.4 TLS 与线程(P4)

  • arch_prctl(ARCH_SET_FS):设置每线程 IA32_FS_BASE
  • 线程切换时保存/恢复 FS_BASE
  • set_tid_address、set_robust_list、rseq(可为 -ENOSYS)
  • futex 至少覆盖 FUTEX_WAIT/FUTEX_WAKE(glibc pthread 必需)

2.5 兼容口径

  • uname().sysname 报 "Linux",release 报一个 Linux 风格版本号;真实产品名放在 version 字段。这样大量脚本/构建系统不会因 sysname != Linux 走特殊分支。
  • 文件权限位使用固定值:普通文件 0644、目录 0755;FAT32 没有 uid/gid。

3. 阶段计划

P0 — ABI 契约与基础设施(本次完成,待编译验证)

  • abi/linux.vp:errno、O_*、AT_*、DT_*、PROT_*、MAP_*、ARCH_*、结构体
  • 统一错误模型:Linux 号段调用返回 -errno;未知调用 -ENOSYS (私有 0x1000+ 段在 P6 删除前保留旧返回值)
  • open 采用 Linux O_*,内核翻译到 FAT32 FA_*
  • lseek 返回新偏移(而不是状态码),支持 64 位 offset(FAT32 上限内)
  • 新调用:uname getuid/geteuid/getgid/getegid getppid gettid getcwd chdir access faccessat readlink readlinkat set_tid_address set_robust_list rseq prlimit64 getrandom
  • Process.cwd(默认 /)
  • syscall 表由 Scripts/syscall_table.py 生成,build.py 自动同步
  • vpsdk 增加对应包装与常量

验收:现有应用行为不回退;所有失败路径可观察到负 errno;Scripts/syscall_table.py --check 通过。

P1 — 文件系统 ABI(本次完成,待编译验证)

  • openat(257)(AT_FDCWD + 绝对路径 + cwd 相对路径)
  • fstat(5)、newfstatat(262)、statx(332)(返回 -ENOSYS,glibc 回退)
  • getdents64(217) + 目录 fd(O_DIRECTORY)
  • dup(32) / dup2(33) / dup3(292)、fcntl(72)(F_GETFL/F_SETFL/F_DUPFD)
  • pread64(17) / pwrite64(18)
  • rename(82)、rmdir(84)、unlinkat(263)
  • 删除私有 OPENDIR/READDIR/CLOSEDIR
  • SDK 的 opendir/readdir/closedir 改由 openat + getdents64 + close 实现, Terminal 等调用方无需改动

验收:ls 走 openat + getdents64;SDK 的 open 也统一经 openat(AT_FDCWD); ld.so 的 openat/read/fstat/getdents64 路径可用(文件映射属于 P2)。

P1 已知限制:

  • getdents64 每次只返回一条记录(glibc 合法,只是多一次 syscall)
  • d_ino 固定 1、d_off 固定 0;telldir/seekdir/rewinddir 暂不保证
  • dirfd != AT_FDCWD 的相对路径查找返回 -ENOTSUP(P3/P6 再补目录 fd 反查)
  • statx 返回 -ENOSYS,glibc 回退到 newfstatat
  • rename 只支持同一 FAT32 卷内;dup 共享文件偏移,最后一次 close 才释放对象

P2 — 内存管理(本次完成,待编译验证)

  • brk(12):独立 brk 区(0x40000000 起),按页增长/收缩
  • mmap 真语义:MAP_FIXED、PROT_*、失败返回 MAP_FAILED(-1)
  • FAT32 文件映射:把文件内容复制进新页(MAP_PRIVATE 语义)
  • munmap(11) 真正解映射并把物理页归还 buddy
  • mprotect(10) 修改 PTE 权限(支持 VMA 拆分)
  • mremap(25)(MREMAP_MAYMOVE / MREMAP_FIXED)、madvise(28)
  • 每进程 VMA 表(64 项),记录 base/size/prot/kind/flags
  • SDK 的 mmap 把 MAP_FAILED 归一化为 0,既有 Explorer/devfs 调用方无需修改

验收:glibc 的 malloc 能使用 brk 主 arena;共享库文件可 mmap 后按段 mprotect; Explorer 的 framebuffer 映射不回退。

P2 已知限制:

  • 文件映射是“读入页”,不支持 MAP_SHARED 回写/msync(ld.so 用 MAP_PRIVATE)
  • PROT_NONE 仍映射为 present(可读),没有 NX;PROT_EXEC 仅记录
  • munmap 只归还数据页,页表页仍留在 paging 池
  • brk 区不在 VMA 表里(glibc 不会对它 munmap)
  • mremap 增长必须带 MREMAP_MAYMOVE,通过“新建+拷贝+解映射”实现

P3 — 进程、启动栈、解释器(本次完成)

  • 用户栈按 Linux 布局写入 argc/argv/envp/auxv (AT_PHDR/PHENT/PHNUM/PAGESZ/ENTRY/BASE/RANDOM/EXECFN/UID/EUID/GID/EGID 等)
  • execve(59) 原型 (path, argv, envp);SDK 保留 spawn 走私有 SPAWN_PROC
  • exit_group(231) 终止进程内所有线程;exit(60) 只退当前线程
  • wait4(61)(WNOHANG、状态字、子进程回收)、getppid
  • clone(56):(flags, stack, ptid, ctid, tls);CLONE_VM 建线程, 不带 CLONE_VM 时复制地址空间
  • fork(57):复制用户页 + 启动在父进程 syscall 返回点(RAX=0)
  • PT_INTERP + ET_DYN 基址加载;AT_BASE 指向解释器
  • 旧的 ViperOS thread_create(func,arg) 移到私有 THREAD_CREATE_OLD

验收:静态非 PIE、动态 PIE(安装 ld.so 与 libc)都可从正确的启动栈进入; fork 子进程在父进程同一返回点继续执行,父进程得到子 pid。

P4 — TLS 与线程(本次完成)

  • arch_prctl(158):SET_FS/GET_FS(SET_GS/GET_GS 返回 -ENOSYS)
  • 线程切换保存/恢复 IA32_FS_BASE(独立表,不改 Thread 的 600B 布局)
  • rseq(334) 返回 -ENOSYS;set_robust_list 记录
  • futex(202):WAIT/WAKE 采用“检查值 + sched_yield”轮询实现
  • clone(CLONE_VM|CLONE_SETTLS):子线程从 post-syscall RIP 开始,FS=tls
  • CLONE_CHILD_CLEARTID:线程退出时写 0,配合 futex 唤醒 join

验收:pthread_create/join 走 Linux clone 路径;TLS 随线程切换。

P5 — 时间、信号、IPC(本次完成)

  • clock_gettime(228) / clock_getres(229)(RTC 秒 + 1ms tick)
  • gettimeofday(96)、nanosleep(35)
  • readv(19) / writev(20)
  • pipe(22) / pipe2(293)(8 个管道 × 4KB,轮询读写)
  • poll(7)(当前固定返回 0 = 超时)
  • rt_sigaction(13) / rt_sigprocmask(14) / kill(62):完整登记
  • 信号投递:syscall 返回前重写用户上下文,进入 handler; rt_sigreturn(15) 恢复原上下文与 syscall 返回值
  • ppoll / select / epoll

验收:signal()/sigaction() 注册的 handler 能在下一次 syscall 返回时执行; raise()/kill(getpid(), sig) 可触发。

P6 — 清理私有段(部分完成)

  • reboot(169):校验 Linux magic,支持 RESTART / POWER_OFF
  • SDK 的 process.reboot/shutdown 改用 reboot(169)
  • [~] 私有 LOAD_SO/SO_CALL1 保留(内核启动仍加载 slog.so,用户态链接器未实现)
  • [~] 私有 SPAWN_PROC / THREAD_CREATE_OLD 保留(Terminal 与旧 SDK)
  • [~] 私有 REBOOT/SHUTDOWN 保留作为兼容别名
  • 用户态动态链接器 + 0x1000+ 段清空

验收:Linux reboot 可用;私有段尚未清零(受用户态加载器缺失限制)。


4. 调用迁移表

P0 = 本次;— 表示尚未实现(内核统一返回 -ENOSYS)。

号 Linux ViperOS 状态 备注
0 read READ P0 字节数 / -errno
1 write WRITE P0 同上
2 open OPEN P0 Linux O_*,内核翻译 FA_*
3 close CLOSE P0 0 / -EBADF
4 stat STAT P0 保留私有 fileinfo(兼容旧 SDK)
5 fstat FSTAT P1 struct stat
7 poll POLL P5 固定返回 0(超时)
8 lseek SEEK P0 返回新偏移
9 mmap MMAP P2 匿名/文件/设备三种;失败 MAP_FAILED
10 mprotect MPROTECT P2
11 munmap MUNMAP P2
12 brk BRK P2
13 rt_sigaction RT_SIGACTION P5 登记 + 投递
14 rt_sigprocmask RT_SIGPROCMASK P5 支持 block/unblock/setmask
15 rt_sigreturn RT_SIGRETURN P5 恢复原上下文
16 ioctl IOCTL P0 devfs 子集
17 pread64 PREAD64 P1
18 pwrite64 PWRITE64 P1
19 readv READV P5
20 writev WRITEV P5
21 access ACCESS P0
22 pipe PIPE P5 8 × 4KB,轮询
24 sched_yield YIELD P0
25 mremap MREMAP P2 增长需 MREMAP_MAYMOVE
28 madvise MADVISE P2 空操作
32 dup DUP P1 末次 close 才释放对象
33 dup2 DUP2 P1
35 nanosleep NANOSLEEP P5 1ms 精度
39 getpid GETPID P0
56 clone CLONE P3 Linux clone;CLONE_VM 建线程
57 fork FORK P3 复制用户地址空间
59 execve SPAWN P3 新进程 + 退出调用者
60 exit THREAD_EXIT P0
61 wait4 WAIT4 P3 支持 WNOHANG
62 kill KILL P5 待下次 syscall 投递
63 uname UNAME P0
72 fcntl FCNTL P1
79 getcwd GETCWD P0
80 chdir CHDIR P0
82 rename RENAME P1
83 mkdir MKDIR P0
84 rmdir RMDIR P1
87 unlink REMOVE P0
89 readlink READLINK P0 当前恒 -EINVAL
96 gettimeofday GETTIMEOFDAY P5 RTC 秒
102 getuid GETUID P0 恒 0
104 getgid GETGID P0 恒 0
107 geteuid GETEUID P0 恒 0
108 getegid GETEGID P0 恒 0
110 getppid GETPPID P0 恒 0
158 arch_prctl ARCH_PRCTL P4 SET_FS/GET_FS
169 reboot REBOOT_LINUX P6 magic 校验;RESTART/POWER_OFF
186 gettid GETTID P0 返回线程 tid
202 futex FUTEX P4 轮询 WAIT/WAKE
217 getdents64 GETDENTS64 P1 每次返回一条记录
218 set_tid_address SET_TID_ADDRESS P0
228 clock_gettime CLOCK_GETTIME P5 RTC/1ms tick
229 clock_getres CLOCK_GETRES P5 1ms
231 exit_group EXIT P3 终止进程内所有线程
257 openat OPENAT P1
262 newfstatat NEWFSTATAT P1
263 unlinkat UNLINKAT P1
267 readlinkat READLINKAT P0 当前恒 -EINVAL
269 faccessat FACCESSAT P0
273 set_robust_list SET_ROBUST_LIST P0 仅记录
292 dup3 DUP3 P1 flags 忽略
293 pipe2 PIPE2 P5 flags 仅记录
302 prlimit64 PRLIMIT64 P0 只读限制
318 getrandom GETRANDOM P0 软件 DRNG
332 statx — P1 -ENOSYS,glibc 回退
334 rseq RSEQ P0 -ENOSYS
0x1006 — LOAD_SO P6 删除
0x1007 — SO_CALL1 P6 删除
0x1008 — REBOOT P6 保留别名
0x1009 — SHUTDOWN P6 保留别名
0x100A — SPAWN_PROC P6 保留 Terminal run 用
0x100B — THREAD_CREATE_OLD P6 保留 旧 thread_create(func,arg)

5. 测试与验收

  1. ABI 单元测试(用户态小程序)
    • 每个调用至少覆盖成功 + 一个错误路径,断言返回值为 -errno
    • 结构体偏移/大小与 Linux 对照(编译期 __sizeof__)
  2. Scripts/syscall_table.py --check
    • CI/本地构建前检查两份表是否与权威表一致
  3. glibc 里程碑
    • M1:静态 hello(write/exit/brk/mmap/arch_prctl)
    • M2:动态 hello(ld.so、openat、mmap、mprotect)
    • M3:pthread + malloc 压力测试
    • M4:ls/cat/sh 等 coreutils
  4. 回归
    • 现有 7 个应用(Terminal / Explorer / Scene3D / Gargantua / FontDemo / HelloWorld / ImageSee)必须继续运行

5.1 已完成的冒烟验证(2026-10-28,QEMU)

  • 7 个内置应用全部启动,进入主循环,无异常
  • ImageSee 在同一启动中解码 PNG/JPEG 并在窗口显示(见 docs/imagesee.md)
  • 临时测试程序调用 fork():父进程得到子 pid,子进程在同一返回点继续运行 (getpid/getppid 正确),wait4 返回子 pid 且状态为 0x700(exit code 7)
  • 信号:rt_sigaction(SIGUSR1) + kill(self) → handler 在 kill 返回时执行, kill 仍返回 0;SIGUSR2 被 rt_sigprocmask 阻塞时不投递,解除阻塞后的下一次 syscall 返回时投递
  • 无人值守运行:python build.py --headless --runsecs=45(-display none, 45 秒后自动结束 QEMU,串口仍写入 serial.log)

6. 风险与已定决策

风险 处理
错误码改造影响旧应用 SDK 已按 <0 判断;FRESULT 正数路径一并改掉,旧应用需回归测试
两份表再次漂移 生成器 + build.py 前缀同步;禁止手改生成段
FAT32 无 inode/权限/符号链接 struct stat 用固定值;readlink 一律 -EINVAL;chmod 后置
mprotect 未实现时 RELRO 失效 P2 前在文档标注为“安全降级”,不假装成功
getrandom 无硬件熵 软件 xorshift + 启动期混合,仅用于 canary/非密码学场景
uname 报 Linux 提升兼容性;version 字段保留 ViperOS 标识
用户态栈布局改动大 P3 单独提交,先静态后动态,避免与内存阶段交叉