Linux调优内核缓存(VM)参数以改善存储系统性能
2022-08-21 20:06:14 阿炯

如何调整Linux内核的内存性能,是一个几乎不能穷尽的话题,本文收集了关于vm方面的参数及其调整策略:
vm.mmap_min_addr
vm.overcommit_memory
vm.overcommit_ratio
vm.swappiness
vm.vfs_cache_pressure
vm.zone_reclaim_mode
vm.dirty_background_bytes
vm.dirty_writeback_centisecs
vm.dirty_expire_centisecs
vm.dirty_ratio

其中与vm直接相关的性能参数有:
/proc/sys/vm/swappiness
/proc/sys/vm/min_free_kbytes
/proc/sys/vm/vfs_cache_pressure

内核在内存中缓存大致分类:

脏缓存(dirty cache)-数据块尚未提交到支持缓存的文件系统(例如ext4)。这可以通过发出sync命令来清空,尽管这可能意味着周期性的性能损失。除非是非常重要数据需要同步提交到硬盘驱动器(例如当预期出现故障时),否则不建议在正常使用情况下使用。

干净缓存(clean cache)-硬盘上的数据块,但也保留在内存中,以便快速访问。删除干净缓存可能会导致性能下降,因为所有数据都将从磁盘读取,而在此之前,频繁使用的数据将直接从RAM中获取。

索引节点缓存(inode cache)-索引节点位置信息的缓存,它可以像清除缓存一样删除,但会带来相应的性能损失。

slab cache-此类型的缓存中存放了应用程序通过malloc分配的对象,以便将来在对象数据已填充的情况下,它们可以再次重新malloc,从而在内存分配过程中提高速度。

内存使用的去向主要有3个:1. 进程消耗,2. slab消耗,3.pagetable消耗。内存管理从三个层次管理内存,分别是node,zone,page;64位的x86物理机内存从高地址到低地址分为: Normal DMA32 DMA,随着地址降低。每个zone都有自己的min low high,单位是page。

如有必要,可按以下步骤清除缓存:
# clear page cache (above type 2 and 3)
$ echo 1 > /proc/sys/vm/drop_caches

# clear slab cache (above type 4)
$ echo 2 > /proc/sys/vm/drop_caches

# clear page and slab cache (types 2,3,4)
$ echo 3 > /proc/sys/vm/drop_caches

大部分空间将被页级缓存占用,而不是slab缓存。建议在清除缓存时,仅删除页面缓存(type 1)。

/proc/sys/vm/watermark_scale_factor
如果被允许或需要交换内存容量,可能需要增加/proc/sys/vm/watermark_scale_factor,以避免一些延迟,建议其值介于100和500之间。可以将此设置视为交换CPU使用率以降低交换延迟。默认值为10,最大可能值为1000。更高的值(根据内核文档)应导致kswapd进程的CPU使用率更高,总体交换延迟更低。

脏页的相关参数
/proc/sys/vm/dirty_ratio
/proc/sys/vm/dirty_background_ratio
/proc/sys/vm/dirty_expire_centisecs
/proc/sys/vm/dirty_writeback_centisecs


存储设备的部分IO调优
Read ahead:
On 64 bit systems:
blockdev --setra 4294967295 /dev/sdaN

set the read-ahead size, where sectors is the size you want in 512 byte sectors.


VM性能优化的内核参数介绍与实践

---------------------------------------------------------------
vfs_cache_pressure

该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低于100,将导致内核倾向于保留它们;增加该值超过100,将导致内核倾向于回收directory和inode cache。
缺省设置:100

控制内核回收用于缓存目录和索引节点对象的内存使用趋势。在vfs_cache_pressure=100的默认值下,内核将尝试以相对于pagecache和swapcache以“公平”的速率回收dentry和inode。降低vfs_cache_pressure会导致内核更倾向于在内存中保留dentry和inode缓存。当vfs_cache_pressure=0时,即使内存使用上有不小的压力,内核将永远不会回收dentry和inode,这将很容易导致内存不足。将vfs_cache_pressure增加到100以上会导致内核更愿意回收dentry和inode。

---------------------------------------------------------------
min_free_kbytes

该文件表示强制Linux VM最低保留多少空闲内存(Kbytes)。
缺省设置:67584(66M物理内存)

改变命令:
sysctl -w vm.vfs_cache_pressure=200
sysctl -w vm.min_free_kbytes=1024

vm.min_free_kbytes是用于 linux 内核的在内存中的强制预留,将在此测试此参数以及它如何影响正在运行的 linux 系统。测试它对 OS 页面缓存和 malloc 的影响,以及设置此参数时 system free 命令显示的内容。后文将对这个可调参数的理想值进行一些有根据的猜测,将展示如何设置 vm.min_free_kbytes 以在重新启动后继续存在。该系统参数的功能是用来设置一个最小内存空间给操作系统内核使用,该值设置过大会浪费空间,保留过小会造成系统压力。

用途:合理设置该值有助于Linux系统更有效地回收内存
注意:对于内存较小(≤32GB)的环境不建议设置,保持默认即可
危险:不要对正在运行的环境进行设置
单位:KB
内存水位
    min水位:下的内存是保留给内核使用的;当到达min,会触发内存的direct reclaim
    low水位:比min高一些,当内存可用量小于low的时候,会触发 kswapd回收内存
    high水位:继续休眠

min 和 low的区别:min下的内存是保留给内核使用的;当到达min,会触发内存的direct reclaim;low水位比min高一些,当内存可用量小于low的时候,会触发 kswapd回收内存,当kswapd慢慢的将内存回收到high水位,就开始继续休眠

内存回收方式
    direct reclaim : 触发min水位线时执行
    kswapd reclaim : 触发low水位线时执行

工作原理

系统可能需要内存分配以确保系统本身的正常运行。如果内核允许分配所有内存,则在需要内存进行常规操作以保持操作系统平稳运行时,它可能会遇到困难。这就是内核提供可调 vm.min_free_kbytes 的原因。该可调参数将强制内核的内存管理器保留至少 X 量的空闲内存。这是来自linux内核文档的官方定义:“这用于强制 Linux VM 保持最小数量的可用千字节。VM 使用这个数字来计算系统中每个 lowmem 区域的 watermark[WMARK_MIN] 值。每个 lowmem 区域都会根据其大小按比例获得一些保留的空闲页面。满足 PF_MEMALLOC 分配需要一些最小的内存量;如果将其设置为低于 1024KB,系统将被巧妙地破坏,并且在高负载下容易死锁。设置得太高会立即 OOM 掉机器。”

验证 vm.min_free_kbytes 工作

为了测试 min_free_kbytes 的设置是否按设计工作,创建了一个只有 3.75 GB RAM 的 linux 虚拟实例。使用free命令来分析系统:
# free -m

查看上面的可用内存实用程序,使用 -m 标志以 MB 为单位打印值。总内存为 3.5 到 3.75 GB 内存,使用了 121 MB 内存,3.3 GB 内存可用,251 MB 用于缓冲区高速缓存。并且有 3.3 GB 的内存可用。

现在改变 vm.min_free_kbytes 的值,看看对系统内存有什么影响。会将新值回显到 proc 虚拟文件系统以更改内核参数值,如下所示:
# echo 1500000 > /proc/sys/vm/min_free_kbytes
# sysctl vm.min_free_kbytes

可以看到参数改成大约1.5GB,已经生效了,现在再次使用free命令来查看系统内存分配的更改。该命令不会更改free内存和缓冲区高速缓存,但显示为可用的内存量(available)已从 3327 MB 减少到 1222 MB。该参数变化的近似减少到 1.5 GB 最小可用内存。

