接口是什么
pids.max 是 cgroup v2 的进程数上限,统计口径是任务数(tasks):组内所有进程与线程都算一个名额,因此多线程服务的实际占用通常远大于「进程数」。触顶后 fork()/clone()/pthread_create() 直接失败并返回 EAGAIN,表现为「Resource temporarily unavailable」而不是变慢。它是防 fork 炸弹、限制单个服务把宿主 PID 表打满的关键开关,配套只读计数接口 pids.current。
基本信息
| 路径 | /sys/fs/cgroup/<组>/pids.current 与 pids.max |
|---|---|
| 取值 | 整数字符串,或 max 表示不限制 |
| 触顶错误 | EAGAIN(fork/clone 失败,线程创建失败) |
| systemd 对应 | TasksMax=,可用 systemctl show -p TasksMax,TasksCurrent 查看 |
| 容器环境 | k8s 由 kubelet 的 PodPidsLimit 控制,容器内改 cgroup 文件通常无权限 |
| 权限 | 写 pids.max 需 root 或委派权限 |
典型使用场景
- 给对外服务设置上限,防止代码里的 fork 循环或进程泄漏拖垮整台机器
- 排查服务偶发「创建线程失败」:先看 pids.current 是否贴顶,再看 ulimit -u
- systemd 托管服务启动后莫名报错:默认的 TasksMax 可能小于服务峰值线程数
- 容器化部署评估:按峰值线程数给 PodPidsLimit 留 2-3 倍余量
- 安全加固:把不可信的批处理任务放到有 pids.max 的独立 cgroup 里运行
查看与设置
查看与设置
cat /sys/fs/cgroup/myapp.slice/pids.current # 当前任务数(进程 + 线程)
cat /sys/fs/cgroup/myapp.slice/pids.max # 当前上限,max 表示不限制
echo 512 > /sys/fs/cgroup/myapp.slice/pids.max # 运行时调整(需权限)
systemctl show myapp.service -p TasksMax,TasksCurrent # 用 systemd 口径核对
systemctl set-property myapp.service TasksMax=1024 # 写入 drop-in 并立即生效
ulimit -u # 同时确认 RLIMIT_NPROC,它与 pids.max 互相独立
ps -eLf | wc -l # 粗略统计本机任务总数,判断是否接近 kernel.pid_max报错现象对照
| 现象 | 原因 | 处理方向 |
|---|---|---|
| pthread_create 失败 EAGAIN | 线程名额触顶 | 看 pids.current 与 pids.max,而不是先怀疑内存 |
| fork: Resource temporarily unavailable | pids.max 触顶,或 RLIMIT_NPROC 触顶 | 两个限制都要查,前者看 cgroup,后者看 ulimit -u |
| 服务启动即失败 | pids.max 设得过小,连初始化线程都不够 | 至少给到服务峰值线程数的 2-3 倍 |
| 新进程创建成功但很慢 | 通常是内存或 IO 压力 | 多半不是 pids 限制,改用其他指标确认 |
线程计入 pids 是最大的认知陷阱:JVM、Go runtime、Nginx、各类线程池框架里,一个进程可能对应几十到几百个任务。估算上限时应按「进程数 × 单进程峰值线程数」而不是只数进程。
systemd 对每个 service 都设有默认 TasksMax,历史上默认策略随版本调整。服务出现「线程创建失败但系统资源充足」时,先执行 systemctl show <svc> -p TasksMax 确认是不是默认值在起作用。