SLUB 简短用户指南

SLUB 的基本理念与 SLAB 非常不同。SLAB 需要重新构建内核才能激活所有 slab 缓存的调试选项。SLUB 始终包含完整的调试,但默认情况下是关闭的。SLUB 可以仅为选定的 slab 启用调试,以避免影响整体系统性能,这可能会使错误更难以找到。

为了打开调试,可以将选项 slab_debug 添加到内核命令行。这将为所有 slab 启用完整调试。

通常,然后会使用 slabinfo 命令来获取统计数据并对 slab 执行操作。默认情况下,slabinfo 仅列出包含数据的 slab。 运行该命令时,请参阅“slabinfo -h”以获取更多选项。slabinfo 可以编译为

gcc -o slabinfo tools/mm/slabinfo.c

slabinfo 的某些操作模式要求在命令行上启用 slub 调试。例如,如果没有打开调试,将没有跟踪信息可用,如果未打开调试,则只能部分执行验证。

slab_debug 的更多复杂用法:

可以向 slab_debug 提供参数。 如果未指定任何参数,则启用完整调试。 格式

slab_debug=<调试选项>

为所有 slab 启用选项

slab_debug=<调试选项>,<slab 名称 1>,<slab 名称 2>,...

仅为选定的 slab 启用选项(逗号后没有空格)

可以为所有 slab 或选定的 slab 提供多个选项块,选项块之间用“;”分隔。 “所有 slab”块的最后一个应用于除匹配“选定的 slab”块之一的 slab 之外的所有 slab。 应用与 slab 名称匹配的第一个“选定的 slab”块的选项。

可能的调试选项包括

F               Sanity checks on (enables SLAB_DEBUG_CONSISTENCY_CHECKS
                Sorry SLAB legacy issues)
Z               Red zoning
P               Poisoning (object and padding)
U               User tracking (free and alloc)
T               Trace (please only use on single slabs)
A               Enable failslab filter mark for the cache
O               Switch debugging off for caches that would have
                caused higher minimum slab orders
-               Switch all debugging off (useful if the kernel is
                configured with CONFIG_SLUB_DEBUG_ON)

例如,为了仅使用完整性检查和红色区域启动,可以指定

slab_debug=FZ

尝试查找 dentry 缓存中的问题?尝试

slab_debug=,dentry

仅在 dentry 缓存上启用调试。您可以在 slab 名称的末尾使用星号,以覆盖所有具有相同前缀的 slab。例如,以下是如何毒化 dentry 缓存以及所有 kmalloc slab 的方法

slab_debug=P,kmalloc-*,dentry

红色区域和跟踪可能会重新对齐 slab。我们可以仅对 dentry 缓存应用完整性检查

slab_debug=F,dentry

调试选项可能需要增加最小可能的 slab 顺序,以存储元数据(例如,具有 PAGE_SIZE 对象大小的缓存)。这更有可能导致低内存情况或内存高度碎片化时出现 slab 分配错误。 要默认关闭此类缓存的调试,请使用

slab_debug=O

您可以使用选项块将不同的选项应用于不同的 slab 名称列表。 这将为 dentry 启用红色区域,并为 kmalloc 启用用户跟踪。 所有其他 slab 将不会启用任何调试

slab_debug=Z,dentry;U,kmalloc-*

您还可以为所有缓存启用选项(例如,完整性检查和毒化),但排除一些被认为对性能至关重要且不需要调试的缓存,方法是指定全局调试选项,然后是带有“-”作为选项的 slab 名称列表

slab_debug=FZ;-,zs_handle,zspage

可以在以下位置的相应文件中找到 slab 的每个调试选项的状态

/sys/kernel/slab/<slab name>/

如果文件包含 1,则启用该选项,0 表示禁用。 slab_debug 参数中的调试选项转换为以下文件

F       sanity_checks
Z       red_zone
P       poison
U       store_user
T       trace
A       failslab

failslab 文件是可写的,因此写入 1 或 0 将在运行时启用或禁用该选项。 如果缓存是别名,则写入返回 -EINVAL。 小心跟踪:如果在错误的 slab 上使用它,它可能会喷出大量信息并且永远不会停止。

Slab 合并

如果未指定任何调试选项,则 SLUB 可能会将相似的 slab 合并在一起,以减少开销并增加对象的缓存热度。 slabinfo -a 显示哪些 slab 已合并在一起。

Slab 验证

如果内核使用 slab_debug 启动,则 SLUB 可以验证所有对象。 为了做到这一点,您必须拥有 slabinfo 工具。 然后你可以这样做

