全部產品
Search
文件中心

Alibaba Cloud Linux:捕獲核心的記憶體汙染問題(KFENCE)

更新時間:Oct 18, 2024

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為單位的大量記憶體,小規格機器慎用。

  1. 建立一個記憶體配置指令碼,並添加如下內容(以指令碼名稱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單頁。

  2. 運行以下命令,執行指令碼,開始探測。

    sudo bash ./kfence.sh kmalloc-64

線下調試情境

通過添加參數開啟KFENCE(x86架構

  1. 運行以下命令,開啟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時,會在捕獲問題的現場宕機,以保留第一現場的核心轉儲檔案。

  2. 重啟系統使配置生效。

    具體操作,請參見重啟執行個體

通過配置指令碼開啟KFENCE(x86/ARM架構

說明
  • 通過該方式開啟KFENCE時,無法捕獲核心啟動過程中可能出現的記憶體汙染問題。

  • 開啟KFENCE後,如果需要修改num_objectssample_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計數增加。

    image.png

  • 查看詳細錯誤資訊。

    dmesg | grep -i kfence

    如下圖所示,查詢到1條錯誤資訊。

    image.png

關閉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"

常見問題

  • KFENCE功能對記憶體和效能有什麼影響?

    • 對記憶體的影響

      KFENCE採用以大量記憶體開銷換取較小的效能幹擾的思路,佔用的記憶體較高。對於重啟開啟的取樣模式(上遊Linux同款)可設定任意較小的num_objects來節約記憶體。其他情況(全量模式及動態開啟)則需消耗GiB層級的記憶體,所以小規格機器慎用。

    • 對效能的影響

      • 取樣模式下,對效能影響較小。

      • 全量模式如果篩選得當(例如只開啟某種類型的slab監控),一般來說對效能的影響也可以接受。

      說明
      • 建議您根據實際的業務情境先進行灰階測試,觀察開啟KFENCE後對實際業務效能的影響,再決定後續部署情況。

      • 線上下調試情境中,如果採用全量模式以及全種類監控,效能和記憶體影響都很大,但是這種情境一般是為了鎖定問題,無需關注效能影響。

  • KFENCE功能與KASAN功能有什麼區別?

    KFENCE與KASAN都是Linux核心內建的用於捕獲記憶體汙染的工具。阿里雲在5.10核心中對KFENCE功能進行了增強,除了開關更加靈活、支援採樣從而可以線上上的業務環境運行之外,它們在功能上還有以下區別:

    • KFENCE支援監控大小不超過4 KiB的slab(例如kmalloc-4k)以及order 0單頁。而KASAN支援監控的記憶體種類更多,包括所有的slab、page、堆棧和全域記憶體等。

    • KFENCE對於監控範圍內的記憶體異常行為捕獲成功率高於KASAN。

    • KFENCE在記憶體開銷方面高於KASAN,但通常情況下對業務的效能的影響小於KASAN。

    通常情況下,不建議同時使用KFENCE和KASAN功能,KFENCE會接管KASAN的監控目標。

  • KFENCE功能的穩定性怎麼樣?

    在核心版本5.10.134-15及其之前的版本存在一個已知問題:order 0單頁和slab混合監控時,在特定情境下可能造成宕機。您可以運行以下命令,禁用order 0單頁監控來預防此問題。

    sudo grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="kfence.order0_page=0"