现在创建一个 2GB 的数据文件,然后看看将该文件读入缓冲区缓存对值的影响。以下的两行 bash 脚本中创建 2GB 数据文件。该脚本将使用 dd 命令生成一个 35MB 的随机文件,然后将其复制 70 次到一个新的data.file输出中:
# dd if=/dev/random of=/tmp/dx.txt count=1000000
# for i in `seq 1 70`; do echo $i; cat /tmp/dx.txt >> /tmp/data.file; done

通过读取文件并将其重定向到 /dev/null 来读取文件并忽略内容,如下所示:
# cat /tmp/data.file > /dev/null

通过上面的操作后的系统内存发生了什么,现在用'free -m'来检查一下。

分析其结果。由于 min_free_kbytes 设置,仍然有 1.8 GB 的空闲内存,因此内核保护了一大块内存作为保留。缓冲区缓存使用了 1691 MB,小于数据文件的总大小 2.3 GB。显然由于缺少用于缓冲区高速缓存的可用内存,整个data_file无法存储在高速缓存中。可以验证整个文件没有存储在缓存中,而是对重复尝试读取文件进行计时。如果它被缓存,读取文件需要几分之一秒。
# cat /tmp/data.file > /dev/null

文件读取花费了将近 20 秒,这意味着它几乎肯定不会全部缓存。作为最后的验证,减少 vm.min_free_kbytes 以允许页面缓存有更多的操作空间,可以期望看到缓存工作并且文件读取变得更快。
# echo 67584 > /proc/sys/vm/min_free_kbytes #time
# cat /tmp/data.file > /dev/null
# time cat /tmp/data.file > /dev/null

由于可用于缓存的额外内存,文件读取时间从之前的 20 秒下降到 0.364 秒,并且全部在缓存中。

再做一个实验。面对这个非常高的 vm.min_free_kbytes 设置,从 C 程序分配内存的 malloc 调用会发生什么情况。它会失败malloc吗,系统会死吗?首先将 vm.min_free_kbytes 设置重置为非常高的值以继续实验:
# echo 1500000 > /proc/sys/vm/min_free_kbytes

再看看空闲内存:理论上有 1.9 GB 可用空间和 515 MB 可用空间。使用stress-ng的压力测试程序来使用一些内存,看看失败的地方。将使用 vm 测试器并尝试分配 1 GB 的内存。由于在 3.75 GB 系统上只保留了 1.5 GB,我想这应该可行。用更多的进程再试一次,可以尝试 1、2、3、4 个进程,但在某些时候它应该会失败。在测试中通过了 1 和 2 进程,但没有通过第3个进程。

将 vm.min_free_kbytes 重置为一个较低的数字,看看这是否有助于在 3.75GB 系统上运行 3 个内存压力源,每个 1GB。
# echo 67584 > /proc/sys/vm/min_free_kbytes
# stress-ng --vm3 --vm-bytes 1G --timeout 60s

这次它运行成功且没有错误,试了两次没有问题。所以可以得出结论:当 vm.min_free_kbytes 值设置为较低值时,有更多内存可用于 malloc 的分配行为。

默认设置

系统上设置的默认值是 67584,大约是系统 RAM 的 1.8% 或 64 MB。出于安全原因,在一个严重颠簸的系统上可将其增加一点,可能会增加到 128MB 以允许更多保留的可用内存,但是对于平均使用情况,默认值似乎足够明智。官方文档警告不要将值设置得太高。将其设置为系统 RAM 的 5% 或 10% 可能不是该设置的预期用途,而且太高了。

固化设置

为确保其设置可以在重新启动后继续存在并且不会在重新启动时恢复为默认值,请务必通过将所需的新值放入 /etc/sysctl.conf 文件中来使 sysctl 设置保持不变。

结论:vm.min_free_kbytes为linux内核可调参数,可以为操作系统内核保留内存,以确保系统更加稳定,尤其是在有内存的大量使用或分配期间(抖动)。默认设置可能有点低,尤其是在高内存系统上,应考虑谨慎增加。此可调参数为操作系统内核保留了合适的内存,可防止系统缓存使用所有内存,也可防止某些 malloc 操作使用掉所有内存。

---------------------------------------------------------------
vm.max_map_count

其包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。

日志中的报错“max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]”

1、原文
This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries. While most applications need less than a thousand maps, certain programs, particularly malloc debuggers, may consume lots of them, e.g., up to one or two maps per allocation. The default value is 65536.

2、译文
此文件包含一个进程可能拥有的最大内存映射区域数。内存映射区域被用作调用 malloc的 side-effect,直接由mmap 和 mprotect 使用,也可以在加载共享库时使用。虽然大多数应用程序需要少于1000个映射,但某些程序,特别是malloc调试器,可能会消耗大量映射,例如,每次分配最多一到两个映射。该参数的默认值为65536。

3、解读
虚拟内存区域:一个连续的虚拟地址空间区域。

在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。

4、如何理解程序设计中的 side-effect
side-effect本身的译文就是副作用的意思,通俗的讲就是除了返回值以外,影响到函数外部的任何东西(显示器、网络、文件、全局变量等)的效果都叫 side effect。

5、如何调整 max_map_count 的大小
在root用户下调整为默认的4倍

设置该值:sysctl -w vm.max_map_count=262144

查看该值:sysctl -a|grep vm.max_map_count

---------------------------------------------------------------
Linux下优化VM缓存的性能参数:vm.dirty_ratio与vm.dirty_ background_ration

在许多关于内存优化的帖子中,Linux客户机上的vm.swappiness和使用RAM磁盘被用于讨论将内存如何用于操作系统本身(内核、缓冲区等)、应用程序以及文件缓存。文件缓存是一项重要的性能改进,在大多数情况下,读缓存是一个明显的优势,与直接使用RAM的应用程序相平衡。写缓存要更复杂一些,Linux内核将磁盘写入分阶段缓存,并随着时间的推移将其异步刷新到磁盘。这对加快磁盘I/O速度有很好的效果,但风险很大。当数据未写入磁盘时,丢失数据的可能性会增加。

大量I/O操作可能会用光缓存,一次将大量数据写入磁盘,并在系统尝试处理所有数据时看到系统出现大暂停?这些暂停是由于缓存决定有太多数据要异步写入(作为非阻塞后台操作,让应用程序进程继续),并切换到同步写入(阻塞并使进程等待,直到I/O提交到磁盘)。当然,文件系统还必须保持写入顺序,因此当它开始同步写入时,首先必须解除缓存的存储。因为这可能导致长时间停顿。

好的是,这些是可控的选项,根据工作负载和数据来决定如何设置它们:
$ sysctl -a | grep dirty
 vm.dirty_background_ratio = 10
 vm.dirty_background_bytes = 0
 vm.dirty_ratio = 20
 vm.dirty_bytes = 0
 vm.dirty_writeback_centisecs = 500
 vm.dirty_expire_centisecs = 3000

vm.dirty_background_ratio是在pdflush/flush/kdmflush后台进程启动将其写入磁盘之前,可以用“脏”页(仍然需要写入磁盘的内存页)填充的系统内存的百分比。示例中是10%,所以如果虚拟服务器有32GB的内存,那么可以在RAM中存储3.2GB的此类数据。

vm.dirty_ratio是在所有内容都必须提交到磁盘之前,可以用脏页填充的系统内存的绝对最大数量。当系统到达这一点时,所有新的I/O将被阻塞直到脏页被写入磁盘。这通常是长时间I/O暂停的原因,但也是防止太多数据不安全地缓存在内存中的一种保护措施。

vm.dirty_background_bytes和vm.dirty_bytes是指定这些参数的另一种方法。如果设置_bytes版本,_ratio版本将变为0,反之亦然。

