POSIX(Portable Operating System Interface)是一组定义”类 Unix 操作系统”应该支持的接口的 IEEE/ISO 标准,现行版本是 POSIX.1-2024(俗称 “Issue 8”)。它覆盖系统调用 API、shell 行为、工具命令集。但 POSIX 不是任何操作系统——每个真实的 OS 都是 POSIX 的子集 + 扩展。
本文对照 POSIX 基线,列出主要 OS 的扩展、偏离,以及做跨平台系统编程时要提防的坑。
用一张图把 POSIX 在真实 OS 里”被谁扩展、被谁绕开”先建立印象:
flowchart LR
POSIX[(POSIX.1-2024<br/>基线)]
POSIX --> Linux[Linux<br/>+epoll/io_uring<br/>+cgroup/ns<br/>+eBPF/seccomp]
POSIX --> FreeBSD[FreeBSD<br/>+kqueue<br/>+jail/Capsicum<br/>+pf]
POSIX --> OpenBSD[OpenBSD<br/>+pledge/unveil<br/>+pf]
POSIX --> macOS[macOS / Darwin<br/>+kqueue<br/>+GCD/XPC<br/>+Mach IPC]
NT[Windows NT<br/>独立谱系]
NT --> Win32[Win32 API<br/>CreateProcess / IOCP]
NT --> WSL1[WSL 1<br/>POSIX 层]
NT --> WSL2[WSL 2<br/>真 Linux 内核]
classDef stdn fill:#3fb950,color:#2d333b,stroke:#3fb950;
classDef ln fill:#388bfd,color:#cdd9e5,stroke:#388bfd;
classDef nt fill:#a371f7,color:#cdd9e5,stroke:#a371f7;
class POSIX stdn
class Linux,FreeBSD,OpenBSD,macOS ln
class NT,Win32,WSL1,WSL2 nt
绿色是标准基线;蓝色是”在 POSIX 基线之上叠加专属扩展”的 Unix 族;紫色是完全独立谱系的 Windows NT。要记住的是:没有任何真实 OS 等于 POSIX——POSIX 是最小可移植交集,生产软件几乎总要用到某平台的专属扩展才能达到”能用”的性能与安全。下面各节展开每一条扩展。
一、POSIX 的层次
- POSIX.1:系统接口(syscall、C 库)
- POSIX.2:shell 和工具(sh、awk、grep 等行为)
- POSIX.1b:实时扩展(SCHED_FIFO、sigqueue、aio_read、POSIX timer、shm_open)
- POSIX.1c:pthread
- POSIX.1-2001 / 2008 / 2017 / 2024:迭代合并
LSB(Linux Standard Base)是 POSIX 的 Linux 侧子集 + 发行约束(文件系统布局、库版本)。但 LSB 在 2015 年后基本停滞,现实里”Linux = glibc + 你的发行版”。
二、Linux-only 的接口
这些接口在 glibc 里常见,但不在 POSIX 里:
| 接口 | 首次出现 | 替代的 POSIX |
|---|---|---|
epoll_* |
2.5.44 | select、poll(POSIX) |
eventfd |
2.6.22 | pipe |
signalfd |
2.6.22 | sigaction+sigprocmask |
timerfd_* |
2.6.25 | timer_create(POSIX.1b) |
inotify_* |
2.6.13 | 无 |
fanotify_* |
2.6.37 | 无 |
pidfd_* |
5.3 | 无 |
io_uring_* |
5.1 | AIO(POSIX.1b) |
clone / clone3 |
2.x / 5.3 | fork、posix_spawn |
unshare |
2.6.16 | 无 |
setns |
3.0 | 无 |
memfd_create |
3.17 | shm_open |
userfaultfd |
4.3 | 无 |
seccomp |
2.6.12 / 3.5 | 无 |
Landlock |
5.13 | 无 |
openat2 |
5.6 | openat |
statx |
4.11 | stat |
copy_file_range |
4.5 | 自行读写 |
prctl / pidns 相关 |
2.x | 无 |
sched_setaffinity |
2.5.8 | 无(pthread_attr_setaffinity_np 是扩展) |
set_robust_list / futex |
2.6.17 | 无 |
注意:POSIX 的
AIO(aio_read、aio_write)在 Linux
上是基于线程池的用户态实现(glibc 或 libaio
的 fallback),性能差;真正高效的异步 I/O 在 Linux 上要用
io_uring(见 G-57)。这是”POSIX 标准但实现差 vs
非标准但好用”的经典对立。
三、BSD 扩展
FreeBSD、OpenBSD、NetBSD、macOS(基于 Darwin,继承部分 BSD)各自维护的非 POSIX 扩展:
- kqueue(FreeBSD 首创,macOS 延用):类似 epoll 的事件通知机制,统一文件、socket、信号、定时器、进程、fs 事件
- jail(FreeBSD):namespace 之父,类似 Linux namespace 的早期全局隔离机制
- Capsicum(FreeBSD):capability-oriented sandbox,进程进入 capability mode 后只能用已持有的 FD
- pf(OpenBSD 起家,FreeBSD 移植):包过滤引擎,BSD 路由器/防火墙的主力
- pledge、unveil(OpenBSD):极简的进程沙箱声明
- sysctl(4.4BSD 发明):Linux 也借鉴了(/proc/sys),但 BSD 保留更正统的 sysctl tree
macOS 特有:
- kqueue(继承 FreeBSD)
- Grand Central Dispatch (GCD) / libdispatch:任务队列抽象
- mach ports、XPC:基于 Mach 微内核的 IPC
- dtrace(原 Sun → 开源,苹果移植到 macOS)
- kernel extensions(kext)→ 现在被 DriverKit / System Extensions 替代
iOS 在 macOS 内核基础上做了大量 sandbox(Seatbelt)、entitlement、codesigning 加固,API 对开发者暴露的是 Foundation/Cocoa,而非 POSIX 系统接口。
四、Windows NT 的独立谱系
Windows NT(1993)不是 Unix。它的内核 API 在 ntdll.dll 级别,上层是 Win32(kernel32.dll、user32.dll 等),POSIX 兼容通过子系统实现。
4.1 WSL:两代兼容
- WSL 1(2016):把 Linux syscall 在 NT 内核里实现一遍。技术惊艳但边界脏(文件系统语义不完全一致)。
- WSL 2(2019):放弃兼容层,直接跑一个真实 Linux 内核在 Hyper-V 轻虚机里。Win32 和 WSL 之间用 9P + tmpfs 桥接。这套方案大大好用。
4.2 关键差异
| 维度 | Windows NT | POSIX / Linux |
|---|---|---|
| 进程创建 | CreateProcess(单步加载新镜像) |
fork + exec |
| 文件句柄 | HANDLE(opaque) | int fd |
| 等待原语 | WaitForMultipleObjects(统一) | select/poll/epoll + signal + waitpid 等 |
| 异步 I/O | IOCP(Overlapped I/O) | AIO/epoll/io_uring |
| 线程 | CreateThread(1:1,无用户态调度) | pthread(1:1),最近 M:N 讨论 |
| 大小写敏感 | 默认不敏感(per-directory 可开启) | 敏感 |
| 路径分隔符 | \(也接受 /) |
/ |
| 文件权限 | ACL | DAC + capability + ACL(可选) |
| 信号 | ConsoleCtrl / Exception handling | signal |
| fork | 不支持 | 支持(历史包袱) |
4.3 “fork 的败北”
fork 是 Unix 最古老的原语之一,但它和现代硬件、现代程序(线程、大内存、JIT)配合得极差:
- 性能:
fork要复制页表、文件表、信号处理表;即使有 COW,页表自身的复制成本也随地址空间大小线性增长。大 JVM 进程 fork 会卡秒级。 - 安全性:
fork后 child 继承一切文件描述符、mmap、共享状态——你得 careful 关闭不必要的 FD(CLOEXEC 不是默认)。 - 与线程不兼容:
fork只复制调用线程,其它线程消失。mutex 留在奇怪状态。POSIX 规定 fork 后只能调 async-signal-safe 函数直到 exec——实际上这意味着几乎不能调任何 C 库函数。
替代:
- posix_spawn:合并 fork+exec,避免中间态。在 glibc 2.24+ 有 clone+execve 的快速实现。
- io_uring 的 exec 命令(尚在演进)
- 设计上避免:大进程用”启动时 fork 小 daemon,再由 daemon 启 exec” 的模式(例:systemd、prefork web server、Python multiprocessing 的 forkserver)
POSIX.1-2024 把 posix_spawn 地位抬高,但
fork 不会被移除(稳定 ABI)。
五、signal 方言
POSIX 定义了 signal 语义,但各家在细节上差异:
- Linux:支持 RT
signal(SIGRTMIN..SIGRTMAX,32 个),可排队带
siginfo_tpayload - BSD:同样支持 RT signal
- macOS:RT signal 支持有缺
- Windows:完全不支持 Unix signal;用 ConsoleCtrl(CTRL_C_EVENT 等)+ SEH 异常
signal 在跨平台代码里是噩梦之源。现代实践:尽量用 signalfd / kqueue / IOCP 把 signal 转成 I/O 事件,在主事件循环里同步处理,不要用异步 handler。
六、文件系统语义的偏离
6.1 路径大小写
Linux/BSD:大小写敏感。macOS:默认 case-preserving but case-insensitive(HFS+ 和 APFS 默认)。Windows:默认不敏感(NTFS 支持可配置)。
这是跨平台构建系统的永恒地雷——在 macOS 上编译通过的代码在
Linux 上因为 #include "Foo.h" 找不到
foo.h 失败。
6.2 符号链接
POSIX 定义 symlink;Windows NT 支持但历史复杂:
- Shortcut (.lnk):Explorer 层面的”链接”,不是文件系统级
- Junction point:NTFS reparse point,仅限本地 volume、仅限目录
- Symbolic link(Vista+):真符号链接,需要管理员权限或开启 Developer Mode
WSL、Git for Windows 常因此爆炸。
6.3 文件名字符集
Linux:字节序列,除 / 和 NUL
外任何都可。POSIX 建议 UTF-8 locale。Windows:UTF-16
底层,文件系统层 case-folding 表有古怪规则。macOS:HFS+ 强制
NFD 归一化(NFC 会被转换)——Git 在 macOS
上对中文/日文文件名要小心。
6.4 inode 概念
POSIX 的 stat 结构包含 st_ino。Linux、BSD 按
inode 展示;Windows 上只能拿 NTFS 的 File
ID(64-bit,不保证所有 FS 都有)。跨平台工具(git,
rsync)处理这个差异有大量 workaround。
6.5 mmap 与 file hole
Linux、BSD、Windows 都支持 sparse file(hole),但 API 不同:
- Linux:
fallocate(FALLOC_FL_PUNCH_HOLE, ...)、SEEK_HOLE/SEEK_DATA - BSD:
SEEK_HOLE、SEEK_DATA - Windows:
FSCTL_SET_SPARSE,FSCTL_SET_ZERO_DATA
七、时间与定时器
- POSIX clock_gettime,各家实现分别:
- Linux:
CLOCK_MONOTONIC、CLOCK_REALTIME、CLOCK_BOOTTIME、CLOCK_TAI、CLOCK_MONOTONIC_RAW、CLOCK_MONOTONIC_COARSE(最后一个不走 vDSO,快但粒度粗) - BSD:类似,不完全一致
- macOS:CLOCK_MONOTONIC 不是真的
monotonic(某些版本会受系统时间调整影响)——工程界臭名昭著。推荐用
mach_absolute_time() - Windows:
QueryPerformanceCounter/QueryUnbiasedInterruptTime
- Linux:
时间的 “绝对稳定” 是跨平台库(zstd benchmark、Rust
std::time::Instant)的长期难题。
八、进程控制
- /proc:Linux 有、FreeBSD 有 procfs(可选挂载)、macOS 没有 procfs、Windows 没有(用 WMI / NT 内核 API 替代)
- ptrace:Linux、BSD 有 ptrace。macOS 有,但对系统进程的追踪受 CS(code signing)限制。Windows 用 DebugActiveProcess 家族
- execve 参数长度:Linux 通常 128KB(可调);BSD 256KB;Windows CreateProcess 命令行 32768 字符
九、多线程与并发
- pthread:Linux、BSD、macOS 都支持(NPTL
on Linux)。Windows 通过 pthreads-win32 移植或原生
CreateThread - futex:Linux 独有。FreeBSD 有
umtx(类似功能,API 不同)。Windows 有
WaitOnAddress(8.1+) - 原子操作:C11
stdatomic.h、C++11std::atomic跨平台可用 - parkinglot / WaitOnAddress 模型:近年各 OS 收敛到同一模型
十、I/O 多路复用的分岔路
| 机制 | 所在 OS | 模型 |
|---|---|---|
| select/poll | 所有 POSIX | level-triggered,线性扫描 |
| epoll | Linux only | level / edge triggered,就绪列表 |
| kqueue | BSD / macOS | filter-based,统一文件/信号/定时器 |
| IOCP | Windows | completion-based(异步) |
| io_uring | Linux | submission/completion ring,异步 |
这是近 20 年高性能网络编程的分叉核心——每个 OS 有自己的答案。跨平台库(libuv、libevent、asio)的大部分代码都在弥合这些差异。
十一、错误码与错误模型
- POSIX:
errno,值域定义一部分(EINVAL、EAGAIN、ENOSYS 等);具体值不在 POSIX 规定里 - Linux 把 errno
值稳定(
/usr/include/asm-generic/errno.h),生态依赖 - BSD 的 errno 值集合有细微差异
- Windows:GetLastError(DWORD),语义不同;NtStatus(HRESULT,31/32 bit 错误码分层)
errno.h 的跨平台兼容层(glibc、musl、LLVM libc)是个默默做了很多工作的地方。
十二、写跨平台系统代码的建议
- 默认写 Linux,再适配:90% 的服务器平台是 Linux;先让核心高效,再做其他 OS 分支
- 抽象层不要太厚:不要自己造一个 “跨平台 I/O” 巨型抽象。具体的 I/O primitive(epoll / io_uring / kqueue / IOCP)有各自最佳调用模式,共性少
- macOS 是 Linux-like,但 Darwin 内核异常多:FS 行为、VM 行为、信号、EFAULT 语义,默认当它是”像 BSD 的 BSD 的 BSD”
- Windows 不要用 POSIX 层:MinGW/MSYS 的 POSIX 兼容是”能跑”,不是”跑得好”;真要 Windows native 用 IOCP / WaitOnAddress / Overlapped
- CI 全平台:Linux + macOS + Windows + FreeBSD + ARM64 全跑。Rust 和 Go 的丰富 target 生态是它们的隐形优势
- 文件系统语义写测试:大小写、symlink、长路径、非 ASCII、sparse、FS 的 atime/mtime 精度
十三、标准的意义与局限
POSIX 让 Unix 世界保持了某种形态一致性,但也正是”保持一致”拖累了 Unix 的现代化——POSIX 委员会审慎,标准化滞后于实现 10 年是常态。现代有影响力的 API 几乎都是平台创新后再被标准引入的:
- pthread 最早是 DCE,1995 进 POSIX
- epoll 2002 Linux 独有,从未标准化
- io_uring 2019 Linux 独有,可能永远不标准化
- posix_spawn 是 fork 替代,但早期实现质量差(glibc 慢),最近才好
给你的启示:别被标准绑架。了解标准是地基,但生产系统要用实际最强的原语。如果你写的是数据库/消息队列/网络服务,Linux 专有 API(io_uring、cgroup v2、eBPF、membarrier)常常是性能差距的来源,放弃跨平台兼容是值得的 trade-off。
下一篇 A-08 把一些 OS 相关的”常识错觉”——零拷贝不等于零开销、微内核不等于安全、实时不等于快、容器不等于虚拟机——一起讲清楚。
参考文献
- IEEE Std 1003.1-2024. POSIX.1-2024 / The Open Group Base Specifications, Issue 8
- Russinovich, M., Solomon, D. Windows Internals, 7th ed.
- McKusick, M. K., et al. The Design and Implementation of the FreeBSD Operating System, 2nd ed., 2014
- Singh, A. Mac OS X Internals: A Systems Approach, 2006
- Microsoft Docs. Windows Native API reference
- FreeBSD Handbook and Capsicum:
capsicum(4),jail(8),pledge(2)(OpenBSD) - Kerrisk, M. The Linux Programming Interface, 2010 —— POSIX 与 Linux 扩展的对照宝典
- Apple. “About Kernel Extensions and System Extensions”, developer.apple.com
- Corbet, J. “fork() and the new MADV_*” 等 LWN 专题
上一篇:内核与用户态的边界 下一篇:关于 OS 的工程常识错觉
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】FreeBSD 与 OpenBSD 的不同选择
BSD 的哪些决定值得 Linux 借鉴?kqueue vs epoll、jail vs namespace、ZFS 一等公民、DTrace、pledge/unveil、网络栈差异——本文对照 BSD 与 Linux 的关键取舍。
【操作系统百科】Windows 内核与 Linux 的关键差异
NT 内核与 Linux 的根本差异——Executive/Kernel 分层、object manager、IRP I/O 模型、Job object、IOCP、Registry、WSL2 的选择。
【操作系统百科】Unix 谱系与设计遗产:Multics、Plan 9、Linux
要理解 Linux 的很多古怪决定,得回到 Multics、Unix V6/V7、BSD 的历史语境。本文沿时间线梳理 Unix 家谱,挑出每一代留下的设计遗产——fork/exec、一切皆文件、管道、可重定向的 stdin/stdout、9P、命名空间——并指出哪些被 Linux 发扬,哪些被抛弃,哪些仍在 Plan 9 的血脉里孤独活着。
【操作系统百科】系统调用 ABI:x86_64 / arm64 / riscv / Windows NT 对照
系统调用是 OS 最稳定的接口。本文拆解 Linux syscall 的参数寄存器约定、返回值规范(负 errno 与 2-value ABI)、x86_64 SYSCALL、arm64 SVC、RISC-V ECALL、Windows NT 的 int 2e/syscall/SYSENTER 历史;说明为什么 Linux 承诺 \"don't break userspace\"、什么东西算 syscall ABI、vDSO 如何用共享内存加速、Go/musl/glibc 各自怎么实现 syscall stub。