IO流起步-从fopen到文件描述符fd

IO 流起步:从 fopen 到文件描述符 fd

时间回到 2026-06-27。进程控制刚收尾,正式进入「IO 流」章节------

也就是那个贯穿后文所有话题(文件系统、缓冲区、重定向)的起点:

进程和文件,到底是怎么"接上头"的?

本篇覆盖:C 库文件接口 vs 系统调用接口、FILE 与 fd 的关系、open 的 flags 与 umask。


一、视角切换:程序与文件的关系 = 进程与文件的关系

开篇先把最核心的一句话立住:

文件 = 文件的内容 + 文件的属性。

而"谁去打开文件、谁去找路径",答案都是:进程

所以「程序操作文件」这个说法,剥开看就是**「进程操作文件」**。整个操作系统的四大管理(进程管理、文件管理、内存管理、驱动管理)在这里交汇。

由此立刻能回答一个铺垫已久的问题:为什么 PCB 里要存 CWD(当前工作目录)?

  • 文件系统需要路径来定位文件,而相对路径的解析基准就是 CWD------没有 CWD,"相对"二字无从谈起;
  • 一切以 -bash 进程的 CWD 为起点"衍生万物";
  • 注意区分:运行可执行文件时找命令看的是环境变量 PATH;进程打开/创建文件时解析路径看的是 CWD。两条机制各管一段,设计者刻意让它们互补连接。

二、为什么文件操作函数都"往内存里搬"?

冯·诺依曼架构的老规矩:CPU 只从内存读取数据,直接读磁盘太慢。

所以:

  • fopen 等一切文件操作函数,本质都是把文件(至少是把需要的部分)加载进内存
  • 进程本身也是如此------二进制文件被加载进内存,CPU 才能执行。

加载属性还是加载内容? 看需求,所以有对应的函数。于是文件有了两种状态:

状态 位置 例子
磁盘文件(未打开) 磁盘上,躺平 上篇博客的 Ext 文件系统部分
内存文件(已打开) 被进程打开,驻留内存 本篇的主角

而"一个进程可以打开很多个文件"(1 : n )------最起码每个进程都得打开自己的二进制文件;100 个进程就至少 100 个。文件一多就要管理,管理方式又双叒叕是那套:先描述(struct file{})、再组织(链表的增删查改),和进程管理如出一辙。


三、两套接口:C 标准库 与 系统调用

函数 返回/标识
C 语言标准(库函数) fopen / fclose / fread / fwrite / fputs FILE*(文件指针/文件句柄)
系统调用标准(Linux 本身) open / close / read / write int fd(文件描述符)

关系一句话:库函数封装了系统调用 ------无论 C 还是 C++ 的文件接口,底层最终都是 open/close/write

3.1 FILE 的本质:一个结构体

FILE* 不是什么神秘的东西,它就是一个结构体 ,里面封装了 fd

  • stdin 里封装的是 0(标准输入)
  • stdout 里封装的是 1(标准输出)
  • stderr 里封装的是 2(标准错误)

也就是说:fputsscanf 这些函数的底层全部都是 write() ,而 write() 只认 0、1、2 这样的整数编号

3.2 为什么从键盘打印的内容会"换行"?------ 文件即 char 数组

一个非常好用的心智模型:

文件在我看来,就是一维 char 数组 char[]

  • \n 也只是数组里的一个下标位置的字符;
  • 内存里存的就是字面量 '\n'
  • 打印到屏幕时,输出函数把它"转换"成换行动作------换行是显示器(驱动)的解读,不是文件里存了个"换行魔法"。

顺带一个 C 语言约定:\0 是字符串结束标记,但操作系统不认识 \0------文本读写函数默认不会把它写进文件。文件是文件,C 字符串是 C 字符串,两层标准别混。


四、open:flags、mode 与 umask

c 复制代码
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

4.1 访问模式:三选一,互斥

标志 含义
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写

4.2 附加标志:按位或 | 随意叠加

