标签归档:内存管理

Erlang进程GC引发RabbitMQ Crash

生产环境下曾数次出现过RabbitMQ异常崩溃的场景,好在镜像队列的部署方式对服务未产生影响。

我们的RabbitMQ部署环境是:

KVM: 2 VCPU, x86_64, 4G RAM, swap disabled。
OS: Debian 7.0, Linux Kernel 3.2.0-4-amd64
Erlang: R15B01 (erts-5.9.1) [64-bit] [smp:2:2]
RabbitMQ: 3.1.5, {vm_memory_high_watermark,0.4}, {vm_memory_limit,1663611699}

分析最近一次崩溃产生的erl_crash.dump文件:

=erl_crash_dump:0.1
Tue Jun 16 00:46:49 2015
Slogan: eheap_alloc: Cannot allocate 1824525600 bytes of memory (of type "old_heap").
System version: Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:2:2] [async-threads:30] [kernel-poll:true]
Compiled: Sun Jan 27 18:19:34 2013
Taints:
Atoms: 22764
=memory
total: 1980253120
processes: 1563398383
processes_used: 1563397555
system: 416854737
atom: 703377
atom_used: 674881
binary: 385608216
code: 18475052
ets: 7643488
......

发现Crash原因是:Cannot allocate 1824525600 bytes of memory (of type "old_heap")。从数据来看,Erlang虚拟机已占用约1.98G内存(其中分配给Erlang进程的占1.56G),此时仍向操作系统申请1.82G,因为操作系统本身以及其他服务也占用一些内存,当前系统已经分不出足够的内存了,所以Erlang虚拟机崩溃。

此处有两个不符合预期的数据:
1. vm_memory_limit控制在1.67G左右,为什么崩溃时显示占用了1.98G?
2. 为什么Erlang虚拟机会额外再申请1.82G内存?

经过一些排查和总结,发现几次崩溃都是出现在队列中消息堆积较多触发了流控或者有大量unack消息requeue操作的时候,基本把原因确定为Erlang对进程进行Major GC时系统内存不足上。

在之前的『RabbitMQ与Erlang』一文中,曾简单介绍过Erlang的软实时特性,其中按进程GC是实现软实时的一个重要手段:

RabbitMQ将每个队列设计为一个Erlang进程,由于进程GC也是采用分代策略,当新老生代一起参与Major GC时,Erlang虚拟机会新开内存,根据root set将存活的对象拷贝至新空间,这个过程会造成新老内存空间同时存在,极端情况下,一个队列可能短期内需要两倍的内存占用量。

这也是RabbitMQ将内存流控的安全阈值设置为0.4的原因,即使double,也是0.8,还是安全的。0.4的意思是,当broker的内存使用量大于40%时,开始进行生产者流控,但是该参数并不承诺broker的内存使用率不大于40%。官方文档也强调过这一点:

The default memory threshold is set to 40% of installed RAM. Note that this does not prevent the RabbitMQ server from using more than 40%, it is merely the point at which publishers are throttled. Erlang's garbage collector can, in the worst case, cause double the amount of memory to be used (by default, 80% of RAM).

实际上通过RabbitMQ运行日志很容易证实,broker的实际内存使用量可以远远大于vm_memory_high_watermark设定值,比如:

$ less /var/log/rabbitmq/rabbit@10.xx.xx.xx.log
=INFO REPORT==== 16-Jun-2015::00:46:25 ===
vm_memory_high_watermark set. Memory used:2497352280 allowed:1663611699
=WARNING REPORT==== 16-Jun-2015::00:46:25 ===
memory resource limit alarm set on node 'rabbit@10.xx.xx.xx'.

内存流控阈值掐在1.6G,但是实际使用了近2.5G!再加上操作系统本身和其他服务吃掉的内存,轻松超过3G。如果此时触发Erlang进程Major GC,需要占用双倍的当前堆内存大小,那么报本文开头的old_heap堆内存分配错误也就不足为奇了。

知道了问题所在,如何解决这个问题呢?

