news 2026/8/25 22:31:25

从标准库到系统调用:open、read、write初体验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从标准库到系统调用:open、read、write初体验

今天学的东西,跟上一篇的fopen、fread、fwrite看着很像,但其实有本质区别。之前学的是C标准库函数,跨平台通用;今天学的是Linux系统调用,是操作系统内核直接提供的接口。简单说,fopen底层也是调用了open,但封装了缓冲区等细节,用起来更方便;而open、read、write更接近底层,粒度更细,控制也更灵活。

这篇就记录一下open、read、write、close这几个核心系统调用的用法,以及文件描述符这个概念。

open:打开文件,拿到“通行证”

系统调用里打开文件用的是open,跟fopen最大的区别是:它返回一个整数,叫文件描述符,而不是FILE指针。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int fd = open("test.txt", O_RDWR | O_CREAT, 0664);

第一个参数是路径,第二个是打开方式(flags),第三个是权限(mode),只在创建文件时才生效。

常用的flags有:

· O_RDONLY:只读
· O_WRONLY:只写
· O_RDWR:读写
· O_CREAT:文件不存在则创建
· O_TRUNC:打开时清空文件内容
· O_APPEND:写入时追加到末尾

这些flags可以用按位或|组合起来。比如O_WRONLY | O_CREAT | O_TRUNC就相当于标准库里的"w"模式——只写、不存在则创建、存在则清空。

第三个参数mode指定文件权限,比如0664表示文件所有者可读写、同组用户可读写、其他人只读。不过实际生效的权限还要减去系统的umask值,这是Linux的安全机制,防止创建权限过宽的文件。

open成功返回一个非负整数(文件描述符),失败返回-1。所以每次open之后都要判断一下fd == -1,不然拿到个无效描述符,后面的read、write全都会出错。

文件描述符:0、1、2的秘密

文件描述符本质上就是一个整数,操作系统用这个整数来代表一个已打开的文件。它的分配规则很简单:每次分配当前最小的未使用整数。

系统默认已经打开了三个:

· 0(STDIN_FILENO):标准输入,默认是键盘
· 1(STDOUT_FILENO):标准输出,默认是终端
· 2(STDERR_FILENO):标准错误,默认也是终端

这三个是每个程序启动时自动打开的,直接就能用。比如用write(1, "hello\n", 6)就能在终端打印"hello",效果和printf一样,只不过printf是标准库封装过的,更方便。

每个进程能打开的文件描述符数量是有限的,默认一般是1024个(0到1023),不过系统上限可以调整。所以用完文件一定记得关,否则会泄漏。

read和write:读写操作

read和write跟标准库里的fread、fwrite类似,但参数顺序和返回值略有不同。

write —— 写入数据
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

参数:文件描述符、数据缓冲区、要写的字节数。返回值是实际写入了多少字节,成功时等于count,失败返回-1。

注意,write不保证一次就能写完所有数据,尤其在网络编程或者磁盘繁忙的时候。所以通常要判断返回值,如果小于count,要循环写入剩余的部分。

read —— 读取数据
ssize_t read(int fd, void *buf, size_t count);

参数类似,从文件描述符fd中读取最多count字节到buf里。返回值是实际读到的字节数:如果大于0,说明读到了数据;如果等于0,说明读到了文件末尾;如果小于0,说明出错了。

read和write操作的是原始字节,没有换行、字符串之类的概念,也不像fgets那样自动帮你加\0,所以缓冲区要自己管理好,防止溢出。

close关掉它
int close(int fd);

close就是关闭文件描述符,释放资源。成功返回0,失败返回-1。

用了open就一定要配close,这点和标准库是一个道理。否则文件描述符用完了,程序后面就再也打不开新文件了。

一个完整的例子:拷贝文件

下面这段代码用系统调用实现一个简单的文件拷贝程序,把src.txt的内容复制到dst.txt:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int src = open("src.txt", O_RDONLY);
if (src == -1)
{
perror("打开源文件失败");
return 1;
}

int dst = open("dst.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (dst == -1)

{
perror("打开目标文件失败");
close(src);
return 1;
}

char buf[1024];
ssize_t n;
while ((n = read(src, buf, sizeof(buf))) > 0)

{
write(dst, buf, n); // 实际应该检查返回值,这里简写了
}

close(src);
close(dst);
return 0;
}

这个例子跟标准库版本的核心区别就是:没有缓冲区自动管理,每次read把数据读进自己定义的buf,再用write写出去。虽然代码看起来差不多,但系统调用少了用户态和内核态之间的一层封装,理论上更直接。

标准库 vs 系统调用,怎么选?

学到这里其实有个疑惑:既然标准库用起来更方便,为什么还要学系统调用?

我的理解是:

· 标准库函数(fopen/fread/fwrite)自带缓冲区,适合常规文件读写,代码可移植,效率也不差。
· 系统调用(open/read/write)更底层,适合需要精细控制的场景,比如网络编程、设备驱动开发,或者需要处理信号中断、非阻塞IO等情况。

另外,open的flags比标准库的模式字符串丰富很多,比如O_NONBLOCK(非阻塞)、O_SYNC(同步写入),这些在标准库层面很难直接控制。

今天的内容偏底层,代码量不大,但理解文件描述符和系统调用的工作方式,对后面学进程、管道、socket这些很有帮助。下一步准备继续看看lseek、dup、fcntl这些更进阶的系统调用,争取把文件IO这块吃透。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 22:28:07

上传图片失败,原来是磁盘满了

以下两张是上传图片失败的截图&#xff1a;查看磁盘使用情况&#xff0c;命令&#xff1a;df -h&#xff0c;下面这张图可以看到磁盘使用率100%接下来&#xff0c;找到大文件&#xff0c;递归查看所有层级中最大的前十个目录 命令如下&#xff1a; du -x / 2>/dev/null | so…

作者头像 李华
网站建设 2026/8/25 22:26:53

需求变更后如何避免漏改漏测?ONES基线、影响追溯与可疑分析实践

需求变更很少只影响需求文档本身。一个性能指标、接口约束或业务规则调整&#xff0c;往往会继续传导到设计、开发任务、测试用例、验收标准甚至交付文档。真正难的不是“允许不允许变”&#xff0c;而是变更发生后&#xff0c;团队能不能快速回答&#xff1a;改了什么、影响了…

作者头像 李华
网站建设 2026/8/25 22:16:10

财税咨询。

济南三千税务师事务所有限公司&#xff08;三千财税&#xff09;聚焦企业全生命周期的各类财税痛点与疑难问1题&#xff0c;结合济南本地产业政策与税收征管特点&#xff0c;提供定制化专属财税咨询服务。服务范畴全面覆盖企业初创期的纳税人身份选择、税种核定、开票规范辅导&…

作者头像 李华
网站建设 2026/8/25 22:15:39

如何用检测站与灰度验证判断你的账号环境是否真的干净

没有任何一款工具能"保证"你的账号不遭到平台限制。把希望寄托在某款软件能替你兜住所有风险上&#xff0c;本身就是高风险思路。多账号运营能不能长期稳定&#xff0c;核心取决于两件事——环境隔离的质量&#xff0c;以及你自己的运营行为是否符合平台规范。环境隔…

作者头像 李华