Each process has a set of resource limits that can be used to restrict the amounts of various system resources that the process may consume. For example, we may want to set resource limits on a process before execing an arbitrary program, if we are concerned that it may consume excessive resources. We can set the resource limits of the shell using the ulimit built-in command (limit in the C shell). These limits are inherited by the processes that the shell creates to execute user commands.
Since kernel 2.6.24, the Linux-specific /proc/
PID/limits
file can be used to view all of the resource limits of any process. This file is owned by the real user ID of the corresponding process and its permissions allow reading only by that user ID (or by a privileged process).
The getrlimit() and setrlimit() system calls allow a process to fetch and modify its resource limits.
#include <sys/resource.h> intgetrlimit
(int resource, struct rlimit *rlim); intsetrlimit
(int resource, const struct rlimit *rlim);
Both return 0 on success, or -1 on error
The resource argument identifies the resource limit to be retrieved or changed. The rlim argument is used to return resource limit values (getrlimit())
or to specify new resource limit values (setrlimit())
, and is a pointer to a structure containing two fields:
struct rlimit { rlim_t rlim_cur; /* Soft limit (actual process limit) */ rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */ };
These fields correspond to the two associated limits for a resource: the soft (rlim_cur) and hard (rlim_max) limits. (The rlim_t data type is an integer type.) The soft limit governs the amount of the resource that may be consumed by the process. A process can adjust the soft limit to any value from 0 up to the hard limit. For most resources, the sole purpose of the hard limit is to provide this ceiling for the soft limit. A privileged (CAP_SYS_RESOURCE
) process can adjust the hard limit in either direction (as long as its value remains greater than the soft limit), but an unprivileged process can adjust the hard limit only to a lower value (irreversibly). The value RLIM_INFINITY
in rlim_cur or rlim_max means infinity (no limit on the resource), both when retrieved via getrlimit() and when set via setrlimit().
In most cases, resource limits are enforced for both privileged and unprivileged processes. They are inherited by child processes created via fork() and are preserved across an exec().
The values that can be specified for the resource argument of getrlimit() and setrlimit() are summarized in Table 36-1 and detailed in Section 36.3.
Although a resource limit is a per-process attribute, in some cases, the limit is measured against not just that process’s consumption of the corresponding resource, but also against the sum of resources consumed by all processes with the same real user ID. The RLIMIT_NPROC
limit, which places a limit on the number of processes that can be created, is a good example of the rationale for this approach. Applying this limit against only the number of children that the process itself created would be ineffective, since each child that the process created would also be able to create further children, which could create more children, and so on. Instead, the limit is measured against the count of all processes that have the same real user ID. Note, however, that the resource limit is checked only in the processes where it has been set (i.e., the process itself and its descendants, which inherit the limit). If another process owned by the same real user ID has not set the limit (i.e., the limit is infinite) or has set a different limit, then that process’s capacity to create children will be checked according to the limit that it has set.
As we describe each resource limit below, we note those limits that are measured against the resources consumed by all processes with the same real user ID. If not otherwise specified, then a resource limit is measured only against the process’s own consumption of the resource.
Be aware that, in many cases, the shell commands for getting and setting resource limits (ulimit in bash and the Korn shell, and limit in the C shell) use different units from those used in getrlimit() and setrlimit(). For example, the shell commands typically express the limits on the size of various memory segments in kilobytes.
Table 36-1. Resource values for getrlimit() and setrlimit()
resource | Limit on | SUSv3 |
---|---|---|
| Process virtual memory size (bytes) | • |
| Core file size (bytes) | • |
| CPU time (seconds) | • |
| Process data segment (bytes) | • |
| File size (bytes) | • |
| Locked memory (bytes) | |
| Bytes allocated for POSIX message queues for real user ID (since Linux 2.6.8) | |
| Nice value (since Linux 2.6.12) | |
| Maximum file descriptor number plus one | • |
| Number of processes for real user ID | |
| Resident set size (bytes; not implemented) | |
| Realtime scheduling priority (since Linux 2.6.12) | |
| Realtime CPU time (microseconds; since Linux 2.6.25) | |
| Number of queued signals for real user ID (since Linux 2.6.8) | |
| Size of stack segment (bytes) | • |
Before going into the specifics of each resource limit, we look at a simple example of the use of resource limits. Example 36-2 defines the function printRlimit(), which displays a message, along with the soft and hard limits for a specified resource.
The rlim_t data type is typically represented in the same way as off_t, to handle the representation of RLIMIT_FSIZE
, the file size resource limit. For this reason, when printing rlim_t values (as in Example 36-2), we cast them to long long and use the %lld
printf() specifier, as explained in I/O on Large Files.
The program in Example 36-3 calls setrlimit() to set the soft and hard limits on the number of processes that a user may create (RLIMIT_NPROC
), uses the printRlimit() function of Example 36-2 to display the limits before and after the change, and then creates as many processes as possible. When we run this program, setting the soft limit to 30 and the hard limit to 100, we see the following:
$ ./rlimit_nproc 30 100
Initial maximum process limits: soft=1024; hard=1024
New maximum process limits: soft=30; hard=100
Child 1 (PID=15674) started
Child 2 (PID=15675) started
Child 3 (PID=15676) started
Child 4 (PID=15677) started
ERROR [EAGAIN Resource temporarily unavailable] fork
In this example, the program managed to create only 4 new processes, because 26 processes were already running for this user.
Example 36-2. Displaying process resource limits
procres/print_rlimit.c
#include <sys/resource.h> #include "print_rlimit.h" /* Declares function defined here */ #include "tlpi_hdr.h" int /* Print 'msg' followed by limits for 'resource' */ printRlimit(const char *msg, int resource) { struct rlimit rlim; if (getrlimit(resource, &rlim) == -1) return -1; printf("%s soft=", msg); if (rlim.rlim_cur == RLIM_INFINITY) printf("infinite"); #ifdef RLIM_SAVED_CUR /* Not defined on some implementations */ else if (rlim.rlim_cur == RLIM_SAVED_CUR) printf("unrepresentable"); #endif else printf("%lld", (long long) rlim.rlim_cur); printf("; hard="); if (rlim.rlim_max == RLIM_INFINITY) printf("infinite\n"); #ifdef RLIM_SAVED_MAX /* Not defined on some implementations */ else if (rlim.rlim_max == RLIM_SAVED_MAX) printf("unrepresentable"); #endif else printf("%lld\n", (long long) rlim.rlim_max); return 0; }procres/print_rlimit.c
Example 36-3. Setting the RLIMIT_NPROC
resource limit
procres/rlimit_nproc.c
#include <sys/resource.h> #include "print_rlimit.h" /* Declaration of printRlimit() */ #include "tlpi_hdr.h" int main(int argc, char *argv[]) { struct rlimit rl; int j; pid_t childPid; if (argc < 2 || argc > 3 || strcmp(argv[1], "--help") == 0) usageErr("%s soft-limit [hard-limit]\n", argv[0]); printRlimit("Initial maximum process limits: ", RLIMIT_NPROC); /* Set new process limits (hard == soft if not specified) */ rl.rlim_cur = (argv[1][0] == 'i') ? RLIM_INFINITY : getInt(argv[1], 0, "soft-limit"); rl.rlim_max = (argc == 2) ? rl.rlim_cur : (argv[2][0] == 'i') ? RLIM_INFINITY : getInt(argv[2], 0, "hard-limit"); if (setrlimit(RLIMIT_NPROC, &rl) == -1) errExit("setrlimit"); printRlimit("New maximum process limits: ", RLIMIT_NPROC); /* Create as many children as possible */ for (j = 1; ; j++) { switch (childPid = fork()) { case -1: errExit("fork"); case 0: _exit(EXIT_SUCCESS); /* Child */ default: /* Parent: display message about each new child and let the resulting zombies accumulate */ printf("Child %d (PID=%ld) started\n", j, (long) childPid); break; } } }procres/rlimit_nproc.c
In some programming environments, the rlim_t data type may not be able to represent the full range of values that could be maintained for a particular resource limit. This may be the case on a system that offers multiple programming environments in which the size of the rlim_t data type differs. Such systems can arise if a large-file compilation environment with a 64-bit off_t is added to a system on which off_t was traditionally 32 bits. (In each environment, rlim_t would be the same size as off_t.) This leads to the situation where a program with a small rlim_t can, after being execed by a program with a 64-bit off_t, inherit a resource limit (e.g., the file size limit) that is greater than the maximum rlim_t value.
To assist portable applications in handling the possibility that a resource limit may be unrepresentable, SUSv3 specifies two constants to indicate unrepresentable limit values: RLIM_SAVED_CUR
and RLIM_SAVED_MAX
. If a soft resource limit can’t be represented in rlim_t, then getrlimit() will return RLIM_SAVED_CUR
in the rlim_cur field. RLIM_SAVED_MAX
performs an analogous function for an unrepresentable hard limit returned in the rlim_max field.
If all possible resource limit values can be represented in rlim_t, then SUSv3 permits an implementation to define RLIM_SAVED_CUR
and RLIM_SAVED_MAX
to be the same as RLIM_INFINITY
. This is how these constants are defined on Linux, implying that all possible resource limit values can be represented in rlim_t. However, this is not the case on 32-bit architectures such as x86-32. On those architectures, in a large-file compilation environment (i.e., setting the _FILE_OFFSET_BITS
feature test macro to 64 as described in I/O on Large Files), the glibc definition of rlim_t is 64 bits wide, but the kernel data type for representing a resource limit is unsigned long, which is only 32 bits wide. Current versions of glibc deal with this situation as follows: if a program compiled with _FILE_OFFSET_BITS=64
tries to set a resource limit to a value larger than can be represented in a 32-bit unsigned long, then the glibc wrapper for setrlimit() silently converts the value to RLIM_INFINITY
. In other words, the requested setting of the resource limit is not honored.
Because utilities that handle files are normally compiled with _FILE_OFFSET_BITS=64
in many x86-32 distributions, the failure to honor resource limits larger than the value that can be represented in 32 bits is a problem that can affect not only application programmers, but also end users.
One could argue that it might be better for the glibc setrlimit() wrapper to give an error if the requested resource limit exceeds the capacity of a 32-bit unsigned long. However, the fundamental problem is a kernel limitation, and the behavior described in the main text is the approach that the glibc developers have taken to dealing with it.