linux 文件缓存

 

缓存I/O

一般来说,当调用 open() 系统调用打开文件时,如果不指定 O_DIRECT 标志,那么就是使用缓存I/O来对文件进行读写操作。我们先来看看 open() 系统调用的定义:

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

下面说明一下各个参数的作用:

  • pathname:指定要打开的文件路径。
  • flags:指定打开文件的标志。
  • mode:可选,指定打开文件的权限。

其中 flags 参数可选值如下表:

标志

说明

O_RDONLY

以只读的方式打开文件

O_WRONLY

以只写的方式打开文件

O_RDWR

以读写的方式打开文件

O_CREAT

若文件不存在,则创建该文件

O_EXCL

以独占模式打开文件;若同时设置 O_EXCL 和 O_CREATE, 那么若文件已经存在,则打开操作会失败

O_NOCTTY

若设置该描述符,则该文件不可以被当成终端处理

O_TRUNC

截断文件,若文件存在,则删除该文件

O_APPEND

若设置了该描述符,则在写文件之前,文件指针会被设置到文件的底部

O_NONBLOCK

以非阻塞的方式打开文件

O_NELAY

同 O_NELAY,若同时设置 O_NELAY 和 O_NONBLOCK,O_NONBLOCK 优先起作用

FASYNC

若设置该描述符,则 I/O 事件通知是通过信号发出的

O_SYNC

该描述符会对普通文件的写操作产生影响,若设置了该描述符,则对该文件的写操作会等到数据被写到磁盘上才算结束

O_DIRECT

该描述符提供对直接 I/O 的支持

O_LARGEFILE

该描述符提供对超过 2GB 大文件的支持

O_DIRECTORY

该描述符表明所打开的文件必须是目录,否则打开操作失败

O_NOFOLLOW

若设置该描述符,则不解析路径名尾部的符号链接

flags 参数用于指定打开文件的标志,比如指定 O_RDONLY,那么就只能以只读方式对文件进行读写。这些标志都能通过 位或 (|) 操作来设置多个标志如:

 

 

O_DIRECT: 无缓冲的输入、输出。

O_SYNC:以同步IO方式打开文件。
下面对这两个flag做一些详细的说明。

O_DIRECT:

直接IO:Linux允许应用程序在执行磁盘IO时绕过缓冲区高速缓存,从用户空间直接将数据传递到文件或磁盘设备,称为直接IO(direct IO)或者裸IO(raw IO)。

应用场景:数据库系统,其高速缓存和IO优化机制均自成一体,无需内核消耗CPU时间和内存去完成相同的任务。
使用直接IO的弊端:可能会大大降低性能,内核对缓冲区告诉缓存做了不少优化,包括:按顺序预读取,在成簇磁盘块上执行IO,允许访问同一文件的多个进程共享高速缓存的缓冲区。
使用方法:在调用open函数打开文件或设备时指定O_DIRECT标志。
注意可能发生的不一致性:若一进程以O_DIRECT标志打开某文件,而另一进程以普通(即使用了高速缓存缓冲区)打开同一文件,则由直接IO所读写的数据与缓冲区高速缓存中内容之间不存在一致性,应尽量避免这一场景。
 
使用直接IO需要遵守的一些限制:
  • 用于传递数据的缓冲区,其内存边界必须对齐为块大小的整数倍
  • 数据传输的开始点,即文件和设备的偏移量,必须是块大小的整数倍
  • 待传递数据的长度必须是块大小的整数倍。

不遵守上述任一限制均将导致EINVAL错误

 

先总结一下stdio函数库和内核采用的缓冲这两级缓冲,然后用图说明两层缓冲机制和各种缓冲类型的控制机制。

  • 首先,通过stdio库将用户数据传递到stdio缓冲区,该缓冲区位于用户态内存区。
  • 当缓冲区填满,stdio库会调用write()系统调用,将数据传递到内核高速缓冲区,该缓冲区位于内核态内存区。
  • 最终,内核发起磁盘操作。
该层次结构如下图所示

 

 

 

输入输出数据的缓冲由内核和stdio库完成。有时可能希望阻止缓冲,但这需要了解其对应用程序性能的影响。

可以使用各种系统调用和库函数来控制内核和stdio缓冲,并执行一次性的缓冲区刷新。
在Linux环境下,open()所特有的O_DIRECT标识允许特定应用跳过缓冲区高速缓存

 

 

