C 编程/低级 I/O
虽然 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
|
请注意,这些文件描述符对于每个进程都是相同的,即使标准流在每个进程中包含不同的数据。这意味着文件描述符不一定在系统范围内是唯一的;每个进程可能有不同的文件描述符映射到哪些流的视图,就像每个进程对系统虚拟地址空间有不同的视图一样。
可以使用以下函数读取文件描述符并向文件描述符写入数据:[1]
#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
基于函数进行比较和对比:[2]
#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 结尾的字符串,并且它将返回写入的字节数,以便程序可以确定传递的数据是否已完全写入流。