标志 含义
O_CREAT 文件不存在则创建,此时必须传第三个参数 mode
O_EXCL 配合 O_CREAT:文件已存在直接报错(防止覆盖)
O_TRUNC 打开时清空文件内容
O_APPEND 写操作永远追加到末尾
O_NONBLOCK 非阻塞打开

flags 的设计思想 :这些宏本质都是只有一个 bit 为 1 的整数 。传参时用按位或 | 组合(遵守交换律,顺序无所谓),内核里用按位与 & 逐位检测------这就是"位标志(bit flag)"这一经典 C 设计。fopen 的 mode 参数最后也被翻译成这套 flags。

4.3 权限与 umask

新建文件的初始权限并不等于你写的 mode:

复制代码
实际权限 = mode & ~umask
  • 一般 mode 设为 0666
  • 每个系统有自己的默认掩码(通常 0002),想把权限"给足"可以用 umask(0) 临时清零;
  • 这就是为什么创建出来的文件权限总比你写的少几位。

4.4 为什么第一个打开的文件 fd 是 3?

因为 0、1、2 在进程启动时就已经被 stdin/stdout/stderr 占用了 ------进程一睁眼就自带三个标准流。所以你 open 的第一个文件自然从 3 开始编号。(这个细节后面讲重定向时威力无穷,此处先埋个种子。)


五、fopen 的 mode 与 shell 重定向的对应

fopen mode 行为 shell 里的影子
"w" 存在则清空,不存在则创建 > 文件名
"a"(append) 追加到结尾 >> 文件名(如 echo "x" >> f
"r" 只读打开 < 输入重定向

你会发现 shell 的重定向符号和 C 的文件模式是一一对应的------因为 shell 的重定向本来就是"打开文件 + 换描述符指向"这套机制的用户级语法糖。


六、小结

复制代码
冯诺依曼架构:CPU 只碰内存
        ↓
打开文件 = 把文件载入内存(内容/属性看需求)
        ↓
一个进程 : n 个文件 → struct file{} + 链表管理(先描述再组织)
        ↓
两套接口:FILE*(库) ↔ fd(系统调用),库函数封装系统调用
        ↓
stdin=0, stdout=1, stderr=2 → 自己 open 的从 3 开始

五条最值钱的结论:

  1. 程序与文件的关系,本质是进程与文件的关系;CWD 为相对路径兜底,PATH 为找命令兜底;
  2. 文件有两种状态 :磁盘文件与内存文件,fopen/open 是两者之间的桥;
  3. FILE 是结构体,内部封装 fd ;一切 f* 函数底层都是 write/read 系统调用;
  4. flags 是位标志| 组合、& 检测;实际权限 = mode & ~umask
  5. fd 从 3 开始,因为 0/1/2 出生即被占用。

本文基于 2026-06-27《IO流》《IO流函数》及《open 函数使用方法》笔记整理。下一篇继续这条线:文件描述符的分配规则与重定向的本质。

相关推荐
Rabitebla1 小时前
【Linux系统编程】 指令(二):一条路径是怎么定位到文件的
linux·c++·笔记·学习·算法
人工智能培训1 小时前
大语言模型:从语言理解到通用智能的跃迁
linux·服务器·前端·人工智能
顶点多余1 小时前
9.20 知识点查漏补缺
linux·面试·职场和发展
阿明62 小时前
Linux进程【Linux】
linux·运维·服务器
Android系统攻城狮2 小时前
Linux Gstreamer深度解析之gst_audio_resampler_update调用流程与实战(三十)
linux·运维·服务器·gstreamer音视频·音视频进阶
qetfw2 小时前
Linux tcpdump 抓包实战:网卡、主机、端口过滤与 PCAP 分析
linux·tcpdump
pilifeng12 小时前
Ubuntu Linux 目录结构详解
linux·ubuntu
电棍2332 小时前
ubuntu网卡失效
linux
律宏阔2 小时前
VMware 能识别 USB 设备,但无法连接(灰色)
linux