马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
很多初学者写过 fopen、fwrite,后来又看到 open、write,容易搞混:它们到底什么关系?差别又在哪?这一篇从 open 一路讲到 write,把系统文件 I/O 的完整链路梳理清楚。
一、库函数与系统调用的两套体系
C 语言中文件操作有“两套”:
- 库函数:
fopen、fclose、fwrite、fread、fseek,对应 C 标准库里的 FILE* 结构。
- 系统调用:
open、close、write、read、lseek,由内核提供,是真正落到内核的文件操作。
简单说:库函数是对系统调用的封装。fopen 底层会调 open,fwrite 底层会调 write,中间还加了缓冲区、格式化、FILE 结构体管理等一层。
二、文件描述符 fd 是什么
系统调用用“整数”标识一个打开的文件,这个整数就是文件描述符(fd)。Linux 中一切皆文件,网络 socket、管道、设备同样有 fd。
fd 的分配规则是:最小可用。内核维护 fd 表,打开文件时总是占用当前未被使用的最小编号。进程启动时通常已经占用 0(标准输入)、1(标准输出)、2(标准错误)。
int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) {
perror("open");
return 1;
}
三、open 与 write 的基本用法
#include <fcntl.h>
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
int close(int fd);
返回值很关键:write/read 返回的是实际写入/读取的字节数,不是 0 也不是 -1 就算完。返回 -1 才表示出错,此时需要看 errno。
char buf[] = "hello\n";
ssize_t n = write(fd, buf, sizeof(buf) - 1);
if (n != (ssize_t)(sizeof(buf) - 1)) {
// 部分写入或出错,都要处理
}
write(1, buf, sizeof(buf) - 1); // 写到 1 号描述符,就等于打印到屏幕
四、从 fopen 到 open:行为差异
| 维度 | fopen / fwrite | open / write |
| 所属层 | C 标准库 | 内核系统调用 |
| 句柄类型 | FILE* 结构体指针 | int 文件描述符 |
| 缓冲区 | 自带用户态缓冲 | 无缓冲,直接进内核 |
| 可移植性 | 跨平台 | 依赖操作系统 |
| 适用 | 普通文本/二进制文件 | 任何内核对象、网络、设备 |
需要注意的是,因为库函数自带缓冲,你在 fwrite 之后立刻看文件大小可能没变化,要等 fflush 或 fclose;而 write 通常石沉大海般直接进了内核缓冲,用 fsync 才能确保落盘。
五、重定向是怎么实现的
我们常说“重定向输出”,本质就是改 fd 的指向:close(1) 之后 open 一个新文件,内核分配的最小 fd 正好是 1,于是后续所有写到 1 的内容都进了这个文件。Shell 里的 >、>> 底层做的就是这件事。
# Shell 层
ls > /tmp/out.txt # 覆盖
ls >> /tmp/out.txt # 追加
# 程序层,dup2 强制把 fd 1 换成目标文件
int fd = open("/tmp/out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1);
close(fd);
六、几个容易踩的坑
- open 的 mode 参数只在创建文件时生效:文件已存在时传什么 mode 都不会改权限。要改权限用
chmod。
- 忘记 close 会耗尽 fd:高并发服务里不关 fd 会很快撞上
too many open files,用 lsof -p pid 查。
- write 可能只写一半:尤其是写 pipe、socket、终端时,必须循环写入或检查返回值。
- flags 用错:
O_TRUNC 清空、O_APPEND 追加、O_CREAT 不存在则建,三者组合最常用。
七、实践建议
- 写普通文件用途的日志
,用库函数更省事;写网络、管道、设备、需要精细控制的文件,直接上系统调用。
- 重要数据写完记得
fsync,避免掉电丢数据。
- 服务里统一封装一层带错误处理的 IO 帮助函数,全局管理 fd 生命周期。
- 排查
Resource temporarily unavailable 之类的错误时,先想清楚读端为什么阻塞了。
qidao123.com企服评测-应用市场 编辑提示 |