System Limits, Process Limits, & Process Usage

System Limits

The amount of RAM, the CPU speed, the number of currently running processes, and other factors limit the resources available to a process.  The operating system is responsible for establishing and enforcing the resource limits.

Recall that the kernel maintains a virtual file system (/proc) to maintain system information.  For example, when on Linux, we can display the total number of files that can be open at a time using the following command:

cat /proc/sys/fs/file-max 

Programmatically, we can call sysconf() which returns some of the values of the system limits.

#include <unistd.h>
long sysconf(int resource)

Sysconf() takes as a resource identifier and returns a long value. Some of the resource identifiers are:

  • _SC_CHILD_MAX:  The max number of simultaneous processes per user ID.
  • _SC_CLK_TCK: The number of clock ticks per second.
  • _SC_OPEN_MAX:  The maximum number of files a process can have open at any time.
  • _SC_STREAM_MAX: The maximum number of streams that a process can have open at any time.

Getting Process Limits

“The resource limits for a process are normally established by (the kernel) when the system is initialized and then inherited by each successive process.  Each implementation has its own way of tuning the various limits” (Advanced Programming in UNIX, section 7.11).  On Linux, the kernel creates directories named /proc/#, where # is a process’ pid, for each user process.  The kernel uses this space to store process information.

We can programmatically determine some of the limits on a process’ resources  using the getrlimit() function.

#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr)

Getrlimit() takes a resource identifier and a pointer to a struct rlimit object (shown below) as parameters. The function determines a resource limit (current and max) for the current process and sets the values in the struct rlimit.  We can then retrieve the limits by accessing the struct fields.

struct rlimit {
    rlim_t rlim_cur;          /* soft limit - current limit */
    rlim_t rlim_max;          /* hard limit - maximum value for rlim_cur */
};

Some notes:

  • The rlim_t type is defined as an unsigned long long.  The C standard says an unsigned long long must be at least 64 bits (8 bytes).  We can print the value of sizeof(unsigned long long) if we need that information.
  • An infinite limit is specified by the constant RLIM_INFINITY.
  • <sys/resource.h> specifies the types of resources and structures for resource limits.

Below is a list of resources that have limits we can query.  All of the resources available can be found on the getrlimit() man page.

  • RLIMIT_CPU: The maximum amount of CPU time in seconds.  When the soft limit is exceeded, the SIGXXCPU signal is sent to the process.
  • RLIMIT_DATA: The maximum size in bytes of the data segment (initialized data, uninitialized data and heap – does not include the stack).
  • RLIMIT_STACK: The maximum size in bytes of the stack.
  • RLIMIT_NOFILE: The maximum number of open files per process.
  • RLIMIT_AS:  The maximum size in bytes of a process’ total available memory.
  • RLIMIT_NPROC:  The maximum number of child processes per real user ID.

Setting Process Limits

A process can change its own limits and can change other process’ limits, if it is privileged.  To do so, it uses the setrlimit() function.

#include <sys/time.h>
#include <sys/resource.h>
int setrlimit(int resource, const struct rlimit *rlptr)

Setrlimit() attempts to change the current and maximum limit for a process resource.  When using this method, values for both limits must be set. It is sometimes helpful to call getrlimit() to get the current limits.  The following rules govern the changing of resource limits for processes:

  1. A process can change its soft limit up to its hard limit.
  2. A process can lower its hard limit down to its soft limit. This process is irreversible for normal users.
  3. Super users processes can raise a hard limit.

Exceeding System Limits

Question: Can a superuser increase the hard limit above the system limit?

Answer: Yes.  According to the setrlimit() man page, “A privileged process (superuser) may make arbitrary changes to either limit value.”

Question: What happens when a system limit has been reached and a system call tries to exceed the system limit?

Answer: The system call will usually return -1 and set errno to an appropriate value.  For example, if the user has a soft limit of unlimited for the number of open files and the system has reached its max, according to the man page for open(), the system call will return -1 and set errno to ENFILE: “The system limit on the total number of open files has been reached”.

Resource Usage

We can get information about the amount of resources currently used by a process using getrusage().

#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage)