vm.dirty_expire_centisecs是在需要写入某个内容之前,它可以在缓存中保存多长时间,默认情况下是30秒。当pdflush/flush/kdmflush进程启动时,它们将检查脏页的使用时间,如果脏页超过此值,则将异步写入磁盘。由于在内存中保存脏页是不安全的,这也是防止数据丢失的一种保障。

vm.dirty_writeback_centisecs是pdflush/flush/kdmflush进程唤醒并检查是否需要完成工作的时间间隔。

还可以在/proc/vmstat中查看页面缓存的统计信息:
$ cat /proc/vmstat | egrep "dirty|writeback"
 nr_dirty 878
 nr_writeback 0
 nr_writeback_temp 0

在上面的例子中,有878个脏页等待写入磁盘。

方法1:减少缓存

与计算机世界中的大多数事情一样,如何调整这些取决于试图做什么。在许多情况下,快速磁盘子系统有自己的大型电池支持NVRAM缓存,因此将内容保留在操作系统页面缓存中是有风险的。尝试以更及时的方式将I/O发送到阵列,并减少本地操作系统陷入混乱的可能性。为此可降低了vm.dirty_background_ratio和vm.dirty_ratio,通过在/etc/sysctl.conf中修改为新的数字来实现。并用“sysctl–p”重新加载:
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10

这是虚拟机以及基于Linux的虚拟机监控程序的常用方法。不建议将这些参数设置为零,因为一些后台I/O可以很好地将应用程序性能与磁盘阵列和SAN上较短时间的较高延迟(“峰值”)分隔。

方法2:增加缓存

在某些情况下,提高缓存会显著提高性能。在这些情况下,Linux客户机上包含的数据并不重要,可能会丢失,并且通常应用程序会重复或以可重复的突发方式写入相同的文件。理论上,通过允许更多脏页存在于内存中,将在缓存中一遍又一遍地重写相同的块,只需要每周期对磁盘进行一次实际的写入。为此提出了以下参数:
vm.dirty_background_ratio = 50
vm.dirty_ratio = 80

有时人们也会增加vm.dirty_expire_centisecs参数以允许缓存中的对象能保留更长时间。除了增加数据丢失的风险外,如果缓存已满并需要卸载,还可能面临长时间I/O暂停的风险;因为在大型虚拟机上,缓存中会有大量数据。

方法3:两者兼顾

还有一些情况下,系统必须处理不频繁的、突发的流量,会拖慢磁盘速度(在最高峰、午夜、在树莓派上写入SD卡等)。在这种情况下,一种可能的方法是允许所有写I/O都存储在缓存中,以便后台刷新操作可以随时异步处理:
vm.dirty_background_ratio = 5
vm.dirty_ratio = 80

在这里,当达到5%的上限时,后台进程将立即开始写入,但系统不会强制同步I/O,直到它达到80%的阀值时。从那时起就需调整系统RAM与vm.dirty_ratio大小,使之能够在VM中暂存所有写入数据。同时,磁盘上的数据一致性也需要权衡,这会转化为数据风险。购买一台UPS,确保可以在其电量耗尽之前卸载缓存并落盘。

无论您选择何种方式,都应该始终收集数据来支撑相关的更改,并确定调整是在改进还是让事情变得更糟。在这种情况下,可以从许多不同的地方获取数据,包括应用程序本身、/proc/vmstat、/pro/meminfo、iostat、vmstat以及/proc/sys/vm中的许多内容。预祝好运。

---------------------------------------------------------------
Linux服务器内存使用量超过阈值分析例1

通过free命令观察系统的内存使用情况,显示如下:
                    total       used       free     shared    buffers     cached
Mem:      24675796   24587144      88652          0     357012    1612488
-/+ buffers/cache:   22617644    2058152
Swap:      2096472     108224    1988248

其中,可以看出内存总量为24675796KB,已使用22617644KB,只剩余2058152KB。在通过top的工作界面中按下大写的M键,使其按内存占用排序后,观察系统中使用内存最大的进程情况,发现只占用了18GB内存,其他进程均很小到可忽略。因此还有将近4GB内存(22617644KB-18GB,约4GB)用到什么地方了呢?

进一步,通过cat /proc/meminfo发现,其中有将近4GB(3688732 KB)的Slab内存:
......
Mapped:          25212 kB
Slab:          3688732 kB
PageTables:      43524 kB
......

Slab是用于存放内核数据结构缓存,再通过slabtop命令查看这部分内存的使用情况:
OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
13926348 13926348 100%    0.21K 773686       18   3494744K dentry_cache
334040 262056  78%    0.09K   8351       40     33404K buffer_head
151040 150537  99%    0.74K  30208        5    120832K ext3_inode_cache

发现其中大部分(大约3.5GB)都是用于了dentry_cache。

可修改/proc/sys/vm/drop_caches的值来释放Slab占用的cache内存空间(参考drop_caches的官方文档):Writing to this will cause the kernel to drop clean caches, dentries and inodes from memory, causing that memory to become free.

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

As this is a non-destructive operation, and dirty objects are notfreeable, the user should run "sync" first in order to make sure allcached objects are freed.

This tunable was added in 2.6.16.

可以通过sysctl命令进行设置:
sysctl -w vm.drop_caches=3
sysctl -w vm.drop_caches=0 #recovery drop_caches

操作后可以通过sysctl -a | grep drop_caches查看是否生效。

修改/proc/sys/vm/vfs_cache_pressure来调整清理inode/dentry caches的优先级(默认为100)

相关的解释:At the default value of vfs_cache_pressure = 100 the kernel will attempt to reclaim dentries and inodes at a “fair” rate with respect to pagecache and swapcache reclaim. Decreasing vfs_cache_pressure causes the kernel to prefer to retain dentry and inode caches. Increasing vfs_cache_pressure beyond 100 causes the kernel to prefer to reclaim dentries and inodes.

上面知道了Linux系统中有大量的dentry_cache占用内存,为什么会有如此多的dentry_cache呢?

1.首先,弄清楚dentry_cache的概念及作用:目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的;它记录了目录项到inode的映射关系。因此,当应用程序发起stat系统调用时,就会创建对应的dentry_cache项(更进一步,如果每次stat的文件都是不存在的文件,那么总是会有大量新的dentry_cache项被创建)。

2.当前服务器是storm集群的节点,首先想到了storm相关的工作进程,strace一下storm的worker进程发现其中有非常频繁的stat系统调用发生,而且stat的文件总是新的文件名:strace -fp -e trace=stat

3.进一步观察到storm的worker进程会在本地目录下频繁的创建、打开、关闭、删除心跳文件,每秒钟一个新的文件名:strace -fp -e trace=open,stat,close,unlink

以上就是系统中为何有如此多的dentry_cache的原因所在。

通过观察/proc/meminfo发现,slab内存分为两部分:
SReclaimable // 可回收的slab
SUnreclaim // 不可回收的slab

当时服务器的现状是:slab部分占用的内存,大部分显示的都是SReclaimable,也就是说可以被回收的。但是通过slabtop观察到slab内存中最主要的部分(dentry_cache)的OBJS几乎都是ACTIVE的,显示100%处于被使用状态。
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
13926348 13926348 100% 0.21K 773686 18 3494744K dentry_cache
334040 262056 78% 0.09K 8351 40 33404K buffer_head
151040 150537 99% 0.74K 30208 5 120832K ext3_inode_cache

为什么显示可回收的,但是又处于ACTIVE状态呢,会不会由于是ACTIVE状态,导致dcache(The Directory Cache)没有被自动回收释放掉呢?

让系统自动回收dcache

