关注

【Linux:文件】文件基础IO进阶


头像


🎬 个人主页艾莉丝努力练剑

专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录
Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平

🎬 艾莉丝的简介:

在这里插入图片描述



在这里插入图片描述


1 ~> 预备阶段

1.1 文件操作归类认知相关重点:文件 = 内容 + 属性

文件时文件属性(元数据)和文件内容的集合,即文件 = 内容 + 属性(元数据),我们可以联想到一些文件操作,比如对内容做操作可以用fread/fwrite,对属性做操作可以用fstat

一个常识:操作文件之前要先打开文件!

打开文件这里有三问,大家可以思考一下:

  • 打开文件的本质是什么?

  • 为什么要打开文件?

  • 谁打开的文件?

举个例子,比如第一问:打开文件的本质是什么?把文件加载到内存!

1.2 对于文件的狭义上的理解:IO

磁盘上的文件本质是对文件的所有操作,都是对外设的输入和输出,简称“IO”。

IO,也就是 InputOutput——我们需要站在内存的角度去理解:

在冯诺依曼体系结构里面,我们说IO的时候永远要站在内存的角度说IO!

在这里插入图片描述

  • 上面的图中写错了,我们站在内存角度!

1.3 文件操作学习的本质:学习进程和文件的关系

在这里插入图片描述

1.4 访问文件就是在访问磁盘

在这里插入图片描述

1.5 思维导图

如下图所示:

在这里插入图片描述


2 ~> C文件操作:挑重点

2.1 句柄

来表征文件唯一性的标识符字段都可以把它叫做句柄。

  • 这个柄可以理解为 “把柄”

在这里插入图片描述


3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术

在 Linux 的世界里,“一切皆文件”不仅仅是一句口号,它是整个系统设计的灵魂。本文将带你深度剖析从 C 语言标准 I/O 到系统调用 I/O 的演进,揭秘文件描述符的底层逻辑,并拆解缓冲区背后的性能陷阱。

3.1 重新定义“文件”:从宏观哲学到系统接口

3.1.1 广义与狭义的文件理解

在普通用户眼中,文件是磁盘上的 .txt.mp4。但在 Linux 开发者看来:

  • 狭义文件:存储在磁盘等永久性介质上的数据集合。

  • 广义文件:一切皆文件。键盘是文件(只读)、显示器是文件(只写)、网卡、磁盘、甚至进程间通信的管道都是文件。

这种抽象极其强大:操作系统通过一套统一的接口(read/write)就能管理所有硬件,屏蔽了不同外设的物理差异。

3.1.2 C 库函数 vs 系统调用接口

我们熟悉的 fopenfwritefread 是 C 标准库提供的函数。而 Linux 内核提供的是系统调用 openwriteread

  • 关系:库函数是对系统调用的二次封装

  • 原因:系统调用接口直接面向内核,使用复杂且不具备跨平台性。标准库通过封装,既提高了易用性,又通过缓冲区提升了性能。

3.2 核心机制:文件描述符(fd)与重定向

3.2.1 什么是文件描述符?

当你调用 open 时,内核会为进程创建一个 struct file 结构体来描述该文件。进程通过一个名为 file_struct 的结构体管理所有打开的文件,其内部包含一个数组(文件描述符表)。

文件描述符 (fd):本质上就是这个数组的下标。

标准分配规则

  • 0:标准输入 (stdin) —— 对应键盘

  • 1:标准输出 (stdout) —— 对应显示器

  • 2:标准错误 (stderr) —— 对应显示器

3.2.2 重定向的本质

所谓的“输出重定向”(如 > log.txt),其底层逻辑非常简单:改变文件描述符数组中下标的内容

例如,原本下标 1 指向显示器,重定向操作让下标 1 指向 log.txt。程序依然向 1 号 fd 写入,数据自然就流向了文件。

  • 关键系统调用:
int dup2(int oldfd, int newfd);
  • 该系统调用的作用:将 oldfd 的内容拷贝给 newfd。如果你想把输出重定向到文件,只需 dup2(fd_of_file, 1)。

3.2.3 “一切皆文件”的底层实现

内核通过 struct file 中的 file_operations(函数指针结构体)来实现多态。

每个硬件驱动都有自己的 read / write 实现。

内核 struct file 指向这些驱动函数。

对于上层应用来说,只需调用同一个 write,底层会自动根据 fd 找到对应的硬件操作。

3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体

3.3.1 为什么会有缓冲区?

磁盘 I/O 的速度远慢于内存。如果每写一个字符都调用一次系统调用,CPU 大量时间会浪费在上下文切换上。

用户级缓冲区:由标准库(Glibc)维护。printf、fwrite 将数据先攒在内存里。

内核缓冲区:由 OS 维护。调用 write 后,数据进入内核缓冲区,由 OS 决定何时刷新到磁盘。

3.3.2 刷新策略的差异

行缓冲 (_IOLBF):遇到 \n 就刷新(如 stdout)。

全缓冲 (_IOFBF):缓冲区满了才刷新(如普通文件)。

无缓冲 (_IONBF):立即刷新(如 stderr)。

3.3.3 一个经典的“坑”:fork 后的重复输出

现象:程序中 printf("hello\n") 一次,如果直接运行显示一行;但如果重定向到文件,再 fork(),结果文件里出现了两行 hello

原因:

  • 1、重定向到文件后,刷新策略由“行缓冲”变为“全缓冲”。

  • 2、printf 的数据留在用户缓冲区内没来得及刷新。

  • 3、执行 fork() 时,缓冲区作为父进程内存的一部分,被写时拷贝到了子进程。

  • 4、程序结束时,父子进程各自刷新缓冲区,导致同一份数据被写了两遍。

注意:write 是系统调用,没有用户级缓冲区,因此不会出现这种现象。

3.4 动手实战:理解 FILE 结构体封装

在 C 语言中,FILE 结构体其实是一个“大管家”,它内部封装了:

  • 1、文件描述符 (fd)

  • 2、用户级缓冲区及其相关的读写指针。

  • 3、刷新模式等信息。

我们可以尝试自己模拟实现一个 my_stdio,定义一个 mFILE 结构体,包含一个 int _fd 和一个 char _buffer[1024],手动管理数据拷贝和 write 调用。通过这个过程,你就能彻底理解 fflush 到底在做什么——它只是把用户缓冲区的数据 write 给了内核。


4 ~> 总结与学习建议

4.1 逻辑架构和切入点

在这里插入图片描述
在这里插入图片描述

4.2 总结

  • 1、分层理解:应用层 (C 库) -> 系统调用层 (OS) -> 驱动层 (硬件)。

  • 2、抓住 fd:fd 是连接用户态与内核态的纽带。

  • 3、缓冲区是性能核心:理解什么时候数据在你的进程里,什么时候在内核里。

4.3 学习建议

下一步挑战: 尝试在你的 minishell (自主实现的命令行)中加入重定向功能,利用 strtok 解析 >>>,并使用 dup2 改变进程的文件去向。


5 ~> 思考

如果我们在程序中先关闭了 1 号文件描述符(close(1)),然后紧接着打开一个新文件,那么这个新文件的 fd 会是多少?此时调用 printf 会发生什么?

希望这篇笔记能帮你打通 Linux I/O 的任督二脉!如果你对“写时拷贝”与缓冲区的结合点还有疑惑,欢迎随时讨论。


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾

【Linux:文件】基础IO

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

在这里插入图片描述

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_89899187/article/details/158285965

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--