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"