遗憾的是,我目前也没有确切的解决方法(如有人知道,烦请告知)。但是从问题的形成原因来看,至少从以下几个方面可以显著降低问题出现概率。

1. RabbitMQ独立部署,不与其他Server共享内存资源。
2. 进一步降低vm_memory_high_watermark值,比如设置成0.3,但是这种方式会造成内存资源利用率太低。
3. 开启swap,问题在于容易造成性能和吞吐量恶化。
4. 升级RabbitMQ至新版(3.4+)。我个人觉得这个问题的根本原因在于RabbitMQ对内存的管理上,特别是早期版本,Matthew(RabbitMQ作者之一)也曾谈到过这个问题。尽管设计之初就考虑了各种优化,使得队列进程私有堆尽量小,比如当触发了某些条件,会page out到磁盘,或以二进制数据的方式存储在共享数据区,但是即便如此,在某些特定时刻,队列进程私有堆仍会消耗大量内存,这点从Management Plugin或者Remote shell里都可以看出来,而每次的“Cannot allocate old_heap”问题恰恰也总是出现在这些时刻。RabbitMQ 3.4版本发布后,官方发布了一篇新版内存管理方面的博客,从介绍上来看,要准确计算和控制当前系统使用的内存量确实非常困难。所以作者的感慨有理:

Of course, this still doesn't give a perfect count of how much memory is in use by a queue; such a thing is probably impossible in a dynamic system. But it gets us much closer.

--EOF--

小内存VPS站点内存使用调优(续)

昨天在『小内存VPS站点内存使用调优』中介绍了在VPS中打开swap分区,解决物理内存不足导致的OOM问题。本以为有了1G swap分区(SSD)做后盾,php-fpm的pm.*参数可以调大一点:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 10
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 1000

结果今天DNSPod继续发来报警邮件,网站不可用,不过这次不可用的原因不是内存不足,而是IO太忙,Nginx提示504 Time-out。

先SSH到VPS上查看系统状态:
top-wa

上图是top命令的执行结果,异常表现在load很高(单核,10+),CPU使用率很低12.7%,大部分时间在等待IO,71.8% wa。

top命令看不出哪个进程在io wait。于是用apt-get安装iotop命令,它可以用来查看每个进程的IO情况:
iotop

通过左右箭头可以指定按某一列排序,按DISK READ列排序后,问题很直观了,清一色的php-fpm worker进程,并且都是99%左右的时间在swapin,所以问题定位在swap分区的使用不合理上。

swap分区本质是用磁盘模拟内存,它的IO特点是随机读写,即便是号称SSD,相比HDD延时低了不少,看来还是达不到系统使用要求。不知道DigitalOcean有没有对磁盘的IOPS进行Qos限制,从iotop上显示读速度62.85M/s来看,跟基准数据相比,处在偏下的水平。

定位出问题的原因之后,只能继续折腾php-fpm配置参数:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 3
pm.max_requests = 1000

另外,调整内核vm.swappiness参数(取值0-100,默认60)。swappiness数值越大,表示越优先使用swap;swappiness数值越低,表示越优先使用物理内存。

1
2
# echo 5 > /proc/sys/vm/swappiness
# echo vm.swappiness=5 >> /etc/sysctl.conf

把swappiness参数设置为5,告诉系统大部分时间优先分配物理内存,这可以解决极端情况下物理内存耗尽时,用swap分区做下缓冲。

初步看来,Nginx提示504 Timeout的问题解决了。

--EOF--

小内存VPS站点内存使用调优

本站托管在DigitalOcean,VPS规格是最低的1VCPU,512M内存,20G SSD存储。即便是对于这种日均PV不过百余的小站点,LNMP架构再加上顺便在主机里装上的一系列Shadowsocks、squid、l2tp等代理和VPN软件,内存逐渐成为最大瓶颈。

终于这两天DNSPod不断发邮件报警网站无法访问,打开网站提示“数据库连接错误”,ssh到主机里,dmesg提示各种OOM Killer:

# dmesg | grep oom
[.] php5-fpm invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] mysqld invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] php5-fpm invoked oom-killer: gfp_mask=0x280da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
[.] php5-fpm invoked oom-killer: gfp_mask=0x201da,order=0,oom_adj=0,oom_score_adj=0
[.]  [<ffffffff810b77f3>] ? oom_kill_process+0x49/0x271
[.] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name

php5-fpm和mysqld进程频繁被杀,php5-fpm进程实际上是fastcgi worker进程,即使被杀,也能被php-fpm master进程重启,因此不影响网站可用性,但是mysqld进程一旦被杀了,数据库就挂了,导致首页出现“数据库连接错误”提示。

低层次的内存使用优化可以从两方面入手:

1. 降低mysql的oom score权重,降低被OOM Killer选中的几率。

这点可参考『Linux OOM Killer问题』,将mysqld进程的oom_score_adj调整成最小值-1000:

1
2
# pid=$(cat /var/run/mysqld/mysqld.pid)
# echo -1000 > /proc/$pid/oom_score_adj

2. 减少php-fpm的内存使用量。

Nginx作为Web Server,本身不具备解释执行php的能力,我们能通过GET /index.php获得HTML响应的原因是Nginx后端对接了php解释程序,这个对接的协议就是fastcgi(早期使用cgi,因为fork-exec的请求分发执行方式效率太低,后来有了fastcgi,采用对象池模型,避免每次解析配置文件、初始化执行环境等),fastcgi与语言无关,php-fpm就是php语言环境下的一个fastcgi实现。关于cgi、fastcgi、php-fpm之类的讨论可以参考这两篇文章: 『FastCgi与PHP-fpm之间的关系』,『概念了解:CGI,FastCGI,PHP-CGI与PHP-FPM』。

默认情况下,php-fpm中关于进程池的配置(/etc/php5/fpm/pool.d/www.conf)如下:

1
2
3
pm = dynamic ;;表示worker进程动态调整。
pm.max_children = 20 ;;表示最大worker进程数量。
pm.max_requests = 0 ;;表示每个worker进程最多处理多少次请求后重启,0表示永不重启。

从中可以看出默认配置对于小内存(512M)的VPS的不合理之处,每个cgi worker进程的初始内存占用量约为10M,并且很容易就涨至50+M,当并发数稍稍上去一点,可能就会产生 20(workers) * 50M ~= 1G 的内存使用量。这还未考虑某些php第三方库存在内存泄露问题,当pm.max_requests参数为0,内存泄露会造成worker进程内存使用量越用越大,最终失控。

权衡之后,我把配置调整为:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 3
pm.start_servers = 2 ;;启动worker数量
pm.min_spare_servers = 2 ;;最小空闲worker数量
pm.max_spare_servers = 3 ;;最大空闲worker数量
pm.max_requests = 500 ;;每个worker处理500次请求后重启

虽然保守了一点,但至少比较保险。

调整pm.*参数时建议taif syslog和php5-fpm.log,实时监控站点运行状况。tailf syslog目的是为了监控还有没有php-fpm进程被oom kill;tailf php5-fpm.log目的是为了查看fpm worker进程的启动和异常关闭情况。如果调整后出现Nginx “502 bad gateway”错误,或者php-fpm频繁的“WARNING: [pool www] child 10595 exited on signal 9 (SIGKILL) after 78.871109 seconds from start”日志,那肯定是pm.*参数调整不合理无疑了。

以上2种手段毕竟治标不治本,是以牺牲性能为前提的。实际上当内存不足时,最直观也是最简单的方法当然是加内存,既然物理内存没法加,那就加虚拟内存(swap)吧,DigtalOcean上的VPS主机默认没有开swap分区:

1
2
3
4
5
6
7
# free -h
             total       used       free     shared    buffers     cached
Mem:          497M       408M        88M         0B       456K        15M
-/+ buffers/cache:       393M       103M
Swap:            0          0          0
# swapon -s
Filename                                Type            Size    Used    Priority

DigitalOcean提供了SSD硬盘,所以开个2倍物理内存大小的swap分区也没什么问题,以下是使用磁盘文件作为swap分区的步骤:

