Linux缓存相关知识整理(史上最全!!)

墨蓝 2022-05-25 01:54 450阅读 0赞
    1. 相关概念

      • 页缓存和块缓存概念

        • 页缓存(page cache)
        • 块缓存(buffer cache)
      • 缓存机制的利弊

        • 写缓存
    1. 数据同步(刷缓存)

      • flush内核线程
      • 可调参数
      • flush/sync/fsync系列API及命令介绍

        • sync 系统命令
        • sync()
        • fsync()
        • fdatasync()
        • open()之O_SYNC/O_DSYNC选项
        • msync()
        • fflush()
    1. 页面回收(page reclaim)

        • 缓存回收策略
        • 双LRU
        • 系统触发页面回收
      • 手动清理缓存
      • 应用
    1. 相关工具

      • 查看缓存中的文件
      • free命令

        • free命令各个指标含义
        • /proc/meminfo
    1. 参考资料

1. 相关概念

  • 页的概念 : 内核把物理页作为内存管理的基本单位. 通常32位系统页大小为4KB, 64位是8KB
  • 如何查看当前系统页大小:

    getconf PAGESIZE

    4096

页缓存和块缓存概念

  • 内核为块设备提供两种通用缓存方案:

页缓存(page cache)

  • 缓存的是内存页面,缓存中的页来自于对正规文件、块设备文件和内存映射文件的读写。也就是说页缓存中包含最近被访问的文件数据块。我们通常所说的系统cache主要就是指页缓存。
  • 在进行一个读操作前(如read),内核会检查数据是否已经在页缓存中,如果在,就可以从内存中快速返回所需的页,而不是从磁盘上读取。

块缓存(buffer cache)

  • 以块(块设备的block)为操作单位。在进行IO操作时,存取的单位是单个块设备的各个块,而不是整个内存页。
  1. * 早期版本写文件,先经过页缓存,刷盘时再同步到块缓存最后再落盘. 即:如果用dd写裸盘,则不经过页缓存。
  2. * linux内核2.4开始二者统一起来了,缓存用页映射块,块缓存实际上就在页缓存中了。

缓存机制的利弊

  • 好处:
  1. * 提高性能(且对应用程序透明)
  2. * 写缓存,可将零碎IO聚齐起来
  3. * ……
  • 负面影响:
  1. * 物理内存通常远小于块设备,必须仔细挑选缓存哪些数据
  2. * 缓存在一定程度上会影响应用程序实际可用的内存容量
  3. * 如果系统崩溃,缓存的数据可能来不及刷回块设备(硬盘/ssd等),造成数据丢失

写缓存

  • 通常缓存有三种实现策略:
  1. * 不缓存(很少使用)
  2. * 写透缓存(write-through cache):写操作会立刻穿透缓存存到磁盘中
  3. * 回写(writeback):即linux采取的策略,写操作后将对应缓存页面标记为“dirty”,由回写进程周期性刷到磁盘
  • “脏页”这个词有误导性,实际上脏的是磁盘上的数据而不是缓存中的数据。准确讲应该叫“未同步的页”。

2. 数据同步(刷缓存)

  • 数据总是在物理内存中操作,随后在适当的时机点写回(或刷出)到磁盘,以持久化保存修改
  • 几种不同的刷出数据机制:
  1. * 周期性内核线程将扫描脏页链表,并根据页面变脏的时间,选择一些页会写
  2. * 如系统中脏页太多,内核将触发进一步机制对脏页与后备存储器进行同步,直到脏页降低到一个可接受的程度
  3. * 内核各个组件要求数据必须在特定事件发生时同步,例如重新装在文件系统

