Linux 基础IO

目录

理解"⽂件"

狭义理解

• ⽂件在磁盘⾥

• 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的

• 磁盘是外设(既是输出设备也是输⼊设备)

• 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出简称IO

⼴义理解

• Linux下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘......这些都是抽象化的过程)

⽂件操作的归类认知

文件=属性(元数据)+内容

• 对于0KB的空⽂件是占⽤磁盘空间的(因为还有属性)

• ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件=属性(元数据)+内容)

• 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作

系统⻆度

• 对⽂件的操作本质是进程对⽂件的操作

• 磁盘的管理者是操作系统

• ⽂件的读写本质不是通过C语⾔/C++的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的

linux内核中传递标记位的方法

系统文件IO

c/c++操作文件的相关接口,本质上底层都是封装了文件操作相关的系统调用。

编程语言通过抽象层屏蔽不同操作系统的底层差异,通过语言接口在不同系统映射对应的系统调用,实现跨平台性,使开发者无需关注系统调用接口。

所以c++/c中描述打开文件的类/结构体中一定封装了底层文件描述符(fd),因为系统调用的接口只匹配fd

系统调用open

系统接口中使用open函数打开文件,open函数的函数原型如下:

int open(const char *pathname, int flags, mode_t mode);

  1. 第一个参数:
    路径+文件名,不加路径直接在当前路径查找文件
  2. 第二个参数:标记位
    open第二个参数是指定打开方式,通过位图的原理,将多种打开方式用一个32位的整数表示。
    选项如下:
    可以通过按位或(|)传入多个选项,如下
    O_WRONLY | O_CREAT
  3. 第三个参数:权限
    传入八进制数表示如果创建新文件的初始权限。结合umask权限掩码可得到最终权限
    返回值
    open的返回值是文件描述符(文件描述符表的下标),失败返回-1

close

系统接口中使用close函数关闭文件,close函数的函数原型如下:

int close(int fd);

使用close函数时传入需要关闭文件的文件描述符即可,若关闭文件成功则返回0,若关闭文件失败则返回-1。

read

系统接口中使用read函数从文件读取信息,read函数的函数原型如下:

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

我们可以使用read函数,从文件描述符为fd的文件读取count字节的数据到buf位置当中。

如果数据读取成功,实际读取数据的字节个数被返回。

如果数据读取失败,-1被返回。

内核中打开文件的组织形式

与进程描述符(struct task_struct)相似已打开文件都会有一个对应的文件描述(struct file)结构体记录文件的动态信息。

进程操作文件的途径

操作文件的本质是进程对文件的操作,一个进程可能操作多个文件

进程操作文件的途径:

  1. 进程描述符(task_struct)中存在一个文件描述符表(struct files_struct),
  2. 文件描述符表中记录进程打开的文件的信息,包括打开文件的进程描述结构体指针数组(struct file *fd),指向文件的进程描述,由此管理进程打开的文件。
  3. 文件描述(struct file)结构体记录文件的动态信息。
  4. 文件描述符(fd)就是该数组的下标,是最典型的 "句柄" ------ 它本质上是操作系统分配给进程的、用于标识打开文件 / IO 资源的整数型句柄
    类比结合内存描述符理解

不同进程打开同一个文件的struct file问题

struct file中包含打开进程的一些信息(文件位置指针等),所以不同进程打开同一个文件,有各自独立的struct file,而struct file中的引用计数是对于单个进程或父子进程内文件描述符表中指向该struct file的个数。(父子进程子进程通过继承父进程的文件描述符,可以共享指向同一个struct file,引用计数++)

本质:struct file 包含了大量进程特定的状态信息,所以每个进程打开文件时需要独立的实例,而 struct inode 才是真正共享的文件元数据。

文件描述符的分配规则

  1. 进程打开时会默认打开0、1、2,对应标准输入流、标准输出流、标准错误流,0对应键盘,12对应显示器。
    而键盘和显示器都属于硬件,属于硬件就意味着操作系统能够识别到,当某一进程创建时,操作系统就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file连入文件双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。
    由此可以理解linux下一切(资源)皆(抽象为)文件
  2. 自己打开的文件的描述符一般从下标3开始,如果关闭012从关闭位置分配(实现重定向)
  3. 本质是从最小但是没有被使用的fd_array数组下标开始进行分配的。

重定向

重定向的原理

