0%

Linux内核调试(三)VM参数调试

vm参数说明

Currently, these files are in /proc/sys/vm:

  • admin_reserve_kbytes:系统中应为用户保留的可用内存量

    默认(3% of free pages, 8MB)。这应该足以让admin登录并终止进程。这个值的设置,最好保证系统足够运行sshd or login + bash (or some other shell) + top (or ps, kill, etc.)

  • block_dump:当设置为非零值时,block_dump 启用块 I/O 调试

  • compact_memory:压缩内存

    仅当设置 CONFIG_COMPACTION 时可用。 当写入1时,所有区域都会被压缩,以便可以更好的提供连续内存。 这在分配大页面时可能很重要,尽管进程也会根据需要直接压缩内存。

  • compact_unevictable_allowed:压缩时检查lru

  • dirty_background_bytes:如果dirty页大于这个值,内核后台线程开始工作,将dirty内存写回到flash

  • dirty_background_ratio:dirty_background_bytes 是 dirty_background_ratio 的对应项, 一次只能指定其中之一。dirty_background_ratio是按照总可用内存的百分比(空闲页和可回收页的总可用内存的百分比)来设置的,总可用内存不等于总系统内存

  • dirty_bytes:系统dirty内存超过该值时,写文件的process 会开始回写dirty内存到flash。dirty_bytes允许的最小值是两页(以字节为单位); 任何低于此限制的值都将被忽略,旧的配置将被保留

  • dirty_ratio:dirty_bytes 是 dirty_ratio 的对应项,一次只能指定其中之一。按照空闲页和可回收页的总可用内存的百分比来设置

  • dirty_expire_centisecs:此参数定义dirty page多久必须刷入flash,他以百分之一秒来表示,如下所示表示30s,如果dirty page超过30s没有被写入flash,那么下次flusher thread被调度的时候该页将回写到flash

1
2
/proc/sys/vm # cat dirty_expire_centisecs
3000
  • dirtytime_expire_seconds:此可调参数用于定义dirty inode 何时足够dirty,超过这个值内核flusher thread进行写回
  • dirty_writeback_centisecs:是用来表示内核检查dirty page的运行间隔,单位是秒的百分之一,与 dirty_expire_centisecs 配合起来使用
