笔记和练习博客总目录见:开始读TLPI。
每个进程都会消耗系统资源,比如内存和CPU时间。本章我们来看跟资源相关的系统调用。我们先从getrusage()系统调用开始,它允许一个进程监控自己或其子进程使用的资源。然后我们看看setrlimit()和getrlimit()系统调用,这两个可以用来修改和获取调用进程对各种资源的消耗限制。
36.1 Process Resource usage
getrusage() 系统调用可以获取调用进程或其所有子进程使用的各种系统资源的统计信息。
c
#include <sys/resource.h>
int getrusage(int who, struct rusage *res_usage);
Returns 0 on success, or --1 on error
who 参数指定要检索资源使用信息的进程。它可以取以下值之一:
- RUSAGE_SELF
返回关于调用进程的信息。 - RUSAGE_CHILDREN
返回关于所有已终止并且已被等待的调用进程子进程的信息。 - RUSAGE_THREAD(从 Linux 2.6.26 开始)
返回关于调用线程的信息。这个值是 Linux 特有的。
res_usage 参数是一个指向 rusage 类型结构的指针,如清单 36-1 所示。
清单 36-1:rusage 结构的定义
c
struct rusage {
struct timeval ru_utime; /* User CPU time used */
struct timeval ru_stime; /* System CPU time used */
long ru_maxrss; /* Maximum size of resident set (kilobytes)
[used since Linux 2.6.32] */
long ru_ixrss; /* Integral (shared) text memory size
(kilobyte-seconds) [unused] */
long ru_idrss; /* Integral (unshared) data memory used
(kilobyte-seconds) [unused] */
long ru_isrss; /* Integral (unshared) stack memory used
(kilobyte-seconds) [unused] */
long ru_minflt; /* Soft page faults (I/O not required) */
long ru_majflt; /* Hard page faults (I/O required) */
long ru_nswap; /* Swaps out of physical memory [unused] */
long ru_inblock; /* Block input operations via file
system [used since Linux 2.6.22] */
long ru_oublock; /* Block output operations via file
system [used since Linux 2.6.22] */
long ru_msgsnd; /* IPC messages sent [unused] */
long ru_msgrcv; /* IPC messages received [unused] */
long ru_nsignals; /* Signals received [unused] */
long ru_nvcsw; /* Voluntary context switches (process
relinquished CPU before its time slice
expired) [used since Linux 2.6] */
long ru_nivcsw; /* Involuntary context switches (higher
priority process became runnable or time
slice ran out) [used since Linux 2.6] */
};
正如清单 36-1 中的注释所示,在 Linux 上,rusage 结构中的许多字段不会被 getrusage()(或 wait3() 和 wait4())填充,或者只有在较新的内核版本中才会填充。在 Linux 上未使用的一些字段在其他 UNIX 实现中是会被使用的。这些字段在 Linux 上提供,是为了在将来某天它们被实现时,rusage 结构无需更改,从而避免破坏现有的应用程序二进制文件。
虽然 getrusage() 出现在大多数 UNIX 实现中,但在 SUSv3 中只是弱规范化的(只指定了 ru_utime 和 ru_stime 字段)。部分原因是 rusage 结构中大部分信息的含义依赖于具体实现。
ru_utime 和 ru_stime 字段都是 timeval 类型的结构(见第 10.1 节),分别返回进程在用户模式和内核模式下消耗的 CPU 时间(秒和微秒数)。类似的信息也可以通过第 10.7 节描述的 times() 系统调用获取。
Linux 特有的 /proc/PID/stat 文件提供了一些关于系统中所有进程的资源使用信息(CPU 时间和页面错误)。更多详情请参阅 proc(5) 手册页面。
getrusage() 的 RUSAGE_CHILDREN 操作返回的 rusage 结构包含调用进程所有子孙进程的资源使用统计数据。例如,如果我们有三个进程,关系是父进程、子进程和孙进程,那么当子进程对孙进程执行 wait() 时,孙进程的资源使用值会加到子进程的 RUSAGE_CHILDREN 中;当父进程对子进程执行 wait() 时,子进程和孙进程的资源使用值都会加到父进程的 RUSAGE_CHILDREN 中。相反,如果子进程没有对孙进程执行 wait(),那么孙进程的资源使用就不会记录到父进程的 RUSAGE_CHILDREN 中。
对于 RUSAGE_CHILDREN 操作,ru_maxrss 字段返回调用进程所有子孙进程中的最大驻留集大小(而不是所有子孙进程的总和)。
SUSv3 指出,如果 SIGCHLD 被忽略(这样子进程不会变成可以等待的僵尸进程),那么子进程的统计信息不应该加到 RUSAGE_CHILDREN 返回的值中。然而,如第 26.3.3 节所述,在 2.6.9 之前的内核中,Linux 偏离了这个要求------如果 SIGCHLD 被忽略,那么已死亡子进程的资源使用值会包含在 RUSAGE_CHILDREN 返回的值中。
36.2 Process Resource Limits
每个进程都有一组资源限制,可用于限制进程可能消耗的各种系统资源的数量。例如,如果我们担心某个程序可能会消耗过多资源,我们可能希望在执行任意程序之前设置进程的资源限制。我们可以使用 shell 内置命令 ulimit(在 C shell 中为 limit)来设置 shell 的资源限制。这些限制会被 shell 创建的执行用户命令的进程继承。
从内核 2.6.24 开始,可以使用 Linux 特有的 /proc/PID/limits 文件查看任何进程的所有资源限制。该文件归对应进程的真实用户 ID 所有,其权限只允许该用户 ID(或特权进程)读取。
bash
$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 13601
max locked memory (kbytes, -l) 8192
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 13601
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
$ cat /proc/$$/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size unlimited unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 13601 13601 processes
Max open files 1024 524288 files
Max locked memory 8388608 8388608 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 13601 13601 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
getrlimit() 和 setrlimit() 系统调用允许进程获取和修改其资源限制。
c
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
Both return 0 on success, or --1 on error
resource 参数用于指定要获取或更改的资源限制。rlim 参数用于返回资源限制值(getrlimit())或指定新的资源限制值(setrlimit()),它是一个指向包含两个字段的结构的指针:
c
struct rlimit {
rlim_t rlim_cur; /* Soft limit (actual process limit) */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};
这些字段对应于资源的两个相关限制:软限制(rlim_cur)和硬限制(rlim_max)。(rlim_t 数据类型是整数类型。)软限制控制进程可以使用的资源数量。进程可以将软限制调整到从 0 到硬限制的任意值。对于大多数资源来说,硬限制唯一的作用就是为软限制提供上限。拥有特权(CAP_SYS_RESOURCE)的进程可以在任何方向上调整硬限制(只要它的值仍然大于软限制),但没有特权的进程只能将硬限制调低(不可逆)。在 rlim_cur 或 rlim_max 中,值 RLIM_INFINITY 表示无限(资源没有限制),无论是通过 getrlimit() 获取还是通过 setrlimit() 设置。
在大多数情况下,资源限制对有特权和没有特权的进程都会生效。它们会被 fork() 创建的子进程继承,并在 exec() 调用中保持不变。
可以为 getrlimit() 和 setrlimit() 的 resource 参数指定的值在表 36-1 中进行了总结,并在第 36.3 节中有详细说明。
虽然资源限制是每个进程的属性,但在某些情况下,限制不仅仅是针对该进程对相应资源的消耗,还会针对所有具有相同真实用户ID的进程消耗资源的总和。RLIMIT_NPROC 限制是一个很好的例子,它对可以创建的进程数量施加限制,解释了采用这种方法的原因。如果只对该进程自己创建的子进程数量施加限制,其效果并不明显,因为该进程创建的每个子进程也可以继续创建更多的子进程,依此类推。相反,这个限制是针对具有相同真实用户ID的所有进程的数量来衡量的。不过需要注意的是,资源限制仅在设置了该限制的进程中进行检查(即该进程本身及继承了该限制的子进程)。如果另一个由相同真实用户ID拥有的进程没有设置限制(即限制是无限的)或设置了不同的限制,那么该进程创建子进程的能力将根据它自身设置的限制来进行检查。
在下面描述每个资源限制时,我们会指出那些限制是针对所有具有相同真实用户ID的进程消耗的资源来衡量的。除非另有说明,否则资源限制仅根据进程自身对资源的消耗来衡量。
请注意,在很多情况下,用于获取和设置资源限制的 shell 命令(bash 和 Korn shell 中的 ulimit,以及 C shell 中的 limit)使用的单位与 getrlimit() 和 setrlimit() 使用的单位不同。例如,shell 命令通常以千字节为单位表示各种内存段的大小限制。
Table 36-1: Resource values for getrlimit() and setrlimit()
| Resource | Description | SUSv3 |
|---|---|---|
| RLIMIT_AS | Process virtual memory size (bytes) | • |
| RLIMIT_CORE | Core file size (bytes) | • |
| RLIMIT_CPU | CPU time (seconds) | • |
| RLIMIT_DATA | Process data segment (bytes) | • |
| RLIMIT_FSIZE | File size (bytes) | • |
| RLIMIT_MEMLOCK | Locked memory (bytes) | |
| RLIMIT_MSGQUEUE | Bytes allocated for POSIX message queues for real user ID (since Linux 2.6.8) | |
| RLIMIT_NICE | Nice value (since Linux 2.6.12) | |
| RLIMIT_NOFILE | Maximum file descriptor number plus one | • |
| RLIMIT_NPROC | Number of processes for real user ID | |
| RLIMIT_RSS | Resident set size (bytes; not implemented) | |
| RLIMIT_RTPRIO | Realtime scheduling priority (since Linux 2.6.12) | |
| RLIMIT_RTTIME | Realtime CPU time (microseconds; since Linux 2.6.25) | |
| RLIMIT_SIGPENDING | Number of queued signals for real user ID (since Linux 2.6.8) | |
| RLIMIT_STACK | Size of stack segment (bytes) | • |
💡• 代表该资源限制属于 SUSv3(POSIX)标准定义;空白为 Linux 扩展,非SUSv3标准。
Example program
在进入每个资源限制的具体内容之前,我们先看一个资源限制使用的简单例子。清单36-2定义了函数printRlimit(),它会显示一条消息,以及指定资源的软限制和硬限制。
rlim_t数据类型通常像off_t一样表示,以处理RLIMIT_FSIZE(文件大小资源限制)的表示。因此,在打印rlim_t值时(如清单36-2中所示),我们将其强制转换为long long,并使用%lld的printf()格式说明符,如第5.10节所解释的那样。
清单36-3中的程序调用setrlimit()来设置用户可能创建的进程数量(RLIMIT_NPROC)的软限制和硬限制,然后使用清单36-2中的printRlimit()函数在更改之前和之后显示限制,最后尽可能多地创建进程。当我们运行这个程序,将软限制设置为30,硬限制设置为100时,我们会看到以下内容:
bash
$ ./rlimit_nproc 30 100
Initial maximum process limits: soft=13601; hard=13601
New maximum process limits: soft=30; hard=100
Child 1 (PID=47989) started
Child 2 (PID=47990) started
Child 3 (PID=47991) started
Child 4 (PID=47992) started
Child 5 (PID=47993) started
Child 6 (PID=47994) started
Child 7 (PID=47995) started
Child 8 (PID=47996) started
Child 9 (PID=47997) started
Child 10 (PID=47998) started
Child 11 (PID=47999) started
Child 12 (PID=48000) started
Child 13 (PID=48001) started
Child 14 (PID=48002) started
Child 15 (PID=48003) started
Child 16 (PID=48004) started
Child 17 (PID=48005) started
Child 18 (PID=48006) started
Child 19 (PID=48007) started
Child 20 (PID=48008) started
Child 21 (PID=48009) started
Child 22 (PID=48010) started
Child 23 (PID=48011) started
Child 24 (PID=48012) started
Child 25 (PID=48013) started
ERROR [EAGAIN/EWOULDBLOCK Resource temporarily unavailable] fork
在这个例子中,程序只创建了26个新进程,因为这个用户已经有4个进程在运行了。
💡 你可以运行几个sleep 10000命令,然后再运行以上程序。
Listing 36-2: Displaying process resource limits
c
// procres/print_rlimit.c
// 略。
Listing 36-3: Setting the RLIMIT_NPROC resource limit
c
// procres/rlimit_nproc.c
// 略。
Unrepresentable limit values
在某些编程环境中,rlim_t 数据类型可能无法表示某个资源限制所能维持的全部值范围。在提供多种编程环境且各自 rlim_t 数据类型大小不同的系统上,可能会出现这种情况。如果在一个传统上 off_t 为 32 位的系统上增加了一个支持 64 位 off_t 的大文件编译环境,就可能出现这种情况。(在每个环境中,rlim_t 的大小与 off_t 相同。)这会导致一种情况:一个小 rlim_t 的程序,在被一个拥有 64 位 off_t 的程序 exec 后,可能继承了一个超过 rlim_t 最大值的资源限制(例如文件大小限制)。
为了帮助可移植的应用程序处理资源限制可能无法表示的情况,SUSv3 指定了两个常量来表示无法表示的限制值:RLIM_SAVED_CUR 和 RLIM_SAVED_MAX。如果软资源限制无法在 rlim_t 中表示,那么 getrlimit() 会在 rlim_cur 字段返回 RLIM_SAVED_CUR。类似地,RLIM_SAVED_MAX 对无法表示的硬限制(在 rlim_max 字段返回)执行相同的功能。
如果所有可能的资源限制值都能用 rlim_t 表示,那么 SUSv3 允许实现将 RLIM_SAVED_CUR 和 RLIM_SAVED_MAX 定义为与 RLIM_INFINITY 相同。在 Linux 上,这些常量就是这样定义的,这意味着 rlim_t 可以表示所有可能的资源限制值。然而,在像 x86-32 这样 32 位的架构上情况就不是这样。在这些架构上,如果是在大文件编译环境中(即如第 5.10 节所述,将 _FILE_OFFSET_BITS 特性测试宏设置为 64),glibc 的 rlim_t 定义是 64 位宽,但内核用于表示资源限制的数据类型是 unsigned long,它只有 32 位宽。当前版本的 glibc 处理这种情况的方法如下:如果一个使用 _FILE_OFFSET_BITS=64 编译的程序尝试将资源限制设置为一个超过 32 位 unsigned long 所能表示的值,那么 setrlimit() 的 glibc 包装会悄悄地将该值转换为 RLIM_INFINITY。换句话说,请求的资源限制设置不会被采纳。
因为处理文件的工具通常在很多 x86-32 发行版中是用 _FILE_OFFSET_BITS=64 编译的,如果不能支持超过 32 位可表示值的资源限制,这个问题不仅会影响应用程序开发者,也会影响最终用户。
有人可能会觉得,如果请求的资源限制超过 32 位无符号长整型的容量,glibc 的 setrlimit() 包装函数最好返回一个错误。然而,根本问题是内核的限制,正如正文中描述的行为,就是 glibc 开发者用来应对这个问题的办法。
36.3 Details of Specific Resource Limits
在本节中,我们将提供 Linux 上每个资源限制的详细信息,并指出那些特定于 Linux 的限制。
-
RLIMIT_AS
RLIMIT_AS 限制指定了进程虚拟内存(地址空间)的最大大小,以字节为单位。尝试超过这个限制的操作(如 brk()、sbrk()、mmap()、mremap() 和 shmat())会失败,并返回 ENOMEM 错误。实际上,程序最常遇到这个限制的地方是在 malloc 包中的函数调用,它们会使用 sbrk() 和 mmap()。当遇到这个限制时,栈的增长也可能失败,并产生 RLIMIT_STACK 所列的后果。
-
RLIMIT_CORE
RLIMIT_CORE 限制指定了进程因某些信号终止时产生的 core dump 文件的最大大小(以字节为单位)(见第 22.1 节)。当达到这个限制时,core dump 文件的生成会停止。将限制设为 0 可以防止创建 core dump 文件,这有时很有用,因为 core dump 文件可能非常大,而且普通用户通常不知道该怎么处理它们。禁用 core dump 还有另一个原因是安全性------防止程序内存的内容被写入磁盘。如果 RLIMIT_FSIZE 限制低于这个值,core dump 文件的大小也会被限制在 RLIMIT_FSIZE 字节。
-
RLIMIT_CPU
RLIMIT_CPU 限制指定了进程可以使用的最大 CPU 时间(包括系统模式和用户模式,即系统模式和用户模式的合计),单位为秒。SUSv3 要求在达到软限制时向进程发送 SIGXCPU 信号,但其他细节没有明确规定。(SIGXCPU 的默认行为是生成核心转储并终止进程。)可以为 SIGXCPU 设置一个处理器,执行所需的处理然后返回主程序。之后,(在 Linux 上)每消耗一秒 CPU 时间就会发送一次 SIGXCPU。如果进程继续执行直到达到硬 CPU 限制,内核会发送 SIGKILL 信号,这始终会终止进程。
UNIX 的实现方式在处理继续消耗 CPU 时间的进程时有所不同。大多数实现会在固定间隔继续发送 SIGXCPU。如果希望这个信号在各平台上都能通用,我们应该在第一次收到这个信号时,让应用程序完成必要的清理并终止。(或者,程序也可以在收到信号后更改资源限制。)
-
RLIMIT_DATA
RLIMIT_DATA 限制指定了进程数据段的最大大小(以字节为单位)(包括已初始化数据、未初始化数据和堆段,总和见第 6.3 节)。尝试(sbrk() 和 brk())将数据段(程序断点)扩展到超过该限制时会失败,并返回 ENOMEM 错误。和 RLIMIT_AS 一样,程序最常碰到这个限制的地方是在 malloc 包中的函数调用。
-
RLIMIT_FSIZE
RLIMIT_FSIZE 限制指定了进程可以创建的文件的最大大小(以字节为单位)。如果进程试图将文件扩展超过软限制,它会收到 SIGXFSZ 信号,并且系统调用(比如 write() 或 truncate())会失败并返回 EFBIG 错误。SIGXFSZ 的默认动作是终止进程并生成核心转储。不过,也可以捕获这个信号,把控制权返回给主程序。但任何后续尝试扩展文件的操作都会再次触发相同的信号和错误。
-
RLIMIT_MEMLOCK
RLIMIT_MEMLOCK 限制(源自 BSD;在 SUSv3 中不存在,仅在 Linux 和 BSD 上可用)指定一个进程可以锁定到物理内存中的虚拟内存的最大字节数,从而防止这些内存被换出。这个限制会影响 mlock() 和 mlockall() 系统调用,以及 mmap() 和 shmctl() 系统调用的锁定选项。详细内容见第 50.2 节。
如果在调用 mlockall() 时指定了 MCL_FUTURE 标志,那么 RLIMIT_MEMLOCK 限制也可能会导致随后调用 brk()、sbrk()、mmap() 或 mremap() 失败。
-
RLIMIT_MSGQUEUE
RLIMIT_MSGQUEUE 限制(Linux 特有;自 Linux 2.6.8 起)规定了可以为调用进程的真实用户 ID 分配的 POSIX 消息队列的最大字节数。当使用 mq_open() 创建 POSIX 消息队列时,会根据以下公式从该限制中扣除字节数:
c
bytes = attr.mq_maxmsg * sizeof(struct msg_msg *) +
attr.mq_maxmsg * attr.mq_msgsize;
在这个公式中,attr 是作为第四个参数传递给 mq_open() 的 mq_attr 结构体。包含 sizeof(struct msg_msg *) 的加数确保用户不能排队无限数量的零长度消息。(msg_msg 结构体是内核内部使用的数据类型。)这是必要的,因为虽然零长度消息不包含数据,但它们确实会消耗一些系统内存用于记录开销。
RLIMIT_MSGQUEUE 限制只会影响调用的进程。属于这个用户的其他进程不会受到影响,除非它们也设置了这个限制或者继承了它。
-
RLIMIT_NICE
RLIMIT_NICE 限制(仅限 Linux,自 Linux 2.6.12 起)指定了可以通过 sched_setscheduler() 和 nice() 为此进程设置的 nice 值的上限。这个上限的计算方式是 20 -- rlim_cur,其中 rlim_cur 是当前的 RLIMIT_NICE 软资源限制。更多细节请参见第 35.1 节。
-
RLIMIT_NOFILE
RLIMIT_NOFILE 限制指定了一个比进程可分配的最大文件描述符编号大 1 的数字。尝试(例如 open()、pipe()、socket()、accept()、shm_open()、dup()、dup2()、fcntl(F_DUPFD) 和 epoll_create())分配超过此限制的描述符都会失败。在大多数情况下,错误是 EMFILE,但对于 dup2(fd, newfd) 是 EBADF,对于 fcntl(fd, F_DUPFD, newfd) 且 newfd 大于或等于限制时,错误是 EINVAL。
对 RLIMIT_NOFILE 限制的更改会反映在 sysconf(_SC_OPEN_MAX) 返回的值中。SUSv3 允许,但不要求,在更改 RLIMIT_NOFILE 限制前后对 sysconf(_SC_OPEN_MAX) 的调用返回不同的值;其他实现可能不会像 Linux 那样表现。
SUSv3 表示,如果应用程序将软限制或硬限制 RLIMIT_NOFILE 设置为小于或等于进程当前打开的最高文件描述符编号,可能会出现意外行为。
在 Linux 上,我们可以通过使用 readdir() 来扫描 /proc/PID/fd 目录的内容,从而检查进程当前打开了哪些文件描述符,该目录包含每个当前打开的文件描述符的符号链接。
内核对 RLIMIT_NOFILE 限制可以提升的最大值设定了一个上限。在 2.6.25 之前的内核中,这个上限是由内核常量 NR_OPEN 定义的硬编码值,其值为 1,048,576。(要提高这个上限,需要重建内核。)从内核 2.6.25 开始,这个限制由 Linux 特有的 /proc/sys/fs/nr_open 文件中的值定义。该文件的默认值是 1,048,576;超级用户可以修改它。试图将软限制或硬限制 RLIMIT_NOFILE 设置高于上限时,会返回 EPERM 错误。
系统对所有进程可以打开的文件总数也有一个全局限制。这个限制可以通过 Linux 特有的 /proc/sys/fs/file-max 文件获取和修改。(参考第 5.4 节,我们可以更准确地把 file-max 定义为对打开的文件描述符数量的全局限制。)只有有特权的(CAP_SYS_ADMIN)进程才能超过 file-max 限制。在普通进程中,如果系统调用遇到 file-max 限制,就会失败并返回 ENFILE 错误。
-
RLIMIT_NPROC
RLIMIT_NPROC 限制(源自 BSD;在 SUSv3 中不存在,仅在 Linux 和 BSD 上可用)指定调用进程的实际用户 ID 可以创建的最大进程数。超过这个限制的尝试(fork()、vfork() 和 clone())会失败,并返回错误 EAGAIN。
RLIMIT_NPROC 限制只影响调用进程。其他属于该用户的进程不会受到影响,除非它们也设置或者继承了这个限制。这个限制不会对具有特权的进程(CAP_SYS_ADMIN 或 CAP_SYS_RESOURCE)强制执行。
Linux 还对所有用户可以创建的进程总数施加了系统范围的限制。在 Linux 2.4 及更高版本中,可以使用 Linux 特有的 /proc/sys/kernel/threads-max 文件来获取和修改这个限制。
准确来说,RLIMIT_NPROC 资源限制和 threads-max 文件实际上是对可以创建的线程数的限制,而不是进程数的限制。
RLIMIT_NPROC 资源限制的默认值设定方式在不同内核版本中有所不同。在 Linux 2.2 中,它是根据固定公式计算的。在 Linux 2.4 及之后的版本中,它是根据可用物理内存的数量来计算的。
SUSv3 并没有指定 RLIMIT_NPROC 资源限制。SUSv3 规定的方法是通过调用 sysconf(_SC_CHILD_MAX) 来获取(但不能更改)用户 ID 允许的最大进程数。这个 sysconf() 调用在 Linux 上是支持的,但在 2.6.23 之前的内核版本中,该调用返回的信息不准确------总是返回 999 的值。从 Linux 2.6.23(以及 glibc 2.4 之后的版本)开始,这个调用能够正确报告限制(通过检查 RLIMIT_NPROC 资源限制的值)。
目前没有可移植的方法可以发现一个特定用户 ID 已经创建了多少进程。在 Linux 上,我们可以尝试扫描系统中所有 /proc/PID/status 文件,并查看 Uid 条目下的信息(它按照实际、有效、保存集和文件系统的顺序列出四个进程用户 ID),以估计用户当前拥有的进程数量。不过要注意,在我们完成扫描的时候,这些信息可能已经发生了变化。
-
RLIMIT_RSS
RLIMIT_RSS 限制(源自 BSD;在 SUSv3 中没有,但广泛可用)指定进程常驻集中的最大页面数;也就是说,当前在物理内存中的虚拟内存页面总数。这个限制在 Linux 上是提供的,但目前没有实际效果。
在旧的 Linux 2.4 内核(包括 2.4.29)中,RLIMIT_RSS 确实会影响 madvise() 的 MADV_WILLNEED 操作(参见第 50.4 节)的行为。如果因为达到了 RLIMIT_RSS 限制而无法执行该操作,会在 errno 中返回错误 EIO。
-
RLIMIT_RTPRIO
RLIMIT_RTPRIO 限制(Linux 特有;自 Linux 2.6.12 起)指定了使用 sched_setscheduler() 和 sched_setparam() 为该进程设置实时优先级时的上限。详细信息请参见第 35.3.2 节。
-
RLIMIT_RTTIME
RLIMIT_RTTIME 限制(仅限 Linux;自 Linux 2.6.25 起)指定了在实时调度策略下运行的进程在不休眠(即不执行阻塞系统调用)的情况下可以消耗的最大 CPU 时间(以微秒为单位)。达到这个限制时的行为和 RLIMIT_CPU 一样:如果进程达到软限制,就会向进程发送 SIGXCPU 信号,并且每消耗额外一秒 CPU 时间会再发送一次 SIGXCPU 信号。达到硬限制时,会发送 SIGKILL 信号。更多细节请参考第 35.3.2 节。
-
RLIMIT_SIGPENDING
RLIMIT_SIGPENDING 限制(Linux 特有,自 Linux 2.6.8 起)指定了调用进程的真实用户 ID 可以排队的最大信号数。尝试(sigqueue())超过此限制会失败,并返回错误 EAGAIN。
RLIMIT_SIGPENDING 限制只影响调用进程。属于该用户的其他进程不会受到影响,除非它们也设置或继承了这个限制。
最初实现时,RLIMIT_SIGPENDING 的默认值是 1024。从内核 2.6.12 起,默认值已改为与 RLIMIT_NPROC 的默认值相同。
在检查 RLIMIT_SIGPENDING 限制时,排队信号的计数包括实时信号和标准信号。(标准信号每个进程只能排队一次。)不过,这个限制只会对 sigqueue() 强制执行。即便已经有进程排队数量达到了这个限制,仍然可以使用 kill() 为每个未排队的信号(包括实时信号)排队一个实例。
从内核 2.6.12 起,Linux 特有的 /proc/PID/status 文件中的 SigQ 字段显示了进程真实用户 ID 的当前排队信号数和最大排队信号数。
-
RLIMIT_STACK
RLIMIT_STACK 限制指定进程栈的最大大小,以字节为单位。
尝试将栈扩展超过这个限制会导致进程收到 SIGSEGV 信号。由于栈已耗尽,捕获这个信号的唯一方法是建立一个备用信号栈,如第 21.3 节所述。
自 Linux 2.6.23 起,RLIMIT_STACK 限制还决定了用于存放进程命令行参数和环境变量的空间量。详情请参见 execve(2) 手册页。
36.4 Summary
进程会消耗各种系统资源。getrusage() 系统调用允许一个进程监控自己及其子进程消耗的某些资源。
setrlimit() 和 getrlimit() 系统调用允许进程设置和获取其对各种资源的消耗限制。每个资源限制有两个组成部分:软限制,即内核在检查进程资源消耗时执行的限制;硬限制,则是软限制值的上限。非特权进程可以将资源的软限制设为从 0 到硬限制范围内的任意值,但只能降低硬限制。特权进程可以对任一限制值进行任何更改,只要软限制小于或等于硬限制即可。如果进程遇到软限制,通常会通过接收信号或系统调用失败来获得提示。