主机上大部分的slab内存是SReclaimable可回收状态的,那么能不能交给操作系统让其在某个时机自动触发回收操作呢?

答案是肯定的。查了一些关于Linux dcache的相关资料,发现操作系统会在到了内存临界阈值后,触发kswapd内核进程工作才进行释放,这个阈值的计算方法如下:

1.首先执行grep low /proc/zoneinfo,得到如下结果:
low 1
low 380
low 12067

2.将以上3列加起来再乘以4KB,就是阈值,通过此方法计算后发现当前服务器的回收阈值只有48.6MB,因此很难看到这一现象,实际中可能等不到回收,操作系统就会hang住没响应了。

3.可以通过以下方法调大这个阈值:将vm.extra_free_kbytes设置为vm.min_free_kbytes和一样大,则/proc/zoneinfo中对应的low阈值就会增大一倍,同时high阈值也会随之增长,以此类推。
sysctl -a | grep free_kbytes
vm.min_free_kbytes = 39847
vm.extra_free_kbytes = 0

sysctl -w vm.extra_free_kbytes=836787

4.当low阈值被设置为1GB的时候,且系统free的内存小于1GB时,观察到kswapd进程开始工作(进程状态从Sleeping变为Running),同时dcache开始被系统回收,直到系统free的内存介于low阈值和high阈值之间时才停止回收。

注意:extra_free_kbytes在较新的Linux上已不再提供了,大约应该是在2.6的内核后没有的吧,在互联网上没有能查到这个参数消失之迷;CentOS6中能看到而第7版中无。

---------------------------------------------------------------
Linux服务器内存使用量超过阈值分析例2

[root@freeoa ~]# vmstat 1 999
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0 36079972 262600      0 387404    6    8   519   278    0    0 11  1 89  0  0
 1  0 36079972 244704      0 387404    0    0     0     8 3400 1942  5  2 93  0  0
 0  1 36079972 255148      0 392900    0    0  5760    32 3009 2115  1  1 98  0  0
 0  0 36079972 250424      0 395984    0    0  2856     0 2385 1649  1  0 99  0  0
 3  0 36079972 246028      0 399792    0    0  3932     0 2158 1728  0  0 99  0  0
 1  3 36079972 245876      0 399876   16    0   152     0 3500 2130  5  2 92  1  0
 1  0 36100772 274472      0 377536   92 21388 62260 21481 10694 4716  8  5 84  4  0
 0  0 36100772 267928      0 384160    0    0  6492     0 2936 2465  1  0 98  1  0
 0  0 36100772 267296      0 384080    0    0     0     0 2240 2245  1  0 99  0  0
 1  0 36100772 239640      0 387500    0    0  3448     0 3914 2528  5  2 93  0  0
 0  0 36100772 252244      0 395820    0    0  8180     0 2983 2321  1  1 98  0  0
 0  0 36100772 252244      0 395820    0    0     0     0 2355 2135  0  0 99  0  0
 1  0 36100772 251180      0 397244    0    0   544     0 2533 2294  0  0 99  0  0
 1  0 36120564 264968      0 376284    0 19924 75372 19988 8610 3090  5  5 86  4  0
 0  0 36120564 289184      0 378064    0    0  1796     0 2687 2332  1  0 99  0  0
 0  0 36120564 287588      0 378524    0    0   328     0 2293 2197  0  0 99  0  0
 2  0 36120564 286440      0 378528    0    0    52     4 2365 2256  0  0 99  0  0
 0  0 36120564 285324      0 378696    0    0   468     0 3987 2573  4  2 93  0  0
 0  0 36120564 284612      0 379072    0    0     0     0 2393 2290  0  0 99  0  0
 0  0 36120564 284240      0 379072    0    0     0     0 2175 2166  0  0 99  0  0
 1  0 36120564 249992      0 387600    0    0  8816     0 2434 2415  1  1 99  0  0
 0  0 36120564 275832      0 387888    0    0     0     4 4028 2488  4  3 93  0  0
 0  0 36120564 274848      0 387892    0    0     4     0 2369 2181  0  0 99  0  0
 0  0 36120564 257012      0 400228    0    0 12788    32 2973 2167  1  1 98  0  0
 1  0 36120564 258836      0 401452    0    0  1388     0 2612 2063  1  1 98  0  0
 0  0 36120564 258612      0 401472    0    0     0     0 3830 2534  4  2 94  0  0
 0  0 36120564 257564      0 402112    0    0     0     0 2283 2282  0  0 99  0  0
 0  0 36120564 257564      0 402112    0    0     0     0 2487 2305  0  0 99  0  0
 1  0 36120564 231312      0 402112    0    0     0     0 2762 2266  2  1 98  0  0
 
# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       31396         254          18         361         260
Swap:         50171       35305       14866

swappiness由9减至0
min_free_kbytes由67584增至262144
vm.vfs_cache_pressure由50增至120

vm.swappiness = 0
vm.min_free_kbytes = 262144
vm.vfs_cache_pressure = 120

sysctl -p适用后用top指令会马上看到kswapd进程开始工作。

top - 11:00:05 up 440 days, 20:52,  2 users,  load average: 0.36, 0.32, 0.32
Tasks: 263 total,   1 running, 261 sleeping,   0 stopped,   1 zombie
%Cpu(s):  2.0 us,  4.9 sy,  0.0 ni, 92.9 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 32780072 total,   773288 free, 31557336 used,   449448 buff/cache
KiB Swap: 51376124 total, 14386988 free, 36989136 used.   463980 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  105 root      20   0       0      0      0 S  67.5  0.0   1962:40 [kswapd0]
 6013 root      20   0  139684  26956    480 S   6.0  0.1   4560:57 /bin/bash /usr/local/freeoa/client/drc-watchdog.sh

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       30858         706          16         446         408
Swap:         50171       36122       14049

top - 11:03:21 up 440 days, 20:56,  1 user,  load average: 0.30, 0.29, 0.31
Tasks: 261 total,   2 running, 258 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 32780072 total,   633428 free, 31688892 used,   457752 buff/cache
KiB Swap: 51376124 total, 14386988 free, 36989136 used.   328296 avail Mem

物理内存占用由99%降到了97%左右,交换分区由69%升级72%。

 Active / Total Objects (% used)    : 715320 / 969838 (73.8%)
 Active / Total Slabs (% used)      : 26034 / 26034 (100.0%)
 Active / Total Caches (% used)     : 74 / 104 (71.2%)
 Active / Total Size (% used)       : 229783.23K / 366148.61K (62.8%)
 Minimum / Average / Maximum Object : 0.01K / 0.38K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
457947 241349  52%    0.57K  16356       28    261696K radix_tree_node
 63232  63232 100%    0.02K    247      256       988K kmalloc-16
 55296  55296 100%    0.01K    108      512       432K kmalloc-8
 44416  37791  85%    0.03K    347      128      1388K kmalloc-32
 35700  25341  70%    0.19K    850       42      6800K dentry
 31314  31314 100%    0.12K    921       34      3684K kernfs_node_cache
 28672  22337  77%    0.06K    448       64      1792K kmalloc-64
 22922  22557  98%    0.05K    314       73      1256K avc_xperms_node
 18768  10173  54%    0.94K    552       34     17664K xfs_inode
 16320  16320 100%    0.04K    160      102       640K selinux_inode_security
 15444  15400  99%    0.18K    351       44      2808K xfs_log_ticket
 14578  14393  98%    0.21K    394       37      3152K xfs_btree_cur
 13431  13431 100%    0.21K    363       37      2904K vm_area_struct
 12474  12474 100%    0.58K    462       27      7392K inode_cache
 11424  11334  99%    0.08K    224       51       896K anon_vma
  9844   9116  92%    0.17K    214       46      1712K xfs_ili
  8229   8034  97%    0.10K    211       39       844K buffer_head
  8190   8190 100%    0.19K    195       42      1560K cred_jar
  7416   7416 100%    0.11K    206       36       824K task_delay_info
  7224   6592  91%    0.38K    172       42      2752K mnt_cache
  7104   6013  84%    0.25K    223       32      1784K kmalloc-256
  6888   6888 100%    0.19K    164       42      1312K kmalloc-192
  6624   6624 100%    0.12K    207       32       828K pid

