SLUB简短用户指南 (翻译 by chatgpt)

发布时间 2023-12-12 20:20:26作者: 摩斯电码

原文:https://www.kernel.org/doc/html/v6.6/mm/slub.html

SLUB简短用户指南

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

要打开调试,可以在内核命令行中添加选项slub_debug。这将为所有slab启用完整的调试。

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

gcc -o slabinfo tools/mm/slabinfo.c

slabinfo的某些操作模式要求在命令行上启用slub调试。例如,如果未启用调试,则将无法获得跟踪信息,并且只有部分验证可以执行。

slub_debug的一些更复杂的用法:

可以向slub_debug提供参数。如果未指定参数,则将启用完整的调试。格式如下:

  • slub_debug=<调试选项>:为所有slab启用选项
  • slub_debug=<调试选项>,<slab名称1>,<slab名称2>,...:仅为选择的slab启用选项(逗号后不要加空格)

可以提供多个块的选项,用于所有slab或选定的slab,选项块之间用分号分隔。"所有slab"块中的最后一个将应用于除与"选择slab"块之一匹配的slab之外的所有slab。将应用与slab名称匹配的第一个"选择slab"块的选项。

可能的调试选项包括:

  • F: 启用一致性检查(启用SLAB_DEBUG_CONSISTENCY_CHECKS,解决SLAB遗留问题)
  • Z: 红色分区(Red zoning)
  • P: 对象和填充区域中的毒化(Poisoning)
  • U: 用户跟踪(释放和分配)
  • T: 跟踪(请仅在单个slab上使用)
  • A: 为缓存启用failslab过滤标记
  • O: 关闭对可能导致更高最小slab order的缓存的调试
  • -: 关闭所有调试(如果内核配置了CONFIG_SLUB_DEBUG_ON,则很有用)

例如,要仅使用一致性检查和红色分区启动,可以指定:

slub_debug=FZ

尝试在dentry缓存中查找问题?尝试:

slub_debug=,dentry

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

slub_debug=P,kmalloc-*,dentry

红色分区和跟踪可能会重新对齐slab。我们可以仅对dentry缓存应用一致性检查:

slub_debug=F,dentry

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

slub_debug=O

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

slub_debug=Z,dentry;U,kmalloc-*

您还可以为所有缓存启用选项(例如,一致性检查和毒化),除了一些被认为对性能至关重要且不需要调试的缓存,通过指定全局调试选项,然后跟随以“-”为选项的slab名称列表:

slub_debug=FZ;-,zs_handle,zspage

每个slab的调试选项状态可以在以下各自文件中找到:

/sys/kernel/slab/<slab名称>/

如果文件包含1,则表示该选项已启用,0表示已禁用。slub_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验证

如果内核是使用slub_debug启动的,SLUB可以验证所有对象。为此,您必须拥有slabinfo工具。然后可以执行以下命令:

slabinfo -v

这将测试所有对象,并将输出生成到syslog中。

即使在没有slab调试的情况下,这也可以以更有限的方式工作。在这种情况下,slabinfo -v仅简单地测试所有可达对象。通常,这些对象位于CPU slab和部分slab中。在非调试情况下,SLUB不会跟踪完整的slab。

提高性能

在某种程度上,SLUB的性能受到需要偶尔获取list_lock以处理部分slab的影响。这种开销受到每个slab分配的顺序的影响。分配可以受到内核参数的影响:

  • slub_min_objects:允许指定至少需要多少个对象才能适应一个slab,以便分配order是可接受的。一般情况下,slub将能够在一个slab上执行这些数量的分配,而无需咨询集中资源(list_lock),从而可能导致争用。
  • slub_min_order:指定slab的最小order。类似于slub_min_objects的效果。
  • slub_max_order:指定slub_min_objects不再被检查的order。这有助于避免SLUB尝试生成超大顺序页面,以适应具有大对象大小的slab缓存的slub_min_objects到一个高order页面中。设置命令行参数debug_guardpage_minorder=N(N > 0)会强制将slub_max_order设置为0,这将导致slab分配的最小可能order。

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遇到损坏的对象(完整的检测需要使用slub_debug启动内核),则以下输出将被转储到syslog中:

  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时,才可以获得对象分配/释放信息。slub_debug设置了该选项)

  1. 如果涉及对象,则以下类型的行可以跟随BUG SLUB行:
  • Bytes b4 <地址><字节>
    显示在检测到问题的对象之前的几个字节。如果损坏没有在对象的起始位置停止,这可能会很有用。

  • Object <地址><字节>
    对象的字节。如果对象处于非活动状态,则字节通常包含毒化值。任何非毒化值都表示在释放后进行了写入的损坏。

  • Redzone <地址><字节>
    对象后面的红色分区。红色分区用于检测对象后的写入。所有字节应始终具有相同的值。如果有任何偏差,则是由于对象边界后的写入。
    (仅当设置了SLAB_RED_ZONE时,才可以获得红色分区信息。slub_debug设置了该选项)

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

  1. 堆栈转储

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

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

这些是以以下内容开头的系统日志中的消息:

FIX <受影响的slab缓存>: <采取的纠正措施>

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

紧急操作

可以通过使用以下启动选项启动来启用最小的调试(仅进行健全性检查):

slub_debug=F

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

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

例如:

slub_debug=F,dentry

如果损坏是在对象结束后进行写入,则建议启用红色分区以避免破坏其他对象的开头:

slub_debug=FZ,dentry

扩展的slabinfo模式和绘图

slabinfo工具有一个特殊的“扩展”('-X')模式,其中包括:

  • Slabcache总计
  • 按大小排序的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个预处理缓存文件):

    • 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有两个“放大”/“缩小”的选项:

    • -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>/目录下,仅对启用了用户跟踪的缓存创建。其中包含两种类型的文件,分别提供以下调试信息:

  1. alloc_traces:打印当前分配对象的唯一分配跟踪信息,并按照每个跟踪的频率进行排序。输出的信息包括对象数量、分配函数、可能的内存浪费情况(总体/每个对象)、自分配以来的最小/平均/最大jiffies、分配进程的pid范围、分配CPU的CPU掩码、内存来源的NUMA节点掩码以及堆栈跟踪信息。

    示例:

    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:打印当前分配对象的唯一释放跟踪信息,这些释放跟踪信息来自对象的先前生命周期,并且不适用于首次分配的对象。输出按照每个跟踪的频率进行排序。输出的信息包括对象数量、释放函数、自释放以来的最小/平均/最大jiffies、释放进程的pid范围、释放CPU的CPU掩码以及堆栈跟踪信息。

    示例:

    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
    

这些文件提供了有关SLUB缓存的更多调试信息,特别是在启用用户跟踪调试选项时。Christoph Lameter于2007年5月30日,Sergey Senozhatsky于2015年10月23日发布了这些信息。