1
2
/proc/sys/vm # cat dirty_writeback_centisecs
500
  • drop_caches:

    Writing to this will cause the kernel to drop clean caches, as well as reclaimable slab objects like dentries and inodes. Once dropped, their memory becomes free

    • To free pagecache::

    1
    echo 1 > /proc/sys/vm/drop_caches

    pagecache是已经和外部磁盘同步过的部分,既然已经同步过了,直接丢弃即可,下次要用再从磁盘上拷贝回来就可以了,如果要clean更多需要先执行sync

    • To free reclaimable slab objects (includes dentries and inodes)::

    1
    echo 2 > /proc/sys/vm/drop_caches

    slab objects包括includes dentries and inodes。对于磁盘文件系统,内存inode存在flash中,因此同page cache一样,较易在内存中重建,释放的代价较低。dentry虽然在磁盘上没有直接对应的结构,但也可根据文件系统中目录inode的信息进行重建

    • To free slab objects and pagecache::

    1
    echo 3 > /proc/sys/vm/drop_caches

    这是一个非破坏性操作,不会释放任何dirty objects。 要增加此操作释放的对象数量,用户可以在写入 /proc/sys/vm/drop_caches 之前运行“sync”。 这将最大限度地减少系统上脏对象的数量,并创建更多要删除的候选对象。

    该文件不是控制各种内核缓存(索引节点、目录项、页面缓存等)增长的手段。当系统上的其他地方需要内存时,这些对象会由内核自动回收。

    使用此文件可能会导致性能问题。 由于它会丢弃缓存的对象,因此可能会花费大量 I/O 和 CPU 来重新创建删除的对象,尤其是在大量使用它们的情况下。 因此,不建议在测试或调试环境之外使用。

    To disable them, echo 4 (bit 2) into drop_caches.

  • extfrag_threshold:该参数影响内核是否压缩内存或直接回收以满足高阶分配,该值设置得越小,越倾向于进行内存整理,默认值500

  • highmem_is_dirtyable:该参数控制是否考虑对高端内存进行dirty写入限制。默认为0,即在计算dirty_ratio和dirty_background_ratio的时候只考虑low mem。当打开之后才会将highmem也计算在内

  • hugetlb_shm_group:包含允许使用 hugetlb 页表创建 SysV共享内存段的组ID

  • laptop_mode:笔记本电脑模式

  • legacy_va_layout:

  • lowmem_reserve_ratio:lowmem_reserve_ratio是防止highmem内存在不充裕情况下,过度借用低端内存。lowmem_reserve_ratio决定了每个zone保留多少数目的页面。lowmem_reserve_ratio中定义了不同zone的预留比例,值越大保留比例越小。如,DMA为1/256,NORMAL为1/32。

    1
    2
    /proc/sys/vm # cat /proc/sys/vm/lowmem_reserve_ratio
    256 32 0

    • 第一个是 normal zone 的保护率,会保护该zone的 1/256 的内存
    • 第二个是 dma zone 或 dma32 zone的保护率, 1/32
    • 第三个是其他 0
  • max_map_count:包含限制一个进程可以拥有的VMA的数量,默认65530

  • memory_failure_early_kill:控制当发生内核无法处理的内存错误时,该如何终止进程

    • 1:一旦检测到损坏,杀死所有损坏的进程和不可重新加载页面的所属进程。请注意,某些类型的页面不支持此功能,例如内核内部分配的数据或交换缓存,但适用于大多数用户页面
    • 0:从所有进程中取消损坏页的映射,只杀死一个试图访问它的进程
  • memory_failure_recovery:启用内存故障恢复

    • 1: Attempt recovery
    • 0: Always panic on a memory failure
  • min_free_kbytes:最少保留的字节数,用于计算zone的低水位值

    这用于强制 Linux VM 保持最少的可用字节数。 VM 使用此数字来计算系统中每个 lowmem 区域的watermark [WMARK_MIN] 值。每个 lowmem 区域根据其大小按比例获取一些保留的空闲页面

    1
    2
    /proc/sys/vm # cat min_free_kbytes
    3987

    如果您将其设置为低于 1024KB,在高负载下容易出现死锁。设置得太高会导致机器容易 OOM。

  • min_slab_ratio:This is available only on NUMA kernels.

    每个区域中总页数的百分比,如果超过该百分比的page都是可回收的,则回收该zone,默认值5

  • min_unmapped_ratio:This is available only on NUMA kernels.

    仅当超过此百分比的页面处于 zone_reclaim_mode 允许回收的状态时,才会发生区域回收。默认值1

  • mmap_min_addr:控制着用户进程mmap能够映射的最低内存地址

  • mmap_rnd_bits:

    该值可用于选择用于确定 vma 区域基地址的随机偏移量的位数,这些偏移量是由支持调整地址空间随机化的架构上的 mmap 分配产生的。 该值将受到架构支持的最小和最大值的限制。

  • mmap_rnd_compat_bits

    该值可用于选择位数,用于确定 vma 区域基地址的随机偏移量,这些偏移量是在支持调整地址空间随机化的体系结构上以兼容模式运行的应用程序的 mmap 分配产生的。该值将受到架构支持的最小和最大值的限制

  • nr_hugepagesChange the minimum size of the hugepage pool.

  • nr_overcommit_hugepages 可以过量使用的大页,最大值=nr_hugepages + nr_overcommit_hugepages

  • nr_trim_pages:仅适用于 NOMMU内核

  • numa_zonelist_order:已废弃

  • oom_dump_tasks:允许在内核执行 OOM 终止时生成系统范围的Task转储(不包括内核线程),并包括 pid、uid、tgid、vm 大小、rss、pgtables_bytes、swapents、oom_score_adj 分数和名称等信息

  • oom_kill_allocating_task:

    • 非0: OOM 只会终止触发内存不足的任务。这避免了昂贵的任务列表扫描
    • 0:OOM 将扫描整个任务列表并根据heuristics kill task。这通常会选择一个流氓内存占用任务,该任务在被杀死时会释放大量内存
  • overcommit_kbytes:overcommit_kbytes 是 overcommit_ratio 的对应项,一次只能指定其中之一,这里是配置允许申请多少K超出的内存

    当 overcommit_memory 设置为 2 时,提交的地址空间不允许超过 swap 加上此物理 RAM 量

  • overcommit_memory:是否允许申请过度可用内存的内存

    • 当此标志为 0 时,内核会将用户空间内存请求大小与总内存加上交换分区大小进行比较,并拒绝明显的过度使用
    • 当此标志为 1 时,内核假装始终有足够的内存,直到实际耗尽为止
    • 当此标志为 2 时,内核使用“never overcommit”策略来尝试防止任何内存过度使用,请注意,user_reserve_kbytes 会影响此策略。
  • overcommit_ratio:当 overcommit_memory 设置为 2 时,提交的地址空间不允许超过 swap 加上物理 RAM 的此百分比

  • page-cluster:从swap单次读入的连续页(与 page cache readahead 互斥)

  • panic_on_oom:用或禁用内存不足功能的panic

    • 0,内核将杀死一些恶意进程,称为 oom_killer。通常,oom_killer 可以杀死恶意进程,系统将继续存在
    • 1,则当发生内存不足时内核会发生Panic。然而,如果某个进程通过mempolicy/cpusets限制使用节点,并且这些节点变成内存耗尽状态,则该进程可能会被oom-killer杀死。在这种情况下不会发生恐慌。因为其他节点的内存可能是空闲的。这意味着系统总体状态可能还不是致命的
    • 2,即使出现上述情况,内核也会强制发生Panic
  • percpu_pagelist_fraction:这是每个区域中可以存储到每个 cpu 页面列表中的页面比例。它是根据在线 CPU 数量划分的上限。最小值为 8,这意味着我们不允许每个区域中超过 1/8 的页面存储在每个 cpu 页面列表中

  • stat_interval:多少s更新一次vm信息,默认是1

  • stat_refresh:任何读写该文件,都会导致vm信息立刻更新

  • numa_stat:允许运行时配置 numa 统计信息

  • swappiness:io和文件cache的权重,范围在0-200,当=100时表示等额,值越小表示IO代价越昂贵,内核更倾向于文件cache,默认值60

  • unprivileged_userfaultfd :控制用户是否可以使用 userfaultfd 系统调用。将其设置为 1 以允许非特权用户不受任何限制地使用 userfaultfd 系统调用。将其设置为 0 以限制非特权用户仅在用户模式下处理页面错误

  • user_reserve_kbytes:overcommit_memory = 2的时,用户进程预留多少空闲内存。默认3%。如果将其减少到零,则用户将被允许使用单个进程分配所有可用内存,减去 admin_reserve_kbytes。任何后续尝试执行命令都将导致“fork: Cannot allocate memory”

  • vfs_cache_pressure:控制内核回收cache和inode的趋势,默认是100,内核以公平的速率回收cache和inode;=0,即内核从不回收cache和inode;>100,内核更倾向回收cache和inode

  • watermark_boost_factor:控制内存碎片时的回收级别,分母是10000,默认值是15000,即回收150%的高水位线的页

  • watermark_scale_factor:swap线程被唤醒后要释放多少内存,分母是10000,最小值是10,最大值是3000。即每次被唤醒都会释放系统可用内存的 0.1%-30%

  • zone_reclaim_mode:zone的会回收模式,默认关闭

    • 0:不在zone回收,从其他zone回收内存
    • 1:打开zone回收
    • 2:将脏页换出
    • 4:回收交换页

