news 2026/9/10 2:27:58

Linux系统篇(二十三) 基础 IO:从“文件”到“文件描述符”,彻底理解重定向

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux系统篇(二十三) 基础 IO:从“文件”到“文件描述符”,彻底理解重定向

观众老爷们大家好 这里是邪修KING的独家频道
本文属于系列Linux系统篇 ——操作指令
一起学Linux的小伙伴可订阅专栏: Linux系统篇

学习定位:这一篇只整理《基础 IO》前四部分。
目标不是马上写工程,而是把 “文件 → 系统调用 → fd → 重定向 → FILE” 这条线真正串起来。
这也是后面 MiniShell 加入重定向功能的理论基础。

一、先建立一个最重要的认识:Linux 中“文件”到底是什么?

我们以前容易把文件理解成:
磁盘上的 test.txt、a.c、a.out。
这是狭义的文件。

但 Linux 更重要的思想是:

Linux 下一切皆文件。

键盘、显示器、磁盘、网卡等,都可以被操作系统抽象成类似“文件”的对象。这样程序就可以用统一的方式进行输入输出(I/O)。

所以从操作系统角度看:

程序 ↓ 进程 ↓ 对“文件”进行操作 ↓ 输入 / 输出 ↓ 设备或磁盘

而且要注意:

真正操作文件的不是 C 语言本身,而是操作系统提供的系统调用;C 标准库只是给我们提供了更方便的接口。

二、C语言中的 fopen,和 Linux 的 open 是两套层次

以前我们写文件:

FILE*fp=fopen("myfile","w");

常见的还有:

fclose(fp);fread(...);fwrite(...);

这些属于:

C 标准库函数(libc)

而 Linux 提供的更底层接口是:

open()close()read()write()lseek()

它们属于:

系统调用接口。

可以这样理解:

我们写的程序 │ C标准库函数 fopen / fread / fwrite │ ▼ 系统调用接口 open / read / write │ ▼ Linux │ ▼ 磁盘 / 键盘 / 显示器

所以不要认为:
fopen 和 open 是完全无关的东西

更准确的理解是:
库函数是在系统调用之上提供的一层更方便的封装。

三、真正需要重点理解:文件描述符 fd

这是这一章最核心的东西。

1. fd是什么?

调用:

intfd=open("myfile",O_RDONLY);

成功后可能得到:

fd=3

这个 3 就叫:
文件描述符(file descriptor,fd)
它本质上就是:
一个小整数,用来代表当前进程打开的某个文件。
PDF进一步解释了它的底层关系:进程内部有文件描述符表,fd本质上就是这张表的下标,通过这个下标可以找到对应的打开文件对象。

可以想成:

当前进程 │ ▼ 文件描述符表 下标 指向 0 ───→ 标准输入 1 ───→ 标准输出 2 ───→ 标准错误 3 ───→ myfile 4 ───→ another.txt

所以以后看到:

read(fd, ...) write(fd, ...) close(fd)

你要想到:

“拿着这个数字,告诉操作系统我要操作哪个打开的文件。”

四、0、1、2:Shell、重定向的钥匙

Linux 进程默认打开三个文件描述符:

0 → stdin → 标准输入 1 → stdout → 标准输出 2 → stderr → 标准错误

一般情况下:

0 → 键盘 1 → 显示器 2 → 显示器

所以:

read(0,buf,sizeof(buf));

可以理解:
从标准输入读取。

而:

write(1,buf,strlen(buf));

就是:

向标准输出写。

这也是为什么下面这个程序:

char buf[1024]; ssize_t s = read(0, buf, sizeof(buf)); if(s > 0) { write(1, buf, s); }

本质上就是:

键盘 ↓ fd 0 ↓ read ↓ buf ↓ write ↓ fd 1 ↓ 显示器

这里一定注意:read/write 的第一个参数就是 fd。

五、open:如何打开一个文件?

最重要的接口:

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);

三个核心参数:

pathname

即"myfile"

表示:

我要打开/创建哪个文件。

