Files
ViperOS/docs/syscall.md
T

383 lines
18 KiB
Markdown
Raw 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.
# 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 号段已覆盖:
```text
文件 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 起改为**生成**:
```text
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 方式布置用户栈:
```text
[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 契约与基础设施(本次完成,待编译验证)
- [x] `abi/linux.vp`:errno、`O_*`、`AT_*`、`DT_*`、`PROT_*`、`MAP_*`、`ARCH_*`、结构体
- [x] 统一错误模型:Linux 号段调用返回 `-errno`;未知调用 `-ENOSYS`
(私有 0x1000+ 段在 P6 删除前保留旧返回值)
- [x] `open` 采用 Linux `O_*`,内核翻译到 FAT32 `FA_*`
- [x] `lseek` 返回新偏移(而不是状态码),支持 64 位 offset(FAT32 上限内)
- [x] 新调用:`uname` `getuid/geteuid/getgid/getegid` `getppid` `gettid`
`getcwd` `chdir` `access` `faccessat` `readlink` `readlinkat`
`set_tid_address` `set_robust_list` `rseq` `prlimit64` `getrandom`
- [x] `Process.cwd`(默认 `/`)
- [x] syscall 表由 `Scripts/syscall_table.py` 生成,`build.py` 自动同步
- [x] `vpsdk` 增加对应包装与常量
验收:现有应用行为不回退;所有失败路径可观察到负 errno;`Scripts/syscall_table.py --check` 通过。
### P1 — 文件系统 ABI(本次完成,待编译验证)
- [x] `openat(257)`(`AT_FDCWD` + 绝对路径 + cwd 相对路径)
- [x] `fstat(5)`、`newfstatat(262)`、`statx(332)`(返回 `-ENOSYS`,glibc 回退)
- [x] `getdents64(217)` + 目录 fd(`O_DIRECTORY`)
- [x] `dup(32)` / `dup2(33)` / `dup3(292)`、`fcntl(72)`(`F_GETFL/F_SETFL/F_DUPFD`)
- [x] `pread64(17)` / `pwrite64(18)`
- [x] `rename(82)`、`rmdir(84)`、`unlinkat(263)`
- [x] 删除私有 `OPENDIR/READDIR/CLOSEDIR`
- [x] 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 — 内存管理(本次完成,待编译验证)
- [x] `brk(12)`:独立 brk 区(`0x40000000` 起),按页增长/收缩
- [x] `mmap` 真语义:`MAP_FIXED`、`PROT_*`、失败返回 `MAP_FAILED`(-1)
- [x] FAT32 文件映射:把文件内容复制进新页(`MAP_PRIVATE` 语义)
- [x] `munmap(11)` 真正解映射并把物理页归还 buddy
- [x] `mprotect(10)` 修改 PTE 权限(支持 VMA 拆分)
- [x] `mremap(25)`(`MREMAP_MAYMOVE` / `MREMAP_FIXED`)、`madvise(28)`
- [x] 每进程 VMA 表(64 项),记录 base/size/prot/kind/flags
- [x] 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 — 进程、启动栈、解释器(本次完成)
- [x] 用户栈按 Linux 布局写入 `argc/argv/envp/auxv`
(AT_PHDR/PHENT/PHNUM/PAGESZ/ENTRY/BASE/RANDOM/EXECFN/UID/EUID/GID/EGID 等)
- [x] `execve(59)` 原型 `(path, argv, envp)`;SDK 保留 `spawn` 走私有 `SPAWN_PROC`
- [x] `exit_group(231)` 终止进程内所有线程;`exit(60)` 只退当前线程
- [x] `wait4(61)`(`WNOHANG`、状态字、子进程回收)、`getppid`
- [x] `clone(56)`:`(flags, stack, ptid, ctid, tls)`;CLONE_VM 建线程,
不带 CLONE_VM 时复制地址空间
- [x] `fork(57)`:复制用户页 + 启动在父进程 syscall 返回点(RAX=0)
- [x] `PT_INTERP` + ET_DYN 基址加载;`AT_BASE` 指向解释器
- [x] 旧的 ViperOS `thread_create(func,arg)` 移到私有 `THREAD_CREATE_OLD`
验收:静态非 PIE、动态 PIE(安装 `ld.so` 与 libc)都可从正确的启动栈进入;
fork 子进程在父进程同一返回点继续执行,父进程得到子 pid。
### P4 — TLS 与线程(本次完成)
- [x] `arch_prctl(158)`:`SET_FS/GET_FS`(`SET_GS/GET_GS` 返回 `-ENOSYS`)
- [x] 线程切换保存/恢复 `IA32_FS_BASE`(独立表,不改 `Thread` 的 600B 布局)
- [x] `rseq(334)` 返回 `-ENOSYS`;`set_robust_list` 记录
- [x] `futex(202)`:`WAIT/WAKE` 采用“检查值 + sched_yield”轮询实现
- [x] `clone(CLONE_VM|CLONE_SETTLS)`:子线程从 post-syscall RIP 开始,FS=tls
- [x] `CLONE_CHILD_CLEARTID`:线程退出时写 0,配合 futex 唤醒 join
验收:`pthread_create/join` 走 Linux clone 路径;TLS 随线程切换。
### P5 — 时间、信号、IPC(本次完成)
- [x] `clock_gettime(228)` / `clock_getres(229)`(RTC 秒 + 1ms tick)
- [x] `gettimeofday(96)`、`nanosleep(35)`
- [x] `readv(19)` / `writev(20)`
- [x] `pipe(22)` / `pipe2(293)`(8 个管道 × 4KB,轮询读写)
- [x] `poll(7)`(当前固定返回 0 = 超时)
- [x] `rt_sigaction(13)` / `rt_sigprocmask(14)` / `kill(62)`:完整登记
- [x] 信号投递:syscall 返回前重写用户上下文,进入 handler;
`rt_sigreturn(15)` 恢复原上下文与 syscall 返回值
- [ ] `ppoll` / `select` / `epoll`
验收:`signal()`/`sigaction()` 注册的 handler 能在下一次 syscall 返回时执行;
`raise()`/`kill(getpid(), sig)` 可触发。
### P6 — 清理私有段(部分完成)
- [x] `reboot(169)`:校验 Linux magic,支持 RESTART / POWER_OFF
- [x] 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 单独提交,先静态后动态,避免与内存阶段交叉 |