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 缓存的 slab_min_objects 拟合到一个高阶页面中非常有用。设置命令行参数 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 <地址><字节>

    显示在检测到问题的对象之前的一些字节。如果损坏不是从对象开头停止的,则此功能可能很有用。

    对象 <地址><字节>

    对象的字节。如果对象处于非活动状态,则这些字节通常包含毒值。任何非毒值都表示释放后写入的损坏。

    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

如果损坏发生在对象末尾之后写入时,那么建议启用 Redzone,以避免破坏其他对象的开头。

slab_debug=FZ,dentry

扩展的 slabinfo 模式和绘图

slabinfo 工具具有一个特殊的“扩展”模式(“-X”),其中包括:
  • Slab 缓存总计

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

  • 按损耗排序的 Slab(最多 -N <num> 个 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 个预处理缓存文件): - Slab 缓存总计:FOO_STATS-totals.png - 按大小排序的 Slab:FOO_STATS-slabs-by-size.png - 按损耗排序的 Slab:FOO_STATS-slabs-by-loss.png

另一个可以使用 slabinfo-gnuplot.sh 的用例是,当您需要比较某些代码修改“之前”和“之后”的 slab 行为时。 为了帮助您解决这个问题,slabinfo-gnuplot.sh 脚本可以“合并”来自不同测量的 Slab 缓存总计 部分。 要可视化比较 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 日