1. 生成1G大小的磁盘文件

1
# dd if=/dev/zero of=/swapfile bs=1M count=1024

2. 将文件格式化为swap分区

1
# mkswap /swapfile

3. 启动swap分区

1
2
3
4
5
6
7
8
9
# swapon /swapfile
# free -h
             total       used       free     shared    buffers     cached
Mem:          497M       462M        34M         0B       252K        21M
-/+ buffers/cache:       440M        56M
Swap:         1.0G          0       1.0G
# swapon -s
Filename                                Type            Size    Used    Priority
/swapfile                               file            1048572 645840  -1

4. 将swap分区加到/etc/fstab,使重启后仍生效

1
# echo /swapfile swap swap defaults 0 0 >> /etc/fstab

有了1G的基于SSD的swap分区后,就可以把php-fpm参数调得任性一点了:

1
2
3
4
5
6
pm = dynamic
pm.max_children = 10
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 5
pm.max_requests = 1000

初步看来,小内存VPS站点内存耗尽问题解决了。

--EOF--

Linux OOM Killer问题

上周有一个线上程序挂了,由于早就知道服务器存在内存不足的情况,因此大概猜测问题应该出在Linux的OOM Killer上,grep了一把syslog,找到真凶,证实了我的猜测:

1
2
3
4
5
6
7
8
/var/log/messages:Nov 10 09:41:53 kernel: [8358409.054]java invoked oom-killer:
gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
/var/log/messages:Nov 10 09:41:53 kernel: [8358409.054] [<ffffffff810b799b>] ?
oom_kill_process+0x49/0x271
/var/log/messages.1:Nov  9 06:29:19 kernel: [8260455.112]ntpd invoked oom-killer:
gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
/var/log/messages.1:Nov  9 06:29:19 kernel: [8260455.112] [<ffffffff810b799b>] ?
oom_kill_process+0x49/0x271

Linux的内存管理存在超售现象,也就是Overcommit策略。举个栗子,当我们用C语言的malloc分配了内存之后,只要指定的内存大小不超过进程的虚拟内存大小,一般都会返回成功,只有程序在使用这块内存时,操作系统才会真正分配给它,比如调用memset()函数填充内存时。在32位操作系统下,进程的虚拟内存是4G,低3G为用户空间,高1G为内核空间,所以程序一次性可以malloc 3G以下的内存而不报错,即使当前的物理内存远远小于这个值。这样必然出现一种情况,当系统中所有进程占用的实际内存大于物理内存和swap分区之和时,Linux需要选择一个策略来处理这个问题,策略的选择就是修改Overcommit参数。

当Overcommit策略启用时,并且此时物理内存已经耗尽而又有程序需要分配内存时,Linux就会触发OOM Killer,它可以简单理解为操作系统选择一些进程杀掉,腾出内存空间。选择被杀进程的算法还是蛮讲究的,既要考虑进程重要性,比如是否root执行、是否系统进程、nice值等,又要兼顾回收效益,比如最好应该通过杀掉最少的进程来回收最多的内存。这方面,不同的内核版本也会有所区别,大概思路如2.4版源码注释所说:

1
2
3
4
5
6
7
1) we lose the minimum amount of work done
2) we recover a large amount of memory
3) we don't kill anything innocent of eating tons of memory
4) we want to kill the minimum amount of processes (one)
5) we try to kill the process the user expects us to kill, this
   algorithm has been meticulously tuned to meet the priniciple
   of least surprise ... (be careful when you change it)

注: 源码路径linux/mm/oom_kill.c,函数调用链为out_of_memory() -> oom_kill() -> select_bad_process() -> badness(),上述注释来自badness()函数。

选择被杀进程的难度,就好比社会学意义上判定一个人死刑一样,无论这个人有多少个该死的理由,但是总能找出一些这个人的存在价值。对应到应用程序,某个应用程序可能吃内存最多,但是同时它对某些业务来说最不可或缺,比如RabbitMQ,Redis,MySQL等等。于是Linux为每个进程都提供了一个可以免死的机会,就是/proc/<pid>/oom_score_adj参数。

