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 启动内核),则会将以下输出转储到系统日志中
遇到的问题描述
这将是系统日志中以以下内容开头的消息
=============================================== 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 会设置该选项)
如果涉及到对象,则对象内容。
BUG SLUB 行之后可以有各种类型的行
- Bytes b4 <地址><字节>
显示在检测到问题的对象之前的一些字节。如果损坏不是从对象开头停止的,则此功能可能很有用。
- 对象 <地址><字节>
对象的字节。如果对象处于非活动状态,则这些字节通常包含毒值。任何非毒值都表示释放后写入的损坏。
- Redzone <地址><字节>
对象之后的红色区域。红色区域用于检测对象之后的写入。所有字节应始终具有相同的值。如果存在任何偏差,则表示对象边界之后有写入。
(仅当设置了 SLAB_RED_ZONE 时,红色区域信息才可用。slab_debug 会设置该选项)
- Padding <地址><字节>
未使用的数据,用于填充空间,以便正确对齐下一个对象。在调试情况下,我们要确保至少有 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
脚本绘制其输出图表。因此,它将分析从查看数字(大量的数字)转换为更容易的方式——可视化分析。
要生成绘图:
收集 slabinfo 的扩展记录,例如:
while [ 1 ]; do slabinfo -X >> FOO_STATS; sleep 1; done
将统计信息文件(-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 个图表:
收集所需的 STATS1、STATS2、.. STATSN 文件。
while [ 1 ]; do slabinfo -X >> STATS<X>; sleep 1; done
预处理这些 STATS 文件
slabinfo-gnuplot.sh STATS1 STATS2 .. STATSN
在“-t”模式下执行
slabinfo-gnuplot.sh
,传递所有生成的预处理的 *-totalsslabinfo-gnuplot.sh -t STATS1-totals STATS2-totals .. STATSN-totals
这将生成一个单独的绘图(png 文件)。
可以预料的是,绘图可能很大,因此一些波动或小的峰值可能会被忽略。 为了解决这个问题,
slabinfo-gnuplot.sh
有两个选项可以“放大”/“缩小”:-s %d,%d
-- 覆盖默认图像宽度和高度-r %d,%d
-- 指定要使用的样本范围(例如,在slabinfo -X >> FOO_STATS; sleep 1;
的情况下,使用-r 40,60
范围将仅绘制在第 40 秒到第 60 秒之间收集的样本)。
SLUB 的 DebugFS 文件¶
有关启用用户跟踪调试选项的 SLUB 缓存的当前状态的更多信息,可以使用 debugfs 文件,通常位于 /sys/kernel/debug/slab/<cache>/ 下(仅为启用了用户跟踪的缓存创建)。 有 2 种类型的文件,包含以下调试信息:
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
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 日