从ulimit命令看Linux进程资源限制

从ulimit命令看Linux进程资源限制

目录

大家好,这里是物联网心球。 ulimit是我们经常会使用到的命令,这个命令对Linux性能优化很有帮助。那么,当我们执行ulimit命令时,系统都做了哪些工作?今天我们来一探究竟。

1.进程资源限制表

在Linux系统中,进程资源限制rlimit(Resource Limit,资源限制)是一套完善的管理机制。它用于控制进程可以使用的系统资源数量,以防止某个进程过度消耗资源而影响系统的整体性能和稳定性。

每个进程的task_struct结构体中包含 struct rlimit rlim[RLIM_NLIMITS]数组(进程资源限制表),RLIM_NLIMITS为16,表示进程限制资源类型最多为16种,见表1。

从ulimit命令看Linux进程资源限制 图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命令可直观展示进程资源限制表,输出如下:

从ulimit命令看Linux进程资源限制 图2

2.进程资源限制表从何而来

如图1所示,Linux系统在启动时,会通过共同祖先进程(1号进程)繁衍出家族进程。

从ulimit命令看Linux进程资源限制 图3

图 1 进程资源限制的来源

当用户登录时,系统会创建sshd或login进程。创建进程的过程中,会解析limits.conf文件获取资源限制相关的配置信息,并通过setrlimit等系统调用设置进程资源限制表。父进程创建子进程时,子进程会继承父进程的进程资源限制表。如果父进程通过setrlimit系统调用修改了资源限制表,那么其子进程也会跟着修改。

如果修改了limits.conf文件,用户下次登录时,进程的资源限制表也将被修改。

为了验证上述想法的正确性,我们可以先启动一个测试程序,再通过pstree -p命令查看Linux系统进程树,最后通过cat /proc/<pid>/limits命令比较家族进程的资源限制表。

启动测试程序rlimit_test,通过pstree -p查看系统进程树,如下:

从ulimit命令看Linux进程资源限制 图4

注意,括号内数值为进程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所示。

从ulimit命令看Linux进程资源限制 图5

图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命令查看,输出结果如下:

从ulimit命令看Linux进程资源限制 图6

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
← 返回文章列表