用户进程因为回写数据导致运行卡顿

我们知道刷dirty数据有两种方式,一种是linux内核线程异步刷dirty数据,另一种是由写数据的线程自己刷(用户线程自己刷dirty数据的时候就干不了其他事情导致线程卡顿)。对于video或者audio应用都会比较常遇到这个问题,这里简单起见,以一个audio例子来说明:

对于48k、8channel、32bit的采样参数来说明,则每秒采样的数据量为4800084=1536000/1024/1024=1.46M

由于数据量是可以精确计算的,这里主要调节dirty_background_bytes、dirty_bytes和dirty_expire_centisecs这三个参数。

调参数原则如下:

  • 尽可能的触发dirty_background_bytes阈值而不触发dirty_bytes
  • 尽可能的利用空闲内存做缓存,避免频繁刷dirty数据

在以上两个原则下我们可以做如下调整:

查看当前dirty相关参数

1
2
3
4
5
6
7
8
9
/ # sysctl -a | grep "dirty"
kernel.osrelease = 5.4.242-09263-g35630c03e7d6-dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
vm.dirtytime_expire_seconds = 43200
  • dirty_expire_centisecs默认值为3000,是按照百分之一秒来表示的,也就是30s,代表30s会检查一次看dirty数据是否超过相关阈值,如果超过则执行刷dirty数据操作,这个值不要设置太小,可以先保持不变
  • dirty_background_bytes:代表dirty数据超过这个值就会唤醒内核线程回写dirty page,30s内会生成1536000*30=46080000字节数据,那么保险起见dirty_background_bytes可以小于46080000 1-2M
  • dirty_bytes:代表dirty数据超过这个值,用户线程将停下正在干的事情去回写dirty page,这样就导致用户线程卡顿,那么解决办法就是不要让用户线程主动回写dirty数据,该值应该设置为大于46080000,这样永远先触发内核线程刷dirty数据,用户线程就不需要主动回写dirty数据了
  • 当然如果内存紧张,这个时候要考虑降低dirty_expire_centisecs了,提高内核回写线程运行频率

为什么drop_caches后cache值并未减少

因为这种方式只能清空page cache中"clean"的部分,也就是已经和外部磁盘同步过的部分。而针对匿名页,内存没有与之对应的文件,如果想要回收这部分内存就需要添加swap分区,如果系统中有大量的使用匿名页的情况想要回收更多的内存应该添加swap分区。

参考文献

《Linux Document》