Processes
Information stored in the process:
| Field |
Meaning |
| rUID |
the real user ID (UID of parent) |
| rGID |
the real group ID (GID of parent) |
| eUID |
the effective user ID (changed on setuid bit or seteuid) |
| eGID |
the effective group ID (changed on setgid bit or setegid) |
| saved set-UIC |
|
| saved set-GIC |
|
| PGID |
Process group ID: process ID which identifies multiple processes and
is the PID of the lead (ancestor) process |
| SID |
Session ID: process ID which identfies multiple processes which are
part of the same terminal session |
| Additional GIDs |
a user can belong to multiple groups |
| Current directory |
current working directory inode id |
| Root directory |
root directory inode id |
| Signal handling |
pending signals, signal handlers, blocking during signal handlers |
| Blocked signals |
set of signals which are blocked |
| Umask |
set of permissions which are removed from created files |
| Nice value |
priority level of the process |
| Controlling Termininal |
Terminal associated with process |
Ordinary users cannot change UID/GID except from the set of UIDs/GIDs
(effective, real, or saved) or via setuid/setgid bit.
Process creation and management
New process creation
The initial process init is created by the kernel. All other
processes are created from some process by a fork. On a fork,
a new process is cloned from the original process (including the u area).
The cloned process is called the child, the original process is
called the parent.
The child differs from the parent in the following way:
-
Process ID (PID)
-
Parent Process ID (PPID)
-
Pending Signals are cleared in the child
-
Alarm clock time: reset to 0
-
file locks are not inherited by child
Calls
The process management calls are:
fork
prototype:
pid_t fork(void);
If the fork succeeds, it returns 0 to the child, and the PID of the
child to the parent.
If the fork fails, it returns -1 and sets the value errno with
the value:
-
ENOMEM: Insufficient memory to create the new process
-
EAGAIN: The number of processes in the system exceeds the limit
POSIX defines the maximum number of processes that can exist in a system
(MAXPID) and for a single user (CHILD_MAX).
Clearly, a new process means copying over the u area, and inserting
a new process in the process table. There are three parts of the process
which reside in user space:
-
text region: contains executable machine instructions
-
stack:
-
data:
The text region, like file table entries, are shared (since they are read-only).
Stack and data are not, and logically need to be copied, even though most
likely a forked process will exec a different executable, replacing all
the regions associated with the parent.
To save extraneous copies, a common implementation technique is copy-on-write
in which the child shares read only copies of all regions. If the process
writes a read-only region, a copy is then made with write permission.
_exit
The exit call terminates a process, freeing up the regions associated with
the process (user memory), closing file descriptors, and releasing the
u area. However, the process table entry remains intact. A process without
u area but with a process table entry is called a zombie.
void _exit(int exit_code)
where the lower 8 bits of exit code is the value returned for the process.
By convention, 0 indicates a successful termination.
Usually, users call exit, which performs the following functions:
-
flushes all streams
-
calls procedures registered with atexit
-
calls _exit
Removing zombies
If a child process, c, dies before its parent, than c becomes a child of
init. To remove the zombied process, a wait (or waitpid) must be performed
by the parent.
pid_t wait(int *status_p);
pid_t waitpid(pid_t child_pid, int *status_p, int options);
Returns the process id of the child process, or -1 on failure. The parameters
are:
-
child_pid
-
> 0: Waits for the child with that PID
-
= 0: Waits for any child in the same process group as parent
-
=-1: Waits for any child
-
< -1: Waits for any child whose process group is the absolute value
of child_pid
-
status_p
-
child exit status (bits 8-15)
-
core file flag (bit 7)
-
signal number (bits 0-6): zero if termination was via _exit
-
options
-
WFNOHANG: declared in . Non-blocking call
-
WNOTRACED: will wait for a process that is stopped (for shell job control)
The errno for wait/waitpid are:
-
EINTR system call interupted by signal
-
ECHILD
-
wait calling process has no unwaited-for child processes
-
waitpid childPid value illegal or process cannot be in state defined
by option value.
-
EFAULT statusPtr is an illegal address
-
EINVAL options value is illegal
waitpid can be either blocking or non-blocking, and can wait for
any child that is stopped due to job control.
Rather than use the bit positions, you should use the following macros:
-
WIFEXITED(* status_p): returns non-zero iff process was terminated via
_exit.
-
WEEXITSTATUS(* status_p): If process terminated via _exit, this returns
the exit paramter
-
WIFSIGNALED(* status_p): Returns a non-zero value iff a child was terminated
due to a signal.
-
WTERMSIG(*status_p): If process terminated via a signal, signal number
that caused termination
-
WSTOPPED(*status_p): Returns a non-zero value if a child process has been
stopped due to job control
-
WSTOPSIG(*status_P): Returns the signal number that had stopped a child
process.
exec
There are several different exec calls, enabling:
-
The exec call contains either a filename or path:
-
filename: if the file name contains a "/", then it specifies the location
of the file, otherwise the shell PATH variable is used to specfy the directories
to look into for filename. (Can be either shell script or binary)
-
path: specifies the directory of the file. (must be binary)
-
An environment to be passed to the executable (has an e in the
exec name.
-
A null terminated set of arguments or a null terminated argument vector.
-
int execl(const char *path, const char *arg, ...):
-
int execlp(const char *filename, const char *arg, ...):
-
int execle(const char *path, const char *arg, ..., const char **env);
-
int execv(const char *path, const char *argv[]):
-
int execvp(const char *filename, const char *argv[]):
-
int execve(const char *path, const char *argv[], const char **env);
The exec system call does the following:
-
It passes the arguments (strings) to the new program. These arguments are
passed to main in the following two variables
-
argc: the count of the number of arguments - 1.
-
argv: an array of strings 0...argc
-
It passes the environment which is an array of name=value
strings. The array comes from:
-
the exec system call (if there is an "e" in the name after exec),
-
otherwise
-
the environ global variable if ANSI C, otherwise
-
as a third argument from main.
Note that by default, most compilers are ANSI C although some (gcc) also
support K&R C.
-
It changes a number of settings in the u area based on the executable
file name
If the exec succeeds:
-
the process's stack, data, and text regions will be replaced
-
file descriptors will be closed if fcntl close-on-exec flags were set.
-
effective UID: changed if exec'ed program has set-UID flag set
-
effective GID: changed if exec'ed program has set-GID flag set
-
saved set-UID: changed if exec'ed program has set-UID flag set
-
installed signal handlers are replaced with signal's default action.
Note that fork and exec are seperate calls, increasing the flexibility
and enabling actions to occur between the fork and exec.
Race conditions, etc.
Process fork, wait and exec are a very effective means of building multi-process
applications.
-
When fork returns to either both parent and child exist
-
Parent can execute system calls to define the state of the child at start
up (eg. blocking signals).
-
Parent can synchronize with the termination of child (via wait), and hence
know that all the childs actions have completed.
We shall see when we get to networked machines, that these conditions do
not hold.
Process groups
A process group is a set of process with the same PGID. The PGID
is the PID of the lead process.
This is the primary mechanism for dealing with groups of processes.
#include
#include
pid_t setpgp(void);
pid_t getpgid(void);
The setpgp sets the process group id to the PID.
The getpgid returns the process group ID.
Sessions
A session contain one or more process groups
#include
#include
pid_t setsid(void);
The setsid sets the session Id and pocess group id to the PID.
Other API's
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); // get process id
pid_t getppid(void); // get parent process id
pid_t getuid(void); // get real user id
pid_t getgid(void); // get real group id
pid_t geteuid(void); // get effective user id
pid_t getegid(void); // get effective group id
pid_t setuid(uid_t uid); // get real user id
pid_t setgid(gid_t gid); // get real group id
pid_t seteuid(uid_t uid); // get effective user id
pid_t setegid(gid_t gid); // get effective group id
Notes:
-
setuid (setgid)
-
Superuser: set real, effective, and save-set uid (gid)
-
otherwise: set effective UID to the parameter if it matches the real or
saved-set UID.
-
seteuid (seteuid) like setuid, but if superuser only sets the effective
UID