每个进程都有一个oom_score值(位于/proc/<pid>/oom_score,总是大于等于0),这个值越大就越容易被OOM Killer杀掉,同时/proc/<pid>/oom_score_adj值(取值范围-1000~1000)可以对oom_score值进行调整,比如oom_score_adj值为-10,则会将最终的oom_score值减10。

做个实验吧,实验环境为:VPS,单核,RAM 128M, swap 256M, 32位debian6,kernel 2.6.32。

1
2
3
4
5
6
7
8
# vi oomscore.sh
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
    printf "%2d %5d %s\n" \
        "$(cat $proc/oom_score)" \
        "$(basename $proc)" \
        "$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

这个oomscore.sh脚本可以显示当前oom_score排名前10的进程oom_score,pid和进程名等,借鉴来辅助观察。

首先执行oomscore.sh脚本,得到当前omm_score排名前10的进程,以及内存使用情况:

1
2
3
4
5
6
7
8
9
10
# sh oomscore.sh 
67 17976 /usr/sbin/apache2 -k start 
40   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
 7 18616 sshd: root@pts/6  
.......
# free -m
             total       used       free     shared    buffers     cached
Mem:           128         75         52          0          0         26
-/+ buffers/cache:         49         78
Swap:          256         15        240

然后跑以下测试程序alloc1mps.c,每秒分配1M内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
 
#define M 1024*1024
int main(){
        int count = 1;
        while(1){
                char* mem = (char*)malloc(sizeof(char) * M);
                if(!mem){
                        break;
                }
                memset(mem, 0, M);
                printf("alloc %d M\n", count++);
                sleep(1);
        }
        printf("mem exhausted!\n");
        return 0;
}

通过sh omm_score.sh,top,free -m,dmesg等命令观察内存分配和omm_score关系,可以得到以下现象和结论:
1. 首先从分配物理内存中分配空间,等物理内存分配得差不多了,-/+ buffers/cache free趋于0时,开始从swap分配内存。
2. 当swap分区内存接近耗尽时,此时alloc1mps进程的omm_score值非常大。
3. 内存(物理内存+swap)耗尽,触发omm killer回收内存,alloc1mps进程被杀,dmesg证实这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
# free -m
             total       used       free     shared    buffers     cached
Mem:           128        124          3          0          0          0
-/+ buffers/cache:        123          4
Swap:          256        249          6
# sh oom.sh 
791 19412 ./alloc1mps 
56 17976 /usr/sbin/apache2 -k start 
36   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
......
# dmesg
[235669.855]Out of memory in UB 35782: OOM killed process 19412 (alloc1mps)
score 0 vm:318156kB, rss:119476kB, swap:196868kB

重跑测试程序,当alloc1mps开始运行后,调整其omm_score_adj参数,设为-800。

1
2
3
4
# Pid=` ps axu | grep alloc1mps | grep -v 'grep' | awk '{print $2}'`
# echo -800 > /proc/${Pid}/oom_score_adj
# cat  /proc/${Pid}/oom_score_adj
-800

继续观察,这次发现内存接近耗尽时,受oom_score_adj影响,alloc1mps进程的oom_score值低于apache2和mysqld进程,因此首先被OOM Killer杀掉的是apache2进程。apache2进程被kill后,alloc1mps继续执行,它的oom_score值也慢慢涨上来了,下一个被kill的就是它了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# free -m
             total       used       free     shared    buffers     cached
Mem:           128        122          5          0          0          0
-/+ buffers/cache:        121          6
Swap:          256        252          3
# sh oom.sh 
36   695 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib
33 21288 ./alloc1mps 
 2 22233 sh oom.sh 
......
# dmesg
[236432.377]Out of memory in UB 35782: OOM killed process 17976 (apache2)
score 0 vm:55760kB, rss:4kB, swap:22284kB
[236455.623]Out of memory in UB 35782: OOM killed process 21288 (alloc1mps)
score 0 vm:339744kB, rss:118540kB, swap:219132kB

