CPythonresource模块深度指南:进程资源限制(RLIMIT_*)与用量统计(getrusage)实践
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
resource是 CPython 中用于测量与控制当前进程系统资源消耗的标准库模块,直接封装了 Unix 系统调用getrlimit(2)、setrlimit(2)、prlimit(2)、getrusage(2)与getpagesize(3)。本文以 CPython 官方文档 Doc/library/resource.rst 为骨架,结合其 C 实现 Modules/resource.c 与测试用例 Lib/test/test_resource.py,完整讲解软/硬限制机制、全部RLIMIT_*常量、getrusage返回的 16 个字段语义,并给出可运行、可防护的实战代码,帮助你读懂并驾驭进程的 CPU、内存、文件描述符等资源边界。
模块概览与适用平台
resource模块提供两类能力:
- 资源限制(Resource Limits):通过
getrlimit()/setrlimit()/prlimit()查看并调整进程在 CPU 时间、堆内存、栈、文件大小、打开文件数等方面的上下限; - 资源用量(Resource Usage):通过
getrusage()获取进程自身或子进程已消耗的 CPU 用户态/内核态时间、内存、缺页、上下文切换等统计信息。
可用性(Availability)
- 平台:Unix(Linux、macOS、BSD、Solaris、AIX 等),不支持 WASI;
- 由于 Windows 没有这些系统调用语义,
resource在 Windows 上不可用; - 因此文档与源码中大量使用
@unittest.skipUnless(hasattr(resource, ...))进行平台适配(见 Lib/test/test_resource.py)。
异常与兼容别名
底层系统调用失败时统一抛出OSError;resource.error是OSError的已弃用别名——自 3.3 起(遵循 PEP 3151),两者完全等价,新代码应直接使用OSError。在 Modules/resource.c 中可以看到别名通过PyModule_AddObjectRef(module, "error", PyExc_OSError)直接绑定到内置异常类型实现。
资源限制机制:软限制与硬限制
每个可限制资源都由一对限制值控制:
| 概念 | 含义 | 能否被进程自身修改 |
|---|---|---|
软限制(soft limit /rlim_cur) | 当前生效的限制值 | 可随时间升高或降低,但永远不能超过硬限制 |
硬限制(hard limit /rlim_max) | 软限制的绝对上限 | 只能降低到不低于软限制的值,不能调高;只有有效 UID 为超级用户(root)的进程才能提升硬限制 |
这一规则对应系统调用getrlimit(2)/setrlimit(2)的 man page 语义;模块不掩盖平台差异,某平台不支持的资源符号在该平台上不会出现在模块中。
哨兵常量
RLIM_INFINITY:表示"无限制"的特殊值。文档指出自3.15起它恒为正数(此前可能是 -1、-3 等负数);测试 test_fsize_ismax 显式断言resource.RLIM_INFINITY > 0。RLIM_SAVED_CUR/RLIM_SAVED_MAX(3.15 新增):当真正的软/硬限制值无法用 C 的rlim_t类型表示时,用于代表"保存的"软/硬限制;取值可能等于RLIM_INFINITY。实现中两者按平台宏条件加入模块,见 Modules/resource.c。
关于"传入负数"的兼容行为:从 Modules/resource.c 的
py2rlim()可以看到——负值若恰好等于RLIM_INFINITY的旧编码,会发出DeprecationWarning: "Use RLIM_INFINITY instead of negative limit value.";其他负数一律抛ValueError。测试 test_fsize_negative 覆盖了这一规则。相关变更记录见 Misc/NEWS.d/3.15.0a1.rst。
三大核心函数
getrlimit(resource)
查询某个资源当前的软、硬限制:
import resource soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) print(f"soft={soft}, hard={hard}")- 返回形如
(soft, hard)的二元组; - 传入非法资源编号抛
ValueError(源码在 Modules/resource.c 中对resource < 0 || resource >= RLIM_NLIMITS进行拦截); - 系统调用意外失败抛
OSError; - 返回的两个值均为 Python 无符号整数(经
rlim2py()用PyLong_FromUnsignedNativeBytes转换,Modules/resource.c),因此即使上限极大(如RLIM_INFINITY)也不会出现负数或溢出。
setrlimit(resource, limits)
设置资源的新消费上限:
resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 8192))limits必须是两个整数组成的元组(soft, hard),可用RLIM_INFINITY表示不限量;- 参数校验(见 py2rlimit):不是二元元组会抛
ValueError(错误信息为"expected a tuple of 2 integers"),元素类型错误抛TypeError,超出 Crlim_t范围抛OverflowError——测试 test_args 对各类非法输入做了穷举断言; - 违反限制规则时抛
ValueError,包括:- 新软限制超过硬限制(errno
EINVAL→"current limit exceeds maximum limit"); - 尝试提升硬限制但权限不足(errno
EPERM→"not allowed to raise maximum limit"); - 请求
RLIM_INFINITY但该资源的硬/系统上限并非无限; - 超级用户可以请求任意合法值,但若超出系统施加的上限仍会抛
ValueError;
- 新软限制超过硬限制(errno
- 底层调用失败同样抛
OSError。错误到异常的映射见 Modules/resource.c; - 审计事件:每次调用触发
resource.setrlimit(resource, limits)(参数依次为资源编号、限制对象),供审计钩子拦截; - VxWorks 特例:仅支持设置
RLIMIT_NOFILE。
一个安全的实践是"先读后写、用完还原",CPython 测试框架自身正是这样处理核心文件限制的(见 Lib/test/support/init.py):
soft, hard = resource.getrlimit(resource.RLIMIT_CORE) old_soft, old_hard = soft, hard try: # 临时下调 core 文件上限,避免测试误产生巨大 core dump resource.setrlimit(resource.RLIMIT_CORE, (0, hard)) ... finally: resource.setrlimit(resource.RLIMIT_CORE, (old_soft, old_hard))prlimit(pid, resource[, limits])(Linux)
把setrlimit与getrlimit合并为一次调用,可作用于任意进程:
pid == 0表示当前进程;- 省略
limits:仅查询进程pid的该资源限制; - 提供
limits:先设置新限制,返回旧限制(soft, hard); - 异常:
pid不存在抛ProcessLookupError;无CAP_SYS_RESOURCE权限访问目标进程抛PermissionError(其余错误为OSError,见 Modules/resource.c); - 审计事件:
resource.prlimit(pid, resource, limits); - 可用性:Linux ≥ 2.6.36 且 glibc ≥ 2.13(由
HAVE_PRLIMIT宏控制编译,Modules/resource.c); - 3.4 加入。
例如用prlimit把另一个进程的地址空间上限收紧为 2 GiB:
import os, resource pid = int(os.environ.get("TARGET_PID", "0")) old = resource.prlimit(pid, resource.RLIMIT_AS, (2 * 1024**3, 2 * 1024**3)) print("old limits were:", old)测试 test_prlimit 同时验证了查询模式与设置模式,并断言对不存在的 pid 抛ProcessLookupError。
资源限制常量:RLIMIT_*全表
以下符号的值与 C 程序使用的常量完全一致,可在setrlimit/getrlimit/prlimit中直接使用。具体哪些可用取决于操作系统与内核版本——本模块不掩盖平台差异,未定义的符号在该平台不可用(这与文档一致)。下方标注了官方文档记载的可用性条件与加入版本。
| 常量 | 含义 | 可用性 / 版本 |
|---|---|---|
RLIMIT_CORE | 进程可生成的 core 文件最大字节数;超出时可能生成不完整的部分 core | 通用 |
RLIMIT_CPU | 进程可使用的 CPU 时间上限(秒);超限后内核向进程发送SIGXCPU信号,可用signal模块捕获并处理(如落盘缓冲) | 通用 |
RLIMIT_FSIZE | 进程可创建文件的最大字节数 | 通用 |
RLIMIT_DATA | 进程堆(heap)的最大字节数 | 通用 |
RLIMIT_STACK | 进程调用栈最大字节数;多线程进程中只影响主线程的栈 | 通用 |
RLIMIT_RSS | 进程可获得的常驻内存集(resident set size)上限 | 通用 |
RLIMIT_NPROC | 当前进程可创建的子进程数量上限 | 通用 |
RLIMIT_NOFILE | 当前进程可打开的文件描述符数量上限 | 通用 |
RLIMIT_OFILE | RLIMIT_NOFILE的 BSD 别名 | BSD |
RLIMIT_MEMLOCK | 可锁定在物理内存中的地址空间大小上限 | 通用 |
RLIMIT_VMEM | 进程可占用的映射内存最大区域;通常是RLIMIT_AS的别名 | Solaris / FreeBSD / NetBSD |
RLIMIT_AS | 进程可占用的地址空间最大字节数 | 通用 |
RLIMIT_MSGQUEUE | POSIX 消息队列可分配的总字节数 | Linux ≥ 2.6.8,3.4 加入 |
RLIMIT_NICE | 进程 nice 值的天花板(按20 - rlim_cur计算) | Linux ≥ 2.6.12,3.4 加入 |
RLIMIT_RTPRIO | 实时优先级天花板 | Linux ≥ 2.6.12,3.4 加入 |
RLIMIT_RTTIME | 实时调度下、不做阻塞系统调用可花费的 CPU 时间上限(微秒) | Linux ≥ 2.6.25,3.4 加入 |
RLIMIT_SIGPENDING | 进程可排队的信号数量上限 | Linux ≥ 2.6.8,3.4 加入 |
RLIMIT_SBSIZE | 该用户可占用的套接字缓冲区总字节数(网络内存/mbufs) | FreeBSD / NetBSD,3.4 加入 |
RLIMIT_SWAP | 该用户 ID 所有进程可保留/使用的交换空间字节数(仅当vm.overcommitsysctl 第 1 位开启时强制) | FreeBSD ≥ 8,3.4 加入 |
RLIMIT_NPTS | 该用户 ID 可创建的伪终端数量上限 | FreeBSD ≥ 8,3.4 加入 |
RLIMIT_KQUEUES | 该用户 ID 可创建的 kqueue 数量上限 | FreeBSD ≥ 11,3.10 加入 |
RLIMIT_NTHR | 该用户 ID 的线程数上限(不计主线程与内核线程) | NetBSD ≥ 7.0,3.15 加入 |
RLIMIT_PIPEBUF | 该用户 ID 可消耗的双向 pipe/fifo 内核缓冲区总量上限 | FreeBSD ≥ 14.2,3.15 加入 |
RLIMIT_THREADS | 每个进程可创建的线程数上限 | AIX,3.15 加入 |
RLIMIT_UMTXP | 该用户 ID 分配的进程共享 POSIX 线程库对象数量上限 | FreeBSD ≥ 11,3.15 加入 |
这些常量在 Modules/resource.c 中逐一以#ifdef条件编译加入(ADD_INT宏),与 C 头文件中的同名宏一一对应;测试 test_linux_constants 与 test_freebsd_contants 分别校验了 Linux 与 FreeBSD 专属常量均为int。
实战选型提醒:若需保证跨平台脚本不报
AttributeError,请先用hasattr(resource, "RLIMIT_XXX")探测,这与模块"不掩盖平台差异"的设计保持一致。
用量统计:getrusage、getpagesize与RUSAGE_*
getrusage(who)
返回当前进程或其子进程的资源消耗描述对象,who由RUSAGE_*常量指定。官方文档给出了一个直观示例(本处稍作整理):
from resource import * import time # 非 CPU 密集型任务 time.sleep(3) print(getrusage(RUSAGE_SELF)) # CPU 密集型任务 for i in range(10 ** 8): _ = 1 + 1 print(getrusage(RUSAGE_SELF))两次输出对比中,第二次的ru_utime(用户态时间)会明显增大,可用来验证"测的是 CPU 忙等时间还是挂起时间"。
返回值类型:该对象是 CPython 定义的结构序列(struct sequence)类型struct_rusage,字段顺序与 16 元组完全一致,既能按属性访问(ru_utime),也能按下标/解包按元组访问。类型定义见 Modules/resource.c,字段构建在getrusage实现中逐个填充(Modules/resource.c)。
官方文档的字段总表(含元组下标):
| 下标 | 字段 | 含义 |
|---|---|---|
| 0 | ru_utime | 用户态运行时间(浮点秒) |
| 1 | ru_stime | 系统态运行时间(浮点秒) |
| 2 | ru_maxrss | 最大常驻内存集大小 |
| 3 | ru_ixrss | 共享内存大小 |
| 4 | ru_idrss | 非共享内存大小 |
| 5 | ru_isrss | 非共享栈大小 |
| 6 | ru_minflt | 无需 I/O 的缺页次数 |
| 7 | ru_majflt | 需要 I/O 的缺页次数 |
| 8 | ru_nswap | 换出次数 |
| 9 | ru_inblock | 块输入操作次数 |
| 10 | ru_oublock | 块输出操作次数 |
| 11 | ru_msgsnd | 发送的消息数 |
| 12 | ru_msgrcv | 接收的消息数 |
| 13 | ru_nsignals | 收到的信号数 |
| 14 | ru_nvcsw | 主动上下文切换次数 |
| 15 | ru_nivcsw | 被动上下文切换次数 |
ru_utime、ru_stime为浮点数(源码中由doubletime()把tv_sec + tv_usec * 1e-6换算成秒,Modules/resource.c),其余字段为整数;- 部分数值依赖时钟 tick 粒度(如
ru_maxrss在部分系统以 KB 为单位); - 传非法
who抛ValueError(源码把 errnoEINVAL映射为ValueError,Modules/resource.c),其余异常抛OSError。
RUSAGE_*:who取值
| 常量 | 含义 | 版本 |
|---|---|---|
RUSAGE_SELF | 调用进程自身消耗的资源(为进程内所有线程之和) | 通用 |
RUSAGE_CHILDREN | 已终止并被wait回收的子进程消耗的资源 | 通用 |
RUSAGE_BOTH | 当前进程与子进程之和;部分系统不可用 | — |
RUSAGE_THREAD | 仅当前线程消耗的资源;部分系统不可用 | 3.2 加入 |
在自由线程(free-threaded)构建下,RUSAGE_THREAD对多线程程序尤其有用——测试 Lib/test/test_free_threading/test_resource.py 分别对比了RUSAGE_SELF与RUSAGE_THREAD的结果。
getpagesize()
返回系统页大小(字节数),注意未必等于硬件页大小:
>>> import resource >>> resource.getpagesize() 4096实现上优先调用getpagesize(),否则回退到sysconf(_SC_PAGE_SIZE),见 Modules/resource.c;测试 test_pagesize 断言其为非负整数。
底层实现细节:从 Python 参数到 Crlim_t
理解数值转换有助于避免边界踩坑:
getrlimit的资源校验:先拦截越界资源编号(抛ValueError),再调用系统调用,见 Modules/resource.c;py2rlim()无符号转换(Modules/resource.c):支持任意实现__index__的对象;负数整体抛ValueError,与RLIM_INFINITY等价的旧式负哨兵值则触发DeprecationWarning;Python 整数超出 Crlim_t宽度时抛OverflowError;rlim2py()反向转换(Modules/resource.c):按无符号原生字节序构造 Python 整数,保证RLIM_INFINITY、RLIM_SAVED_CUR/MAX这些大哨兵值无损往返;- 模块级并发安全:
struct_rusage类型存放于 per-module state(resourcemodulestate),且模块槽位声明了Py_MOD_PER_INTERPRETER_GIL_SUPPORTED与Py_MOD_GIL_NOT_USED(Modules/resource.c),表明该模块在多解释器与自由线程(free-threaded)构建下均可安全使用。
在 CPython 生态中的真实应用场景
1. 测试运行器提升文件描述符上限:CPython 的测试框架在运行前会读取RLIMIT_NOFILE,必要时临时把软限制提高到接近硬限制,以保证大量并发套接字/文件句柄的测试不被系统默认限制卡死,见 Lib/test/libregrtest/utils.py。
2. 环境信息采集:resource.getrlimit(...)与resource.getrusage(...)被用于测试报告辅助脚本,把软/硬限制与资源用量写入诊断信息,见 Lib/test/pythoninfo.py。
3. 限制真正生效的验证:测试 test_fsize_enforced 演示了端到端行为——将RLIMIT_FSIZE软限制设为 1024 字节后,写入第 1025 字节即触发OSError(SIGXFSZ语义),并在finally中恢复原限制。该模式正是"给不可信任务上保险丝"的范本:
import resource, signal, time def run_with_cpu_guard(seconds): soft, hard = resource.getrlimit(resource.RLIMIT_CPU) def handler(signum, frame): raise TimeoutError("CPU 配额耗尽") old = signal.signal(signal.SIGXCPU, handler) try: resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard)) ... # 执行你的计算任务 finally: resource.setrlimit(resource.RLIMIT_CPU, (soft, hard)) signal.signal(signal.SIGXCPU, old)说明:
RLIMIT_CPU超限后内核先发SIGXCPU(默认动作是终止进程),Python 中可通过signal模块捕获该信号执行清理或转储,这正是文档 Doc/library/resource.rst 在RLIMIT_CPU条目中特别提醒的用法。
4. 运行期可观测性:在长期服务中周期性采样getrusage(RUSAGE_SELF).ru_maxrss与上下文切换计数,可廉价地获得内存与调度健康度指标,无需引入外部监控依赖。
相关参考路径
- 权威接口文档:Doc/library/resource.rst
- C 实现(常量注入、参数校验、系统调用封装):Modules/resource.c
- 完整测试(边界值、跨平台、refcount、prlimit):Lib/test/test_resource.py
- 自由线程下的
RUSAGE_THREAD测试:Lib/test/test_free_threading/test_resource.py - 3.15 新增常量与
RLIM_INFINITY语义变更记录:Misc/NEWS.d/3.15.0a1.rst - 当前仓库版本(本仓库主分支对应 3.16 开发版):Include/patchlevel.h
一句话总结:凡是涉及"限制自身或子进程的 CPU/内存/文件资源"或"测量自身消耗了多少 CPU 时间、内存、IO 与调度代价"的需求,在 Unix 平台优先想到resource——它薄薄一层直接映射系统调用,是最轻量、最可靠的基础设施之一。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考