slabinfo -v

这将测试所有对象。 输出将生成到系统日志。

如果在没有 slab 调试的情况下启动,这也可以在更有限的方式下工作。 在这种情况下,slabinfo -v 仅测试所有可访问的对象。 通常,这些对象位于 cpu slab 和部分 slab 中。 在非调试情况下,SLUB 不会跟踪完整的 slab。

获得更多性能

在某种程度上,SLUB 的性能受到需要不时获取 list_lock 来处理部分 slab 的限制。 该开销由每个 slab 的分配顺序控制。 分配可以受到内核参数的影响

slab_min_objects

允许指定必须至少有多少对象适合一个 slab,分配顺序才能被接受。 通常,slub 将能够在 slab 上执行此数量的分配,而无需咨询集中式资源 (list_lock),在这些资源中可能会发生争用。

slab_min_order

指定 slab 的最小顺序。 与 slab_min_objects 类似的效果。

slab_max_order

指定 slab_min_objects 不应再检查的顺序。 这对于避免 SLUB 尝试生成超大型页面以将 slab_min_objects 的具有大对象大小的 slab 缓存放入一个高阶页面中非常有用。 设置命令行参数 debug_guardpage_minorder=N (N > 0) 会强制将 slab_max_order 设置为 0,这将导致最小可能的 slab 分配顺序。

slab_strict_numa

在每次分配时启用内存策略的应用。 这可以更准确地放置对象,这可能会减少对远程节点的访问。 默认是在获取新页面或从列表中检索页面时,仅在页面级别应用内存策略。 启用此选项会降低 slab 分配器的快速路径性能。

SLUB 调试输出

这是一个 slub 调试输出的示例

====================================================================
BUG kmalloc-8: Right Redzone overwritten
--------------------------------------------------------------------

INFO: 0xc90f6d28-0xc90f6d2b. First byte 0x00 instead of 0xcc
INFO: Slab 0xc528c530 flags=0x400000c3 inuse=61 fp=0xc90f6d58
INFO: Object 0xc90f6d20 @offset=3360 fp=0xc90f6d58
INFO: Allocated in get_modalias+0x61/0xf5 age=53 cpu=1 pid=554

Bytes b4 (0xc90f6d10): 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a ........ZZZZZZZZ
Object   (0xc90f6d20): 31 30 31 39 2e 30 30 35                         1019.005
Redzone  (0xc90f6d28): 00 cc cc cc                                     .
Padding  (0xc90f6d50): 5a 5a 5a 5a 5a 5a 5a 5a                         ZZZZZZZZ

  [<c010523d>] dump_trace+0x63/0x1eb
  [<c01053df>] show_trace_log_lvl+0x1a/0x2f
  [<c010601d>] show_trace+0x12/0x14
  [<c0106035>] dump_stack+0x16/0x18
  [<c017e0fa>] object_err+0x143/0x14b
  [<c017e2cc>] check_object+0x66/0x234
  [<c017eb43>] __slab_free+0x239/0x384
  [<c017f446>] kfree+0xa6/0xc6
  [<c02e2335>] get_modalias+0xb9/0xf5
  [<c02e23b7>] dmi_dev_uevent+0x27/0x3c
  [<c027866a>] dev_uevent+0x1ad/0x1da
  [<c0205024>] kobject_uevent_env+0x20a/0x45b
  [<c020527f>] kobject_uevent+0xa/0xf
  [<c02779f1>] store_uevent+0x4f/0x58
  [<c027758e>] dev_attr_store+0x29/0x2f
  [<c01bec4f>] sysfs_write_file+0x16e/0x19c
  [<c0183ba7>] vfs_write+0xd1/0x15a
  [<c01841d7>] sys_write+0x3d/0x72
  [<c0104112>] sysenter_past_esp+0x5f/0x99
  [<b7f7b410>] 0xb7f7b410
  =======================

FIX kmalloc-8: Restoring Redzone 0xc90f6d28-0xc90f6d2b=0xcc

