KFENCE(Kernel Electric-Fence)是Linux内核内置的一项工具,可在在线环境中启用,旨在捕获内核及内核模块的内存污染问题。当检测到内存污染问题时,KFENCE会触发错误报告,并提供关于该问题的详细信息。阿里云在Alibaba Cloud Linux 3中对KFENCE功能进行了增强,支持灵活的动态开关KFENCE以及全面捕获内存污染问题,从而兼顾了线上探测与线下调试的需求。
操作系统限制
x86架构
Alibaba Cloud Linux 3(内核
5.10.84-10
及以上版本)ARM架构
Alibaba Cloud Linux 3(内核
5.10.134-16
及以上版本)
如果您是一名内核或内核模块的开发者,可以用此工具来检查您开发的内核或内核模块是否存在内存污染问题。
如果您是一名普通用户但遇到了内核崩溃的问题,可以打开KFENCE帮助我们或第三方驱动模块的开发者收集更多信息。
基本概念
名词 | 说明 |
内存污染 | 指在程序运行过程中,内存区域被错误地修改或破坏,导致程序行为异常或崩溃的问题。内存污染可能是由于编程错误、软件漏洞、恶意软件或硬件故障等原因引起的。 |
slab | slab是Linux内核中一种高效的内存分配机制。它通过预先分配一定数量的内存对象,组织成一个内存缓存池,用于快速分配和释放内存。slab可以避免频繁的内存分配和释放操作,提高内存分配的效率。 |
order 0单页 | order 0单页是Linux内核中一种内存分配机制,内存被分割成固定大小的页框(page frame),一般为4 KiB。order 0的单页指的就是一个普通的4 KiB大小的内存页框,它是内存分配的基本单位。当应用程序或内核需要分配一小块内存时,通常会以order 0的方式进行分配。 |
开启KFENCE
在以下业务场景中经常会用到开启KFENCE功能,具体说明如下:
线上探测场景
场景1:使用KFENCE探测内存是否存在污染
该场景占用2 MiB内存,基本不会影响性能。
添加
kfence.sample_interval
参数开启KFENCE。<kfence.sample_interval>需替换为要配置的值,例如100,表示系统下次启动时自动启用kfence调试工具,并设置其采样间隔为100次事件。
sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.sample_interval=<kfence.sample_interval>"
添加
kfence.booting_max
根据内存规格控制KFENCE最大的内存消耗。说明内核
5.10.134-17
及以上版本,新增boot commandline
默认配置kfence.booting_max=0-2G:0,2G-32G:2M,32G-:32M
用于根据内存规格控制KFENCE最大的内存消耗,配合默认的参数num_objects=255
(通常情况下占用2 MiB内存,64 K大页内核占用32 MiB内存),可以控制在任何规格下KFENCE的内存开销均不超过千分之一。此参数仅控制内存开销的上限,是对num_objects参数的制约,不代表实际内存开销(实际内存开销会小于等于此值)。
<kfence.booting_max>
需替换为要配置的值,例如0-128M:0,128M-256M:1M,256M-:2M
,参数说明如下。机器内存<128 MiB,不开启KFENCE。
128 MiB≤机器内存≤256 MiB,控制KFENCE使用的内存开销上限为1 MiB(对应num_objects上限被制约为127)。
机器内存>256 MiB,控制KFENCE使用的内存开销上限为2 MiB(对应num_objects上限被制约为255)。
sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.booting_max=<kfence.booting_max>"
此配置仅针对使用boot commandline方式开机自启KFENCE的场景,对于系统已经启动后再配置KFENCE(即场景2)无效。
系统在下次重启时,配置会自动生效。
场景2:使用KFENCE捕获内存污染问题
该场景将消耗以GiB为单位的大量内存,小规格机器慎用。
新建一个内存分配脚本,并添加如下内容(以脚本名称kfence.sh,监控的目标slab类型是
kmalloc-64
为例)。#!/bin/bash # usage: ./kfence.sh kmalloc-64 SLAB_PREFIX=/sys/kernel/slab MODULE_PREFIX=/sys/module/kfence/parameters if [ $# -eq 0 ]; then echo "err: please input slabs" exit 1 fi #check whether slab exists for i in $@; do slab_path=$SLAB_PREFIX/$i if [ ! -d $slab_path ]; then echo "err: slab $i not exist!" exit 1 fi done #calculate num_objects sumobj=0 for i in $@; do objects=($(cat $SLAB_PREFIX/$i/objects)) maxobj=1 for ((j=1; j<${#objects[@]}; j++)); do nodeobj=$(echo ${objects[$j]} | awk -F= '{print $2}') [ $maxobj -lt $nodeobj ] && maxobj=$nodeobj done ((sumobj += maxobj)) done echo "recommend num_objects per node: $sumobj" #check kfence stats if [ $(cat $MODULE_PREFIX/sample_interval) -ne 0 ]; then echo "kfence is running, disable it and wait..." echo 0 > $MODULE_PREFIX/sample_interval sleep 1 fi #disable all slabs catching for file in $SLAB_PREFIX/* do (echo 0 > $file/kfence_enable) 2>/dev/null || echo 1 > $file/skip_kfence done #disable order0 page catching echo 0 > $MODULE_PREFIX/order0_page #enable setting slabs catching for i in $@; do (echo 1 > $SLAB_PREFIX/$i/kfence_enable) 2>/dev/null || echo 0 > $SLAB_PREFIX/$i/skip_kfence done #setting num_objects and node mode echo $sumobj > $MODULE_PREFIX/num_objects echo node > $MODULE_PREFIX/pool_mode #start kfence echo -1 > $MODULE_PREFIX/sample_interval if [ $? -ne 0 ]; then echo "err: kfence enable fail!" exit 1 fi echo "kfence enabled!"
该脚本将探测目标slab的活跃对象数量,并根据该数量估算出合适的KFENCE池子大小,然后启用KFENCE以捕获所有目标slab的分配。
说明slab是内存管理中常用的概念和技术,用于优化内存的分配和释放操作,提高系统的性能和效率。KFENCE支持监控slab以及order 0单页。
运行以下命令,执行脚本,开始探测。
sudo bash ./kfence.sh kmalloc-64
线下调试场景
通过添加参数开启KFENCE(x86架构)
运行以下命令,开启KFENCE。
sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.num_objects=1000000" sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.sample_interval=-1" sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.fault=panic"
num_objects
:决定了KFENCE池子的大小。num_objects≤131071时,其占用内存为(num_objects + 1)* 8 KiB。
num_objects>131071时,其占用内存为⌈num_objects / 131071⌉ GiB(⌈⌉表示向上取整)。
说明建议该值配置为最大可用内存的10%。例如设置
num_objects
为1,000,000时,占用内存为⌈1,000,000 / 131071⌉ GiB = 8 GiB。
sample_interval
:取值包含以下三种情况。0:表示关闭KFENCE功能。
正数:采样间隔(ms),例如设置为100时,表示每隔100 ms分配的内存将进入KFENCE的监控范围内。
负数:全量模式,所有符合(slab类型筛选)条件的内存均将进入KFENCE的监控范围内。
fault
:自内核版本5.10.134-16
开始新增的参数,默认是report
。当设置为panic
时,会在捕获问题的现场宕机,以保留第一现场的内核转储文件。
重启系统使配置生效。
具体操作,请参见重启实例。
通过配置脚本开启KFENCE(x86/ARM架构)
通过该方式开启KFENCE时,无法捕获内核启动过程中可能出现的内存污染问题。
开启KFENCE后,如果需要修改
num_objects
或sample_interval
配置,需先关闭KFENCE再进行修改。
运行以下命令,开启KFENCE。
sudo sh -c 'echo 1000000 > /sys/module/kfence/parameters/num_objects'
sudo sh -c 'echo -1 > /sys/module/kfence/parameters/sample_interval'
sudo sh -c 'echo panic > /sys/module/kfence/parameters/fault'
num_objects
:决定了KFENCE池子的大小。其占用内存为 ⌈num_objects / 131071⌉ GiB(⌈⌉表示向上取整)。说明建议该值配置为最大可用内存的10%。例如设置
num_objects
为1,000,000时,占用内存为⌈1,000,000 / 131071⌉ GiB = 8 GiB。sample_interval
:取值包含以下三种情况。0:表示关闭KFENCE功能。
正数:采样间隔(ms),例如设置为100时,表示每隔100 ms分配的内存将进入KFENCE的监控范围内。
负数:全量模式,所有符合(slab类型筛选)条件的内存均将进入KFENCE的监控范围内。
fault
:自内核版本5.10.134-16
开始新增的参数,默认是report
。当设置为panic
时,会在捕获问题的现场宕机,以保留第一现场的内核转储文件。说明如果您的内核是
5.10.134-16
之前的版本,执行该条命令会报错,您直接忽略即可,不影响开启KFENCE。
查看结果
KFENCE捕获到内存污染问题后,您可以查看捕获到的问题个数以及详细的错误信息。
查看捕获问题的个数。
sudo cat /sys/kernel/debug/kfence/stats
结果如下图所示,
total bugs
计数增加。查看详细错误信息。
dmesg | grep -i kfence
如下图所示,查询到1条错误信息。
关闭KFENCE
运行以下命令,关闭KFENCE。
sudo bash -c 'echo 0 > /sys/module/kfence/parameters/sample_interval'
关闭KFENCE功能后,KFENCE不再捕获任何内存分配。当池子内监控的所有内存均释放后,KFENCE将以1 GiB单位为粒度向内核伙伴系统返还内存。
通过添加boot commandline参数开启KFENCE的场景,您可以运行以下命令移除这些参数,下次系统重启将不再自动打开KFENCE。
sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --remove-args="kfence.sample_interval"