Shell脚本如何限制用户进程数

wen 实用脚本 1

Shell脚本如何限制用户进程数:从原理到实战的完整指南

📖 目录导读

  1. 为什么需要限制用户进程数?
  2. 核心机制:Linux进程限制的原理
  3. 使用ulimit命令临时限制
  4. 通过/etc/security/limits.conf持久配置
  5. Shell脚本动态监控与限制
  6. 实战技巧:编写健壮的进程限制脚本
  7. 常见问题与问答

为什么需要限制用户进程数?

在服务器运维中,用户进程数失控是导致系统负载过高、内存耗尽甚至服务雪崩的常见原因。

Shell脚本如何限制用户进程数

  • 恶意用户通过fork炸弹(如(){ :|:& };:)瞬间创建海量进程
  • 程序异常导致子进程不断派生,耗尽系统资源
  • 多用户环境下个别用户占用过多进程影响其他用户

限制用户进程数的核心价值

  • 系统稳定性:防止单个用户耗尽进程表、内存或CPU
  • 公平调度:确保多用户环境下的资源公平性
  • 安全防护:阻断简单的DoS攻击

核心机制:Linux进程限制的原理

Linux通过PAM(Pluggable Authentication Modules)cgroup实现进程数控制,关键参数包括:

参数 含义 示例值
nproc 用户可创建的最大进程数 1024
nofile 打开文件描述符数量 65535
sigpending 挂起的信号数 128

限制级别

  • 软限制(soft limit):当前生效值,用户可自行提高至硬限制
  • 硬限制(hard limit):系统允许的最大值,仅root可修改

使用ulimit -a可查看当前用户的软限制:

$ ulimit -a | grep processes
max user processes              (-u) 2048

方法一:使用ulimit命令临时限制

1 基础用法

# 将当前用户的进程数限制为100
ulimit -u 100
# 查看当前限制
ulimit -u

2 在Shell脚本中应用

#!/bin/bash
# 在脚本开头限制进程数
ulimit -u 200
# 执行可能产生大量进程的操作
for i in {1..300}; do
    (sleep 1 &)
done

注意ulimit仅影响当前Shell及其子进程,退出后失效。

3 限制不同Shell配置

~/.bashrc~/.profile中添加:

if [ "$(ulimit -u)" -gt 500 ]; then
    ulimit -u 500
fi

方法二:通过/etc/security/limits.conf持久配置

1 配置文件语法

# /etc/security/limits.conf
<domain> <type> <item> <value>
  • domain:用户名、组名(@group)或通配符(*)
  • typesofthard
  • itemnproc(进程数)
  • value:数值

2 示例配置

# 限制用户john最多100个进程
john    soft    nproc   100
john    hard    nproc   200
# 限制所有用户软限制1024
*       soft    nproc   1024
# 限制所有用户硬限制2048
*       hard    nproc   2048

3 验证生效

  1. 重启登录或使用pam_limits.so
    # 手动应用配置
    sudo pam_limits --apply
  2. 切换到受限用户测试:
    su - john
    ulimit -u
    # 输出应为100

4 常见陷阱

  • /etc/security/limits.d/目录:该目录下的配置文件优先级高于limits.conf
  • nproc对root无效:root账户不受nproc限制
  • 需要pam支持:确保/etc/pam.d/common-session包含:
    session required pam_limits.so

方法三:Shell脚本动态监控与限制

1 监控当前用户进程数

#!/bin/bash
USER=$1
MAX_PROCESS=50
CURRENT_PROCESS=$(ps -u $USER --no-headers | wc -l)
if [ $CURRENT_PROCESS -gt $MAX_PROCESS ]; then
    echo "警告: $USER 当前进程数 $CURRENT_PROCESS 超过限制 $MAX_PROCESS"
    # 可选:终止多余进程
    # 获取该用户PID列表,保留前MAX_PROCESS个进程
    ps -u $USER -o pid --sort=pid | tail -n +2 | head -n -$MAX_PROCESS | xargs kill -9
fi

2 实时杀死超出进程

#!/bin/bash
# 限制用户进程数不超过200,自动杀死多出的子进程
while true; do
    for user in $(ps -eo user --no-headers | sort -u); do
        proc_count=$(ps -U $user --no-headers | wc -l)
        if [ "$proc_count" -gt 200 ]; then
            # 按进程启动时间排序,杀死最旧的多余进程
            ps -u $user -o pid,lstart --sort=lstart | tail -n +2 | head -n -200 | awk '{print $1}' | xargs -r kill -9
            logger -t process-limiter "Killed $(($proc_count - 200)) processes for user $user"
        fi
    done
    sleep 30
