大家好,这里是物联网心球。 ulimit是我们经常会使用到的命令,这个命令对Linux性能优化很有帮助。那么,当我们执行ulimit命令时,系统都做了哪些工作?今天我们来一探究竟。
1.进程资源限制表
在Linux系统中,进程资源限制rlimit(Resource Limit,资源限制)是一套完善的管理机制。它用于控制进程可以使用的系统资源数量,以防止某个进程过度消耗资源而影响系统的整体性能和稳定性。
每个进程的task_struct结构体中包含 struct rlimit rlim[RLIM_NLIMITS]数组(进程资源限制表),RLIM_NLIMITS为16,表示进程限制资源类型最多为16种,见表1。

表1 进程资源限制表
资源限制的数据结构为struct rlimit,定义如下:
struct rlimit {
rlim_t rlim_cur; /* 软限制(Soft limit)*/
rlim_t rlim_max; /* 硬限制(Hard limit)*/
};
rlim_cur:软限制值,进程实际可用的资源阈值,用户进程可自行调低或调高(不能超过硬限制)。
rlim_max:硬限制值,是软限制的上限,只有特权进程(如root用户创建的进程) 可提高。普通进程仅能降低硬限制,且调整后不可逆。
通过cat /proc/<pid>/limits命令可直观展示进程资源限制表,输出如下:

2.进程资源限制表从何而来
如图1所示,Linux系统在启动时,会通过共同祖先进程(1号进程)繁衍出家族进程。

图 1 进程资源限制的来源
当用户登录时,系统会创建sshd或login进程。创建进程的过程中,会解析limits.conf文件获取资源限制相关的配置信息,并通过setrlimit等系统调用设置进程资源限制表。父进程创建子进程时,子进程会继承父进程的进程资源限制表。如果父进程通过setrlimit系统调用修改了资源限制表,那么其子进程也会跟着修改。
如果修改了limits.conf文件,用户下次登录时,进程的资源限制表也将被修改。
为了验证上述想法的正确性,我们可以先启动一个测试程序,再通过pstree -p命令查看Linux系统进程树,最后通过cat /proc/<pid>/limits命令比较家族进程的资源限制表。
启动测试程序rlimit_test,通过pstree -p查看系统进程树,如下:

注意,括号内数值为进程pid。通过cat /proc/<pid>/limits查看进程资源限制表。
3.管理进程资源限制
内核创建进程时,会复制父进程task_struct结构的rlim数组至子进程,这个过程由内核默默完成,用户程序是没有感知的。用户程序想要查看和修改进程资源限制表,需要通过特定系统调用完成,如setrlimit、getrlimit、prlimit。
setrlimit用于设置当前进程的资源限制,getrlimit用于获取当前进程的资源限制,prlimit用于动态查询或修改运行中进程的资源限制。
三者定义如下:
#include<sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
参数说明:
resource:资源类型,见表1。
struct rlimit:限制值。
prlimit 特有参数:
pid:目标进程ID(0表示当前进程)。
new_limit:指向struct rlimit类型的指针,用于指定新的资源限制,如果为NULL,则表示不设置新的限制。
old_limit:指向struct rlimit类型的指针,用于存储当前的资源限制,如果为NULL,则表示不获取当前限制。
返回值:成功返回0;失败返回-1,并设置errno。
示例代码如下:
#define SOFT (10000)
#define HARD (2 * 1024 * 1024)
void getrlimit_test(){
struct rlimit old = {0};
int ret = getrlimit(RLIMIT_NOFILE, &old);
if (ret == -1) {
return;
}
printf("rlim_cur:%lu, rlim_max:%lu\n", old.rlim_cur, old.rlim_max);
}
void setrlimit_test(){
struct rlimit new = {.rlim_cur = SOFT, .rlim_max = HARD};
int ret = setrlimit(RLIMIT_NOFILE, &new);
if (ret == -1) {
return;
}
}
void prlimit_test(){
struct rlimit new = {.rlim_cur = SOFT, .rlim_max = HARD};
struct rlimit old = {0};
int ret = prlimit(0, RLIMIT_NOFILE, &new, &old);
if (ret == -1) {
return;
}
printf("rlim_cur:%lu, rlim_max:%lu\n", old.rlim_cur, old.rlim_max);
}
注意,用户程序不管是使用哪种方式(如ulimit、prlimit命令)设置和获取进程资源限制表,最终都是调用以上几种系统调用。下面,我们来介绍一下设置进程资源限制表的原理,如图2所示。