The first parameter of the function identifies whose statistics should collected.  The valid values for who are:

  • RUSAGE_SELF (the calling process)
  • RUSAGE_CHILDREN (all children that have terminated and have been waited for)
  • RUSAGE_THREAD (the calling thread)

The second parameter is a pointer to a struct (shown below) that will hold the statistics.

struct rusage {
    struct timeval ru_utime; /* user CPU time used */
    struct timeval ru_stime; /* system CPU time used */
    long   ru_maxrss;        /* maximum resident set size */
    long   ru_ixrss;         /* integral shared memory size */
    long   ru_idrss;         /* integral unshared data size */
    long   ru_isrss;         /* integral unshared stack size */
    long   ru_minflt;        /* page reclaims (soft page faults) */
    long   ru_majflt;        /* page faults (hard page faults) */
    long   ru_nswap;         /* swaps */
    long   ru_inblock;       /* block input operations */
    long   ru_oublock;       /* block output operations */
    long   ru_msgsnd;        /* IPC messages sent */
    long   ru_msgrcv;        /* IPC messages received */
    long   ru_nsignals;      /* signals received */
    long   ru_nvcsw;         /* voluntary context switches */
    long   ru_nivcsw;        /* involuntary context switches */
};

Process CPU Time

We can see how much time a process has executed on the CPU using the times() function.  Some of these usage statistics are also supplied by getrusage().

<sys/times.h>
clock_t times (struct tms *buffer)

Times() returns the number of clock ticks from some arbitrary but particular point in time (like system start-up).  The tms struct has the following fields:

  • tms_utime: the CPU time used during the execution of user instructions of the calling process
  • tms_stime: the CPU time used by the system on behalf of the calling process
  • tms_cutime: the recursive sum of tms_utime and tms_cutime of all child processes /* only for children that the process waited for using a wait() function */
  • tms_cstime: the recursive sum of tms_stime and tms_cstime of all child processes /* only for children that the process waited for using a wait() function */

We can get the number of ticks per second by passing _SC_CLK_TCK  to sysconf().  The function will return the number of clock ticks per second.

Host Information

We can get information about the host machine using uname().

#include <sys/utsname.h>
int uname(struct utsname *name)

uname() takes a pointer to a struct utsname object and sets the fields of that struct.  These fields include:

  • sysname: Name of the operating system implementation
  • nodename: Network name of this machine
  • release: Release level of the operating system
  • version: Version level of the operating system
  • machine: Machine hardware platform

User IDs

Every process has 6 or more user ids associated with it.  The real user ID and real group ID specify who is running the program.  They are taken from the password file when the user logs in.  These can be changed by super user.

  • real user ID
  • real group ID

We can set a special bit in the file’s mode word (suid bit) that says, “when this file is executed, set the effective user ID of the process to be the owner of the file”.

  • effective user ID
  • effective group ID

We can set the suid bit of an executable file using chmod.  We preface the usual 3-digit argument to chmod with a 4th digit with value 4 to turn the suid bit on.  For example:

$ chmod ­4755 executable_file

If the file is owned by root, when the file as executed it would run as root.  This is what happens, for example, when you run passwd.

The original effective user and group ids that are set by the exec() function are saved.

  • saved set-user-ID
  • saved set-group-ID

We can get the current ids using the following system functions.

#include <unistd.h>
uid_t getuid(void)                /* get the user id of the calling process */
uid_t geteuid(void)               /* get the effective user id */
gid_t getegid(void)               /* get the effective group id */
get_t getgid(void)                /* get the gid of the calling process */

We can change the current ids using the following system functions.

int setuid(uid_t uid)
int setgid(gid_t gid);
int seteuid(uid_t uid);
int setegid(gid_t gid);

Process Ids

We can get the pid and parent pid for the calling process using the following functions:

#include <unistd.h>
pid_t getpid(void)               /* get the pid of the calling process */
pid_t getppid(void)              /* get the parent pid of the calling process */

Login Name

A program, when run by a super user, can get the username of the person running the process by calling getlogin().

#include <unistd.h>
char* getlogin(void)

© 2017 – 2019, Eric. All rights reserved.