flags

告诉操作系统:

我想以什么方式打开?

最常见:

O_RDONLY → 只读 O_WRONLY → 只写 O_RDWR → 读写

三者必须选一个。

还可以组合:

O_CREAT → 不存在就创建 O_APPEND → 追加写 O_TRUNC → 清空原内容

例如:

open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);

意思:

打开 log.txt,只写;不存在就创建;如果原来有内容就清空。
这里的 | 是按位或,用于把多个选项组合起来。
mode
只有涉及创建文件时才重点看:
0666
表示:
新创建文件的默认权限。

open返回值 成功 → 返回新的fd 失败 → -1

例如:

intfd=open("myfile",O_RDONLY);if(fd<0){perror("open");}

六、read / write / close:真正进行 IO

这三个先和 open 一起记。

open ↓ 得到fd ↓ read / write ↓ close

read

核心形式:

ssize_tread(intfd,void*buf,size_tcount);

可以理解成:

read(从哪个文件读? → fd 读到哪里? → buf 最多读多少? → count)

例如:

charbuf[1024];ssize_ts=read(fd,buf,sizeof(buf));

意思:

从 fd 对应的文件中读取数据,放到 buf,最多读取 1024 字节。

write

可以理解成:

write(写到哪个文件? → fd 写什么? → buf 写多少? → count)

例如:

write(1,buf,s);

意思:

把 buf 中的 s 个字节写到标准输出。

closeclose(fd);

意思:

关闭这个文件描述符。

所以最基本的文件操作框架就是:

open ↓ fd ↓ read / write ↓ close

七、真正理解“重定向”:它不是魔法,只是换了 fd 指向

这是这一章最值得你记住的一张图。

平时: printf() ↓ stdout ↓ fd 1 ↓ 显示器

所以:

./test

输出自然出现在屏幕。

但是如果:

./test>log.txt

本质上发生了:

原来

fd 1 ─────────→ 显示器

重定向后

fd 1 ─────────→ log.txt

于是程序仍然:

printf("hello");

但它访问的是:

stdout ↓ fd 1 ↓ log.txt

所以:

程序自己甚至不需要知道“我要输出到文件”。
它还是向 stdout 输出,只是 Shell 在运行程序之前,把 fd 1 的去向换成了文件。

PDF也通过关闭 1 后再 open 文件得到 fd=1 的实验,直观展示了为什么原本输出到显示器的内容会进入文件,这就是输出重定向。

dup2:更规范地完成重定向

接口:

#include<unistd.h>intdup2(intoldfd,intnewfd);

例如:

dup2(fd,1);

可以理解成:

让 fd 1 也指向 fd 所指向的文件。

于是:

fd ↓ log.txt fd 1 ↓ log.txt

以后:

printf(...)

最终走 stdout → fd 1,自然就进入 log.txt。

PDF的 MiniShell 重定向实现也是在子进程中先 open() 文件,再通过 dup2(fd, 0/1) 替换标准输入或标准输出。

八、把这一章和前面的进程控制串起来

这就是你现在最应该形成的总框架:

Shell │ 用户输入命令 │ ▼ 解析命令 │ ▼ fork() ┌──────┴──────┐ │ │ 父进程 子进程 │ │ │ 判断有没有重定向 │ │ │ open() │ │ │ dup2() │ │ │ ▼ │ exec() │ │ │ ▼ │ ls / cat ... │ │ │ ▼ └──── wait ← exit

你可以把它理解成:

前一章的 fork + exec + wait,这一章又给子进程加上了 open + dup2,于是 Shell 就开始拥有真正的重定向能力。

例如:

ls -l > log.txt

实际上就是:

Shell │ ├─ fork │ ├─ 子进程 │ │ │ ├─ open("log.txt", ...) │ │ │ ├─ dup2(fd, 1) │ │ │ └─ exec("ls") │ └─ 父进程 wait

这就是为什么你之前学的 MiniShell 没有白学。

九、FILE 和 fd:最后把这两个概念分清