图2 进程限制资源管理
用户程序调通过setrlimit系统调用设置进程资源限制。内核会对用户指定的新的限制值进行有效性判断。
首先,判断软限制是否大于硬限制,如果前者大于后者,setrlimit将返回失败,错误码:EINVAL(“Invalid argument”)。
如果资源类型为RLIMIT_NOFILE(进程可打开的文件描述符最大数量),则需要判断硬限制是否大于sysctl_nr_open。如果进程硬限制大于sysctl_nr_open,setrlimit将返回失败,错误码:EPERM(“Operation not permitted”)。
如果是普通用户,新的硬限制值不能大于旧的硬限制值。普通用户是没有权限调高进程硬限制值的,如果前者大于后者,setrlimit将返回失败,错误码:EPERM(“Operation not permitted”)。
新的限制值的有效性判断完之后,内核会将新的限制值同步至进程资源限制表,新的限制值将立即生效。
4.ulimit命令
ulimit 是Linux中用于动态查看或设置当前Shell进程及其子进程资源限制的内建命令。ulimit命令用于临时设置Shell进程的进程资源限制,其底层依赖setrlimit系统调用,系统重启后失效。
ulimit命令格式:
ulimit <选项> <限制值>
选项:指定要操作的资源类型或操作模式,常见选项如下:
-a:显示所有当前的资源限制,如:ulimit -a。
-c:设置或显示core文件大小限制(单位:512字节块),如:ulimit -c 1024。
-f:设置或显示文件大小限制(单位:512字节块),如:ulimit -f 2048。
-n:设置或显示最大打开文件数,如:ulimit -n 4096。
-s:设置或显示堆栈大小(单位:KB),如:ulimit -s 16384。
-H:设置硬限制,如:ulimit -Hn 8192。
-S:设置软限制,如:ulimit -Sn 4096。
限制值:为指定资源设置的具体数值,或使用unlimited表示无限制。
未指定限制值时,表示查看资源限制值,如:ulimit -n显示最大打开文件数。未指定-H或-S选项时,ulimit将同时修改软限制和硬限制。
资源限制的逻辑并不简单,为了避免设置时出错,请按照图2原理正确设置软、硬限制值。ulimit的资源类型选项和表1的资源类型是一一对应的。如果我们不清楚资源类型对应的选项,可通过ulimit -a命令查看,输出结果如下:

5.limits.conf文件
limits.conf是Linux系统中用于管理用户或进程资源限制的核心配置文件,文件路径为/etc/security/limits.conf。每次修改完limits.conf,用户重新登录后才能生效。由于是通过文件保存配置信息,所以limits.conf是永久生效的。
limits.conf文件配置格式如下:
<domain> <type> <item> <value>
字段解析:
<domain>:作用对象
*:所有用户
用户名:特定用户(如 root)
@组名:用户组(如 @developers)
<type>:限制类型
soft:软限制
hard:硬限制
-:同时设置软硬限制
<item>:资源类型,常见类型如下:
nofile:最大文件描述符数量
nproc:最大进程数
as:虚拟内存大小(字节)
core :核心转储文件大小(字节)
data:数据段(堆+全局变量)大小
stack:进程栈大小
<value>:限制值(数字或 unlimited)
示例如下:
root soft nofile 1001024 # root用户文件描述符软限制为1001024
root hard nofile 1001024 # root用户文件描述符硬限制为1001024
