C编程
文件描述符
虽然 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);
可以明显看到以下三点区别:
- 从流中读取和写入的数据并不假定是字符串。
- 文件描述符作为参数传递,而不是
FILE
类型。 - 返回值使用一致的类型。
read
和 fgets
的参数集类似:都包含表示流的某种方式、一个缓冲区以及一个大小;此外,如果读取的数据量等于请求的大小,那么缓冲区的内容无论使用哪个函数都应该是相同的。然而,在读取的数据量与请求的大小不匹配时,这些函数的行为不同。fgets
用于处理字符串时,如果遇到换行符,它会提前停止读取;如果流中没有更多的字符串,它可能会阻塞等待。read
则不会在遇到特殊值时提前停止读取,但如果没有写入请求的所有数据到管道中,它会停止读取。由于 read
不能保证缓冲区中完全可用的数据(如果它提前停止读取),返回值包含写入缓冲区的字节数。这使得 read
更适用于需要更高控制权的数据读取情况,或者程序员愿意通过减少阻塞 I/O 操作来接受部分读取的数据。
同样,write
需要显式的大小参数,因为它不能假设写入的是一个以 NULL
结尾的字符串,它将返回已写入的字节数,以便程序确定传递的数据是否已完全写入流中。
获取和丢弃文件描述符
FILE
与文件描述符的转换
(此部分略,内容未完全展开)
通过 openat
实现安全性
(此部分略,内容未完全展开)
参考文献
read(2)
和write(2)
,Linux 程序员手册,2019-10-10fgets(3)
和fputs(3)
,Linux 程序员手册,2020-08-13