Page Cache层
引入Cache层的目的是为了提高Linux操作系统对磁盘访问的性能。
Cache层在内存中缓存了磁盘上的部分数据。
当数据的请求到达时,如果在Cache中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
Cache层也正是磁盘IOPS为什么能突破200的主要原因之一。
在Linux的实现中,文件Cache分为两个层面,一是Page Cache,另一个Buffer Cache,每一个Page Cache包含若干Buffer Cache。
Page Cache主要用来作为文件系统上的文件数据的缓存来用,尤其是针对当进程对文件有read/write操作的时候。
Buffer Cache则主要是设计用来在系统对块设备进行读写的时候,对块进行数据缓存的系统来使用。
磁盘Cache有两大功能:预读和回写。
预读其实就是利用了局部性原理,具体过程是:对于每个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面(通常是三个页面),这时的预读称为同步预读。对于第二次读请求,如果所读页面不在Cache中,即不在前次预读的页中,则表明文件访问不是顺序访问,系统继续采用同步预读;如果所读页面在Cache中,则表明前次预读命中,操作系统把预读页的大小扩大一倍,此时预读过程是异步的,应用程序可以不等预读完成即可返回,只要后台慢慢读页面即可,这时的预读称为异步预读。
任何接下来的读请求都会处于两种情况之一:
第一种情况是所请求的页面处于预读的页面中,这时继续进行异步预读;
第二种情况是所请求的页面处于预读页面之外,这时系统就要进行同步预读。
回写是通过暂时将数据存在Cache里,然后统一异步写到磁盘中。
通过这种异步的数据I/O模式解决了程序中的计算速度和数据存储速度不匹配的鸿沟,减少了访问底层存储介质的次数,使存储系统的性能大大提高。
Linux 2.6.32内核之前,采用pdflush机制来将脏页真正写到磁盘中,什么时候开始回写呢?
下面两种情况下,脏页会被写回到磁盘:
- 在空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。
- 当脏页在内存中驻留超过一定的阈值时,内核必须将超时的脏页写会磁盘,以确保脏页不会无限期地驻留在内存中。
回写开始后,pdflush会持续写数据,直到满足以下两个条件:
- 已经有指定的最小数目的页被写回到磁盘。
- 空闲内存页已经回升,超过了阈值。
Linux 2.6.32内核之后,放弃了原有的pdflush机制,改成了bdi_writeback机制。bdi_writeback机制主要解决了原有fdflush机制存在的一个问题:在多磁盘的系统中,pdflush管理了所有磁盘的Cache,从而导致一定程度的I/O瓶颈。
bdi_writeback机制为每个磁盘都创建了一个线程,专门负责这个磁盘的Page Cache的刷新工作,从而实现了每个磁盘的数据刷新在线程级的分离,提高了I/O性能。
回写机制存在的问题是回写不及时引发数据丢失(可由sync|fsync解决),回写期间读I/O性能很差。
Buffer & Cache
Buffer 和 Cache 的设计目的,是为了提升系统的 I/O 性能。它们利用内存,充当起慢速磁盘与快速 CPU 之间的桥梁,可以加速 I/O 的访问速度。
Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。
这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
Cache(即我们所说的文件读写加速器Page Cache) 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据,所有常用的文件I/O操作都要经过Page Cache。
读文件首先会在page cache中查找文件是否已被缓存了,如果没有缓存则通过磁盘IO进行读取,读取完成的文件会在page cache中缓存一次;
写文件不会立即将修改后的文件写入磁盘,而是仅仅将进程地址空间相应页中的数据copy至相应的page cache,并将该页设置为dirty,标记完后write函数就可以返回了。
操作系统中有专门的内核线程周期性写回这些页面。
Buffer 和 Cache 分别缓存磁盘和文件系统的读写数据。
磁盘是一个块设备,可以划分为不同的分区;
在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件。
在读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互;
而读写磁盘或者分区时,就会跳过文件系统,也就是所谓的“裸I/O“。
这两种读写方式所使用的缓存是不同的,也就是 Cache 和 Buffer 区别。