Kernel 内存杂项

2015-02-21 kernel linux

简单介绍下内核中与内存相关的内容。

OverCommit

内核中内存的 OverCommit 就是操作系统承诺给所有进程的内存大小超过了实际可用的内存,主要是由于物理内存页的分配发生在使用时,而非申请时,也就是所谓的 COW 机制。

例如,程序通过 malloc() 申请了 200MB 内存,不过实际只使用了 100MB 的内存,也就是说,只要你来申请内存我就给你,寄希望于进程实际上用不到那么多内存。

如果申请了内存之后,确实会有大量进程使用内存,那么就会发生类似银行的 “挤兑”,此时就会使用 Linux 的 OOM killer 机制。

其行为可以通过 vm.overcommit_memory 参数修改,包括了三种取值:

  • 0 (Heuristic Overcommit Handling),默认值
    允许 OC ,不过如果申请的内存过大,那么系统会拒绝请求,而判断的算法在后面介绍。
  • 1 (Always Overcommit)
    允许 OC,对内存申请来者不拒。
  • 2 (Don’t Overcommit)
    禁止 OC 。

当上述的值为 2 时,会禁止 OC,那么怎么才算是 OC 呢?实际上,Kernel 设有一个阈值,申请的内存总数超过这个阈值就算 OC,阀值可以通过 /proc/meminfo 文件查看:

# grep -i commit /proc/meminfo
CommitLimit:    12162784 kB
Committed_AS:    6810268 kB

其中 CommitLimit 就是对应的阈值,超过了该值就算 Overcommit ,该值通过 vm.overcommit_ratiovm.overcommit_kbytes 间接设置的,公式如下:

CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap

其中 vm.overcommit_ratio 的默认值是 50,也就是表示物理内存的 50% ,当然,也可以直接通过 vm.overcommit_kbytes 即可。注意,如果 huge pages 那么就需要从物理内存中减去,公式变成:

CommitLimit = ([total RAM] – [total huge TLB RAM]) * vm.overcommit_ratio / 100 + swap

另外,/proc/meminfo 中的 Committed_AS 表示所有进程已经申请的内存总大小;注意,是已经申请的,不是已经分配的。如果 Committed_AS 超过 CommitLimit 就表示发生了 OC 。

Dentry Cache

free 命令主要显示的用户的内存使用 (新版available可用),包括使用 top 命令 (可以通过 Shift+M 内存排序),对于 slab 实际上没有统计 (可以使用 slabtop 查看),真实环境中,经常会出现 dentry(slab) 的内存消耗过大的情况。

dentry cache 是目录项高速缓存,记录了目录项到 inode 的映射关系,用于提高目录项对象的处理效率,当通过 stat() 查看文件信息、open() 打开文件时,都会创建 dentry cache 。

----- 查看当前进程的系统调用
# strace -f -e trace=open,stat,close,unlink -p $(ps aux | grep 'cmd' | awk '{print $2}')

不过这里有个比较奇怪的现象,从 /proc/meminfo 可以看出,slab 中的内存分为 SReclaimable 和 SUnreclaim 两部分;而占比比较大的是 SReclaimable 的内存,也就是可以回收的内存,但是,当通过 slabtop 命令查看 dentry 对应的使用率时,基本处于 100% 的使用状态。

那么,到底是什么意思?这部分内存能不能回收?

首先,/proc/meminfo 对应到源码中为 fs/proc/meminfo.c,在内核中与这两个状态相关的代码在 mm/slub.c 中 (如果是新版本的内核);简单来说就是对应到了 allocate_slab()__free_slab() 函数中,实际上源码中直接搜索 NR_SLAB_RECLAIMABLE 即可。

对应到源码码中,实际只要申请 cache 时使用了 SLAB_RECLAIM_ACCOUNT 标示,那么在进行统计时,就会被标记为 SReclaimable 。

也就是说 meminfo 中的 SReclaimable 标识的是可以回收的 cache,但是真正的回收操作是由各个申请模块控制的,对于 dentry 来说,真正的回收操作还是 dentry 模块自己负责。

解决方法

对于上述的 slab 内存占用过多的场景,在 Linux 2.6.16 之后提供了 drop caches 机制,用于释放 page cache、inode/dentry caches ,方法如下。

----- 刷新文件系统缓存
# sync
----- 释放Page Cache
# echo 1 > /proc/sys/vm/drop_caches
----- 释放dentries+inodes
# echo 2 > /proc/sys/vm/drop_caches
----- 释放pagecache, dentries, inodes
# echo 3 > /proc/sys/vm/drop_caches
----- 如果没有root权限,但是有sudo权限
$ sudo sysctl -w vm.drop_caches=3

注意,修改 drop_caches 的操作是无损的,不会释放脏页,所以需要在执行前执行 sync 以确保确实有可以释放的内存。

另外,除了上述的手动处理方式之外,还可以通过修改 /proc/sys/vm/vfs_cache_pressure 的参数,来调整自动清理 inode/dentry caches 的优先级,默认为 100 此时需要调大;关于该参数可以参考内核文档 vm.txt,简单摘抄如下:

This percentage value controls the tendency of the kernel to reclaim
the memory which is used for caching of directory and inode objects.

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. When vfs_cache_pressure=0, the kernel will
never reclaim dentries and inodes due to memory pressure and this can easily
lead to out-of-memory conditions. Increasing vfs_cache_pressure beyond 100
causes the kernel to prefer to reclaim dentries and inodes.

Increasing vfs_cache_pressure significantly beyond 100 may have negative
performance impact. Reclaim code needs to take various locks to find freeable
directory and inode objects. With vfs_cache_pressure=1000, it will look for
ten times more freeable objects than there are.

也就是说,当该文件对应的值越大时,系统的回收操作的优先级也就越高,不过内核对该值好像不敏感,一般会设置为 10000 。

接下来,看看如何保留部分内存。

min_free_kbytes

该参数在内核文档 vm.txt,简单摘抄如下:

This is used to force the Linux VM to keep a minimum number
of kilobytes free.  The VM uses this number to compute a
watermark[WMARK_MIN] value for each lowmem zone in the system.
Each lowmem zone gets a number of reserved free pages based
proportionally on its size.

Some minimal amount of memory is needed to satisfy PF_MEMALLOC
allocations; if you set this to lower than 1024KB, your system will
become subtly broken, and prone to deadlock under high loads.

Setting this too high will OOM your machine instantly.

该参数值在 init_per_zone_wmark_min() 函数中设置,

该参数的主要用途是计算影响内存回收的三个参数 watermark[min/low/high],每个 Zone 都保存了各自的参数值,不同的范围操作如下。

< watermark[low]
  触发内核线程kswapd回收内存,直到该Zone的空闲内存数达到watermark[high];
< watermark[min]
  进行direct reclaim (直接回收),即直接在应用程序的进程上下文中进行回收,再用
  回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟,
  而且可能会触发系统OOM。

实际上,可以认为 watermark[min] 以下的内存属于系统的自留内存,用以满足特殊使用,所以不会给用户态的普通申请来用。

参考

关于内存的 OverCommit 可以参考内核文档 [vm/overcommit-accounting]( {{ site.kernel_docs_url }}/Documentation/vm/overcommit-accounting ) 。