# grep low /proc/zoneinfo
    low      38
    low      7205
    low      74673

vm.min_free_kbytes = 307200
vm.vfs_cache_pressure = 160

适用后没有看到kswapd工作

top - 11:16:55 up 440 days, 21:09,  1 user,  load average: 0.66, 0.40, 0.35
Tasks: 261 total,   6 running, 253 sleeping,   1 stopped,   1 zombie
%Cpu(s):  3.2 us,  2.4 sy,  0.0 ni, 94.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 32780072 total,   545332 free, 31814396 used,   420344 buff/cache
KiB Swap: 51376124 total, 13975852 free, 37400272 used.   153700 avail Mem

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       31112         547          16         351         135
Swap:         50171       36523       13648

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       30727         859          16         424         484
Swap:         50171       36932       13239

物理内存占用由97%降到了97.3%左右,交换分区由72.1%升级73.6%。

 Active / Total Objects (% used)    : 709252 / 963298 (73.6%)
 Active / Total Slabs (% used)      : 25844 / 25844 (100.0%)
 Active / Total Caches (% used)     : 74 / 104 (71.2%)
 Active / Total Size (% used)       : 227415.48K / 363187.73K (62.6%)
 Minimum / Average / Maximum Object : 0.01K / 0.38K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
455847 240275  52%    0.57K  16281       28    260496K radix_tree_node
 63232  63232 100%    0.02K    247      256       988K kmalloc-16
 55296  55296 100%    0.01K    108      512       432K kmalloc-8
 44416  37769  85%    0.03K    347      128      1388K kmalloc-32
 34818  25349  72%    0.19K    829       42      6632K dentry
 31314  31314 100%    0.12K    921       34      3684K kernfs_node_cache
 28672  21080  73%    0.06K    448       64      1792K kmalloc-64
 22922  22557  98%    0.05K    314       73      1256K avc_xperms_node
 17714   8966  50%    0.94K    521       34     16672K xfs_inode

sysctl -a|grep vm.max_map_count

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       31073         563          17         374         162
Swap:         50171       36932       13239

将vm.max_map_count由262144减半看看Slabs会不会减少一些。。

top - 11:28:04 up 440 days, 21:20,  1 user,  load average: 0.33, 0.30, 0.32
Tasks: 261 total,   4 running, 256 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.4 us,  6.5 sy,  0.0 ni, 91.8 id,  1.2 wa,  0.0 hi,  0.2 si,  0.0 st
KiB Mem : 32780072 total,   720228 free, 31691488 used,   368356 buff/cache
KiB Swap: 51376124 total, 13404372 free, 37971752 used.   301444 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                       
  105 root      20   0       0      0      0 R 100.0  0.0   1962:52 [kswapd0]

kswapd开始工作了。。

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       30883         704          17         423         328
Swap:         50171       37159       13012

 Active / Total Objects (% used)    : 709186 / 963442 (73.6%)
 Active / Total Slabs (% used)      : 25843 / 25843 (100.0%)
 Active / Total Caches (% used)     : 74 / 104 (71.2%)
 Active / Total Size (% used)       : 227224.70K / 363110.65K (62.6%)
 Minimum / Average / Maximum Object : 0.01K / 0.38K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
455763 240108  52%    0.57K  16278       28    260448K radix_tree_node
 63232  63232 100%    0.02K    247      256       988K kmalloc-16
 55296  55296 100%    0.01K    108      512       432K kmalloc-8
 44416  37769  85%    0.03K    347      128      1388K kmalloc-32
 34734  24915  71%    0.19K    827       42      6616K dentry
 31314  31314 100%    0.12K    921       34      3684K kernfs_node_cache
 28672  20648  72%    0.06K    448       64      1792K kmalloc-64
 22922  22557  98%    0.05K    314       73      1256K avc_xperms_node
 17714   8966  50%    0.94K    521       34     16672K xfs_inode

top - 11:30:24 up 440 days, 21:23,  1 user,  load average: 0.13, 0.27, 0.30
Tasks: 259 total,   2 running, 256 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.3 us,  0.2 sy,  0.0 ni, 99.4 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 32780072 total,   596256 free, 31738004 used,   445812 buff/cache
KiB Swap: 51376124 total, 13324588 free, 38051536 used.   217052 avail Mem

物理内存使用比只有很小的上升,交换分区使用比有轻微的上升。看来vm.max_map_count这个参数缓解作用不大。。

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       30951         601          17         459         242
Swap:         50171       37878       12293

再来调整vm.vfs_cache_pressure为220看一下。

top - 11:51:09 up 440 days, 21:43,  1 user,  load average: 0.66, 0.37, 0.31
Tasks: 263 total,   5 running, 256 sleeping,   1 stopped,   1 zombie
%Cpu(s):  3.5 us,  2.4 sy,  0.0 ni, 94.1 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 32780072 total,   576204 free, 31779952 used,   423916 buff/cache
KiB Swap: 51376124 total, 12588892 free, 38787232 used.   185708 avail Mem

没有看到kswapd工作了。。

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       31057         540          18         414         158
Swap:         50171       37878       12293

# free -m
              total        used        free      shared  buff/cache   available
Mem:          32011       30708         881          18         422         503
Swap:         50171       38295       11876

物理内存使用比几乎不变,交换分区使用比有所上升由73%升至76.3%,期间有cpu使用率略有上升。

看来vm.min_free_kbytes参数有一定的强制性外,其它参数在资源紧张的情况下对其调整对系统影响较小。还是要借助cgroup来控制进程对系统计算资源的使用了。

