文件描述符

虽然 C 标准中没有指定文件描述符的概念,但许多操作系统提供了文件描述符(有时缩写为 fd)。虽然 stdio.h 中的 FILE 类型及其相关函数封装了流的低级细节,但文件描述符是一个整数,用来表示操作系统正在跟踪的流。

本节将探讨在 POSIX 系统(例如 Linux)中实现的文件描述符。

标准流作为文件描述符

当进程被创建时,操作系统为该进程分配了三种流:标准输入流 stdin,标准输出流 stdout,和标准错误流 stderr。通常,标准流是通过 stdio.h 中的 FILE 类型来交互的,如前面所述。标准流也可以通过它们的原始文件描述符来交互,这些文件描述符对于每个进程都是相同的:

unistd.h 符号 文件描述符
STDIN_FILENO stdin 0
STDOUT_FILENO stdout 1
STDERR_FILENO stderr 2

请注意,这些文件描述符对于每个进程都是相同的,尽管标准流包含不同的数据。也就是说,文件描述符不一定是全局唯一的;每个进程可能对哪些文件描述符映射到哪些流有不同的视图,就像每个进程对系统的虚拟地址空间有不同的视图一样。

基本的读写操作

可以使用以下函数进行文件描述符的读取和写入操作:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

与基于 FILE 的函数进行比较:

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);

可以明显看到以下三点区别:

  1. 从流中读取和写入的数据并不假定是字符串。
  2. 文件描述符作为参数传递,而不是 FILE 类型。
  3. 返回值使用一致的类型。

readfgets 的参数集类似:都包含表示流的某种方式、一个缓冲区以及一个大小;此外,如果读取的数据量等于请求的大小,那么缓冲区的内容无论使用哪个函数都应该是相同的。然而,在读取的数据量与请求的大小不匹配时,这些函数的行为不同。fgets 用于处理字符串时,如果遇到换行符,它会提前停止读取;如果流中没有更多的字符串,它可能会阻塞等待。read 则不会在遇到特殊值时提前停止读取,但如果没有写入请求的所有数据到管道中,它会停止读取。由于 read 不能保证缓冲区中完全可用的数据(如果它提前停止读取),返回值包含写入缓冲区的字节数。这使得 read 更适用于需要更高控制权的数据读取情况,或者程序员愿意通过减少阻塞 I/O 操作来接受部分读取的数据。

同样,write 需要显式的大小参数,因为它不能假设写入的是一个以 NULL 结尾的字符串,它将返回已写入的字节数,以便程序确定传递的数据是否已完全写入流中。

获取和丢弃文件描述符

FILE 与文件描述符的转换

(此部分略,内容未完全展开)

通过 openat 实现安全性

(此部分略,内容未完全展开)

参考文献

  • read(2)write(2),Linux 程序员手册,2019-10-10
  • fgets(3)fputs(3),Linux 程序员手册,2020-08-13
最后修改: 2025年01月12日 星期日 13:32