linux kswapd0占用CPU 100%卡死解决方案

670 阅读3分钟

我因为kswapd0占用cpu 100%问题查了很多资料,尝试了各种方案,大部分参考价值不大,下面是我最终的建议。

CPU占用高直接原因

kswapd0正尝试将使用频率不高的程序数据移动到swap,此过程占用CPU极高。耗时几分钟到几十分钟。

间接原因

某个进程申请内存过多,系统内存不足,触发kswapd0转移内存数据腾出空间

根本原因

占用内存过多的进程是否真的应该占用这么多内存?有几个可能的原因:

  • 在内存较小的计算机,打开需要较多内存的应用:比如打开非常多网页,或打开CAD,PS,虚拟机,非常大的文件等。
  • 该程序有内存泄露(或不合理使用内存),会线性不断申请更多内存
  • 感染病毒,伪装为kswapd0进程,非root进程,可通过"killall -9 kswapd0"杀死
  • linux内核bug,某些情况会触发(这里有个2016年的帖子讨论相关bug,已在2022年修复)

更合理的解决方案?

遇到内存不足时,一般有2种措施可选:

  1. 使用kswapd0腾空闲置数据,释放更多内存
  2. 杀死占用内存最多的进程

遇到内存不足时,在windows上,一般是kill掉占用内存最多的进程,所以有时候会遇到OOM错误,但不会出现类似kswapd0导致的系统卡死。 而linux考虑到OOM终止进程可能造成大量数据丢失或损坏,所以linux默认会使用kswapd0腾出更多内存空间。

但计算机硬件内存始终是有限的,释放内存可能有帮助,也可能没有帮助。更多时候会导致长达十几分钟CPU高占用系统卡死。

在我遇到的问题里,我使用llm生成的banbot量化策略代码自动编译执行回测,它大部分情况生成的代码都不错,但偶尔生成的代码会导致内存泄露。目前我已通过调整提示词和内置检查来避免。不过llm生成代码越来越多,因代码缺陷导致内存泄露可能会更加常见。

所以我感觉发现内存不足时,杀死申请内存最多的进程或许也是一种不错的方案。下面是一个sh脚本,它应该在crontab中配置为每分钟运行:

  1. 检查当前是否存在kswapd0进程
  2. 如果kswapd0进程不是root用户,直接kill掉
  3. 如果kswapd0进程是root用户,且CPU占用率超过70%,找到占用内存最大的进程,kill掉

在决定使用此脚本前,请先排查明确您导致此问题的根本原因,如果确认符合您的场景才可使用

#!/bin/bash
# https://github.com/anyongjin/linux_oom_killer
# mail  anyongjin163@163.com

# Function to log with timestamp
log_with_timestamp() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# Function to check and possibly kill kswapd0 and the highest memory usage process
check_and_kill() {
    # Check if kswapd0 process exists
    kswapd_pid=$(pgrep -x kswapd0)
    if [ -z "$kswapd_pid" ]; then
        exit 0
    fi

    # Check if current user is root
    if [ "$EUID" -ne 0 ]; then
        # Non-root user, directly attempt to kill kswapd0 (Note: kernel processes cannot be killed, this operation may fail)
        kill -9 "$kswapd_pid" 2>/dev/null
        if [ $? -eq 0 ]; then
            log_with_timestamp "kswapd0 process killed (non-root user)."
        else
            log_with_timestamp "Failed to kill kswapd0 (likely due to permissions or kernel process)."
        fi
        return
    fi

    # For root user, check kswapd0's CPU usage
    # Use top with shorter interval for faster but still accurate reading
    # Run top for 1 second instead of 2 seconds
    cpu_usage=$(top -b -n 2 -d 0.5 -p "$kswapd_pid" | tail -n 1 | awk '{print int($9)}')

    if [ "$cpu_usage" -gt 10 ]; then
        log_with_timestamp "kswapd0 CPU usage is ${cpu_usage}% (threshold: 70%), $([ "$cpu_usage" -gt 70 ] && echo "taking action..." || echo "no action needed.")"
    fi

    if [ "$cpu_usage" -gt 70 ]; then
        # Find the process with the highest memory usage (exclude header row, using ps aux --sort=-%mem)
        max_mem_pid=$(ps aux --sort=-%mem | awk 'NR==2 {print $2}')
        if [ -n "$max_mem_pid" ]; then
            kill -9 "$max_mem_pid"
            log_with_timestamp "Killed process $max_mem_pid with highest memory usage."
        else
            log_with_timestamp "No processes found to kill."
        fi
    fi
}

# Main script logic
# Check for the correct number of arguments
if [ "$#" -ne 2 ]; then
    log_with_timestamp "Usage: $0 <number_of_runs> <interval_in_seconds>"
    exit 1
fi

# Assign command line arguments to variables
runs=$1
interval=$2

# Loop to run the check_and_kill function the specified number of times
for (( i=1; i<=$runs; i++ )); do
    check_and_kill
    # Sleep for the specified interval, unless it's the last run
    if [ $i -lt $runs ]; then
        sleep "$interval"
    fi
done

授予执行权限:chmod +x oom_killer.sh

然后在crontab -e中配置此脚本10s运行一次(每分钟启动1次,启动后间隔9s执行6次检查,每次检查耗时1s)

* * * * * /root/oom_killer.sh 6 9 >> /var/log/oom_killer.log 2>&1

为了尽可能方便使用,我写了一个自动安装脚本,您可执行下面命令:

curl -sL https://banbot.site/set_oom_killer.sh | sudo bash

或者您可通过[github仓库]查看更详细信息。