了解了OOM Killer机制后,想要规避重要进程被kill的命运,就只能在进程运行时调整其omm_score_adj参数了,这种调整是临时的,当系统重启后会清零。所以,最根本的解决方法还是加内存。

--EOF--

从free命令看Linux内存管理

free命令是Linux系统下用来查看内存使用情况的,例如:

$ free -h
             total       used       free     shared    buffers     cached
Mem:          7.8G       6.6G       1.3G         0B       600M       1.9G
-/+ buffers/cache:       4.1G       3.7G
Swap:         2.0G         0B       2.0G

第一行Mem是从操作系统层面看到的内存使用情况,total表示物理内存总量,used表示当前已使用量,free表示当前未分配内存,shared表示共享内存大小,可能已废弃,固定为0,buffers和cached表示被操作系统用来当做磁盘高速缓存的内存大小。这里total=used+free,并且buffers和cached包含在used里,因为在操作系统看来,用于缓存的内存也是属于被用掉的内存。

第二行-/+ buffers/cache从程序角度看到的内存使用情况,used表示被当前系统中所有进程用掉的物理内存大小,free表示当前程序可用的物理内存大小。从数值上看,used=Mem:used-buffers-cached,free=Mem:free+buffers+cached。这里涉及到Linux对内存的管理方式,buffers和cached是两种不同类型的磁盘高速缓存,分别称为块缓存(buffer cache)和页缓存(page cache)。当物理内存剩余充分的时候,操作系统可能会将非常多的空闲内存用作缓存,借以提升磁盘IO性能,这种机制造成的特有现象就是很多时候Mem:free值很低,而buffers和cached的值很高,这个时候并不意味着内存吃紧。当有应用程序需要分配内存时,操作系统会逐渐回收buffers和cached占用的内存空间,这个过程中,Mem:used和Mem:free值不会有大的变动,但是-/+ buffers/cache used和free会体现出变化,具体表现为free值减少,used值增加。所以,第二行的-/+ buffers/cache往往比第一行的Mem统计数据更有参考意义,因为第二行显示的才是当前操作系统真正可用的内存使用量和剩余量。

第三行Swap表示交换空间的使用情况,Swap存在于磁盘中,可以认为是内存的扩展,当应用程序继续吃进内存,操作系统已经没有多余buffers和cached可供回收时,就会将一部分内存页交换到Swap区,以便腾出可用的物理内存。假如此时某个进程关闭腾出了大块物理内存(used值降低,free值上升),操作系统不会把原先置换到Swap区的内存页交换回物理内存,空出来的物理内存会被用来做缓存(buffers+cached)。

为了有个更加直观的解释,特地从网上盗图一张,加深理解:
free

上文的描述都未对页缓存和块缓存进行具体区分,实际上,两者还是有一定区别的。简单来说,页缓存(page cache)为了提升文件内容的读写效率,属于文件系统级别的缓存;块缓存(buffer cache)为了提升块设备的读写效率,属于磁盘级别的缓存。

Linux下文件系统中的文件是由文件内容和元信息(inode)两部分组成的,其中文件内容是分散存储在磁盘的不同块中,通过元信息来组织和描述,这些磁盘上的块数据形成了一个逻辑意义上的文件。因此,磁盘高速缓存机制分成两个层次。当操作系统通过文件系统接口读出文件时,文件内容会被缓存在页缓存里;当文件系统继而将文件内容从磁盘中读出时,文件内容会被缓存在块缓存里。很显然,一次读文件操作会导致文件内容被页缓存和块缓存分别缓存一次,Linux 2.4版本之前就是这么设计的。2.6版本以后,这部分功能进行了优化,对于文件内容的读写,块缓存中的地址被指向到页缓存中,从此,内存中只有一份文件内容的缓存。但是,这并不意味着块缓存可以去除,因为有些场景是需要内核直接读写块设备的,这时候块缓存仍旧有着用武之地,比如读写inode,或者一些跳过文件系统直接操作块设备的命令,如dd等。

References:
[1] Linux Kernel: What is the major difference between the buffer cache and the page cache?
[2] linux cache and buffer

--EOF--