flush内核线程

  • 上述刷新机制前2条由flusher线程实现。内核中每个磁盘设备对应一个flush线程,通过块设备号区分,[flush-8:48]
  • 如何查看块设备号,可参考 http://www.lenky.info/archives/2012/02/1141 ,块设备一般都是8开头
  • 历史上(2.6以前)还有bdflush,kupdated和pdflush等.其中pdflush线程数量不固定,而现在flush线程通常和磁盘一一对应,这样有助于在某个磁盘拥塞情况下避免所有IO拥塞,并简化处理逻辑。
  • 关于线程数量和磁盘对应关系,以及周期性创建flush线程这部分逻辑,2.6前后内核版本有一定改动。查阅了几本内核的参考书,这部分介绍都不完全相同。
  • 有些资料中提到,如1秒内没有空闲的flusher线程可用,内核将创建新的线程;如某个线程空闲超过1秒,将被销毁.flush线程数量一般被限制在2-8个

可调参数

  • 可通过/proc/sys/vm/目录下的内核参数控制脏页回写行为:

































参数 描述
dirty_background_ratio 占全部内存的百分比,当脏页数量超过该比例时,pdflush机制开始(在后台)回写脏页.此时对write调用没有影响
dirty_background_bytes 同上,以字节描述。与dirty_background_ratio不可同时生效
dirty_writeback_centisecs pdflush线程运行的频率,单位1/100秒。默认500,即pdflush两次调用间隔是5秒。早期版本该参数可能是 dirty_writeback_interval
dirty_ratio 脏页占全部内存的比例,超出该阈值时脏页开始刷出。此时新的IO请求可能会被阻塞直到脏页刷完
dirty_bytes 同上,以字节描述。与dirty_ratio不可同时生效
dirty_expire_centisecs 以百分之一秒为单位,默认3000.即超时30秒的脏页将会被pdflush线程写出,或者说一个脏页在回写之前,保持脏页状态最长可达30秒

- 在写密集的系统中,通常可配置dirty_ratio较高(如60)以增大可用缓存、dirty_background_ratio较低(如20)以遍在后台及时将数据刷入磁盘。同时保持dirty_writeback_centisecs较高(如5秒)
- 内核源码中好像有一个检查,dirty_background_ratio不能大于dirty_ratio的1/2,否则内核会自动调整为后者的1/2.参见内核代码global_dirty_limits() 函数:

  1. void global_dirty_limits(unsigned long *pbackground, unsigned long *pdirty)
  2. {
  3. ……
  4. if (background >= dirty)
  5. background = dirty / 2;
  6. ……
  7. }
  • 效果示意图:
    这里写图片描述
    这里写图片描述

flush/sync/fsync系列API及命令介绍

sync 系统命令

  • Force changed blocks to disk, update the super block.

sync()

  • 允许进程把所有脏缓冲区刷新到磁盘.网上有说:
  1. > sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数
  • 但从代码看,do_sync()会调用2次sync_inodes()sync_filesystems(),第一次传参数0,表示异步执行;第二次传参数1,表示同步执行.

fsync()

  • 允许进程把属于特定打开文件的所有块刷新到磁盘. 它等待写磁盘操作结束,然后返回(The call blocks until the device reports that the transfer has completed. It also flushes metadata information associated with the file)。fsync可用于数据库这样的应用程序.
  • 但正由于它会将元数据也刷回磁盘,所以相比fdatasync()它多了一次IO操作。对于机械盘来说,这个耗时通常是毫秒(平均10ms左右)级别的!!
  • fync() 不保证其所在目录也被刷到磁盘,因此针对其所在目录显示调用一次 fsync() 是有必要的

