1. 概述

Linux内核为了提高文件访问速度和效率,在内存中保留了一个页面缓存(Page Cache)。当你访问一个文件时,Linux首先会检查页面缓存是否有该文件的副本。如果有,则直接从内存中读取;如果没有,则从硬盘读取并将其添加到页面缓存区以备后用。

然而,在某些情况下,可能希望释放这些占用大量物理内存以避免OOM(Out of Memory)的产生。此时就需要使用 drop_caches 命令了。它是通过向 /proc/sys/vm/drop_caches(实现文件 fs/drop_caches.c,默认是0) 写入特定值来清除不同类型的页式和slab式对象:

# echo 1 > /proc/sys/vm/drop_caches; //清除页式对象;
# echo 2 > /proc/sys/vm/drop_caches; //清除slab对象(dentries 和 inodes);
# echo 3 > /proc/sys/vm/drop_caches; //同时清理页式对象和slab对象。

虽然 drop_caches 能够清理缓存,但是它并不会影响到系统的正常运行。因为当系统再次需要这些数据时,它会自动从硬盘中读取。但是,在清理缓存后可能会暂时降低系统性能(因为硬盘读取速度慢于内存),出现大量卡I/O的状态,所以在使用drop_caches之前一定要考虑清楚。

另外,在执行 drop_cache 操作后,请务必记得检查是否有足够的磁盘空间来容纳新生成的数据和文件。如果磁盘空间不足,则可能导致新生成的文件无法保存。

直接echo只能清空page cache中"clean"的部分,也就是已经和外部磁盘同步过的部分。因为"clean"的部分回收起来最简单,既然已经同步过了,直接丢弃即可,下次要用再从磁盘上拷贝回来就可以了,而"dirty"的部分需要先writeback到磁盘,才能释放。所以,"drop cache"准确地应该叫drop clean cache(如果想释放"dirty"的page cache,可以先使用"sync"命令强制同步一下)。

与实现自动回收的 kswapd 线程通过扫描LRU链表不同,手动回收page cache是遍历各个文件系统的各个文件,来寻找可供回收的clean pages。

page cache对应的是文件系统中的文件数据(userdata),而inode cache对应的是文件系统中文件的控制结构(metadata)。对于磁盘文件系统,内存inode存在后备存储,因此同page cache一样,较易在内存中重建,释放的代价较低。dentry虽然在磁盘上没有直接对应的结构,但也可根据文件系统中目录inode的信息进行重建。

echo 2前后可以看dentry的状态:

cat /proc/sys/fs/dentry-state
cat /proc/sys/fs/inode-state

一个内存inode被一个或多个dentry指向,如果指向一个inode的dentry都被释放,那么该inode也就没有继续存在于内存的意义,也会被同步释放,且由于这种指向关系,释放的inode object总数应略小于释放的dentry object的总数。

借助crash工具的"struct"命令,可以快速得知一个结构体的大小,以笔者使用的"4.18.0-80.el8.x86_64"内核版本为例,dentry和inode的结构体大小分别是208字节和648字节,计算一下,差不多就等于"SReclaimable"中减少的部分。

从dcache和icache的LRU链表获取可回收的数量后,还可以通过"/proc/sys/vm/vfs_cache_pressure"参数来调节slab cache的「回收倾向」。该参数的值越小,则回收slab cache的比例也就越低,当值为0时,即使内存资源紧张也不回收slab cache(非常类似于调节anonymous page和page cache回收比例的"swappiness"参数)。

 

注意:使用之前需要先sync,将缓存刷到磁盘中。此操作需要root权限,因此在执行这些命令时可能需要使用sudo。

 

2. 实验

单位:MB

# free -m
              total        used        free      shared  buff/cache   available
Mem:           1825         381        1363           8          79        1413
Swap:             0           0           0
# cp /etc/* /mytest/
# free -m
              total        used        free      shared  buff/cache   available
Mem:           1825         382        1341           8         101        1412
Swap:             0           0           0

过一段时间:

# free -m
              total        used        free      shared  buff/cache   available
Mem:           1825         382        1341           8         101        1412
Swap:             0

# cat /proc/sys/vm/drop_caches
0

# sync

# echo 3 > /proc/sys/vm/drop_caches
# free -m
              total        used        free      shared  buff/cache   available
Mem:           1825         382        1405           8          37        1413
Swap:

echo 2 之后,/proc/meminfo 中的可回收部分 SReclaimable 字段的值也会明显降低。

 

3. 写个脚本做这个事情

# cat cleanup_cache.sh 
#!/bin/sh
# drop_caches for every 5 mins

drop_caches() {
    echo "Drop caches."
    sync
    echo 3 > /proc/sys/vm/drop_caches &
    return 0
}

while true; do
    sleep 300
    drop_caches
done

exit 0

 

4. 小结

清除page cache和slab cache可以快速释放内存,cache虽然占据了内存空间,但其本来就是为了提高性能而存在的,在手动清除后的一段时间里,系统的整体运行效率将受到影响。如果发现即便负责自动回收的kswapd频繁启动,系统的内存资源依然吃紧,应尝试去寻找部分内存始终不能有效释放的原因,手动清除并不能解决根本问题。

笔者在工作中就曾经遇到过这样的案例:

kswapd由于持续运行,导致占据了大量的CPU资源,后来发现root cause是有进程一直再往tmpfs里面打印log,而tmpfs属于shmem,在性质上是anonymous pages,内存回收时应该被swap out到外部的swap space。

但系统没有在外部flash设置swap分区,手动drop cache只能回收page cache和slab cache,对anony page是没有用的。tmpfs里的内容不断增加,导致空闲内存低于low watermark,触发kswapd,但没有swap分区,kswapd怎么使劲也是无能为力的,最终的结果就是内存占用率和CPU占用率都很高,系统可能就挂掉了。

 

参考:
Linux内存回收之drop cache:https://zhuanlan.zhihu.com/p/93962657