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 启动内核),则以下输出将被转储到系统日志中
遇到的问题的描述
这将是系统日志中的一条消息,以
=============================================== 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 <地址><字节>
显示在检测到问题之前对象的前几个字节。 如果损坏没有停止于对象的开头,则可能很有用。
- Object <地址><字节>
对象的字节。 如果对象不活动,则字节通常包含毒化值。 任何非毒化值都显示在释放后被写入损坏。
- 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
如果损坏是通过在对象末尾之后写入发生的,那么建议启用红色区域,以避免损坏其他对象的开头
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` 脚本绘制其输出。 因此,它将分析从查看数字(大量数字)转移到更简单的内容 - 可视化分析。
生成图表
收集 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 个预处理缓存文件): - 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 个图
收集您需要的 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 日