Linux 基础 IO 详解
在深入操作系统内核之前,我们需要先理解用户态程序如何与外部世界交互。Linux 下的文件操作不仅仅是读写磁盘,更涉及内存管理、设备抽象以及进程间的资源调度。
1. 文件的本质与内存映射
从逻辑上看,文件 = 文件内容 + 文件属性。虽然数据存储在磁盘上,但 CPU 无法直接访问磁盘,必须通过操作系统将数据加载到内存中。这个过程由系统调用完成,而编程语言(如 C/C++)则对这些底层调用进行了封装。
当用户打开多个文件时,操作系统需要维护这些资源的元数据。在本篇讨论的范畴内,我们主要关注'内存级'的文件操作,即进程如何通过文件描述符来管理已打开的资源。
2. 接口回顾:stdio 与系统调用
C 语言标准库提供了 fopen、fwrite、fclose 等函数,它们属于 stdio 层。为了理解其背后的机制,我们先回顾一下标准用法。
文件流指针
使用 fopen 打开文件时,返回的是 FILE* 指针。默认情况下,进程启动时会打开三个标准流:
- stdin (0): 标准输入
- stdout (1): 标准输出
- stderr (2): 标准错误
这体现了 Linux'一切皆文件'的设计哲学,键盘和显示器也被抽象为文件流。
底层系统调用
stdio 函数本质上是对内核系统调用的封装。例如,fopen("text.txt", "w") 最终会调用 open 系统调用,并设置相应的标志位(flags)。
open 系统调用
#include <fcntl.h>
int fd = open("text.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
- 参数: 文件名、标志位、权限模式。
- 标志位: 使用位图思想组合,如
O_RDONLY、O_WRONLY、O_APPEND、O_CREAT等。 - 权限: 创建文件时的初始权限受
umask影响。实际权限 =~umask & 请求权限。默认 umask 通常为 022,因此传入 0666 可能生成 0644 的文件。
read / write 系统调用
一旦获得文件描述符(fd),即可使用 read 和 write 进行二进制读写。
// 写入
const char* msg = "hello world\n";
write(fd, msg, strlen(msg));
// 读取
char buffer[1024];
ssize_t n = read(fd, buffer, (buffer));


