第 36 章 进程资源 (Process Resources)
核心结论
-
getrusage() 取资源使用统计:RUSAGE_SELF/CHILDREN/THREAD(Linux 2.6.26+);rusage 结构含 user/system CPU time、page faults、context switches、max RSS、block I/O 等(部分字段 Linux 未实现)。
-
setrlimit/getrlimit 设/取资源限制:每资源有 soft(实际)+ hard(上限)两值;soft 0 到 hard 之间可任意调;非特权只能降低 hard(irreversibly);RLIM_INFINITY = 无限制;fork 继承、exec 保留。
-
RLIMIT_NPROC 限制 per-UID 进程数:基于 real UID 计数(同 UID 所有进程合计);fork/vfork/clone 超限 → EAGAIN;不影响特权进程(CAP_SYS_ADMIN 或 CAP_SYS_RESOURCE);其实是「per-UID 线程数」。
-
RLIMIT_NOFILE 限制 fd 数:值为「最大 fd 号 + 1」;open/pipe/socket/accept/dup 等超限 → EMFILE(dup2 EBADF,fcntl F_DUPFD EINVAL);硬上限 NR_OPEN = 1048576(2.6.25 前硬编码,后 /proc/sys/fs/nr_open)。
-
RLIMIT_CPU 限制 CPU 时间(秒):达 soft → SIGXCPU(每秒一次),hard → SIGKILL(不可 catch);默认值 = RLIM_INFINITY;用于防 runaway 进程。
-
RLIMIT_FSIZE 限制文件大小:write/truncate 超限 → SIGXFSZ + EFBIG;默认 RLIM_INFINITY;RLIMIT_CORE 0 可禁止 core dump。
-
Linux 特有 RLIMIT_NICE/RLIMIT_RTPRIO/RLIMIT_RTTIME/RLIMIT_MSGQUEUE/RLIMIT_SIGPENDING/RLIMIT_MEMLOCK:分别限制 nice 上限、实时优先级上限、实时 CPU 时间(μs)、POSIX 消息队列字节数、per-UID 排队信号数、可锁内存字节数。
|
本章主旨
本章深入进程资源使用统计与限制——getrusage 监控自身/子进程资源消耗;setrlimit/getrlimit 控制进程可消耗的资源上限。读者应掌握:rusage 字段含义(哪些 Linux 实现了、哪些未实现)、rlimit 结构(soft/hard + RLIM_INFINITY)、各 RLIMIT_* 资源限制的含义与单位(虚拟内存、文件大小、CPU、fd 数、per-UID 进程数、栈大小等)、soft 达限行为(信号 vs 系统调用失败)、Linux 特有 RLIMIT_*(NICE/RTPRIO/RTTIME/MSGQUEUE/SIGPENDING/MEMLOCK)。理解「资源限制是 per-process 但有时 per-UID 累计」(RLIMIT_NPROC 是 per-UID)是写 fork bomb 防御、资源监控、daemon 资源隔离的关键。 |
一、核心概念
本章围绕 6 个核心概念展开:从 getrusage 资源统计入手,到 rlimit 软硬限制、各 RLIMIT_* 资源(标准 + Linux 特有)、资源限制的 per-process vs per-UID 语义、信号触发与系统调用失败。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
getrusage() 资源使用统计 |
取资源使用(who: RUSAGE_SELF/CHILDREN/THREAD);rusage 结构含 ru_utime/stime/ru_maxrss/ru_minflt/ru_majflt/ru_inblock/ru_oublock/ru_nvcsw/ru_nivcsw 等;多数 ru_*ixrss 字段 Linux 未用。 |
§36.1;RUSAGE_CHILDREN 包含所有 wait() 过的子进程(含 grandchild 在子 wait 后算到子,子 wait 后算到父);2.6.26+ RUSAGE_THREAD 线程级。 |
rlimit 软硬限制结构 |
struct rlimit { rlim_cur (soft); rlim_max (hard) };soft 0-hard 间可调;hard 非特权只能降低;特权(CAP_SYS_RESOURCE)可双向调(hard ≥ soft);RLIM_INFINITY 表示无限;fork 继承、exec 保留。 |
§36.2;rlim_t 整数类型(通常同 off_t);RLIM_SAVED_CUR/MAX 表示不可表示(Linux 上等同 RLIM_INFINITY);x86-32 大文件编译环境下 setrlimit 超 32-bit unsigned long → RLIM_INFINITY。 |
RLIMIT_NPROC per-UID 进程数限制 |
限制 per-real-UID 的进程/线程数;超限 fork/vfork/clone → EAGAIN;同 UID 所有进程合计计数;不影响 CAP_SYS_ADMIN/CAP_SYS_RESOURCE 特权进程;Linux 上其实限制「线程数」。 |
§36.3;/proc/sys/kernel/threads-max 系统级;默认根据物理内存计算;RLIMIT_NPROC 不影响其他 UID 的进程;sysconf(_SC_CHILD_MAX) 2.6.23+ 准确(之前返回 999)。 |
RLIMIT_CPU/FSIZE/CORE 信号触发限制 |
RLIMIT_CPU(秒):达 soft → SIGXCPU(每秒一次),hard → SIGKILL;handler 可清理后退出。RLIMIT_FSIZE(字节):write/truncate 超限 → SIGXFSZ + EFBIG。RLIMIT_CORE(字节):core dump 上限;0 = 禁止 core dump。 |
§36.3;默认值 RLIM_INFINITY;用于防 runaway;handler 后建议退出(避免无限 SIGXCPU);用 ulimit -c 0 禁用 core dump(生产环境)。 |
RLIMIT_NOFILE / RLIMIT_AS / RLIMIT_STACK / RLIMIT_DATA |
RLIMIT_NOFILE:max fd + 1;open 超限 EMFILE(dup2 EBADF,fcntl F_DUPFD EINVAL);上限 NR_OPEN = 1048576(2.6.25+ /proc/sys/fs/nr_open)。RLIMIT_AS:虚拟内存字节数;超限 ENOMEM。RLIMIT_STACK:栈字节数;超限 SIGSEGV(需 alternate stack)。 |
§36.3;sysconf(_SC_OPEN_MAX) 与 RLIMIT_NOFILE 同步(Linux);/proc/sys/fs/file-max 系统级 fd 数;ulimit -n 改 RLIMIT_NOFILE。 |
Linux 特有 RLIMIT_NICE/RTPRIO/RTTIME/MSGQUEUE/SIGPENDING/MEMLOCK |
RLIMIT_NICE(2.6.12+):nice 上限 = 20 - rlim_cur;非特权设实时策略。RLIMIT_RTPRIO(2.6.12+):实时优先级上限。RLIMIT_RTTIME(2.6.25+):实时进程连续 CPU 时间(μs)→ SIGXCPU/SIGKILL。RLIMIT_MSGQUEUE:per-UID POSIX 消息队列字节数。RLIMIT_SIGPENDING:per-UID 排队信号数。RLIMIT_MEMLOCK:可锁内存字节数(mlock)。 |
§36.3;与第 35 章调度、信号、内存锁章节交叉;详见各章节。 |
二、详细笔记
36.1 getrusage() 资源使用统计
What:getrusage(who, &ru) 取进程资源使用统计;who 决定 SELF/CHILDREN/THREAD。
Why:监控进程 CPU/内存/I/O/上下文切换消耗;写 time(1) 类似的资源监控工具;监控子进程累计资源。
How:
// 摘自《The Linux Programming Interface》第 36 章
#include <sys/resource.h>
int getrusage(int who, struct rusage *res_usage);
who 取值:
-
RUSAGE_SELF:调用进程。 -
RUSAGE_CHILDREN:所有已 wait 的子进程(含 grandchild 链)。 -
RUSAGE_THREAD(Linux 2.6.26+):调用线程。
rusage 字段(§36.1):
| 字段 | 含义 | Linux 状态 |
|---|---|---|
ru_utime |
user CPU time |
✓ |
ru_stime |
system CPU time |
✓ |
ru_maxrss |
最大驻留集大小(KB) |
✓ (2.6.32+) |
ru_minflt |
minor page fault(无需 I/O) |
✓ |
ru_majflt |
major page fault(需 I/O) |
✓ |
ru_inblock |
块设备读操作 |
✓ (2.6.22+) |
ru_oublock |
块设备写操作 |
✓ (2.6.22+) |
ru_nvcsw |
voluntary context switch |
✓ (2.6+) |
ru_nivcsw |
involuntary context switch |
✓ (2.6+) |
ru_ixrss/idrss/isrss |
积分共享/数据/栈内存 |
✗ unused |
ru_nswap |
swap 次数 |
✗ unused |
ru_msgsnd/msgrcv |
IPC 消息计数 |
✗ unused |
ru_nsignals |
收到信号数 |
✗ unused |
RUSAGE_CHILDREN 累加规则(§36.1):
-
子 wait 自己的 child → 累加到子 RUSAGE_CHILDREN。
-
父 wait 子 → 子 + grandchild 都累加到父 RUSAGE_CHILDREN。
-
子不 wait grandchild → grandchild 不计入父。
-
ru_maxrss 是所有 descendant 的最大值(不是总和)。
-
SIGCHLD = SIG_IGN 不 wait → 资源丢失(2.6.9 之前 Linux 偏离 SUSv3 计入;之后符合)。
When:写 time(1) 工具;监控子进程资源;性能调优时看 page fault / context switch。
Example:
// 摘自《The Linux Programming Interface》第 36 章
struct rusage ru;
getrusage(RUSAGE_SELF, &ru);
printf("user CPU: %ld.%06ld, sys CPU: %ld.%06ld\n",
ru.ru_utime.tv_sec, ru.ru_utime.tv_usec,
ru.ru_stime.tv_sec, ru.ru_stime.tv_usec);
printf("page faults: min=%ld maj=%ld\n", ru.ru_minflt, ru.ru_majflt);
printf("context switches: vol=%ld invol=%ld\n", ru.ru_nvcsw, ru.ru_nivcsw);
36.2 setrlimit/getrlimit 资源限制
What:每个进程有 rlimit 表(每资源 soft + hard);setrlimit 改、getrlimit 查;限制进程可消耗的资源。
Why:防止进程消耗过多资源(CPU、内存、fd、文件大小);set-UID 程序在 exec 前重置资源限制;fork bomb 防御。
How:
// 摘自《The Linux Programming Interface》第 36 章
#include <sys/resource.h>
struct rlimit { rlim_t rlim_cur; rlim_t rlim_max; };
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
rlimit 结构(§36.2):
-
rlim_cur:soft 限制(实际生效值)。 -
rlim_max:hard 限制(rlim_cur 的上限,ceil)。 -
RLIM_INFINITY:无限制(特殊值)。
权限规则:
-
非特权:soft 可任意调(0-hard);hard 只能降低(irreversibly);不能增加。
-
特权(CAP_SYS_RESOURCE):hard 可双向调(必须 ≥ soft)。
-
行为:soft 限制达限 → 信号 或 系统调用失败。
资源限制 vs 进程 vs UID(§36.2):
-
多数限制:per-process(只影响本进程)。
-
RLIMIT_NPROC/RLIMIT_MSGQUEUE/RLIMIT_SIGPENDING:per-real-UID(所有同 UID 进程合计)。
-
限制检查:进程及其 descendants(继承限制的);同 UID 其他进程不受影响(除非它们也设了)。
未表示限制值(§36.2):
-
RLIM_SAVED_CUR/RLIM_SAVED_MAX:表示 rlim_t 无法表示的限制值。 -
Linux:定义为 RLIM_INFINITY(所有值可表示)。
-
x86-32 + 大文件编译:setrlimit 超 32-bit unsigned long → 静默转 RLIM_INFINITY(问题)。
shell 命令 vs getrlimit 单位(§36.2):
-
bash ulimit / ksh ulimit / csh limit:单位可能与系统调用不同(如栈大小用 KB)。
-
建议:直接用 getrlimit/setrlimit 编程。
When:写 set-UID wrapper 程序(exec 前重置资源);fork bomb 防御(设 RLIMIT_NPROC);debug 资源问题;生产环境 RLIMIT_CORE=0。
Example(§36.2):
// 摘自《The Linux Programming Interface》第 36 章 — printRlimit 简化版
int printRlimit(const char *msg, int resource) {
struct rlimit rlim;
getrlimit(resource, &rlim);
printf("%s soft=%s; hard=%s\n", msg,
rlim.rlim_cur == RLIM_INFINITY ? "inf" :
(char *)(long long)rlim.rlim_cur,
rlim.rlim_max == RLIM_INFINITY ? "inf" :
(char *)(long long)rlim.rlim_max);
}
36.3 各 RLIMIT_* 资源详解
What:每个具体资源限制的含义、单位、达限行为、Linux 特有性。
Why:理解每个 RLIMIT 的语义是写资源受限程序的基础。
How:
标准 RLIMIT_*(§36.3):
| 限制 | 含义 | 达限行为 |
|---|---|---|
RLIMIT_AS |
进程虚拟内存(字节) |
brk/sbrk/mmap/mremap/shmat → ENOMEM |
RLIMIT_CORE |
core dump 文件大小(字节) |
0 = 禁止 core dump |
RLIMIT_CPU |
CPU 时间(秒,user + sys) |
达 soft → SIGXCPU(每秒);达 hard → SIGKILL |
RLIMIT_DATA |
数据段(init+bss+heap,字节) |
sbrk/brk → ENOMEM |
RLIMIT_FSIZE |
文件大小(字节) |
write/truncate → SIGXFSZ + EFBIG |
RLIMIT_NOFILE |
最大 fd 号 + 1 |
open/pipe/… → EMFILE;dup2 → EBADF;fcntl F_DUPFD → EINVAL |
RLIMIT_STACK |
栈大小(字节) |
栈增长 → SIGSEGV(需 alternate signal stack catch) |
Linux 特有 RLIMIT_*(§36.3):
| 限制 | 含义 | 达限行为 | 引入版本 |
|---|---|---|---|
RLIMIT_MEMLOCK |
可锁内存字节数(mlock/mlockall) |
mlock → ENOMEM |
BSD-derived |
RLIMIT_MSGQUEUE |
per-UID POSIX 消息队列字节数 |
mq_open → EAGAIN |
2.6.8 |
RLIMIT_NICE |
nice 上限 = 20 - rlim_cur |
nice/sched_setscheduler 受限 |
2.6.12 |
RLIMIT_NPROC |
per-UID 进程/线程数 |
fork/vfork/clone → EAGAIN |
BSD-derived |
RLIMIT_RSS |
驻留集大小(页面数) |
未实现 |
BSD-derived |
RLIMIT_RTPRIO |
实时优先级上限 |
sched_setscheduler/setparam 受限 |
2.6.12 |
RLIMIT_RTTIME |
实时 CPU 时间(μs)连续不阻塞 |
达 soft → SIGXCPU;达 hard → SIGKILL |
2.6.25 |
RLIMIT_SIGPENDING |
per-UID 排队信号数 |
sigqueue → EAGAIN |
2.6.8 |
系统级限制(§36.3):
-
/proc/sys/fs/file-max:系统级最大 open file description 数;超限 ENFILE;CAP_SYS_ADMIN 可超过。
-
/proc/sys/fs/nr_open(2.6.25+):RLIMIT_NOFILE 硬上限(默认 1048576)。
-
/proc/sys/kernel/threads-max:系统级最大线程数(2.4+)。
RLIMIT_CPU 详细(§36.3):
-
软限制达:SIGXCPU 信号,默认动作 = 终止 + core dump。
-
handler 可清理资源后退出;建议首次收到就退出(避免持续 SIGXCPU)。
-
Linux:handler 返回后 SIGXCPU 每秒一次;达硬限制 → SIGKILL(不可 catch)。
-
移植性:多数 UNIX 持续 SIGXCPU 直到退出或 setrlimit 调整。
RLIMIT_STACK 详细(§36.3):
-
栈增长超限 → SIGSEGV。
-
因栈耗尽,handler 只能在 alternate signal stack 运行(sigaltstack,§21.3)。
-
2.6.23+ RLIMIT_STACK 还限制命令行参数 + 环境变量总大小。
RLIMIT_NPROC 详细(§36.3):
-
限制 per-real-UID 的线程数(不是进程)。
-
不影响 CAP_SYS_ADMIN/CAP_SYS_RESOURCE 特权进程。
-
默认值:2.2 固定公式;2.4+ 基于物理内存。
-
sysconf(_SC_CHILD_MAX):2.6.23+ 准确(之前总是返回 999)。
RLIMIT_RSS 详细(§36.3):
-
Linux 上未实现——存在但无效果。
-
旧 2.4(≤2.4.29)影响 madvise MADV_WILLNEED → EIO。
RLIMIT_SIGPENDING 详细(§36.3):
-
仅 sigqueue 受限(kill 不受限——可入队 1 实例/信号/进程)。
-
默认 1024(初始)→ 2.6.12+ 同 RLIMIT_NPROC。
-
/proc/PID/status SigQ 字段显示当前排队信号数。
When:见每个 RLIMIT 的应用场景——CPU 限制防 runaway;fd 数限制防 fd exhaustion;per-UID 进程数防 fork bomb;MLOCK 限制防内存锁滥用。
Example(§36.3):
// 摘自《The Linux Programming Interface》第 36 章 — Listing 36-3
// 摘自 procres/rlimit_nproc.c
struct rlimit rl;
rl.rlim_cur = getInt(argv[1], 0, "soft-limit");
rl.rlim_max = (argc == 2) ? rl.rlim_cur : getInt(argv[2], 0, "hard-limit");
setrlimit(RLIMIT_NPROC, &rl);
/* 接下来 fork 直到 EAGAIN */
for (;;) {
pid_t pid = fork();
if (pid == -1) { /* EAGAIN */
perror("fork");
break;
}
/* 子立即 _exit */
}
三、关键图表
|
标准 + Linux 特有 RLIMIT 一览(表 36-1)
|
|
rlimit 权限规则
|
|
rusage 字段 Linux 状态
|
四、思维导图
mindmap
root((第 36 章 进程资源))
getrusage
RUSAGE SELF CHILDREN THREAD
rusage 结构
utime stime CPU
maxrss 2 6 32
page faults min maj
block I/O 2 6 22
context switch vol invol
ixrss idrss 未用
RUSAGE CHILDREN 累加
rlimit 结构
rlim cur soft
rlim max hard
RLIM INFINITY
0 hard 范围
非特权只能降 hard
CAP SYS RESOURCE
fork 继承 exec 保留
RLIM SAVED CUR MAX
x86 32 大文件问题
RLIMIT 详解
AS 虚拟内存
CORE core dump 0
CPU 秒 SIGXCPU KILL
DATA 数据段
FSIZE 文件 SIGXFSZ
NOFILE max fd 1
EMFILE EBADF EINVAL
STACK 栈 SIGSEGV
alternate stack
命令行参数上限 2 6 23
Linux 特有 RLIMIT
NPROC per UID 线程
EAGAIN
CAP SYS ADMIN 例外
threads max 系统级
NICE 20 rlim cur
RTPRIO 实时优先级
RTTIME 实时 CPU 微秒
SIGXCPU SIGKILL
MSGQUEUE per UID
SIGPENDING per UID
sigqueue EAGAIN
kill 不受限
MEMLOCK mlock
RSS 未实现
系统级限制
file max open fd 数
nr open NOFILE 上限
1048576 默认
threads max 线程数
CAP SYS ADMIN 超 file max
ENFILE
应用
fork bomb 防御 NPROC
set UID 重置限制
防止 runaway CPU
生产 CORE 0
内存锁定上限
五、重点与易错点
-
getrusage RUSAGE_CHILDREN 只含已 wait 的子:未 wait 的 child/grandchild 不计入;这是 wait 后才能回收子状态的副作用。
-
RLIMIT_NPROC 是 per-real-UID 线程数:不是 per-process;同 UID 所有进程合计计数;限制检查:进程及其 descendants(继承限制的)。
-
RLIMIT_NPROC 不影响特权进程(CAP_SYS_ADMIN 或 CAP_SYS_RESOURCE);fork bomb 防御不能依赖此限制。
-
rlimit soft 是实际生效值;hard 是 ceiling;非特权只能降低 hard(irreversibly);特权可双向调但必须 soft ≤ hard。
-
RLIM_INFINITY 是特殊值:表示无限;getrlimit 返回时无限制 → RLIM_INFINITY;setrlimit 接受 RLIM_INFINITY 表示设无限。
-
x86-32 + 大文件编译(_FILE_OFFSET_BITS=64):setrlimit 超 32-bit unsigned long → 静默转 RLIM_INFINITY——glibc 的 workaround;程序不会报错。
-
RLIMIT_CPU 达 soft → SIGXCPU(每秒一次);达 hard → SIGKILL(不可 catch);handler 后建议立即退出。
-
RLIMIT_FSIZE 达限 → SIGXFSZ + EFBIG;write/truncate 失败;handler 后续 write 仍触发(持续 SIGXFSZ)。
-
RLIMIT_NOFILE 限制「最大 fd 号 + 1」——不是 fd 数;默认值通常 1024;硬上限 NR_OPEN = 1048576(2.6.25 前硬编码,后 /proc/sys/fs/nr_open)。
-
RLIMIT_STACK 达限 → SIGSEGV——因栈耗尽只能靠 alternate signal stack catch(sigaltstack)。
-
RLIMIT_AS vs RLIMIT_DATA:AS 是整个虚拟地址空间;DATA 仅数据段(init+bss+heap);AS 限制更严格。
-
RLIMIT_RTPRIO + RLIMIT_RTTIME 防止实时应用滥用——与第 35 章 sched_setscheduler 配合;非特权设实时策略受 RTPRIO 限制。
-
RLIMIT_RSS Linux 上未实现——存在但无效果;不要用它期望限制 RSS。
-
RLIMIT_MSGQUEUE/SIGPENDING per-real-UID 累计——同 RLIMIT_NPROC 语义;只影响自己 + descendants。
-
RLIMIT_SIGPENDING 仅 sigqueue 受限——kill 仍可入队 1 实例/信号/进程(标准信号和实时信号)。
-
Linux 上 RLIM_SAVED_CUR/MAX 定义为 RLIM_INFINITY——所有限制值可表示;不需要处理「unrepresentable」情况。
-
shell ulimit 与 getrlimit 单位差异——bash/ksh ulimit 栈大小用 KB;getrlimit/setrlimit 用字节;编程用后者。
-
fork bomb 防御多层:RLIMIT_NPROC + 系统 threads-max + ulimit -u + cgroup pids.max(容器环境)。
-
跨章衔接:第 24 章 fork 基础 → 第 35 章 nice/sched_setscheduler → 第 36 章资源限制;第 18 章 mmap → RLIMIT_AS;第 5 章 fcntl F_DUPFD → RLIMIT_NOFILE;第 50 章 mlock → RLIMIT_MEMLOCK;第 47 章 SysV IPC → RLIMIT_MSGQUEUE;第 22 章信号 → RLIMIT_SIGPENDING;第 9 章 set-UID → exec 前 resetrlimit。
-