---------------------------------------------------------------
参考来源:
Linux内存管理
Linux中关于交换分区与虚拟内存和页的认识
Documentation for /proc/sys/vm/* kernel version 2.6
Linux内存调节之zone watermark

---------------------------------------------------------------
说了上面的内存相关参数后,再来看一下内存中所存放对象的一些情况,主要是目录项(dentry)。

用sar命令以秒为间隔查看dentry中dentunusd变化。
sar -v 1
09时41分32秒 dentunusd   file-nr  inode-nr    pty-nr
09时41分33秒     11691      1408     26281         6
...

会有如下的4列输出:
dentunusd    file-nr  inode-nr    pty-nr

dentunusd:目录高速缓存中未被使用的条目数量
file-nr:文件句柄(file handle)的使用数量
inode-nr:索引节点句柄(inode handle)的使用数量
pty-nr:使用的pty数量

1)dentunusd
dentunusd数据的数据来源是/proc/sys/fs/dentry-state的第二项数据.
要弄明白它的意义首先要弄明白dcache(目录高速缓存),因为系统中所有的inode都是通过文件名来访问的,而为了解决文件名到inode转换的时间,就引入了dcache.
它是VFS层为当前活动和最近使用的名字维护的一个cache.
dcache中所有处于unused状态和negative(消极)状态的dentry对象都通链入到dentry_unused链表中,这种dentry对象在回收内存时可能会被释放.
如果我们在系统中运行ls -ltR /etc/会看到dentunusd的数量会多起来,而通过mount -o remount /dev/sda1会看到dentunusd会迅速会回收。

2)file-nr
file-nr的的数据来源是/proc/sys/fs/file-nr文件的第一项数据,实际上file-nr不是一个准确的值,它每次增加的步长是64(64位系统),如现在file-nr为2528,实际上可能只打开了2527个文件,此时打开两个文件后它就会变成2592,而不是2530。

[root@cos2 tmp]# more /proc/sys/fs/file-nr
1344    0    100914
另开一终端,用vim打开一个文件后再查看:
[root@cos2 tmp]# more /proc/sys/fs/file-nr
1408    0    100914

3)inode-nr
inode-nr的数据来源是/proc/sys/fs/inode-nr文件的第一项数据减去第二项数据的值。文件的第一项数据是已经分配过的inode节点,第二项数据是空闲的inode节点。
例如inode-nr文件里的值为:13720 7987

新建一个文件file1,此时inode-nr第一项数据会加1,就是13721,表示系统里建立了这么多的inode。再删除掉file1,此时就会变成13720。

[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70351    17353
另开一终端,用ps指令将结果输出到一文件后再查看:
[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70352    17353
删除该文件后再查看(由于是在使用的系统,这个动态值的查看间隔必须非常及时):
[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70348    17353

[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70348    17353
>for i in {0..9}; do echo $i && touch zip.$i; done
[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70358    17353
>rm -fv zip.*
[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70348    17353

空闲的inode节点表示已经有这么多的inode节点曾经有过被利用,但没有被释放。所以inode节点总数减去空闲的inode,就是正在被用的inode。

可通过使用mount -o remount /dev/sda1命令,空闲节点会被刷新,因此inode-nr的值会有所变化。

[root@cos2 tmp]# mount -o remount /dev/mapper/centos-root
[root@cos2 tmp]# more /proc/sys/fs/inode-nr
70350    54145

inode-nr与df -i中的inode关系

在各自的返回使用中的inode数都记录了与文件相关信息,但是它们显然返回的是不相关的数。

/proc/sys/fs/inode-nr shows data about inodes in RAM (kernel cache), first currently allocated ones, and then free ones. The kernel uses inodes for many things, not just files. For example, network sockets are identified by inodes too.

df -i shows inodes on disk, so whatever each filesystem manages to do with its specific set of inodes.

因此这是两类文件的inode的计数,两组数字集合彼此无关。原因很简单:在一切皆文件的逻辑里,文件必须有其本身的基本inode属性;不管文件是在块存储上真实存在的还是仅存于内核的虚拟文件系统中,也不论其是普通文件还是socket或各类设备。在5.10的内核参考文档中转述如下:

与文件句柄一样,内核动态分配inode结构,但还不能释放它们。

inode-max(在较新版本的内核中已经被移除了,但新版本的内核文档中依然保留着介绍)中的值表示inode处理程序的最大数量。这个值应该比file-max中的值大3-4倍,因为stdin、stdout和网络套接字也需要一个inode结构来处理它们。当经常用完索引节点时,需要增加此值。

文件inode-nr包含inode-state的前两项,因此可以跳到该文件…

inode状态包含三个实际数字和四个虚拟位。按其自然顺序,实际使用的只有前三位:nr_inodes、nr_ free_inodes和preshrink。

nr_inode表示操作系统分配的inode数,这可能略大于inode-max,因为Linux一次分配一页。nr_free_inode表示可自由分配inode(之前分配过目前不用了,但没有回收释放),并且当nr_inodes>inode-max时,系统需要调整inode列表,而不是分配更多时,且preshrink为非零。

4)pty-nr
pty-nr的数据来源是/proc/sys/kernel/pty/nr
表示登陆过的终端总数,如果我们登录过10回,退出了3回,最后的结果还是10回.


再来具体讲一下dentry-state

/proc/sys/fs/dentry-state显示目录项高速缓存的一些信息:
nr_dentry - number of dentries currently allocated
nr_unused - nuber of unused dentries
age_limit - seconds after the entry may be reclaimed, when memory is short
remaining - reserved.

通常linux文件系统中目录项高速缓存的age_limit是45s,也就是说该目录项在目录项高速缓存中停留45s还无访问,就将它换出。关于linux dentry占用过高,不外乎两种处理方法,一种是直接通过proc系统清理dentry,命令如下:
echo 2 > /proc/sys/vm/drop_caches

缺点是执行命令过程容易hang住一段时间,而且官方也不建议使用这种方式清理cache;另外一种方法是通过调整内核参数vm.vfs_cache_pressure,先调整为'vm.vfs_cache_pressure=999'观察一天以上会发现能使dentry降下去,不过过程非常的缓慢但确实有效,交换分区的使用率在缓慢下降而物理内存的使用率在同步轻微上升;在调整前系统上有一个日志文件分析的任务,从监控上可以看到物理内存的占用在比较快速的下降,而交换分区使用率在在快速上升。

vm.vfs_cache_pressure控制内核回收用于dentry和inode cache内存的倾向,默认值是100。核会根据pagecache和swapcache的回收情况,让dentry和inode cache的内存占用量保持在一个相对公平的百分比上。减小vfs_cache_pressure会让内核更倾向于保留dentry和inode cache。当其值为0时,在内存紧张时内核也不会回收dentry和inode cache,这容易导致OOM。如果vfs_cache_pressure的值超过100,内核会更倾向于回收dentry和inode cache。

用户态与内核态的切换

内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

1)OS采用系统调用实现用户态进程与I/O进行交互,用户态下调用系统资源须采用系统调用。
2)从用户态进入内核态有2种方式:系统调用(trap陷入)、中断。
3)状态切换时会保存寄存器上下文,如用户态堆栈顶地址、当时的状态字、当时的cs:eip值。
4)system_ call是linux中所有系统调用的入口点,系统调用的参数由eax传递。

dentry的作用:当读写文件时内核会为该文件对象建立一个dentry,并将其缓存起来,方便下一次读写时直接从内存中取出提高效率。


linux文件系统dentry

inode仅仅只是保存了文件对象的属性信息,包括:权限、属组、数据块的位置、时间戳等信息。但是并没有包含文件名,文件在文件系统的目录树中所处的位置信息。那么内核又是怎么管理文件系统的目录树呢?答案是目录项。dentry的中文名称是目录项,是Linux文件系统中某个索引节点(inode)的链接。这个索引节点可以是文件的,也可以是目录的。

目录项在内核中起到了连接不同的文件对象inode的作用,进而起到了维护文件系统目录树的作用。dentry是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接建立(并没有实际对应的磁盘上的描述,是虚拟的文件)。在内存中,每个文件都有一个dentry(目录项),dentry记录文件名、父目录、子目录等信息,形成文件树结构。而且每个dentry都有一个唯一对应的inode,而每个inode则可能有多个dentry(这种情况是由ln硬链接产生的)。

Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。Linux下在/proc/sys/fs/dentry-state中存放了其统计信息。

读取文件

文件由文件名、文件属性和数据组成。文件的inode中不包含文件的名字,文件的名字位于存放文件所在的目录中,在目录中通过查看目录项,因为每个目录项包含两项信息,即这个目录中的文件名字及对应的inode编号,通过这种对应关系找到文件。

具体来说VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。又inode储存有一些指针,这些指针指向存储设备中的一些数据块,文件的内容就储存在这些数据块中。当Linux想要打开一个文件时,只需要找到文件对应的inode,然后沿着指针,将所有的数据块收集起来,就可以在内存中组成一个文件的数据了。例如:cat一个文件/newtest/subtest/freeoa.pl时(/、newtest、subtest、freeoa.pl都是一个目录项),它的查找过程如下:系统通过挂载信息(在超级块中,位置固定)找到根目录(/)的inode编号,根目录对应的inode是固定的。在dentry中找到对应的inode信息,从inode信息中找到存储根目录信息的数据块。从目录块中存储的信息中,找到文件名(目录名)为newtest所对应的inode编号;找到newtest的inode编号后,从该inode信息中,找到newtest目录存放的数据块,再从该数据块存储的信息中,找到subtest目录对应的inode编号;重复上述步骤,直至找到freeoa.pl文件对应的inode结点,根据inode结点中记录的message文件内容对应的数据块,从数据块中读取内容。

写入文件

当写入一个文件时,是分配一个空白inode给该文件,将其inode编号记入该文件所属的目录,然后选取空白的数据块,让inode的指针指像这些数据块,并放入内存中的数据。

删除文件

实质上就是减少link count,当link count为0时,就表示这个Inode可以使用,并把Block标记为可以写,但并没有清除Block里面数据,除非是有新的数据需要用到这个block。

结构
{
atomic_td_count;目录项对象使用计数器
unsignedintd_flags;目录项标志
structinode*d_inode;与文件名关联的索引节点
structdentry*d_parent;父目录的目录项对象
structlist_headd_hash;散列表表项的指针
structlist_headd_lru;未使用链表的指针
structlist_headd_child;父目录中目录项对象的链表的指针
structlist_headd_subdirs;对目录而言,表示子目录目录项对象的链表
structlist_headd_alias;相关索引节点(别名)的链表
intd_mounted;对于安装点而言,表示被安装文件系统根项
structqstrd_name;文件名
unsignedlongd_time;/*usedbyd_revalidate*/
structdentry_operations*d_op;目录项方法
structsuper_block*d_sb;文件的超级块对象
vunsignedlongd_vfs_flags;
void*d_fsdata;与文件系统相关的数据
unsignedchard_iname[DNAME_INLINE_LEN];存放短文件名
};

