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

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

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


🎬 艾莉丝的简介:


文章目录

  • [1 ~> 预备阶段](#1 ~> 预备阶段)
    • [1.1 文件操作归类认知相关重点:文件 = 内容 + 属性](#1.1 文件操作归类认知相关重点:文件 = 内容 + 属性)
    • [1.2 对于文件的狭义上的理解:IO](#1.2 对于文件的狭义上的理解:IO)
    • [1.3 文件操作学习的本质:学习进程和文件的关系](#1.3 文件操作学习的本质:学习进程和文件的关系)
    • [1.4 访问文件就是在访问磁盘](#1.4 访问文件就是在访问磁盘)
    • [1.5 思维导图](#1.5 思维导图)
  • [2 ~> C文件操作:挑重点](#2 ~> C文件操作:挑重点)
    • [2.1 句柄](#2.1 句柄)
  • [3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术](#3 ~> 从内核视角看 Linux 基础 I/O:一切皆文件与缓冲区的艺术)
    • [3.1 重新定义"文件":从宏观哲学到系统接口](#3.1 重新定义“文件”:从宏观哲学到系统接口)
      • [3.1.1 广义与狭义的文件理解](#3.1.1 广义与狭义的文件理解)
      • [3.1.2 C 库函数 vs 系统调用接口](#3.1.2 C 库函数 vs 系统调用接口)
    • [3.2 核心机制:文件描述符(fd)与重定向](#3.2 核心机制:文件描述符(fd)与重定向)
      • [3.2.1 什么是文件描述符?](#3.2.1 什么是文件描述符?)
      • [3.2.2 重定向的本质](#3.2.2 重定向的本质)
      • [3.2.3 "一切皆文件"的底层实现](#3.2.3 “一切皆文件”的底层实现)
    • [3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体](#3.3 效率之争:内核缓冲区、用户缓冲区与 FILE 结构体)
      • [3.3.1 为什么会有缓冲区?](#3.3.1 为什么会有缓冲区?)
      • [3.3.2 刷新策略的差异](#3.3.2 刷新策略的差异)
      • [3.3.3 一个经典的"坑":fork 后的重复输出](#3.3.3 一个经典的“坑”:fork 后的重复输出)
    • [3.4 动手实战:理解 FILE 结构体封装](#3.4 动手实战:理解 FILE 结构体封装)
  • [4 ~> 总结与学习建议](#4 ~> 总结与学习建议)
    • [4.1 逻辑架构和切入点](#4.1 逻辑架构和切入点)
    • [4.2 总结](#4.2 总结)
    • [4.3 学习建议](#4.3 学习建议)
  • [5 ~> 思考](#5 ~> 思考)
  • 结尾


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 写入,数据自然就流向了文件。

  • 关键系统调用:
cpp 复制代码
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

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

相关推荐
@haihi2 小时前
ESP32 MQTT示例解析
开发语言·网络·mqtt·github·esp32
Agent产品评测局2 小时前
企业自动化项目,如何做好内部推广与员工培训?——企业级智能体落地与人才赋能实测指南
运维·人工智能·ai·chatgpt·自动化
艾莉丝努力练剑2 小时前
【MYSQL】MYSQL学习的一大重点:表的约束
linux·运维·服务器·开发语言·数据库·学习·mysql
程序猿编码2 小时前
基于ncurses的TCP连接可视化与重置工具:原理与实现(C/C++代码实现)
linux·c语言·网络·c++·tcp/ip
℡ 萧2 小时前
OSPF开销值、协议优先级及计时器的修改-新版(17)
网络·网络协议·网络安全·智能路由器·信息与通信
nunca_te_rindas2 小时前
算法刷体小结汇总(C/C++)20260328
c语言·c++·算法
Sunshine for you2 小时前
高性能压缩库实现
开发语言·c++·算法
Sunshine for you2 小时前
C++中的表达式模板
开发语言·c++·算法
qwehjk20082 小时前
C++中的状态模式
开发语言·c++·算法