重定向的本质就是修改文件描述符下标对应的struct file*的内容。

  1. 输出重定向:将本应该输出到一个文件的数据重定向输出到另一个文件中。

  2. 追加重定向:和输出重定向相同但唯一区别是,输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据。虽然都对应显示器,但其中一个重定向不会影响另一个。

    分别对应传递open标志位参数的两个宏,追加、覆盖

  3. 输入重定向:将本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。

dup

完成重定向我们只需进行fd_array数组当中元素的拷贝即可,系统调用dup可以实现重定向

原型:int dup2(int oldfd, int newfd);

参数:将第一个参数下标的指针拷贝到第二个参数下标的指针

返回值:成功返回newfd,失败返回-1

注意这里关闭一个fd不会关闭文件,因为文件描述采用类似智能指针的引用计数

stdout和stderr为什么分开

主要原因是为了区分程序的正常输出和错误信息,确保错误信息优先输出。

一切皆文件

"一切皆文件" 的本质是通过统一的抽象接口,将复杂的系统资源转化为可通过文件操作逻辑管理的实体。

统一抽象的文件接口,与底层实现分开,

不同外设都有自己的功能实现,但file提供统一的接口

由此将所有资源用抽象的文件接口进行管理,

这便是一切皆文件

之所以文件系统可以封装设备管理也就是抽象操作方法,本质是因为外设无非也是资源,无非也是I/O数据流动所以可以当作文件

通过在file结构体中存储对应实现方法的指针(与类似c++虚函数表指针)实现多态思想,将接口抽象,与实现解耦,实现一切皆文件

缓冲区

缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备,分为输⼊缓冲区和输出缓冲区。

常见的缓冲区类型:

  1. 标准 I/O 缓冲区(C 语言库层面)
    由 stdio.h 提供(如 printf、fwrite 使用的缓冲区),在FILE中设置指针进行管理,分为三种模式:
  • 全缓冲:缓冲区满时刷新(如普通文件)。
  • 行缓冲:遇到 \n 或缓冲区满时刷新(如终端 stdout)。
  • 无缓冲:数据立即输出(如 stderr 错误流)。
    FILE中的关键部分:fd(系统文件描述符)、缓冲区相关指针
  1. 内核缓冲区(操作系统层面)
    即使应用程序的缓冲区已刷新,数据也不会直接写入物理设备,而是先进入内核缓冲区,由操作系统统一调度写入(如 Linux 的页缓存)。
  2. 硬件缓冲区
    外设自带的缓冲区(如磁盘的缓存、网卡的接收缓冲区),进一步减少与内存的交互次数。

缓冲区的刷新时机:

  • 主动触发:调用 fflush()(标准 I/O)、sync()(内核缓冲)等函数。
  • 条件满足:缓冲区写满、行缓冲遇到 \n(仅终端)。
  • 被动触发:进程正常退出、关闭文件(fclose() 会自动刷新)。

缓冲区的核心作用:

  1. 平衡速度:让快速设备(如 CPU)不用等待慢速设备(如磁盘),提高整体效率。
  2. 减少开销:批量处理 I/O 操作,降低设备访问频率。
  3. 数据暂存:应对突发的大量数据(如网络峰值流量),避免数据丢失。

标准I/O缓冲区与操作系统内核缓冲区的关系

标准I/O库缓冲区将用户程序中的输入数据暂存,避免频繁调用系统调用(调用成本),当缓冲区刷新时才会将数据发送到下一层(内核缓冲区)。

内核中有内核缓冲区,操作系统有刷新方案进行缓冲区管理,来提升磁盘、网络等设备的 I/O 性能

实例综合理解

相关推荐
Jay Chou why did14 分钟前
程序启动地址0x80000000
linux
JAY_LIN——81 小时前
C-语言联合体和枚举
c语言
落笔映浮华丶1 小时前
c程序的翻译过程 linux版
linux·c语言
阮松云1 小时前
code-server 配置maven
java·linux·maven
水饺编程1 小时前
第4章,[标签 Win32] :获取设备环境句柄的第一个方法
c语言·c++·windows·visual studio
Pomelo_刘金1 小时前
Linux I/O 方式进化史(内核/性能视角):从“睡死”到“就绪队列”再到“完成队列”
linux
提伯斯6461 小时前
解决 PX4 + ROS px4ctrl 「No odom!」自动起飞失败问题
linux·ros·px4·fastlio·mid360·egoplanner
Once_day1 小时前
CC++八股文之内存
c语言·c++
2301_765715142 小时前
C语言轮子制造
c语言·开发语言·制造
牛奶咖啡132 小时前
shell脚本编程(八)
linux·shell脚本编程·while循环语句·计数器控制的while循环·标志控制的while循环·until循环·select循环菜单