dentry与inode

(可参照理解为ext2 inode)inode对应于物理磁盘上的具体对象,dentry是一个内存实体,其中的d_inode成员指向对应的inode。
也就是说,一个inode可以在运行的时候链接多个dentry,而d_count记录了这个链接的数量。
按照d_count的值,dentry分为以下四种状态:
0:空闲状态:处于该状态的目录项对象不包括有效信息,且没有被VFS使用。
1:未使用(unused)状态:该dentry对象的引用计数d_count的值为0,但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。这种dentry对象在回收内存时可能会被释放。
2:正在使用(inuse)状态:处于该状态下的dentry对象的引用计数d_count大于0,且其d_inode指向相关的inode对象。这种dentry对象不能被释放。
3:负(negative)状态:与目录项相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。这种dentry对象在回收内存时将首先被释放。

dentry与dentry_cache

dentry_cache简称dcache,中文名称是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的。它主要由两个数据结构组成:
1、哈希链表dentry_hashtable:dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。
2、未使用的dentry对象链表dentry_unused:dcache中所有处于unused状态和negative状态的dentry对象都通过其d_lru指针域链入dentry_unused链表中。该链表也称为LRU链表。
目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即dcache中的dentry对象控制着icache中的inode对象的生命期转换。无论何时,只要一个目录项对象存在于dcache中(非negative状态),则相应的inode就将总是存在,因为inode的引用计数i_count总是大于0。当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用。

函数得到当前文件或目录的inode值后,进入dcache查找对应的dentry,然后顺着父目录指针d_parent得到父目录的dentry,这样逐级向上直到dentry= root,就得到全部目录名称。

在内核代码中,这些信息是记录在"dentry_stat_t"结构体中的:
struct dentry_stat_t {
    long nr_dentry;
    long nr_unused;
    long age_limit;        /* age in seconds */
    long want_pages;    /* pages requested by system */
    long nr_negative;    /* # of unused negative dentries */
    long dummy;        /* Reserved for future use */
};


dentry内存持续增长问题分析

查看内存使用情况

发现slab 占用过高时的的查看及粗暴处理
1)cat /proc/meminfo
2)执行 slabtop 发现 dentry 占用过高
3)执行 cat /proc/sys/fs/dentry-state 查看状态
4)执行 echo 2 > /proc/sys/vm/drop_caches 内存得到释放。

## 执行命令
more /proc/meminfo

## 输出结果
## 服务器总内存
MemTotal:132030344 kB
## 服务器空闲内存
MemFree:1396884 kB
## Buffer使用的内存
Buffers:409812 kB
## Cache使用的内存
Cached:53136072 kB
## 使用Slab分配的内存。
Slab:16681824 kB
## 使用Slab分配且可回收内存。
SReclaimable:16592540 kB
## 使用Slab分配但不可以回收内存。
SUnreclaim:89284 kB

查看slab方式分配的内存

可以使用/proc/slabinfo文件来查看Slab分配的内存情况,如:

执行命令
head -n 19 /proc/slabinfo

输出结果
slabinfo - version: 2.1
# name     <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
fib6_nodes     19     59     64   59    1 : tunables  120   60    8 : slabdata      1      1      0
ip6_dst_cache  13     20    384   10    1 : tunables   54   27    8 : slabdata      2      2      0
ndisc_cache     0      0    256   15    1 : tunables  120   60    8 : slabdata      0      0      0
ip6_mrt_cache   0      0    128   30    1 : tunables  120   60    8 : slabdata      0      0      0
RAWv6   27     28   1088    7    2 : tunables   24   12    8 : slabdata      4      4      0
UDPLITEv60      0   1024    4    1 : tunables   54   27    8 : slabdata      0      0      0
UDPv6    2      8   1024    4    1 : tunables   54   27    8 : slabdata      2      2      0
tw_sock_TCPv6   0      0    320   12    1 : tunables   54   27    8 : slabdata      0      0      0

但上述结果需要自行计算各模块使用的内存情况,可以使用slabtop来查看主要的内存使用:

执行命令
slabtop --sort=c -o |head -n 19

输出结果
 Active / Total Objects (% used)    : 363001 / 393469 (92.3%)
 Active / Total Slabs (% used)      : 18261 / 18261 (100.0%)
 Active / Total Caches (% used)     : 92 / 128 (71.9%)
 Active / Total Size (% used)       : 124956.40K / 137786.40K (90.7%)
 Minimum / Average / Maximum Object : 0.01K / 0.35K / 23.19K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
121492 113273  93%    0.57K   8678       14     69424K radix_tree_node        
 18577  17956  96%    0.59K   1429       13     11432K inode_cache            
 11628   6278  53%    0.94K    684       17     10944K xfs_inode              
 37653  32097  85%    0.19K   1793       21      7172K dentry                 
  5304   5158  97%    0.66K    442       12      3536K proc_inode_cache       
 26220  26094  99%    0.13K    874       30      3496K kernfs_node_cache      
 14364  14234  99%    0.20K    756       19      3024K vm_area_struct         
   584    578  98%    4.00K     73        8      2336K kmalloc-4096           
   976    924  94%    2.00K     61       16      1952K kmalloc-2048           
   549    539  98%    3.44K     61        9      1952K task_struct            
  1744   1688  96%    1.00K    109       16      1744K kmalloc-1024           
  6000   5098  84%    0.25K    375       16      1500K filp

命令slabtop介绍

slabtop displays detailed  kernel  slab  cache  information in real time. It displays a listing of the top caches sorted by one of the listed sort criteria.  It also displays a statistics header filled with slab layer information.

该命令可实时显示内核slab缓存信息,linux系统通过/proc/slabinfo来向用户暴露slab的使用情况。在进程较多或内存较大的服务器如Redis服务器上要警慎运行slabtop命令。

查看dentry状态