问题:

在InnoDB存储引擎的配置中参数innodb_flush_method通常设置为O_DIRECT,这也是官方文档所推荐的设置值。DBA或开发人员知道该参数是文件打开的一个标识,启用后文件的写入将绕过操作系统缓存,直接写文件。其在InnoDB存储引擎中的表现为对于写入到数据表空间将绕过操作系统缓存。这样设置通常不会有更好的性能,但是数据库已经有自己的缓存系统,这样的设置可以确定数据库系统对于内存的使用。下面是man手册中对于O_DIRECT选项的说明:

然而在源码内,细心的读者可能会产生一些困惑,因为不管参数 innodb_flush_method的设置为何值,在刷新脏页时都会调用fsync操作,具体见函数buf_flush_buffered_writes。那么,当用户已经打开文件操作的O_DIRECT标识,为什么还需要进行一次fsync操作确保文件的写入呢?

 

inode中的元数据是保存在inode cache中,inode的保存文件的数据是保存在操作系统缓存中(若未开启O_DIRECT标识)
fsync操作会同步上图中的Inode cache,Buffer cache(也就是操作系统缓存),Directory cache。因此这就是为什么InnoDB存储引擎即使在文件打开时加上O_DIRECT标识,刷新脏页依然需要fsync操作。这是因为O_DIRECT标识只是忽略了图中Buffe cache。刷新文件的另一个函数fdatasync,其仅刷新buffer cache的内容到磁盘,因此比fsync有更好的性能,但是存在数据丢失的风险。

 

 buffer 与cache:

通过这个文档,我们可以看到:

  • Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
  • Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
  • SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。

好了,我们终于找到了这三个指标的详细定义。到这里,你是不是长舒一口气,满意地想着,总算弄明白 Buffer 和 Cache 了。不过,知道这个定义就真的理解了吗?这里我给你提了两个问题,你先想想能不能回答出来。

第一个问题,Buffer 的文档没有提到这是磁盘读数据还是写数据的缓存,而在很多网络搜索的结果中都会提到 Buffer 只是对将要写入磁盘数据的缓存。那反过来说,它会不会也缓存从磁盘中读取的数据呢?

第二个问题,文档中提到,Cache 是对从文件读取数据的缓存,那么它是不是也会缓存写文件的数据呢?

为了解答这两个问题,接下来,我将用几个案例来展示, Buffer 和 Cache 在不同场景下的使用情况。

 

然后运行dd命令向磁盘分区/dev/sdb1写入2G数据

1$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048

1procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- 2 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st 31  0      0 7584780 153592  97436    0    0   684     0   31  423  1 48 50  2  0 4 1  0      0 7418580 315384 101668    0    0     0     0   32  144  0 50 50  0  0 5 1  0      0 7253664 475844 106208    0    0     0     0   20  137  0 50 50  0  0 6 1  0      0 7093352 631800 110520    0    0     0     0   23  223  0 50 50  0  0 7 1  1      0 6930056 790520 114980    0    0     0 12804   23  168  0 50 42  9  0 8 1  0      0 6757204 949240 119396    0    0     0 183804   24  191  0 53 26 21  0 9 1  1      0 6591516 1107960 123840    0    0     0 77316   22  232  0 52 16 33  0

 

从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是 bo 大于  0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快得多。

这说明,写磁盘用到了大量的 Buffer,这跟我们在文档中查到的定义是一样的

 

观察 vmstat 的输出,你会发现读磁盘时(也就是 bi 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快很多。这说明读磁盘时,数据缓存到了 Buffer 中。

当然,我想,经过上一个场景中两个案例的分析,你自己也可以对比得出这个结论:读文件时数据会缓存到 Cache 中,而读磁盘时数据会缓存到 Buffer 中。

到这里你应该发现了,虽然文档提供了对 Buffer 和 Cache 的说明,但是仍不能覆盖到所有的细节。比如说,今天我们了解到的这两点:

Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存”。
Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。
这样,我们就回答了案例开始前的两个问题。

 

 

 

参考:

https://www.cnblogs.com/suzhou/p/5381738.html

 https://blog.csdn.net/shaochenshuo/article/details/72963122

https://cloud.tencent.com/developer/article/1553680

 

posted @   redrobot  阅读(822)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示