Table of Contents
A running instance of a program is called a process. Each process has its own protected memory, named the process address space. This address space consists of two areas: the text area and the data area. The text area contains the actual program code, and tells the system what to do. The data area stores constant and runtime data of a process. Since there are many processes on a system, and only one or a few processors, the operating system kernel divides processor time between processes. This process is called time-sharing.
Table 10.1. The structure of a process
| Field | Description |
|---|---|
| pid | The numeric process identifier |
| ppid | The process identifier of the parent process |
| euid | The effective user ID of the process. |
| ruid | The real user ID of the process |
| egid | The group ID of the process |
| rgid | The real group ID of the process |
| fd | Pointer to the list of open file descriptors |
| vmspace | Pointer to the process address space. |
Table 10.1, “The structure of a process” lists the most important fields of information that a kernel stores about a process. Each process can be identified uniquely by its PID (process identifier), which is an unsigned number. As we will see later, a user can easily retrieve the PID of a process. Each process is associated with a UID (user ID) and GID (group ID) on the system. Each process has a real UID, which is the UID as which the process was started, and the effective UID, which is the UID as which the process operates. Normally, the effective UID is equal to the real UID, but some programs ask the system to change its effective UID. The effective UID determines is used for access control. This means that if a user named joe starts a command, say less, less can only open files that joe has read rights for. In parallel, a process also has an real GID and an effective GID.
Many processes open files, the handle used to operate on a file is called a file descriptor. The kernel manages a list of open file descriptors for each process. The fd field contains a pointer to the list of open files. The vmspace field points to the process address space of the process.
Not every process is in need of CPU time at a given moment. For instance, some processes maybe waiting for some I/O (Input/Output) operation to complete or may be terminated. Not taking subtleties in account, processes are normally started, running, ready (to run), blocked (waiting for I/O), or terminated. Figure 10.1, “Process states” shows the lifecycle of a process. A process that is terminated, but for which the process table entry is not reclaimed, is often called a zombie process. Zombie processes are useful to let the parent process read the exit status of the process, or reserve the process table entry temporarily.
New processes are created with the fork() system call. This system call copies the process address space and process information of the caller, and gives the new process, named the child process, a different PID. The child process will continue execution at the same point as the parent, but will get a different return value from the fork() system call. Based on this return value the code of the parent and child can decide how to continue executing. The following piece of C code shows a fork() call in action:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0)
printf("Hi, I am the child!\n");
else
printf("Hi, I am the parent, the child PID is %d!\n", pid);
return 0;
}
. This little program calls fork(), storing the return value of fork() in the variable pid. fork() returns the value 0 to the child, and the PID of the child to the parent. Since this is the case, we can use a simple conditional structure to check the value of the pid variable, and print an appropriate message.
You may wonder how it is possible to start new programs, since the fork() call duplicates an existing process. That is a good questions, since with fork() alone, it is not possible to execute new programs. UNIX kernels also provide a set of system calls, starting with exec, that load a new program image in the current process. We saw at the start of this chapter that a process is a running program -- a process was constructed in memory from the program image that is stored on a storage medium. So, the exec family of system calls gives a running process the facilities to replace its contents with a program stored on some medium. In itself, this is not wildly useful, because every time the an exec call is done, the original calling code (or program) is removed from the process. This can be witnessed in the following C program:
#include <stdio.h>
#include <unistd.h>
int main() {
execve("/bin/ls", NULL, NULL);
/* This will never be printed, unless execve() fails. */
printf("Hello world!\n");
return 0;
}
This program executes ls with the execve() call. The message printed with printf() will never be shown, because the running program image is replaced with that of ls. Though, a combination of fork() and the exec functions are very powerful. A process can fork itself, and let the child “sacrifice” itself to run another program. The following program demonstrates this pattern:
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0)
execve("/bin/ls", NULL, NULL);
printf("Hello world!");
return 0;
}
This program forks itself first. The program image of the child process will be replaced with ls, while the parent process prints the “Hello world!” message to the screen and returns.
This procedure is followed by many programs, including the shell, when a command is executed from the shell prompt. In fact all processes on a UNIX system are directly or indirectly derrived from the init process, which is the first program that is started on a UNIX system.
Although forks are very useful to build some parallelism[7], they can be to expensive for some purposes. Copying the whole process takes some time, and there is cost involved if processes want to share data. This is solved by offering a more lightweight alternative, namely allowing more than one thread of execution. Each thread of execution is executed separately, but the process data is shared between the threads.
Writing good multithreaded programs requires good knowledge of data sharing and locking. Since all data is shared, uncareful programming can lead to bugs like race conditions.
UNIX systems provide the ps command to show a list of running processes. Unfortunately, this command is an example of the pains of the lack of standardization. The BSD and System V variants of ps have their own sets of options. Fortunately, GNU/Linux implements both the System V and BSD-style parameters, as well as some (GNU-style) long options. Options preceded by a dash are interpreted as System V options and options without a dash as BSD options. We will describe the System V-style options in this section.
If ps is used without any parameters, it shows all processes owned by the user that invokes ps and that are attached to the same controlling terminal. For example:
$ ps
PID TTY TIME CMD
8844 pts/5 00:00:00 bash
8862 pts/5 00:00:00 ps
A lot of useful information can be distilled from this output. As you can see, two processes are listed: the shell that we used to call ps (bash), and the ps command itself. In this case there are four information fields. PID is the process ID of a process, TTY the controlling terminal, TIME the amount of CPU time the proces has used, and CMD the command or program of which a copy is running. The fields that are shown by default may vary a bit per system, but usually at least these fields are shown, with somewhat varying field labels.
Sometime you may want to have a broader view of processes that
are running. Adding the -a option shows all processes that
are associated with terminals. For instance:
$ ps -a
PID TTY TIME CMD
7487 pts/1 00:00:00 less
8556 pts/4 00:00:10 emacs-x
11324 pts/3 00:00:00 ps
As you can see, processes with different controlling terminals are shown. Though, in contrast to the plain ps output, only processes that control the terminal at the moment are shown. For instance, the shell that was used to call ps is not shown.
You can also print all processes that are running, including
processes that are not associated with a terminal, by using the
-A option:
$ ps -A | head -n 10
PID TTY TIME CMD
1 ? 00:00:01 init
2 ? 00:00:00 migration/0
3 ? 00:00:00 ksoftirqd/0
4 ? 00:00:00 watchdog/0
5 ? 00:00:00 migration/1
6 ? 00:00:00 ksoftirqd/1
7 ? 00:00:00 watchdog/1
8 ? 00:00:00 events/0
9 ? 00:00:00 events/1
You can print all processes with a certain user ID, with the -U option. This option accepts a user name as a parameter, or multiple user names that are separated by a comma. The following command shows all processes that have xfs or rpc as their user ID:
$ ps -U xfs,rpc
PID TTY TIME CMD
2409 ? 00:00:00 portmap
2784 ? 00:00:00 xfs
Likewise, you can also print processes with a particular group ID, with the -G option:
$ ps -G messagebus,haldaemon
PID TTY TIME CMD
8233 ? 00:00:00 dbus-daemon
11312 ? 00:00:00 hald
11320 ? 00:00:00 hald-addon-keyb
11323 ? 00:00:00 hald-addon-acpi
If you would like to have a list for a physical or
pseudo-terminal, you can use the -t option:
$ ps -t tty2
PID TTY TIME CMD
2655 tty2 00:00:00 getty
Signals are a crude, but effective form of inter-process communication (IPC). A signal is basically a number that is delivered to a process that has a special meaning. For all signals there are default signal handlers. Processes can install their own signal handlers, or choose to ignore signals. Some signals (normally SIGKILL and SIGSTOP) can not be ignored. All signals have convenient symbolic names.
Only a few signals are normally interesting for interactive use on UNIX(-like) systems. These are (followed by their number):
SIGKILL (9): forcefully kill a process.
SIGTERM (15): request a process to terminate. Since this is a request, a program could ignore it, in contrast to SIGKILL.
SIGHUP (1): Traditionally, this has signalled a terminal hangup. But nowadays some daemons (e.g. inetd) reread their configuration when this signal is sent.
The kill command is used to send a signal to a process. By default, kill sends the SIGTERM signal. To send this signal, the process ID of the process that you would like to send this signal to should be added as a parameter. For instance:
$ kill 15631
To send another signal, you can use one of two options: -signalnumber or -signalname. So, the following commands both send the SIGKILL signal to the process with process ID 15631:
$ kill -9 15631
$ kill -SIGKILL 15631
In an act of altruism you can be nice to other users of computer resources. If you plan to run a CPU-time intensive process, but do not want that to interfere with work of other users on the system (or other processes), you can assign some grade of 'niceness' to a process. Practically, this means that you will be able to influence the scheduling priority of a process. Nicer processes get a lower scheduling priority. The normal niceness of a process is 0, and can be changed by executing a program with the nice command. The -n [niceness] option can be used to specify the niceness:
$ nice -n 20 cputimewaster
The maximum number for niceness is implementation-dependent. If a program was started with nice, but no niceness was specified, the niceness will be set to 10. In case you were wondering: yes, you can also be rude, but this right is restricted to the root user. You can boost the priority of a process by specifying a negative value as the niceness.
You can also modify the niceness of a running processes with
the renice command. This can be done for
specific process IDs (-p
PIDs), users (-u
user/uid), and effective groups (-g group/gid). The new niceness
is specified as the first parameter.
The niceness of a process can only be increased. And, of course, no user except for root can affect the niceness of processes of other users.
Lets look at an example, to set the niceness of a process with PID 3108 to 14, you could use the following command:
$ renice 14 -p 3108
It is often useful to group processes to allow operations on a set of processes, for instance to distribute a signal to all processes in a group rather than a single process. Not too suprisingly, these sets of processes are called program groups in UNIX. After a fork, a child process is automatically a member of the process group of the parent. Though, new process groups can be created by making one process a process group leader, and adding other processes to the group. The process group ID is the PID of the process group leader.
Virtually all modern UNIX shells give processes that are created through the invocation of a command their own process group. All processes in a pipeline are normally added to one process group. If the following commands that create a pipepine are executed
cat | tr -s ' ' | egrep 'foob.r'
the shell roughly performs the following steps:
Three child processes are forked.
The first process in the pipeline is put in a process group with its own PID as the process group ID, making it the process leader. The other processes in the pipeline are added to the process group.
The file descriptors of the processes in the pipeline are reconfigured to form a pipeline.
The programs in the pipeline are executed.
The shell uses process groups to implement job control. A shell can run multiple jobs in the background, there can be multiple stopped job, and one job can be in the foreground. A foreground job is wired to the terminal for its standard input (meaning that it is the job the gets user input).
A job that is in the foreground (thus, a job that potentially accepts userinput from the terminal) can be stopped by pressing Ctrl-z (pressing both the 'Ctrl' and 'z' keys simultaneously). This will stop the job, and will handle control over the terminal back to the shell. Let's try this with the sleep command, which waits for the number of seconds provided as an argument:
$sleep 3600Ctrl-z[1]+ Stopped sleep 3600
The process group, which we will refer to as a job has been stopped now, meaning the the sleep has stopped counting - it's execution is completely stopped. You can retrieve a list of jobs with the jobs command:
$ jobs
[1]+ Stopped sleep 3600
This shows the job number (1), its state, and the command that was used to start this job. Let's run another program, stop that too, and have another look at the job listing.
$catCtrl-z[2]+ Stopped cat $jobs[1]- Stopped sleep 3600 [2]+ Stopped cat
As expected, the second job is also stopped, and was assigned job number 2. The plus sign (+) following the first job has changed to a minus (-) sign, while the second job is now marked by a plus sign. The plus sign is used to indicate the current job. The bg and fg commands that we will look at shortly, will operate on the current job if no job was specified as a parameter.
Usually, when you are working with jobs, you will want to move jobs to the foreground again. This is done with the fg command. Executing fg without a parameter will put the current job in the foreground. Most shells will print the command that is moved to the foreground to give an indication of what process was moved to the foreground:
$ fg
cat
Of course, it's not always useful to put the current job in the foreground. You can put another job in the foreground by adding the job number preceded by the percentage sign (%) as an argument to fg:
$ fg %1
sleep 3600
Switching jobs my stopping them and putting them in the foreground is often very useful when the shell is used interactively. For example, suppose that you are editing a file with a text editor, and would like to execute some other command and then continue editing. You could stop the editor with Ctrl-z, execute a command, and put the editor in the foreground again with fg.
Besides running in the foreground, jobs can also run in the background. This means that they are running, but input from the terminal is not redirected to background processes. Most shells do configure background jobs to direct output to the terminal of the shell where they were started.
A process that is stopped can be continued in the background with the bg command:
$sleep 3600[1]+ Stopped sleep 3600 $bg[1]+ sleep 3600 & $
You can see that the job is indeed running with jobs:
$ jobs
[1]+ Running sleep 3600 &
Like fg, you can also move another job than the current job to the background by specifying its job number:
$ bg %1
[1]+ sleep 3600 &
You can also run put a job directly in the background when it is started, by adding an trailing ampersand (&) to a command or pipeline. For instance:
$ sleep 3600 &
[1] 5078