你之前接触的是:

FILE*fp;

现在又出现:

intfd;

不要混。

可以简单理解成:

FILE * ↓ C标准库提供的“高级文件对象” ↓ 内部最终还是要依靠 ↓ fd ↓ Linux系统调用

PDF明确指出:C 库的 IO 函数最终还是通过 fd 访问文件,因此 FILE 结构体内部必然包含与 fd 相关的信息。

所以:

高级 ↑ FILE *fp │ │ C标准库 ↓ int fd │ │ 系统调用 ↓ Linux内核 ↓ 文件/设备

这条关系记住就够了。

至于 PDF 后面讲的:

FILE 内部怎么维护缓冲区、为什么 fork 后 printf 会出现重复输出……

这些都属于缓冲区部分,你现在既然还没上这节,先完全跳过。

🧠 最后:6个问题,检验你是不是真的理解了

建议你先别看答案,自己在脑子里说一遍。

① 为什么 open() 返回的是一个整数 fd,而不是一个 FILE *?

答案:因为 open() 是 Linux 系统调用层面的接口,它返回的是当前进程文件描述符表中的一个下标;FILE * 是 C 标准库提供的更高层对象。

② 为什么 Linux 默认有 0、1、2?

答案:

0 → stdin → 标准输入
1 → stdout → 标准输出
2 → stderr → 标准错误

它们让程序可以用统一的 fd 进行输入输出。

③ 为什么 write(1, buf, size) 能输出到显示器?

答案:因为当前进程中 fd=1 默认对应标准输出,而标准输出通常指向终端显示器。

④ ./test > log.txt 为什么能让程序输出到文件?

答案:Shell 在程序执行前改变了标准输出 fd 1 的指向,让它指向 log.txt,所以程序继续向 stdout/fd1 写时,数据就进入文件。

⑤ 为什么 dup2(fd, 1) 能实现重定向?

**答案:**因为它让新的 fd=1 指向与 fd 相同的打开文件对象,因此程序后续向标准输出 1 写入时,就会写入那个文件。

⑥ FILE * 和 fd 到底是什么关系?

答案:

FILE * ↓ C标准库层 ↓ 底层依靠 fd ↓ Linux系统调用 ↓ 内核

所以不是两个完全独立的 IO 系统,而是不同层次的接口。

📌 这一篇最终只记这一张图

Linux 基础 IO │ ┌───────────┴───────────┐ ▼ ▼ 文件是什么 怎么操作文件 │ │ Linux下一切皆文件 C库 / 系统调用 │ ┌─────────────┴─────────────┐ ▼ ▼ C标准库 系统调用 fopen/fclose/fwrite open/read/write │ │ └──────────┬────────────────┘ ▼ fd │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ 0 1 2 stdin stdout stderr │ │ │ 输入 输出 错误 │ ▼ dup2(fd,1) │ ▼ 重定向 │ ▼ ./test > log.txt

你现阶段真正需要形成的认知链:

文件 → 操作系统管理 → 系统调用 → fd → 0/1/2 → dup2 → 重定向 → FILE 是更高层的封装
而且这一次不要急着写代码。你老师让你先“理解吸收”是非常合理的:现在你能把这张图和前面的 fork → exec → wait 串起来,等老师后面真正开始做 MiniShell 重定向时,你会突然发现——原来 >、>>、< 就是这么来的。

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

职场人AI漫剧提效指南:轻量级视听叙事工作流

1. 职场人做漫剧不是“玩票”&#xff0c;而是时间成本的硬核博弈你有没有过这样的经历&#xff1a;下班后想用AI做个职场主题的漫剧小样&#xff0c;发在内部分享群或知识星球里——结果花3小时调参数、修提示词、等渲染&#xff0c;最后成片节奏拖沓、角色口型对不上、背景音…

作者头像 李华
网站建设 2026/9/10 2:26:54

断言、日志、异常、重试:企业级脚本稳定性四件套

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:26:21

KTV歌厅从设备选型到音响隔音调试的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华