好的,我们来深入学习 setrlimit
系统调用。
1. 函数介绍
在 Linux 系统中,为了保证系统的稳定性和公平性,防止某个程序因为 bug 或恶意行为而耗尽系统资源(如内存、CPU 时间、打开的文件数量等),内核提供了一种资源限制 (Resource Limits) 机制。
setrlimit
(Set Resource Limit) 系统调用的作用就是设置调用进程(及其未来创建的子进程)对某一类系统资源的使用上限。
你可以把它想象成你给一个程序分配一个“资源使用预算”或“配额”。比如,你可以告诉内核:“这个程序最多只能使用 100MB 的内存”、“最多只能打开 10 个文件”、“最多只能运行 10 秒钟”等等。当程序试图超出这个限制时,内核会根据资源类型采取不同措施,通常是拒绝其请求(例如 malloc
失败)或发送一个信号(例如 SIGXCPU
)来终止它。
简单来说,setrlimit
就是让你用程序来给另一个程序(或自己)“上规矩”,限制它能用多少系统资源。
2. 函数原型
#include <sys/resource.h> // 包含系统调用声明和常量
int setrlimit(int resource, const struct rlimit *rlim);
3. 功能
为调用进程设置指定资源 resource
的软限制 (soft limit) 和硬限制 (hard limit)。
4. 参数
resource
:int
类型。- 指定要设置限制的资源类型。常见的资源类型定义在
<sys/resource.h>
中,例如:RLIMIT_AS
: 进程虚拟地址空间的最大总大小(字节)。限制进程能分配的总内存。RLIMIT_CORE
: 程序崩溃时创建的核心转储文件 (core dump) 的最大字节数。设置为 0 可以禁用 core dump。RLIMIT_CPU
: 进程可以使用的 CPU 时间(秒)。达到软限制会收到SIGXCPU
信号,达到硬限制会被SIGKILL
终止。RLIMIT_DATA
: 进程数据段的最大字节大小(通过brk
/sbrk
分配的内存)。RLIMIT_FSIZE
: 进程可以创建的文件的最大字节大小。超出限制时写操作会失败,并可能收到SIGXFSZ
信号。RLIMIT_NOFILE
: 进程可以同时打开的文件描述符(File Descriptor)的最大数量。RLIMIT_NPROC
: 调用用户 ID (Real User ID) 可以拥有的最大进程/线程数量。RLIMIT_STACK
: 进程栈的最大字节大小。RLIMIT_MEMLOCK
: 可以使用mlock
锁定在内存中的最大字节数。RLIMIT_RSS
: 进程在物理内存中驻留的最大字节数(Resident Set Size)。(在 Linux 上可能不强制执行)。RLIMIT_NICE
: nice 值的上限(影响进程调度优先级)。- … 还有其他一些资源类型。
rlim
:const struct rlimit *
类型。- 一个指向
rlimit
结构体的指针,该结构体定义了资源的限制。rlimit
结构体定义如下:struct rlimit { rlim_t rlim_cur; // Soft limit (软限制) rlim_t rlim_max; // Hard limit (硬限制) };
rlim_cur
(软限制):- 这是内核实际执行强制限制的值。
- 进程可以随时将其修改为小于或等于当前硬限制 (
rlim_max
) 的任何值。 - 超过软限制通常会导致内核发送一个信号(如
SIGXCPU
)来警告进程。
rlim_max
(硬限制):- 这是软限制可以被设置的上限。
- 普通用户只能降低硬限制,不能提高它。
- 只有特权用户 (root) 才能提高硬限制。
- 进程可以在任何时候将硬限制降低到等于或低于当前硬限制的值。
5. 返回值
- 成功: 返回 0。
- 失败: 返回 -1,并设置全局变量
errno
来指示具体的错误原因。
6. 错误码 (errno
)
EFAULT
:rlim
指向了调用进程无法访问的内存地址。EINVAL
:resource
参数无效,或者指定的限制值无效(例如,负数,或者对于某些资源类型不合适)。EPERM
: 调用进程没有权限设置指定的限制。最常见的原因是普通用户试图提高硬限制 (rlim_max
)。
7. 相似函数或关联函数
getrlimit
: 用于获取当前进程对某类资源的限制设置。prlimit
: 一个更现代的系统调用,可以同时设置和获取任意进程的资源限制(需要CAP_SYS_RESOURCE
能力)。ulimit
: 命令行工具(在 shell 中),用于设置当前 shell 及其子进程的资源限制。它在底层调用setrlimit
和getrlimit
。struct rlimit
: 定义限制值的数据结构。
8. 示例代码
下面的示例演示了如何使用 setrlimit
来设置几种常见的资源限制。
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h> // 包含 setrlimit, getrlimit, struct rlimit
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h> // 包含 timeval, 用于 CPU 时间限制
#include <fcntl.h> // 包含 open
// 信号处理函数,用于捕获因资源限制而产生的信号
void signal_handler(int sig) {
printf("\nCaught signal %d\n", sig);
if (sig == SIGXCPU) {
printf("CPU time limit (soft) reached. Exiting gracefully.\n");
exit(EXIT_FAILURE);
} else if (sig == SIGXFSZ) {
printf("File size limit reached. Write operation failed.\n");
// 可以选择继续运行或退出
}
}
// 打印特定资源的当前限制
void print_resource_limit(const char* resource_name, int resource) {
struct rlimit rl;
if (getrlimit(resource, &rl) == 0) {
printf("%-15s: Soft = ", resource_name);
if (rl.rlim_cur == RLIM_INFINITY) printf("unlimited");
else printf("%ld", (long)rl.rlim_cur);
printf(", Hard = ");
if (rl.rlim_max == RLIM_INFINITY) printf("unlimited");
else printf("%ld", (long)rl.rlim_max);
printf("\n");
} else {
perror("getrlimit");
}
}
int main() {
struct rlimit rl;
struct sigaction sa;
printf("--- Demonstrating setrlimit ---\n");
printf("PID: %d\n", getpid());
// 1. 显示初始的一些资源限制
printf("\n--- Initial Resource Limits ---\n");
print_resource_limit("CPU Time", RLIMIT_CPU);
print_resource_limit("File Size", RLIMIT_FSIZE);
print_resource_limit("Data Segment", RLIMIT_DATA);
print_resource_limit("Stack Size", RLIMIT_STACK);
print_resource_limit("Virtual Memory", RLIMIT_AS);
print_resource_limit("Open Files", RLIMIT_NOFILE);
print_resource_limit("Max Processes", RLIMIT_NPROC);
print_resource_limit("Core File Size", RLIMIT_CORE);
// 2. 设置 CPU 时间限制
printf("\n--- Setting CPU Time Limit ---\n");
rl.rlim_cur = 5; // 软限制:5 秒
rl.rlim_max = 10; // 硬限制:10 秒
if (setrlimit(RLIMIT_CPU, &rl) == 0) {
printf("Set CPU time limit: Soft = %lds, Hard = %lds\n", (long)rl.rlim_cur, (long)rl.rlim_max);
// 设置信号处理函数以捕获 SIGXCPU
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGXCPU, &sa, NULL) == -1) {
perror("sigaction SIGXCPU");
}
} else {
perror("setrlimit RLIMIT_CPU");
}
// 3. 设置最大打开文件数限制
printf("\n--- Setting Open File Descriptor Limit ---\n");
rl.rlim_cur = 10; // 软限制:最多 10 个文件描述符
rl.rlim_max = 20; // 硬限制:最多 20 个文件描述符
if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
printf("Set open file limit: Soft = %ld, Hard = %ld\n", (long)rl.rlim_cur, (long)rl.rlim_max);
} else {
perror("setrlimit RLIMIT_NOFILE");
}
// 4. 设置最大文件大小限制
printf("\n--- Setting File Size Limit ---\n");
rl.rlim_cur = 1024 * 1024; // 软限制:1MB
rl.rlim_max = 2 * 1024 * 1024; // 硬限制:2MB
if (setrlimit(RLIMIT_FSIZE, &rl) == 0) {
printf("Set file size limit: Soft = %ld bytes, Hard = %ld bytes\n", (long)rl.rlim_cur, (long)rl.rlim_max);
// 设置信号处理函数以捕获 SIGXFSZ
sa.sa_handler = signal_handler;
if (sigaction(SIGXFSZ, &sa, NULL) == -1) {
perror("sigaction SIGXFSZ");
}
} else {
perror("setrlimit RLIMIT_FSIZE");
}
// 5. 设置虚拟内存限制 (RLIMIT_AS)
printf("\n--- Setting Virtual Memory Limit ---\n");
rl.rlim_cur = 50 * 1024 * 1024; // 软限制:50 MB
rl.rlim_max = 100 * 1024 * 1024; // 硬限制:100 MB
if (setrlimit(RLIMIT_AS, &rl) == 0) {
printf("Set virtual memory limit: Soft = %ld bytes (%.2f MB), Hard = %ld bytes (%.2f MB)\n",
(long)rl.rlim_cur, (double)rl.rlim_cur / (1024*1024),
(long)rl.rlim_max, (double)rl.rlim_max / (1024*1024));
} else {
perror("setrlimit RLIMIT_AS");
}
// 6. 设置 core dump 大小为 0,禁用它
printf("\n--- Disabling Core Dump ---\n");
rl.rlim_cur = 0;
rl.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &rl) == 0) {
printf("Disabled core dump generation.\n");
} else {
perror("setrlimit RLIMIT_CORE");
}
// 7. 验证设置后的限制
printf("\n--- Resource Limits After setrlimit ---\n");
print_resource_limit("CPU Time", RLIMIT_CPU);
print_resource_limit("File Size", RLIMIT_FSIZE);
print_resource_limit("Virtual Memory", RLIMIT_AS);
print_resource_limit("Open Files", RLIMIT_NOFILE);
print_resource_limit("Core File Size", RLIMIT_CORE);
// 8. 演示资源限制的效果
// --- 演示 RLIMIT_FSIZE ---
printf("\n--- Testing RLIMIT_FSIZE (File Size Limit) ---\n");
const char* test_filename = "test_limited_file.txt";
int fd = open(test_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("open test file");
} else {
char data[1024];
memset(data, 'A', sizeof(data));
ssize_t written;
long total_written = 0;
// 尝试写入超过 1MB 的数据
while (total_written < 2 * 1024 * 1024) {
written = write(fd, data, sizeof(data));
if (written == -1) {
perror("write");
printf("Write failed after writing approximately %ld bytes. File size limit likely reached.\n", total_written);
break;
}
total_written += written;
}
close(fd);
printf("Finished writing (or failed) to file.\n");
// 清理测试文件
unlink(test_filename);
}
// --- 演示 RLIMIT_AS ---
printf("\n--- Testing RLIMIT_AS (Virtual Memory Limit) ---\n");
printf("Attempting to allocate large chunks of memory until limit is hit...\n");
size_t chunk_size = 10 * 1024 * 1024; // 10MB
long allocated_mb = 0;
char *ptr;
while (1) {
ptr = malloc(chunk_size);
if (ptr == NULL) {
printf("malloc failed after allocating approximately %ld MB. Memory limit likely reached.\n", allocated_mb);
break;
}
// Touch the memory to ensure it's actually allocated
memset(ptr, 0, chunk_size);
allocated_mb += chunk_size / (1024 * 1024);
printf("Allocated %ld MB so far...\n", allocated_mb);
// 添加一点延迟,方便观察
sleep(1);
}
// --- 演示 RLIMIT_CPU (放在最后,因为它会终止程序) ---
printf("\n--- Testing RLIMIT_CPU (CPU Time Limit) ---\n");
printf("Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).\n");
printf("You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.\n");
while(1) {
// 空循环,消耗 CPU 时间
}
// 程序通常不会执行到这里,因为 RLIMIT_CPU 会终止它
printf("Program finished normally (unexpected).\n");
return 0;
}
9. 编译和运行
# 假设代码保存在 setrlimit_example.c 中
gcc -o setrlimit_example setrlimit_example.c
# 运行程序
./setrlimit_example
10. 预期输出 (片段)
--- Demonstrating setrlimit ---
PID: 12345
--- Initial Resource Limits ---
CPU Time : Soft = unlimited, Hard = unlimited
File Size : Soft = unlimited, Hard = unlimited
Data Segment : Soft = unlimited, Hard = unlimited
Stack Size : Soft = 8388608, Hard = unlimited
Virtual Memory : Soft = unlimited, Hard = unlimited
Open Files : Soft = 1024, Hard = 1048576
Max Processes : Soft = 62545, Hard = 62545
Core File Size : Soft = 0, Hard = unlimited
--- Setting CPU Time Limit ---
Set CPU time limit: Soft = 5s, Hard = 10s
--- Setting Open File Descriptor Limit ---
Set open file limit: Soft = 10, Hard = 20
--- Setting File Size Limit ---
Set file size limit: Soft = 1048576 bytes, Hard = 2097152 bytes
--- Setting Virtual Memory Limit ---
Set virtual memory limit: Soft = 52428800 bytes (50.00 MB), Hard = 104857600 bytes (100.00 MB)
--- Disabling Core Dump ---
Disabled core dump generation.
--- Resource Limits After setrlimit ---
CPU Time : Soft = 5, Hard = 10
File Size : Soft = 1048576, Hard = 2097152
Virtual Memory : Soft = 52428800, Hard = 104857600
Open Files : Soft = 10, Hard = 20
Core File Size : Soft = 0, Hard = 0
--- Testing RLIMIT_FSIZE (File Size Limit) ---
Finished writing (or failed) to file.
--- Testing RLIMIT_AS (Virtual Memory Limit) ---
Attempting to allocate large chunks of memory until limit is hit...
Allocated 10 MB so far...
Allocated 20 MB so far...
...
Allocated 50 MB so far...
malloc failed after allocating approximately 50 MB. Memory limit likely reached.
--- Testing RLIMIT_CPU (CPU Time Limit) ---
Entering infinite loop. Should be killed by SIGKILL after 10 seconds (hard limit).
You might see 'CPU time limit (soft) reached' message first (after 5s), then termination.
Caught signal 24
CPU time limit (soft) reached. Exiting gracefully.
[程序被终止]
11. 总结
setrlimit
是一个非常有用的系统调用,用于管理和控制进程对系统资源的消耗。它对于编写健壮、安全的系统程序和服务至关重要,可以防止资源耗尽导致的系统不稳定。理解软限制和硬限制的区别,以及不同资源类型的行为,是掌握 Linux 进程管理的基础。