您现在的位置是:首页 >技术交流 >Linux之文件IO前世今生网站首页技术交流

Linux之文件IO前世今生

xiaolingting 2025-03-30 12:01:01
简介Linux之文件IO前世今生

Linux之文件系统前世今生(一) VFS中,我们提到了文件的读写,并给出了简要的读写示意图,本文将分析文件I/O的细节。
在这里插入图片描述

一、Buffered I/O(缓存I/O)& Directed I/O(直接I/O)

1.1、Page Cache

我们读写一个文件时,会从磁盘加载文件到内存中,以便我们快速读写文件;我们把内存中用于缓存文件的这块区域记为 Page CachePage Cache 位于内核态(所以也叫OS cache)。

  • page 是内存管理分配的基本单位, Page Cache 由多个 page 构成;
  • page 在操作系统中通常为 4KB 大小,而 Page Cache 的大小则为 4KB 的整数倍;
  • 更多 page 细节参见 Linux之内存管理前世今生(一)

1.2、预读

根据程序的局部性原理,加载文件时除了加载文件指定位置内容,同时会加载该位置后续一部分连续内容到内存中,这个机制就是预读。所以 Page Cache 中额外包含了程序后续可能读写的内容。

1.2.1、Page Cache + 预读优势

  • 加速数据访问

    由于内存访问比磁盘访问快的多,且预读了后续数据;

  • 提高系统磁盘I/O吞吐量

    通过一次 I/O 将多个 page 装入 Page Cache 能够减少磁盘 I/O 次数, 进而提高系统磁盘 I/O 吞吐量;

1.3、Write back(写回)& Write Through(写穿)

由于我们在内核态引入的Page Cache机制,所以我们对文件的读写都是基于Page Cache,但文件最终还是需要持久化到磁盘中去的。Linux 提供两种策略将Page Cache脏页(dirty page) 刷回磁盘:

  • Write back(写回)
    • 内核线程周期性地将脏页刷回磁盘,Linux 默认采用此策略 ;
    • 该策略存在数据丢失的风险(比如遇到系统宕机、断电),理论上操作系统不宕机,数据就保证会刷回磁盘,即使用户程序崩溃;
  • Write Through(写穿)
    • 向用户层提供特定接口,应用程序可主动调用接口来直接刷新数据到磁盘
    • 以牺牲系统 I/O 吞吐量作为代价,向上层应用确保一旦写入,数据就已经落盘,不会丢失;

1.3.1、Page Cache刷盘涉及的系统调用

Write back(写回)& Write Through(写穿)这两种写策略均依赖系统调用,分为如下3种:

  • sync()

    将所有修改过的缓冲区排入写队列,然后就返回了,它并不等实际的写磁盘的操作结束。所以它的返回并不能保证数据的安全性。通常会有一个update系统守护进程每隔30s调用一次sync。

  • fsync(fd)
    • fd 代表的文件的脏数据和文件属性全部刷新至磁盘中;
    • 确保一直到写磁盘操作结束才会返回。数据库一般使用 fsync
  • fdatasync(fd)
    • fd 代表的文件的脏数据刷新至磁盘,fdatasync的功能与fsync类似,但是仅仅在必要的情况下才会同步文件属性,因此可以减少一次IO写操作;
    • 举例来说,文件的尺寸(st_size)如果变化,是需要立即同步的,否则OS一旦崩溃,即使文件的数据部分已同步,由于文件属性没有同步,依然读不到修改的内容。而最后访问时间(atime)/修改时间(mtime)是不需要每次都同步的,只要应用程序对这两个时间戳没有苛刻的要求,基本无伤大雅。

1.3.2、Write back 刷盘时机

  • Page Cache 脏页数量超过设定阈值;
  • Page Cache 脏页缓存超过设定缓存时间;
  • 应用程序主动刷盘,即调用 sync()fdatasync(fd)fsync(fd) 三者任一;
  • 物理内存分配告警;

