Orientation
This lab will introduce you to the basics of programming with multiple processes. You will learn how one process can split itself into two, or transform itself so that it is running an entirely different program. At the end, you will write your own version of a user shell, that will accept typed commands and run the appropriate programs.
Most of the information you will need is spelled out in the lab description below, but in a few places you are referred to one of the online manual pages. These are identified by a command name, and sometimes also with a volume number noted in parentheses afterwards. For example, wait(2). To see this manual page, type man 2 wait at the command prompt.
A word of caution: As you begin to develop your program, while you are still debugging you may accidentally make a program that creates processes uncontrollably over and over, eventually causing your machine to crash. If this happens, you will probably need to reboot and fix your program before running it again. Because of this possibility, you should not work on this project using any of the shared CS machines (i.e., beowulf or grendel). Use the lab computers instead. Also, a buggy program may create extra processes that persist after the parent process has finished. Use the ps command to detect this situation, and kill to deal with it and clean up.
Contents
Processes on Linux
A process, also known as a task under Linux, is a
running instance of a program. This means that if 10 users on
a server are all using the same program, such as
emacs
, there are 10 emacs
processes
running on the server, although they all share the same
executable code.
The processes on a UNIX system can be viewed using the
ps
command:
[ealtieri@italia labos]$ ps -e PID TTYTIME CMD 1 ?00:00:04 init 2 ?00:00:00 keventd 3 ?00:00:00 ksoftirqd_CPU0 4 ?00:00:00 kswapd 5 ?00:00:00 bdflush 6 ?00:00:00 kupdated 8 ?00:00:00 khubd 9 ?00:00:00 kjournald 14 ?00:00:00 devfsd 128 ?00:00:00 kjournald ...... ... 2322 pts/1 00:00:05 emacs 2421 pts/2 00:00:07 emacs 2922 ?00:00:00 aterm 2923 pts/3 00:00:00 bash 2979 pts/3 00:00:00 emacs 2981 pts/3 00:00:00 ps
The -e
option tells the command to show all of
the processes on the system. Without this option, the command
shows only the processes on the current terminal.
In the example above, the CMD
column identifies
the name of the running process, such as "emacs
".
The first column indicates the process identifier (PID)
assigned to the process by the operating system. The second
column shows the terminal associated with a process, or "?" if
the process is not associated with any terminal (e.g. it's a
terminal itself!). Finally, the third column shows the CPU
time of the process.
We can see from the example above that there are three
emacs
processes running at the same time. Each of
them corresponds to an emacs window on the screen. These
processes have PIDs 2322, 2421 and 2979. Also notice the
ps
command at the end of the list. This is
because the command itself is of course a process too.
The process ID (PID) is a unique identifier for a process. The
operating system uses a 32-bit counter last_pid
to keep track of the last PID assigned to a process. When a
process is created, the counter is increased and its value
becomes the PID of the new process. Because the counter may
wrap around at some point, the kernel needs to check if the
value of last_pid++
already belongs to a task,
before it can assign it to a new process.
More information about the process list can be displayed using the
-l
option of the ps
command.
[ealtieri@italia os]$ ps -l F SUIDPID PPID C PRI NI ADDR SZ WCHAN TTYTIME CMD 000 S500 1518 1517 0 750 -645 11cf00 pts/1 00:00:00 bash 000 S500 1549 1518 0 710 - 2557 147d0d pts/1 00:00:02 emacs 000 R500 1874 1518 0 790 -660 - pts/1 00:00:00 ps
Recall that without the -e
option,
ps
only shows the processes on the current
terminal, in this case pts/1
.
The first column (F) of the output above identifies the process flags (see manual page if you are interested). The "S" column indicates the state of a process. Possible state codes are the following:
Duninterruptible sleep (TASK_UNINTERRUPTIBLE) Rrunnable (on run queue) (TASK_RUNNING) Ssleeping (TASK_INTERRUPTIBLE) Ttraced or stopped(TASK_STOPPED) Za defunct ("zombie") process (TASK_ZOMBIE)
You will notice that most of the processes on the system are
sleeping, that is waiting for some kind of event, such as a
mouse click or a key press. In the example above, the only
running command is ps
.
The output also shows the user owning the process (UID), the
process identifier (PID), and the
parent PID (PPID). The PPID identifies the process from
which a given process originated. For example, you can see
above that both emacs
and ps
have
originated from the same bash
shell (PID=1418),
because their PPIDs are equal to the PID of
bash
. On the other hand, a process that
originates from another process is called a child
process.
Thanks to the PPID field, the process list can also be viewed
as a tree, at the top of which lies the father of
all of the processes: the init
process
(PID=1). This tree can be viewed with the pstree
command.
[ealtieri@italia os]$ pstree init-+-atd |-bonobo-moniker- |-crond ... |-evolution-execu |-evolution-mail---evolution-mail---5*[evolution-mail] |-gconfd-1 |-gdict |-gdm---gdm-+-X | `-gnome-session---ssh-agent |-gmc |-gnome-name-serv |-gnome-smproxy |-gpm |-gweather |-kbdd-+-aterm---bash-+-emacs | | `-pstree | |-evolution | `-opera---opera---opera |-keventd ...
In bold you can see the three processes from the previous
ps
output.
Processes whose PPID equals 1 (init
) and that do
not have a controlling terminal are called
daemons. These processes run in the background and are
not normally visible to the user.
Another useful command is top
. This command
provides an ongoing look at the processor activity in real
time. It displays a listing of the most CPU-intensive tasks on
the system, and can provide an interactive interface for
manipulating processes. The GUI counterpart of this command,
called gtop
, is shown below. (Depending on which
packages you selected while installing Linux, it may or may not be
available on your system.)

![]() |
Q1. Give some examples of daemons running on your computer. |
![]() |
Q2. Draw the process relationship tree for the following
processes. Next to each node of the tree, write the PID
of the corresponding process.
F SUIDPID PPID C PRI NI ADDR SZ WCHAN TTYTIME CMD 000 S500 1492 1491 0 750 -641 11cf00 pts/0 00:00:00 bash 000 S500 2281 1492 0 690 - 2790 147d0d pts/0 00:00:00 emacs 000 S500 2284 1492 0 690 -767 147d0d pts/0 00:00:00 aterm 000 S500 2336 1492 0 690 - 4282 148496 pts/0 00:00:00 xmms 040 S500 2337 2336 0 690 - 4282 148496 pts/0 00:00:00 xmms 040 S500 2338 2337 0 690 - 4282 147d0d pts/0 00:00:00 xmms 040 S500 2342 2337 0 690 - 4282 121f03 pts/0 00:00:00 xmms 000 S500 2352 1492 0 690 -554 11cf00 pts/0 00:00:00 opera 000 S500 2353 2352 1 700 - 4597 147d0d pts/0 00:00:01 opera 040 S500 2354 2353 0 680 - 4597 148496 pts/0 00:00:00 opera 000 R500 2359 1492 0 790 -660 - pts/0 00:00:00 ps |
Sending signals to processes
Signals are sometimes referred to as software
interrupts. Similarly to hardware interrupts, signals are
random interruptions in the execution of program that signal
an event to that program. Examples of events could be a
segmentation fault caused by the program itself
(SIGSEGV
), or a CTRL-C key combination sent by
the user (SIGINT
). A complete list of signals can
be viewd by viewing the signal(7) manual page (type
"man 7 signal"
at the shell prompt). Signals
are defined in include/asm/signal.h (line 31).
Signals are sent to a program using the kill
command.
For example:
[ealtieri@italia os]$ sleep 20 & [1] 2611 [ealtieri@italia os]$ ps PID TTYTIME CMD 2535 pts/4 00:00:00 bash 2611 pts/4 00:00:00 sleep 2612 pts/4 00:00:00 ps [ealtieri@italia os]$ kill -s SIGINT 2611 [ealtieri@italia os]$ [1]+ Interruptsleep 20 [ealtieri@italia os]$
The sleep
command creates a process that sleeps
for 20 seconds and then terminates (the "&" symbol tells
bash to run the command in the background). We can see this
process in the output of ps
immediately next. We
then send a CTRL-C signal (SIGINT
) to the
sleep
command using kill
and the PID
of sleep
. The signal terminates the program. This
is confirmed by the message that appears on the terminal if
you press enter once after the kill
command has
been issued.
One of the most useful applications of signals is to request
the termination of a program. There are two termination
signals that can be sent to a program: SIGTERM
and SIGKILL
.
SIGTERM
terminates a process nicely. The process has a chance to catch this signal and do some cleanup work before terminating. However, a process can also choose to ignore this signal.SIGKILL
terminates a process immediately. The process does not have a chance to catch this signal.SIGKILL
should only be used to kill a process that crashed and does not respond to theSIGTERM
signal.
The following example shows how to kill emacs
nicely:
[ealtieri@italia os]$ emacs & [1] 2695 [ealtieri@italia os]$ ps PID TTYTIME CMD 2535 pts/4 00:00:00 bash 2695 pts/4 00:00:00 emacs 2696 pts/4 00:00:00 ps [ealtieri@italia os]$ kill -s SIGTERM 2695 [ealtieri@italia os]$ [1]+ Terminated emacs [ealtieri@italia os]$
Without the -s <sig>
option, kill sends a
SIGTERM
signal by default, so the third line above could
be rewritten simply as:
kill 2695
From the point of view of a process, a signal can be either
ignored, sent to a default handler, or caught by a function
handler provided by the process. To make your program catch a
signal you simply need to add the following code to the
beginning of main()
:
#include <signal.h> ... int main(void) { signal(SIGINT, &on_sigint);/* catch CTRL^C */ ...
where on_sigint
is the function in charge of handling
the signal, and could be implemented as shown below:
/* CTRL^C handler */ void on_sigint(int signo) { printf("CTRL^C pressed!\n"); /* do any necessary cleanup work here... */ exit(0); }
![]() | sigint.c is a
simple example showing how to catch the
SIGINT (CTRL^C) signal. |
![]() |
Q3. Modify signint.c so that it also catches
the SIGTERM signal. Make the program display different
messages for the SIGINT and SIGTERM signals. |
![]() |
Q4. The counterpart of the kill bash command
in C is the kill(pid_t pid, int sig)
function (see the kill(2) manual page). Use
this function to create a program that sends a
SIGINT signal to the PID specified in the
command line. |
![]() |
Q5. Some signals cannot be ignored or caught by
processes. One example is the SIGKILL
signal, described earlier. Read the
signal(7) manual page and find out which
other signal cannot be ignored or caught by a
process. Also find out the default action for the
SIGINT signal. |
Process Creation
Process creation under Linux is made possible by a few system calls. The most important is the fork() function.
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
fork()
works by splitting the caller process
into
a parent and child process. Both the parent and the child
processes share the same executable code, but have different
data and stack segments. An example that uses this system
call is shown below:
#include <sys/types.h> #include <unistd.h> int main() { pid_t pid; pid = fork(); /* the process is split here !! */ /* * Now we have two processes (child and parent) running on the * same executable code. The way we tell who we are is by * looking at the return value of fork() - 0 for the child * process and > 0 for the parent. */ if (pid == 0) { /* CHILD PROCESS */ printf("Child process!\n"); } else { /* PARENT PROCESS */ printf("Parent process!\n"); } exit(0); }
A slightly modified version of the program above could print the
process list before and after the fork()
function is called. This is the resulting output:
[ealtieri@italia process]$ ./a.out Processes before forking: UIDPID PPID C STIME TTYTIME CMD ealtieri 2242 2241 0 11:23 pts/2 00:00:00 bash ealtieri 2980 2242 0 15:35 pts/2 00:00:00 ./a.out ealtieri 2981 2980 0 15:35 pts/2 00:00:00 ps -f Processes after forking: UIDPID PPID C STIME TTYTIME CMD ealtieri 2242 2241 0 11:23 pts/2 00:00:00 bash ealtieri 2980 2242 0 15:35 pts/2 00:00:00 ./a.out// parent ealtieri 2982 2980 0 15:35 pts/2 00:00:00 ./a.out// child ealtieri 2983 2980 0 15:35 pts/2 00:00:00 ps -f
As you can see, after fork()
the original process
is split into two processes, the child and the parent. By
looking at the PID and PPID of the two a.out
processes we can tell that the second a.out
is
the child, while the first is the parent.
![]() | fork.c uses
fork() to create a child process. The
program prints the list of processes before and after
the forking. |
![]() |
Q6. Consider the following code taken from
forkq.c :
int main() { pid_t pid; int look_at_this; look_at_this = 99; if ((pid = fork()) < 0) { perror("fork()"); exit(1); } else if (pid == 0) { /* CHILD PROCESS */ look_at_this = 33; /* <<<<<< changing look_at_this here */ exit(0); /* child process terminates here */ } /* PARENT PROCESS */ wait(NULL); /* wait until child terminates */ printf("The value of look_at_this is %d\n", look_at_this); exit(0); }The wait(2) function puts the parent process to
sleep until the child process terminates. According to the
definition of fork() , which value of
look_at_this will be displayed on the screen?
Explain your answer. |
So far we have talked about how to fork a new process from an
existing one, but what if we want to execute a different
program altogether? For
this purpose Linux provides the exec()
family of functions (see the exec(3)
manual
page). Differently from fork()
, the
exec()
functions do
not create a process. The functions replace the image
of the caller process with the program specified as the first
argument. This means that the caller process terminates and
the program specified in exec()
takes its place.
For example:
#include <unistd.h> int main() { /* FILE TO EXECUTEARG0*/ execl("/usr/bin/emacs", "emacs", NULL); /* should never get here, unless execl() returned with an error */ perror("execl()"); exit(1); }
The program above tries to execute emacs
. If the
execl()
function succeeds, the process will be
replaced by emacs
and we will never reach the
following lines. However, execl()
may not find
the specified program, return an error and continue execution
of the current process, therefore we always need to provide
some error handling after the call.
A slightly modified version of the program above could print the
process list just before calling execl()
. We can
then manually check the list again with the ps
command after the exec()
fnction has been called.
[ealtieri@italia process]$ ./a.out & [2] 3106 Processes before the exec() call: UIDPID PPID C STIME TTYTIME CMD ealtieri 2242 2241 0 11:23 pts/2 00:00:00 bash ealtieri 3106 2242 0 16:14 pts/2 00:00:00 ./a.out ealtieri 3107 3106 0 16:14 pts/2 00:00:00 ps -f [ealtieri@italia process]$ ps -f UIDPID PPID C STIME TTYTIME CMD ealtieri 2242 2241 0 11:23 pts/2 00:00:00 bash ealtieri 3106 2242 0 16:14 pts/2 00:00:00 emacs ealtieri 3108 2242 0 16:15 pts/2 00:00:00 ps -f
After the call to execl()
, the original process
disappeared and in its place we have emacs
. Notice
how emacs
maintains the PID and PPID of the original
process.
Executing a program while continuing the current
process involves two steps. First, a child process has
to be forked by the parent task. Second, the child
process executes the desired
program using one of the exec(3)
functions. This way, the child is replaced with the image
of the new program, while the parent is untouched.
![]() | exec.c shows the
use of the execl() function. |
![]() |
Q7. You're familiar with so-called shell programs;
these are the programs that run in a terminal window and accept commands
typed by the user. Implement a small shell that repeatedly asks for
the a program name and then executes that program
using Your program should also respond to signals as follows: If SIGINT (CTRL-C) is typed while a child process is executing, your program should pass along the signal to that process (and keep itself running). If no child process is currently running, your program should catch the signal and print an informative message explaining how to terminate the program (by typing exit at the prompt). On the other hand, if your program receives a SIGTERM signal, it should first send the SIGTERM signal to any child processes and then terminate itself as well. (Remember for testing that you can use the kill command from another terminal window to send signals to your shell program.) |
Resources
![]() | Advanced Programming in the UNIX Environment, by Richard W. Stevens. Addison Wesley |