fdatasync()

  • 与fsync()类似,但不刷新文件的索引节点块(inode等)
  • 诸如对文件access time等的修改,不会被fsync刷回磁盘; 但对文件大小的改变,由于会影响到后面的读数据,会被刷回磁盘。
  • 通过这种机制减少不必要的磁盘IO,提高性能
  • 网上提到,数据库系统中为了使用fdatasync来记录WAL日志的方法(因为日志都是追加写,文件长度会不断变化,不太适合直接用fdatasync):(https://www.cnblogs.com/hadis-yuki/p/5961949.html)
  1. > 1.每个log文件固定为10MB大小,从1开始编号,名称格式为“log.%010d
  2. > 2.每次log文件创建时,先写文件的最后1page,将log文件扩展为10MB大小
  3. > 3.log文件中追加记录时,由于文件的尺寸不发生变化,使用fdatasync可以大大优化写log的效率
  4. > 4.如果一个log文件写满了,则新建一个log文件,也只有一次同步metadata的开销
  • 看了Postgresql,默认也是使用fdatasync,并且可配置:

    define PLATFORM_DEFAULT_SYNC_METHOD_STR “fdatasync”

    define DEFAULT_SYNC_METHOD_STR PLATFORM_DEFAULT_SYNC_METHOD_ST

    const char XLOG_sync_method_default[] = DEFAULT_SYNC_METHOD_STR;

    static struct config_string ConfigureNamesString[] =
    {

    1. ……
    2. {
    3. {

    “wal_sync_method”, PGC_SIGHUP, WAL_SETTINGS,

    1. gettext_noop("Selects the method used for forcing WAL updates to disk."),
    2. NULL,
    3. GUC_NOT_IN_SAMPLE | GUC_NO_SHOW_ALL | GUC_DISALLOW_USER_SET
    4. },
    5. &XLOG_sync_method,
    6. XLOG_sync_method_default, assign_xlog_sync_method, NULL
    7. },
    8. ……

    }

    wal_sync_method = fsync # the default is the first option

    1. # supported by the operating system:
    2. # open_datasync
    3. # fdatasync (default on Linux)
    4. # fsync
    5. # fsync_writethrough
    6. # open_sync
  • 但glibc中fdatasync函数的实现已经和fsync一摸一样(至少到glibc2.24中都是如此。既然这样,不知道数据库中调用fdatasync是否还有意义?)

    int
    fdatasync (int fildes)
    {
    return fsync (fildes);
    }

open()之O_SYNC/O_DSYNC选项

  • open的参数O_SYNC/O_DSYNC有着和fsync/fdatasync类似的含义:使每次write都会阻塞等待硬盘IO完成
  • O_SYNC 使每次write等待物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O
  • O_DSYNC 使每次write等待物理I/O操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需等待文件属性被更新
  • 相对于fsync/fdatasync,这样的设置不够灵活,应该很少使用

msync()

  • 通常用于内存映射文件的同步,需要精确控制同步的内存地址、长度
  • int msync(void *addr, size_t length, int flags);

fflush()

  • fflush() 是C库提供的接口,它只是从C库的缓冲区刷到内核缓冲区. 接下来还需要通过sync()/fsync()等调用才能刷到磁盘

3. 页面回收(page reclaim)

  • 页交换(swapping) : 将很少使用的内存换出到块设备
  • 与块设备同步 : 如一个很少使用的页对应后端存储器是一个块设备(如文件的内存映射),则可以直接与块设备同步。腾出来的页可重用。
  • 丢弃: 例如页的后端存储器是文件且不能在内存中修改该文件(如二进制文件数据),在当前不使用的情况下课直接丢失该页。

缓存回收策略

  • 缓存回收是通过选择干净页(不脏)进行简单替换。如没有足够的干净页面,则强制进行回写操作,以腾出更多干净可用页面
  • 难点在于如何决定哪些页面该回收。理想策略是回收以后不可能使用的页面。
  • LRU策略:对于许多文件被访问一次就不再被访问的场景,LRU表现很差

双LRU

  • 物理内存被划分为多个zone(如ZONE_DMA,ZONE_DMA32,ZONE_NORMAL).每个zone都有一组lru链表,页面都被放到自己对应的zone的LRU中。
  • 一组LRU由几对链表组成,有磁盘高速缓存页面(包括文件映射页面)的链表、匿名映射页面的链表等。
  • 一对链表实际上是active和inactive两个链表,前者是最近使用过的页面、后者是最近未使用的页面
    这里写图片描述
  • 页面根据其活跃程度会在active链表和inactive链表之间来回移动: 初始状态两个链表都为空,当某个页面第一次被访问时会先进入inactive链表。在第二次被引用的时候,被提升到了active链表。
  1. * 下图出自《深入理解Linux内核架构》18.6.3章节,P850
  2. * 内核使用两个标识位,`PG_referenced``PG_active`.如果只用一个active标识位,为了清除该标志,不得不设置大量内核定时器
  3. * 上述标识位,一个表示当前活动程度,一个表示该页最近是否被引用过
  4. ![这里写图片描述][70 3]
  • 内存管理器将可用的内存公平的分发给两个队列,尽量在保护active链表(频繁访问的页面)和inactive链表(探测最近使用的页面)之间达到一个折衷的平衡。换言之,内核为频率队列保留了50%的可用内存
  • see: https://blog.csdn.net/zouxiaoting/article/details/8824896

系统触发页面回收

  • 一种是内核检测到某个操作期间内存严重不足,调用try_to_free_pages
  • 另一种是后台守护进程kswapd(可能有多个,每个NUMA节点对应一个实例)会定期检查内存使用情况,并检测即将发生的内存不足,可使用该守护进程换出页,作为预防措施, 防止内核在执行其他操作期间发生严重内存不足
  • 上述两种机制触发入口函数不同,但代码路径上在shrink_zone()函数中合并
  • 如前面讲到,会优先选择非dirty的页。但也可能选择脏页,此时需要将这些页先回写到后备设备(如磁盘设备).如因任何原因无法回写,会将这些页放回LRU的inactive链表.
  • 这里写图片描述

手动清理缓存

  • To free pagecache : - echo 1 > /proc/sys/vm/drop_caches
  • To free dentries and inodes : echo 2 > /proc/sys/vm/drop_caches
  • To free pagecache, dentries and inodes : echo 3 > /proc/sys/vm/drop_caches
  • 1和2的区别: 2针对的是目录、文件inode的缓存,1针对文件数据内容的缓存。
  • 具体说明:在一个目录如test下,创建3万个子目录,在每个子目录中创建100个文件,并随机写入几个字节的数据。即test目录中共有大约300万文件:
  1. * 多次执行`time find /home/hailong/cache_test -name file* | wc -l`,观察耗时
  2. * 执行`echo 1 > /proc/sys/vm/drop_caches`之后再次执行上述命令,观察耗时
  3. * 执行`echo 2 > /proc/sys/vm/drop_caches`之后再次执行上述命令,观察耗时
  4. * 测试结果:`echo 1`对上述命令执行没有影响。但`echo 2`之后该命令执行耗时明显变长。同理,`echo 3` 也类似。
  5. * Note: 在一些特殊场景下,比如只想清除inode缓存,或只想清除数据缓存,区别使用上述几个命令是非常有意义的。

应用

  • 思考: 考虑实际业务场景,假设首先从数据库中查询4.1-4.30号数据,并进行多次分析计算。然后查询3.1-3.31号数据,那么查询3月份数据时,系统会马上将4月对应数据的缓存淘汰掉吗?(假设3月和4月的数据量都是刚好接近缓存大小上限)
  • see: http://blog.jobbole.com/52898/
  • 实际验证: 系统稳定状态下内存情况如下表,构造两个32GB大小的文本文件。先多次执行time wc -l file1.csv,观察每次执行所用时间。然后再不清理缓存情况下,多次执行time wc -l file2.csv,观察每次执行所用时间。

    [root~]# free -g

    1. total used free shared buffers cached

    Mem: 62 10 52 0 0 2
    -/+ buffers/cache: 7 55
    Swap: 31 0 31

    [root]# ls -lhtr
    total 64G
    -rw-r—r— 1 root root 32G May 3 14:36 file1.csv
    -rw-r—r— 1 root root 32G May 3 14:45 file2.csv

PS:执行结果可知,上述链接中提到的问题已经修复(CentOS 6.5)。但和我们预期的简单LRU可能不太一样,并非访问一次file2就完全淘汰file1的cache。事实上,由于file1曾经被多次访问、已经占据了active链表,file2是随着访问增加,一点一点将file1从cache中“赶”出去的,这也符合上述连接相关的patch修复描述。 但作为对比,如果一开始file1仅被访问1次,然后就wc -l file2,那么file1会马上被“赶出”cache.

4. 相关工具

查看缓存中的文件

  • linux-ftools工具集中的linux-fincore命令:

    [root]#./linux-fincore —pages=false —summarize —only-cached *
    filename size total_pages min_cached page cached_pages cached_size cached_perc


    aclocal.m4 34,611 9 0 9 36,864 100.00
    Could not mmap file: autom4te.cache: No such device
    config.log 19,768 5 0 5 20,480 100.00
    config.status 29,788 8 0 8 32,768 100.00
    configure 171,672 42 0 42 172,032 100.00
    configure.ac 864 1 0 1 4,096 100.00

free命令

free命令各个指标含义

这里写图片描述
- Buffer: 指写缓冲、还未刷入磁盘的部分.

  1. # 图中是在某测试环境中(写入速度大约30MB/s)抓取到的数据.
  2. root@Storage:~# free
  3. total used free shared buffers
  4. Mem: 7800312 7212832 587480 0 12176
  5. Swap: 0 0 0
  6. Total: 7800312 7212832 587480
  • Cache : 从磁盘中读出来的数据,暂时缓存以备后面再次使用。
  1. > free的”cache”里也包括了“已使用的”共享内存数据。“已使用的”共享内存数据 是指shmget申请下来后真正写过的数据。
  2. > 共享内存其实是通过mmap file来实现的,而所有的mmap的内存都是属于file back的,这些内存理所当然的就被统计进cache里了

/proc/meminfo

  • 基本上涵盖系统内存的各种状态信息

    root@Storage:~# cat /proc/meminfo
    MemTotal: 7800312 kB
    MemFree: 605236 kB
    Buffers: 12192 kB
    Cached: 3821896 kB
    SwapCached: 0 kB
    Active: 4074880 kB
    Inactive: 1904592 kB
    Active(anon): 2240692 kB
    Inactive(anon): 59436 kB
    Active(file): 1834188 kB
    Inactive(file): 1845156 kB
    Unevictable: 0 kB
    Mlocked: 0 kB
    SwapTotal: 0 kB
    SwapFree: 0 kB
    Dirty: 358764 kB
    Writeback: 0 kB
    AnonPages: 2144676 kB
    ……

5. 参考资料

  • 《深入Linux内核架构》 16、17、18章
  • 《深入理解Linux内核》第三版 第15章
  • 《Linux内核设计与实现》 第三版 第12、16章
  • https://events.static.linuxfound.org/images/stories/pdf/lcjp2012_wu.pdf
  • https://blog.csdn.net/zouxiaoting/article/details/8824896
  • https://www.cnblogs.com/liudehao/p/6647674.html
  • https://www.cnblogs.com/hadis-yuki/p/5961949.html
  • http://blog.jobbole.com/52898/
  • https://lkml.org/lkml/2013/11/24/133

发表评论

表情:
评论列表 (有 0 条评论,450人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Linux教程

    一. Linux简介 1.硬件和软件 我们所熟知的计算机是由硬件和软件组成。 硬件:计算机系统中由电子,机械和光电子元件等组成的各种物理装置装置的统称; 简单来

    相关 Java知识整理

    偶然从一个网友群中发现了整理的这份资料,不论是从整个 Java 知识体系,还是从面试的角度来看,都是一份含技术量很高的资料。 也不知道这位作者是谁,里面的内容也大多整理来自于