内存分配分析

所有内存分配的低开销(适用于生产环境)的统计,按文件和行号跟踪。

用法:kconfig 选项:- CONFIG_MEM_ALLOC_PROFILING

  • CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT

  • CONFIG_MEM_ALLOC_PROFILING_DEBUG 为因缺少注释而未被统计的分配添加警告

启动参数

sysctl.vm.mem_profiling={0|1|never}[,compressed]

当设置为“never”时,内存分配分析的开销将被最小化,并且无法在运行时启用(sysctl 变为只读)。 当 CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=y 时,默认值为“1”。当 CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=n 时,默认值为“never”。 可选参数“compressed”将尝试以紧凑格式存储页面标签引用,从而避免页面扩展。 这将提高性能和内存消耗,但可能会因系统配置而失败。 如果压缩失败,将发出警告,并且内存分配分析将被禁用。

sysctl

/proc/sys/vm/mem_profiling

运行时信息

/proc/allocinfo

示例输出

root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec
      2.8M    22648 fs/kernfs/dir.c:615 func:__kernfs_new_node
      3.8M      953 mm/memory.c:4214 func:alloc_anon_folio
      4.0M     1010 drivers/staging/ctagmod/ctagmod.c:20 [ctagmod] func:ctagmod_start
      4.1M        4 net/netfilter/nf_conntrack_core.c:2567 func:nf_ct_alloc_hashtable
      6.0M     1532 mm/filemap.c:1919 func:__filemap_get_folio
      8.8M     2785 kernel/fork.c:307 func:alloc_thread_stack_node
       13M      234 block/blk-mq.c:3421 func:blk_mq_alloc_rqs
       14M     3520 mm/mm_init.c:2530 func:alloc_large_system_hash
       15M     3656 mm/readahead.c:247 func:page_cache_ra_unbounded
       55M     4887 mm/slub.c:2259 func:alloc_slab_page
      122M    31168 mm/page_ext.c:270 func:alloc_page_ext

工作原理

内存分配分析建立在代码标记的基础上,代码标记是一个用于声明静态结构体(通常以某种方式描述文件和行号,因此称为代码标记)的库,然后在运行时查找和操作它们,- 例如,迭代它们以在 debugfs/procfs 中打印它们。

为了添加分配调用的统计,我们将其替换为宏调用 alloc_hooks(),该宏调用:- 声明一个代码标记 - 在 task_struct 中存储一个指向它的指针 - 调用真正的分配函数 - 最后,将 task_struct 分配标记指针恢复为其先前的值。

这允许嵌套 alloc_hooks() 调用,最近的调用生效。这对于 mm/ 代码内部的分配很重要,这些分配不恰当地属于外部分配上下文,应该单独计算:例如,slab 对象扩展向量,或者 slab 从页面分配器分配页面时。

因此,正确的使用需要确定在分配调用堆栈中应该标记哪个函数。 有许多辅助函数本质上包装了例如 kmalloc() 并做更多的工作,然后在多个地方调用;我们通常希望统计发生在这些辅助函数的调用者中,而不是在辅助函数本身中。

要修复给定的辅助函数,例如 foo(),请执行以下操作:- 将其分配调用切换到 _noprof() 版本,例如 kmalloc_noprof()

  • 将其重命名为 foo_noprof()

  • 像这样定义 foo() 的宏版本

    #define foo(...) alloc_hooks(foo_noprof(__VA_ARGS__))

也可以在您自己的数据结构中存储指向分配标记的指针。

当您正在实现一个“代表”其他代码进行分配的通用数据结构时,请执行此操作 - 例如,rhashtable 代码。 这样,我们就可以按 rhashtable 类型将其分解,而不是在 /proc/allocinfo 中看到 rhashtable.c 的一大行。

要执行此操作:- 像其他任何分配函数一样,挂钩数据结构的 init 函数。

  • 在 init 函数中,使用便捷宏 alloc_tag_record() 在数据结构中记录分配标记。

  • 然后,对您的分配使用以下形式:alloc_hooks_tag(ht->your_saved_tag, kmalloc_noprof(...))