最轻量无副作用的文件描述符有效性检查是调用fcntl(fd, F_GETFD):成功返回标志值(通常0),失败返回-1且errno为EBADF;其他方法如read/write、dup、poll/select均有副作用或不可靠性。
fcntl(fd, F_GETFD) 检查文件描述符有效性Linux/macOS 下最轻量、不触发副作用的判断方式是调用 fcntl(fd, F_GETFD)。它只读取文件描述符标志,不会修改状态,也不引发 I/O 或信号。如果 fd 无效(已关闭或根本不存在),系统调用返回 -1 并置 errno 为 EBADF;否则返回当前文件描述符标志值(通常为 0)。
注意:不能用 read(fd, &buf, 1) 或 write(fd, &buf, 1) 判断——即使 fd 已关,某些场景下(如管道写端已关但读端仍开)可能返回 0 或阻塞,且会改变文件偏移、触发 SIGPIPE 等副作用。
fcntl 是原子操作,线程安全,适合多线程环境快速探活GetFileType() + GetLastError() == ERROR_BAD_UNIT
dup(fd) 是否成功:它在 fd 有效时返回新 fd,但失败时也可能因资源耗尽而返回 -1,无法区分原因poll() / select() 不可靠poll() 对已关闭的 fd 可能返回 POLLNVAL,但行为不一致:对 socket fd,若对端已关闭连接但本端未 close(),它仍可能返回 POLLIN;对普通文件 fd,poll() 总是立即返回 POLLNVAL,但 POSIX 并未保证这点,glibc 实现也依赖内核版本。
poll() 需要构造 struct pollfd,开销比 fcntl 大,且引入超时逻辑干扰判断意图select() 更糟:它会修改传入的 fd_set,且对非法 fd 的行为未明确定义,部分实现直接 abortEBADF ——这又绕回了需要先做一次系统调用验证,不如直接用 fcntl
即使 fcntl(fd, F_GETFD) 返回成功,也不能保证下一刻 fd 仍有效——其他线程或信号处理函数可能在检查后、使用前调用 close(fd)。这不是检测方法的问题,而是 Unix fd 模型本身的限制。
EBADF(以及 EIO、EAGAIN 等常见错误),而不是预先检查os.fstat() 或 os.dup()
Python 用户常误用 os.fstat(fd) 判断——它底层调用 fstat(),对已关闭 fd 抛 OSErr,无法“不抛异常”。同理,
os.dup(fd) 也会抛异常。
正确方式是用 ctypes 调用 fcntl,或更简单:捕获异常并忽略:
import errno
try:
os.fstat(fd)
is_valid = True
except OSError as e:
is_valid = (e.errno == errno.EBADF)
但要注意:这仍是“事后捕获”,不是无异常检测;若想严格符合“不抛异常”要求,只能走 ctypes + fcntl 路径。
真正难的不是怎么查,而是查完之后信不信这个结果——fd 的有效性本质上是瞬态的,靠单次检查无法建立可靠契约。