如果 SLUB 遇到损坏的对象(完整检测需要使用 slab_debug 启动内核),则以下输出将被转储到系统日志中

  1. 遇到的问题的描述

    这将是系统日志中的一条消息,以

    ===============================================
    BUG <slab cache affected>: <What went wrong>
    -----------------------------------------------
    
    INFO: <corruption start>-<corruption_end> <more info>
    INFO: Slab <address> <slab information>
    INFO: Object <address> <object information>
    INFO: Allocated in <kernel function> age=<jiffies since alloc> cpu=<allocated by
       cpu> pid=<pid of the process>
    INFO: Freed in <kernel function> age=<jiffies since free> cpu=<freed by cpu>
       pid=<pid of the process>
    

    开头(仅当为 slab 设置了 SLAB_STORE_USER 时,对象分配/释放信息才可用。slab_debug 设置该选项)

  2. 如果涉及对象,则对象的内容。

    各种类型的行可以跟随 BUG SLUB 行

    Bytes b4 <地址><字节>

    显示在检测到问题之前对象的前几个字节。 如果损坏没有停止于对象的开头,则可能很有用。

    Object <地址><字节>

    对象的字节。 如果对象不活动,则字节通常包含毒化值。 任何非毒化值都显示在释放后被写入损坏。

    Redzone <地址><字节>

    对象之后的红色区域。 红色区域用于检测对象之后的写入。 所有字节应始终具有相同的值。 如果有任何偏差,则这是由于在对象边界之后写入造成的。

    (仅当设置了 SLAB_RED_ZONE 时,红色区域信息才可用。slab_debug 设置该选项)

    Padding <地址><字节>

    未使用的数据,用于填充空间,以便正确对齐下一个对象。 在调试情况下,我们确保至少有 4 个字节的填充。 这允许检测对象之前的写入。

  3. 堆栈转储

    堆栈转储描述了检测到错误的位置。 通过查看分配或释放该对象的函数,可能更容易找到损坏的原因。

  4. 报告如何处理该问题以确保系统的持续运行。

    这些是系统日志中的消息,以

    FIX <slab cache affected>: <corrective action taken>
    

    开头在上面的示例中,SLUB 发现活动对象的红色区域已被覆盖。 在这里,一串 8 个字符被写入长度为 8 个字符的 slab 中。 但是,8 个字符的字符串需要一个终止符 0。该零已覆盖红色区域字段的第一个字节。 在报告遇到的问题的详细信息后,FIX SLUB 消息告诉我们,SLUB 已将红色区域恢复为其正确值,然后系统操作继续。

紧急操作

可以通过使用以下方式启动来启用最小调试(仅完整性检查)

slab_debug=F

通常,这足以启用 slub 的弹性功能,即使错误的内核组件会继续损坏对象,也能使系统保持运行。 这对于生产系统可能很重要。 完整性检查会影响性能,并且系统日志中会持续显示错误消息,但不会使用额外的内存(与完整调试不同)。

不提供保证。 仍然需要修复内核组件。 通过找到经历损坏的 slab 并仅为该缓存启用调试,可以进一步优化性能

slab_debug=F,dentry

如果损坏是通过在对象末尾之后写入发生的,那么建议启用红色区域,以避免损坏其他对象的开头

slab_debug=FZ,dentry

扩展的 slabinfo 模式和绘图

slabinfo 工具具有特殊的“扩展”('-X') 模式,其中包括
  • Slabcache 总计

  • 按大小排序的 Slab(最多 -N <数量> Slab,默认为 1)

  • 按损失排序的 Slab(最多 -N <数量> Slab,默认为 1)