dentry的使用状态可以使用/proc/sys/fs/dentry-state来获取:

执行命令
more /proc/sys/fs/dentry-state

输出信息
nr_dentry    nr_unused    age_limit    want_pages    nr_negative      dummy        
73563262    73554846  45               0           0      0

查看dentry增长情况

通过脚本来监控slab可以回收内存和dentry使用内存的增长情况:
while true;
do
    date_str=`date "+%Y-%m-%d %H:%M:%S"`;
    memory_info=`cat /proc/meminfo |grep SReclaimable`;
    dentry_info=`cat /proc/slabinfo  |grep dentry`;
    echo "${date_str}    ${memory_info}";
    echo "${date_str}    ${dentry_info}";
    sleep 1;
done

观察dentry内存回收

通过监控可发现dentry内存持续增长,可以通过监控dentry内存分配(d_alloc)和内存释放(d_free)来定位,创建dentry_chek.stp文件:

## dentry.stp
## 监控kernel上dentry内存分配和内存释放的进程信息
probe kernel.function("d_alloc"){
    printf("%s[%ld] %s %s\n", execname(), pid(), pp(), probefunc())
}
probe kernel.function("d_free"){
    printf("%s[%ld] %s %s\n", execname(), pid(), pp(), probefunc())
}
probe timer.s(5){
    exit()
}

执行脚本并分析结果:
执行脚本并输入到日志文件
stap dentry_chek.stp 1>/tmp/dentry_chek.log 2>&1

分析日志文件命令
cat /tmp/dentry_chek.log | awk '{print $1"-->"$3}' | sort  | uniq -c | sort  -k1 -n -r| head -n 50

分析日志文件输出结果
  15049 BackgrProcPool[13267]-->d_alloc
  14707 BackgrProcPool[13267]-->d_free
   8367 yum[8606]-->d_alloc
    265 agent_daemon.py[5830]-->d_free
    262 ZooKeeperRecv[13267]-->d_free
    262 ZooKeeperRecv[13267]-->d_alloc
    254 agent[7269]-->d_alloc
    166 clickhouse-serv[13267]-->d_alloc
    162 clickhouse-serv[13267]-->d_free
    131 ZooKeeperSend[13267]-->d_free
    131 ZooKeeperSend[13267]-->d_alloc
    122 agent_daemon.py[5830]-->d_alloc
    114 mysqld[2822]-->d_alloc
    113 mysqld[2822]-->d_free
    107 rdk:b/source[13267]-->d_free
    107 rdk:b/source[13267]-->d_alloc
    105 bash[27747]-->d_free
     89 netstat[8586]-->d_alloc
     62 TCPHandler[13267]-->d_free
     60 irqbalance[3611]-->d_free
     60 irqbalance[3611]-->d_alloc
     58 TCPHandler[13267]-->d_alloc
     56 sh[8578]-->d_free
     55 SystemLogFlush[13267]-->d_alloc
     50 bash[27747]-->d_alloc
     47 snmpd[3679]-->d_free
     47 snmpd[3679]-->d_alloc
     42 netstat[8586]-->d_free
     38 ParalInputsProc[13267]-->d_alloc
     36 ParalInputsProc[13267]-->d_free

从上面的日志分析输出结果可发现yum进程执行d_alloc的次数远大于执行d_free的次数。BackgrProcPool进程的d_alloc与d_free相差较大是由于脚本执行时间点导致。

安装stap工具
yum install systemtap

stap运行依赖的kernel-devel和kernel-debuginfo,推荐使用stap-prep来安装。如果通过yum来安装kernel-debuginfo,需要开启debug的yum源,并且严格匹配kernel版本,否则很容易报错或不兼容。

下载包:kernel-debuginfo-common-x86_64-xxx.rpm,kernel-debuginfo-xxx.rpm

centos 7的下载路径
http://debuginfo.centos.org/7/x86_64/

监控进程请求命令

对于周期性执行命令,可以通过watch来监控:
watch -n 1 -d 'ps -ef|grep yum'


参考来源:
一个较复杂dcache问题
Linux的VFS实现之dcache

---------------------------------------------------------------
题外:
字节跳动:利用 AI 自动调优 Linux 内核参数

在2023年11月举办的 Linux Plumbers Conference 上提出,字节跳动 Linux 内核工程师 Cong Wang 发表了一个 “Linux Kernel Autotuning” 的主题演讲,提议可以使用人工智能(AI)和机器学习(ML)来调整 Linux 内核,从而为特定工作负载带来效果最优化。

他指出现在的 Linux 内核为用户提供了数以千计的参数,因此想要通过调整参数以获得最佳性能已经变得越来越困难。大多数情况下,不同的工作负载需要对不同的 Linux 内核参数集进行不同的调整。而在像字节跳动这样的大型数据中心,要针对数百种不同的工作负载手动调整 Linux 内核参数几乎是不可能的。因此字节跳动提出了一个解决方案,尝试以最小的工程投入实现整个 Linux 内核参数调整过程的自动化。另外还注意到,内存管理是 Linux 内核子系统中对自动调优的需求较多的一个。借助贝叶斯优化等机器学习算法,我们相信自动调优甚至可以击败大多数 Linux 内核工程师。在本次演讲中将介绍 Linux 内核自动调整解决方案的工作原理以及其设计和架构的概述。我们还将研究 Linux 内核内存管理的一些特定案例,以展示我们的结果作为概念验证。

对于未来的工作,我们希望利用这个机会提出并讨论一个内核内机器学习框架,该框架可以进一步推动这个项目,在内核空间中完全优化 Linux 内核快速路径。Wang 打趣称,此举并不是为了 “让 Linux 内核工程师失业”,而是旨在将人类工程师从调整每个单独工作负载的性能的工作中解放出来,同时利用历史数据做出更好的决策,找到比目前使用试错和启发式方法得出的解决方案更好的解决方案。

自动调整系统旨在根据特定的工作负载和硬件配置,自动调整 Linux 内核的内部设置。这种动态调整可确保最佳性能,解决 Linux 社区长期以来面临的一个针对特定场景手动调整内核的难题:
动态优化:系统持续监控内核性能,对 CPU 频率缩放和内存管理等设置进行实时调整。
提高效率:通过优化资源使用,自动调整系统大大提高了 Linux 系统的效率,尤其是在不同工作负载的环境中。
用户友好界面:该系统包含一个用户友好界面,即使技术知识有限的用户也能从增强的内核性能中受益。
可定制的设置:高级用户可以自定义自动调整参数,根据其特定需求定制系统。

虽然目前还处于早期阶段,但字节方面表示已经取得了一些进展。例如通过将 DAMON(一个用于内存访问监控和优化的 Linux 内核子系统)与该框架结合使用,能够为 MySQL 应用程序找到最佳方案。为此他们运行了不同的 DAMON 方案并对其性能进行了比较,发现可以将应用程序的内存使用量减少 30%。在另一项案例中,该公司通过优化调整 16 个内核 sysctl 参数,优化了 NGINX 服务器上的 HTTP 网络延迟。在最佳情况下,与专家手动调整相比,ML 调整使 NGNIX 网络性能提升了 12%。

字节并未宣称其 AI/ML 方法适用于所有 Linux 调整工作,但工程师也表示:“虽然存在局限性,但我们相信内核机器学习不仅是可能的,而且是必要的。”

科技媒体 ZDNet 的编辑对字节此举表达了认可,并认为其可能改变 Linux 应用程序的游戏规则。“通过简化内核优化,将使 Linux 对更广泛的用户和应用程序来说更易用、更高效。特别是看到自动调整系统几乎可以提升所有服务器、云计算和数据中心应用的性能。”