导读
- 可以了解到 Buffer 和 Cache 的区别
- 传统 IO 模型中对 Buffer 和 Cache 的使用
- Linux 的 PageCache 和 BufferCahe 是什么以及它们的关系
起因
- hello,大家好,我是 Lorin,事情起因源于在知乎看到一篇问答: Cache 和 Buffer 的区别是什么?
![企业微信截图_16923347739903.png](https://i-blog.csdnimg.cn/blog_migrate/438b3222ed6a99ce51492ee80ca583e4.png)
- 下面有很多知乎博主进行了回答,下面摘抄了几个个人觉得比较好的回答:
![企业微信截图_16923349618621.png](https://i-blog.csdnimg.cn/blog_migrate/c542d07ab1fdaa0066fb24f640e1abfa.png)
![企业微信截图_1692335068454.png](https://i-blog.csdnimg.cn/blog_migrate/44230ce7dd0c273f2d9aa3118afdc95e.png)
![企业微信截图_16923351012800.png](https://i-blog.csdnimg.cn/blog_migrate/7d491edd8904cf54b672851dc173f326.png)
Buffer 和 Cache
- 通过大家的回答和自己查阅资料,以下是个人见解:
- Cache 标准可以翻译为缓存或者快取,主要用于解决系统两端性能不匹配的问题,利用空间局部性原理和时间局部性原理加快访问速度,提高性能。比如我们常见的CPU多级缓存;通俗的讲,比如我们每天出门会带个包,包的空间有限,我们会选取经常使用的物品放在其中,避免我们身上没有带这个物品而需要辛辛苦苦跑回家去取,这个包相当于缓存。
- Buffer 翻译为缓冲区,更偏向解决生产者和消费者速率不匹配的问题,平滑整个传输过程减少延迟,提高性能。比如我们常见的磁盘缓冲区;通俗的例子,比如以前还小的时候,大家还没有自来水,水都来自山上,但每到晚上用水量就会剧增,怎么办呢?比如大家都会修建蓄水池来储水,避免没有晚上水不够用,这个蓄水池就称为缓冲区。
![企业微信截图_1692352919646.png](https://i-blog.csdnimg.cn/blog_migrate/45267f8feb7a25c0c230c4592a897e50.png)
- Cache 和 Buffer 在系统实现中,特别是在 IO 系统中,Cache 和 Buffer 是一体的,同时具备缓冲区和缓存的作用,比如内核缓冲区就同时具备缓存和缓冲区的作用,提高了我们文件访问速率,同时也避免频繁的写入数据到磁盘中,起到了保护磁盘和提高文件写入速度的作用。
传统 IO 模型中的 Buffer 和 Cache
![企业微信截图_16923530711803.png](https://i-blog.csdnimg.cn/blog_migrate/377492d67e2ce282b827519a2f1a5277.png)
- 传统 IO 模型涉及到 Buffer 和 Cache的地方主要有以下几个地方:用户缓冲区、内核缓冲区、磁盘缓冲区,我们下面一一进行简单介绍:
用户缓冲区
- 所谓用户缓冲区就是用户进程在读取文件等场景时,常常先申请一块内存存储我们读取的数据,我们称为 Buffer,每次 read 调用将 Buffer 填充满之后再进行下一次 read 调用,从而减少 read 系统调用,降低系统调用用户态和内核态切换的开销。
内核缓冲区
- 数据预读及缓存:当一个用户进程需要读取磁盘数据时,不会直接读取磁盘而是会先读取内核缓存区,若内核缓冲区存在数据,则直接内核缓冲区的数据;若内核缓冲区中不存在,则请求从磁盘读取,读取时不仅会读取我们需要的数据,还会预读相邻的数据(空间局部性原理)。
- 延时回写:当一个用户进程需要写数据时,数据不会直接写入到磁盘中,而是将数据写入到内核缓冲区,在适当的时机,内核会将内核缓冲区的数据写入到磁盘中,避免频繁的磁盘的写入以及提高写入速度。
- 从上面我们可以看出,内核缓冲区不仅仅具有 Buffer 的作用,同时也具备 Cache 的作用。
内核缓冲区的写入时机
- 缓冲区满: 当内核缓冲区达到一定的阈值或满载时,操作系统会触发将数据写入磁盘,以释放缓冲区空间。
- 定期刷新: 操作系统可能会定期进行刷新,将内核缓冲区中的数据写入磁盘,以确保数据的一致性。这种刷新通常通过一些后台进程或线程来完成。
- 文件关闭: 当一个文件被关闭时,操作系统会将该文件的内核缓冲区中的数据写入磁盘,以确保数据的持久性。
- sync() 和 fsync(): 程序员可以显式地调用sync()或fsync()系统调用来要求操作系统将缓冲区中的数据立即写入磁盘。fsync()将特定文件的缓冲区数据刷新到磁盘,而sync()会刷新所有文件的缓冲区数据。
不使用内核缓冲区
- 内核缓冲区虽然带来了许多好处,但是并不是适用所有场景,比如可能造成数据丢失、大文件读写等并不适合使用内核缓冲区,这时候我们可以使用一些方法不使用内核缓冲区:
// O_DIRECT:对于类UNIX系统,可以使用O_DIRECT选项来绕过内核缓冲区,直接将数据写入磁盘。这需要在打开文件时使用特定的标志。
#include <fcntl.h>
int main() {
int fd = open("output.txt", O_WRONLY | O_DIRECT);
// 写入数据到文件描述符 fd
close(fd);
return 0;
}
一个好问题:为什么有了内核缓冲区还需要应用层的缓存?
- 简单来说,操作系统提供的内核缓冲区是一个通用选择,无法适应我们应用层的个性化场景。仅对于顺序读写可以很好的进行处理,比如 kafka,但实际上对于大多数应用层的热点数据都分布不均。
- 例如:MySQL的innoDB缓存,如果采用OS的缓存策略,来一次全表扫描那么就可以让InnoDB辛辛苦苦热起来的数据冷了。但是InnoDB自己维护缓存情况下,就可以处理得很好,例如MySQL的InnoDB会对缓冲数据拆分为young以及old数据;会在整个缓存空间中腾出3/8的数据来用缓存这种多次访问的热点数据;这样全表扫描情况下,至少大多数热点数据还在内存中。
磁盘缓冲区
- 磁盘缓冲区是硬盘与PCI总线之间的容量固定的硬件。同样是一种优化磁盘 I/O 性能的重要技术,通过减少磁盘访问次数和优化数据写入方式,提高了计算机系统的整体效率和响应速度。
- 这里留一个小问题,为什么有了内核缓冲区还需要磁盘缓冲区,猜测是和上面的内核缓冲区一样的原因,留到后面有机会解答。
// 待考证
一、它是容量固定的硬件,而不像内核缓冲区是可以由操作系统在内存中动态分配的。
二、它对性能的影响大大超过磁盘缓存对性能的影响,因为如果没有缓冲区,就会要求每传一个字(通常是4字节)就需要读一次磁盘或写一次磁盘。
Linux 的 PageCache 和 BufferCahe
- 以前在各种文章中经常看到 PageCache 和 BufferCahe 两个概念,但具体是什么不是很了解,趁着上面这个问题就一起进行了简单了解。实际上 PageCache 和 BufferCahe 实际上是 Linux 文件系统发展中不同时期的产物。
仅有 Buffer Cache
- 在Linux-0.11版本的代码中,buffer cache是完全独立的实现,甚至都还没有基于page作为内存单元,而是以原始指针的系形式出现。每一个block sector,在kernel内部对应一个独立的buffer cache单元,这个buffer cache单元通过buffer head来描述:
![8d40855de8d6e4173a9b3123f2afe271.png](https://i-blog.csdnimg.cn/blog_migrate/78798b7127d5ff2fef21b30a69186d7a.png)
Page Cache、Buffer Cache两者并存
- 到Linux-2.2版本时,磁盘文件访问的高速缓冲仍然是缓冲区高速缓冲(Buffer Cache)。其访问模式与上面Linux-0.11版本的访问逻辑基本类似。但此时,Buffer Cache已基于page来分配内存,buffer_head内部,已经有了关于所在page的一些信息:
![7892a6decc1696dba09a11023631cc2f.png](https://i-blog.csdnimg.cn/blog_migrate/5ac94ecae8c3c5f7fb706fa74139acde.png)
Page Cache 的用途
- page cache was used to cache pages of files mapped with mmap MAP_FILE among other things.(即用于 MMAP)
- 此时, Page Cache和Buffer Cache的关系如下图所示:
![32e9d23adb5dcce3148c86518df80821.png](https://i-blog.csdnimg.cn/blog_migrate/fdc3a8a8ad25979d4a22314c83e49554.png)
- 从上图我们可以看出,Page Cache仅负责其中mmap部分的处理,而Buffer Cache实际上负责所有对磁盘的IO访问。但是我们会发现一个问题:write绕过了Page Cache,这里导致了一个同步问题。当write发生时,有效数据是在Buffer Cache中,而不是在Page Cache中。这就导致mmap访问的文件数据可能存在不一致问题。为了解决这个问题,所有基于磁盘文件系统的write,都需要调用 update_vm_cache() 函数,该操作会修改write相关Buffer Cache对应的Page Cache。
Page Cache、Buffer Cache两者融合
- 为了解决上述Page Cache、Buffer Cache分离设计的弊端,Linux-2.4版本对Page Cache、Buffer Cache的实现进行了融合,融合后的Buffer Cache不再以独立的形式存在,Buffer Cache的内容,直接存在于Page Cache中,同时,保留了对Buffer Cache的描述符单元:buffer_head
![4c25c4103535b21b565fd22c07bbc838.png](https://i-blog.csdnimg.cn/blog_migrate/935d1134f1e3a9da769a0c8e8e8814d0.png)
free 命令 Buffer Cache 解读
![企业微信截图_16923616648101.png](https://i-blog.csdnimg.cn/blog_migrate/9ae36dd39ee8c267713e8efabf58acfa.png)
参考