此外,在此模式下,slabinfo 不会动态缩放大小 (G/M/K) 并且以字节为单位报告所有内容(此功能也可通过 '-B' 选项用于其他 slabinfo 模式),这使得报告更加精确和准确。 此外,在某种意义上,-X’ 模式还简化了对 slab 行为的分析,因为可以使用 ``slabinfo-gnuplot.sh` 脚本绘制其输出。 因此,它将分析从查看数字(大量数字)转移到更简单的内容 - 可视化分析。

生成图表

  1. 收集 slabinfo 扩展记录,例如

    while [ 1 ]; do slabinfo -X >> FOO_STATS; sleep 1; done
    
  2. 将统计文件 (-s) 传递给 slabinfo-gnuplot.sh 脚本

    slabinfo-gnuplot.sh FOO_STATS [FOO_STATS2 .. FOO_STATSN]
    

    slabinfo-gnuplot.sh 脚本将预处理收集的记录,并为每个 STATS 文件生成 3 个 png 文件(和 3 个预处理缓存文件): - Slabcache 总计:FOO_STATS-totals.png - 按大小排序的 Slab:FOO_STATS-slabs-by-size.png - 按损失排序的 Slab:FOO_STATS-slabs-by-loss.png

另一个用例是,当您需要比较“之前”和“之后”某些代码修改的 slab 行为时,slabinfo-gnuplot.sh 会很有用。 为了帮助您,slabinfo-gnuplot.sh 脚本可以“合并”来自不同测量的 Slabcache 总计 部分。 要直观地比较 N 个图

  1. 收集您需要的 STATS1、STATS2、.. STATSN 文件

    while [ 1 ]; do slabinfo -X >> STATS<X>; sleep 1; done
    
  2. 预处理这些 STATS 文件

    slabinfo-gnuplot.sh STATS1 STATS2 .. STATSN
    
  3. 在‘-t’模式下执行 slabinfo-gnuplot.sh,传递所有生成的预处理的 *-totals

    slabinfo-gnuplot.sh -t STATS1-totals STATS2-totals .. STATSN-totals
    

    这将生成一个图(png 文件)。

    可以预期的是,图会很大,因此一些波动或小尖峰可能会被忽略。 为了处理这个问题,slabinfo-gnuplot.sh 有两个选项可以“放大”/“缩小”

    1. -s %d,%d -- 覆盖默认的图像宽度和高度

    2. -r %d,%d -- 指定要使用的样本范围(例如,在 slabinfo -X >> FOO_STATS; sleep 1; 的情况下,使用 -r 40,60 范围将仅绘制在第 40 秒和第 60 秒之间收集的样本)。

SLUB 的 DebugFS 文件

有关启用了用户跟踪调试选项的 SLUB 缓存的当前状态的更多信息,debugfs 文件通常在 /sys/kernel/debug/slab/<cache>/ 下可用(仅为启用了用户跟踪的缓存创建)。 有 2 种类型的这些文件,具有以下调试信息

  1. alloc_traces

    Prints information about unique allocation traces of the currently
    allocated objects. The output is sorted by frequency of each trace.
    
    Information in the output:
    Number of objects, allocating function, possible memory wastage of
    kmalloc objects(total/per-object), minimal/average/maximal jiffies
    since alloc, pid range of the allocating processes, cpu mask of
    allocating cpus, numa node mask of origins of memory, and stack trace.
    
    Example:::
    
    338 pci_alloc_dev+0x2c/0xa0 waste=521872/1544 age=290837/291891/293509 pid=1 cpus=106 nodes=0-1
        __kmem_cache_alloc_node+0x11f/0x4e0
        kmalloc_trace+0x26/0xa0
        pci_alloc_dev+0x2c/0xa0
        pci_scan_single_device+0xd2/0x150
        pci_scan_slot+0xf7/0x2d0
        pci_scan_child_bus_extend+0x4e/0x360
        acpi_pci_root_create+0x32e/0x3b0
        pci_acpi_scan_root+0x2b9/0x2d0
        acpi_pci_root_add.cold.11+0x110/0xb0a
        acpi_bus_attach+0x262/0x3f0
        device_for_each_child+0xb7/0x110
        acpi_dev_for_each_child+0x77/0xa0
        acpi_bus_attach+0x108/0x3f0
        device_for_each_child+0xb7/0x110
        acpi_dev_for_each_child+0x77/0xa0
        acpi_bus_attach+0x108/0x3f0
    
  2. free_traces

    Prints information about unique freeing traces of the currently allocated
    objects. The freeing traces thus come from the previous life-cycle of the
    objects and are reported as not available for objects allocated for the first
    time. The output is sorted by frequency of each trace.
    
    Information in the output:
    Number of objects, freeing function, minimal/average/maximal jiffies since free,
    pid range of the freeing processes, cpu mask of freeing cpus, and stack trace.
    
    Example:::
    
    1980 <not-available> age=4294912290 pid=0 cpus=0
    51 acpi_ut_update_ref_count+0x6a6/0x782 age=236886/237027/237772 pid=1 cpus=1
        kfree+0x2db/0x420
        acpi_ut_update_ref_count+0x6a6/0x782
        acpi_ut_update_object_reference+0x1ad/0x234
        acpi_ut_remove_reference+0x7d/0x84
        acpi_rs_get_prt_method_data+0x97/0xd6
        acpi_get_irq_routing_table+0x82/0xc4
        acpi_pci_irq_find_prt_entry+0x8e/0x2e0
        acpi_pci_irq_lookup+0x3a/0x1e0
        acpi_pci_irq_enable+0x77/0x240
        pcibios_enable_device+0x39/0x40
        do_pci_enable_device.part.0+0x5d/0xe0
        pci_enable_device_flags+0xfc/0x120
        pci_enable_device+0x13/0x20
        virtio_pci_probe+0x9e/0x170
        local_pci_probe+0x48/0x80
        pci_device_probe+0x105/0x1c0
    

Christoph Lameter,2007 年 5 月 30 日 Sergey Senozhatsky,2015 年 10 月 23 日