done

3 结合cgroup实现精细控制

#!/bin/bash
# 创建cgroup限制用户进程数
CG_NAME="user/$(id -un)"
cgcreate -g pids:/$CG_NAME
cgset -r pids.max=100 /$CG_NAME
# 将当前Shell加入cgroup
cgclassify -g pids:/$CG_NAME $$
# 后续所有子进程都会受到限制
./my_heavy_script.sh

实战技巧:编写健壮的进程限制脚本

1 核心设计原则

  1. 非侵入性:先记录日志,再执行kill操作
  2. 白名单机制:允许系统关键进程(如sshd)豁免
  3. 降级策略:达到限制时先发送警告邮件,再强制终止
  4. 性能优化:使用pgrep配合awk提高查询效率,避免grep泄漏

2 完整脚本示例

#!/bin/bash
# 文件名:limit_user_procs.sh
# 用途:按用户限制进程数,支持配置和日志
CONFIG_FILE="/etc/process_limits.conf"
LOG_FILE="/var/log/process_limiter.log"
INTERVAL=300  # 检查间隔(秒)
MAIL_CMD="/usr/sbin/sendmail"
# 读取配置
declare -A LIMITS
while IFS=: read user limit; do
    LIMITS["$user"]=$limit
done < "$CONFIG_FILE"
while true; do
    for user in "${!LIMITS[@]}"; do
        max_proc=${LIMITS[$user]}
        cur_proc=$(pgrep -u "$user" -c)
        if [ "$cur_proc" -gt "$max_proc" ]; then
            # 生成要终止的进程列表
            kill_pids=$(ps -u "$user" -o pid --no-headers | sort -n | head -n -$max_proc)
            for pid in $kill_pids; do
                kill -9 "$pid" 2>/dev/null
                echo "$(date) - 终止用户 $user 的进程 PID=$pid" >> "$LOG_FILE"
            done
            # 发送警报(可选)
            if [ -x "$MAIL_CMD" ]; then
                echo "用户 $user 超出进程限制 ($cur_proc/$max_proc),已终止 $((cur_proc - max_proc)) 个进程" | \
                    $MAIL_CMD -v
            fi
        fi
    done
    sleep "$INTERVAL"
done

3 配置示例:/etc/process_limits.conf

john:100
www-data:500
ftp:50
*:200

常见问题与问答

❓ 问题1:为什么ulimit -u在脚本中设置了但没生效?

答案:因为ulimit只影响当前Shell和它的子进程,如果脚本中设置后执行./sub_script.sh,子脚本会继承限制,但如果在脚本中使用&后台运行,后台进程不受限制!解决方案:在父Shell中用ulimit -u后,所有后台进程需要由该Shell直接派生。

❓ 问题2:如何限制root用户的进程数?

答案nproc对root无效(出于系统稳定性考虑),要限制root,只能通过:

  • cgroup限制(如pids.max
  • 或修改系统守护进程(如systemd的服务限制)
  • 或使用其他安全模块(如AppArmor)

❓ 问题3:fork炸弹如何防御?

答案:最有效的方法:

# 1. 在bash中禁用函数递归
ulimit -u 50
# 2. 或使用cgroup限制
# 3. 配置limits.conf对所有用户限制进程数

在shell中可设置:

# 禁止递归函数调用
shopt -s extdebug
function forkbomb { :; }; trap 'ulimit -u 0; exit' DEBUG

❓ 问题4:监控脚本对性能影响大吗?

答案:影响很小。ps命令本身消耗资源,建议:

  • 检查间隔至少30秒以上
  • 使用pgrep -c替代ps | wc -l
  • 避免频繁调用sorthead,可用awk代替管道。
  • 对于大型系统(1000+用户),考虑使用systemd的资源控制模块或cgroup的监听事件。

❓ 问题5:如何测试进程数限制?

答案:使用简单的测试脚本:

#!/bin/bash
# test_limit.sh
count=0
while true; do
    ( sleep 1000 & )
    count=$((count + 1))
    echo "当前进程数: $count"
    if [ $count -gt 100 ]; then
        break
    fi
done

观察何时出现Resource temporarily unavailable错误。


通过本文介绍的方法,您可以根据实际场景选择最适合的进程数控制方案:简单场景用ulimit,生产环境用limits.conf,复杂动态控制用Shell脚本。没有银弹,最佳实践是结合limits.conf(限制最大阈值)和Shell脚本(动态清理异常)共同防御,希望这篇文章能帮助您构建更稳定的服务器环境。

抱歉,评论功能暂时关闭!