1.4、Buffered I/O(缓存I/O)& Directed I/O(直接I/O)

  • 前面我们在内核态引入了Page Cache用于加速文件I/O的操作就是 Buffered I/O(缓存I/O)

在这里插入图片描述

  • 相反,如果在内核态关闭Page Cache的使用(通过参数O_DIRECT),文件I/O直接与磁盘交互,我们称为Directed I/O(直接I/O)

在这里插入图片描述

问题来了:Page Cache 这么好,什么场景需要关闭?

  • Page Cache 位于内核态,对用户态提供的API灵活性差,用户态的应用程序无法对Page Cache 进行个性化定制,比如什么时间刷盘,刷哪些数据……
  • Page Cache 容量受限,大文件读写时,很快会把Page Cache消耗完,导致之前缓存的常用的、热点数据被移出内存,下次访问热点数据时产生磁盘I/O,从而降低系统性能;即Page Cache 缓存的是小文件的热点数据。
  • 举例:Mysql 中 InnoDB :
    • Buffer Pool 关闭了Page Cache,即不在内核态缓存数据,直接在用户态缓存数据;
    • redo log buffer 通过参数innodb_flush_log_at_trx_commit(取值为0,1,2)设置为2来开启 Page Cache。

二、Blocking I/O(阻塞I/O)& Non Blocking I/O(非阻塞I/O)

  • 前面我们从 Page Cache 的维度,将 I/O分为 缓存I/O 和 直接I/O;
  • 接下来,我们从进程阻塞阶段的维度,将 I/O 分为 阻塞I/O 和 非阻塞I/O;

2.1、阻塞定义

阻塞 的主体是进程当进程进入阻塞状态,是不占用CPU资源的

2.2、阻塞时机

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使当前进程由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,所以只有处于运行态(获得CPU)的进程,才可能将其转为阻塞状态。

2.3、阻塞I/O

由前面定义,I/O时期待的事件未发生,产生阻塞,那到底期待啥呢?

等待内核将数据准备好,换言之,等待 Page Cache 中有程序请求的数据。

以文件读取为例:当一个read操作发生时,它会经历两个阶段:

第一阶段:等待数据准备 (Waiting for the data to be ready)。

第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。

2.3.1、阻塞I/O vs 非阻塞I/O

当应用程序发起read时,且Page Cache 中没有程序请求的数据时,内核会加载磁盘数据,若加载数据同时,

  • read调用立即返回告诉程序,数据没有准备好,这就是非阻塞I/O

    非阻塞 I/O 在I/O执行的第二个阶段仍然被阻塞了。

  • 相反,内核闷声干活,直到数据加载完,并且数据从内核拷贝到应用程序中,才返回,这就是阻塞I/O

    阻塞 I/O 在I/O执行的两个阶段都被阻塞了。

在这里插入图片描述

在这里插入图片描述

三、同步 I/O(synchronous I/O)& 异步 I/O(asynchronous I/O)

POSIX(Portable Operating System Interface, 可移植操作系统接口)关于同步I/O和异步I/O的定义如下:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

An asynchronous I/O operation does not cause the requesting process to be blocked;

在这里插入图片描述

说人话就是,同步I/O会阻塞进程,异步I/O不会阻塞进程
我们之前提到的 阻塞I/O 和 非阻塞I/O 都是同步I/O

  • 阻塞I/O 两个阶段都阻塞;
  • 非阻塞I/O 第二个阶段阻塞;

四、小节

  • Page Cache 的维度,将 I/O分为 缓存I/O 和 直接I/O;
  • 进程阻塞阶段的维度,将 I/O 分为 阻塞I/O 和 非阻塞I/O;
  • 进程阻塞的维度,将 I/O 分为 同步I/O 和 异步I/O。

文件 I/O 至此基本介绍完毕,后续会介绍网络 I/O。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。