Chapter Outline
Processes and Signals
What is a Process
Process StructureThe Process Table
Starting New Processes
Viewing Processes
System Processes
Process SchedulingWaiting for a Process
Signals
Zombie Processes
Input and Output Redirection
ThreadsSending Signals
Summary
Signal SetsLecture Notes
Processes and Signals
Processes and signals form a fundamental part of the UNIX operating environment, controlling almost all activities performed by a UNIX computer system.
Here are some of the things you need to understand.
![]()
What is a Process?
The X/Open Specification defines a process as an address space and single thread of control that executes within that address space and its required system resources.
A process is, essentially, a running program.
Process Structure
Here is how a couple of processes might be arranged within the operationg system.
![]()
Each process is allocated a unique number, a process identifier, or PID.
The program code that will be executed by the grep command is stored in a disk file.
The system libraries can also be shared.
A process has its own stack space.
The Process Table
The UNIX process table may be though of as a data structure describing all of the processes that are currently loaded.
Viewing Processes
We can see what processes are running by using the ps command.
Here is some sample output:
![]()
The PID column gives the PIDs, the TTY column shows which terminal started the process, the STAT column shows the current status, TIME gives the CPU time used so far and the COMMAND column shows the command used to start the process.
Let's take a closer look at some of these:
![]()
The initial login was performed on virtual console number one (v01). The shell is running bash. Its status is s, which means sleeping. Thiis is because it's waiting for the X Windows sytem to finish.
![]()
X Windows was started by the command startx. It won't finished until we exit from X. It too is sleeping.
![]()
The fvwm is a window manager for X, allowing other programs to be started and windows to be arranged on the screen.
![]()
This process represents a window in the X Windows system. The shell, bash, is running in the new window. The window is running on a new pseudo terminal (/dev/ptyp0) abbreviated pp0.
![]()
This is the EMACS editor session started from the shell mentioned above. It uses the pseudo terminal.
![]()
This is a clock program started by the window manager. It's in the middle of a one-minute wait between updates of the clock hands.
System Processes
Let's look at some other processes running on this Linux system. The output has been abbreviated for clarity:
![]()
Here we can see one very important process indeed:
![]()
In general, each process is started by another, known as its parent process. A process so started is known as a child process.
When UNIX starts, it runs a single program, the prime ancestror and process number one: init.
One such example is the login procedure init starts the getty program once for each terminal that we can use to long in.
These are shown in the ps output like this:
![]()
Process Scheduling
One further ps output example is the entry for the ps command itself:
![]()
This indicates that process 192 is in a run state (R) and is executing the command ps-ax.
We can set the process priority using nice and adjust it using renice, which reduce the priority of a process by 10. High priority jobs have negative values.
Using the ps -l (forlong output), we can view the priority of processes. The value we are interested in is shown in the NI (nice) column:
![]()
Here we can see that the oclock program is running with a default nice value. If it had been stated with the command,
![]()
it would have been allocated a nice value of +10.
We can change the priority of a ruinning process by using the renice command,
![]()
So that now the clock program will be scheduled to run less often. We can see the modified nice value with the ps again:
![]()
Notice that the status column now also contains N, to indicate that the nice value has changed from the default.
Starting New Processes
We can cause a program to run from inside another program and thereby create a new process by using the system. library function.
![]()
The system function runs the command passed to it as string and waits for it to complete.
The command is executed as if the command,
![]()
has been given to a shell.
Try It Out - system
1. We can use system to write a program to run ps for us.
![]()
2. When we compile and run this program, system.c, we get the following:
![]()
3. The system function uses a shell to start the desired program.
We could put the task in the background, by changing the function call to the following:
![]()
Now, when we compile and run this version of the program, we get:
![]()
How It Works
In the first example, the program calls system with the string "ps -ax", which executes the ps program. Our program returns from the call to system when the ps command is finished.
In the second example, the call to system returns as soon as the shell command finishes. The shell returns as soon as the ps program is started, just as would happen if we had typed,
![]()
at a shell prompt.
Replacing a Process Image
There is a whole family of related functions grouped under the exec heading. They differ in the way that they start processes and present program arguments.
![]()
The exec family of functions replace the current process with another created according to the arguments given.
If we wish to use an exec function to start the ps program as in our previous examples, we have the following choices:
![]()
Try It Out - exclp
Let's modify our example to use an exexlp call.
![]()
Now, when we run this program, pexec.c, we get the usual ps output, but no Done. message at all.
Note also that there is no reference to a process called pexec in the output:
![]()
How It Works
The program prints its first message and then calls execlp, which searches the directories given by the PATH environment variable for a program called ps.
It then executes this program in place of our pexec program, starting it as if we had given the shell command:
![]()
Duplicating a Process Image
To use processes to perform more than one function at a time, we need to create an entirely separate process from within a program.
We can create a new process by calling fork. This system call duplicates the current process.
Combined with exec, fork is all we need to create new processes to do our bidding.
![]()
The fork system call creates a new child process, identical to the calling process except that the new process has a unique process ID and has the calling process as its parent PID.
A typical code fragment using fork is:
![]()
Try It Out - fork
Let's look at a simple example, fork.c:
![]()
This program runs as two process. A child prints a message five times. The parent prints a message only three times.
![]()
How It Works
When the call to fork is made, this program divides into two separate processes.
Waiting for a Process
We can arrange for the parent process to wait until the child finishes before continuing by calling wait.
![]()
The wait system call causes a parent process to pause until one of its child processes dies or is stopped.
We can interrogate the status information using macros defined in sys/wait.h. These include:
![]()
Try It Out - wait
1. Let's modify our program slightly so we can wait for and examine the child process exit status. Call the new program wait.c.
![]()
2. This section of the program waits for the child process to finish:
![]()
When we run this program, we see the parent wait for the child. The output isn't confused and the exit code is reported as expected.
![]()
How It Works
The parent process uses the wait system call to suspend its own execution until status information becomes available for a child process.
Zombie Processes
When a child process terminates, an association with its parent survives until the parent in turn either terminates normally or calls wait.
This terminated child process is known as a zombie process.
Try It Out - Zombies
fork2.c is jsut the same as fork.c, except that the number of messages printed by the child and paent porcesses is reversed.
Here are the relevant lines of code:
![]()
How It Works
If we run the above program with fork2 & and then call the ps program after the child has finished but before the parent has finished, we'll see a line like this:
![]()
There's another system call that you can use to wail for child processes. It's called waitpid and youu can use it to wait for a specific process to terminate.
![]()
If we want to have a parent process regularly check whether a specific child process had terminated, we could use the call,
![]()
which will return zero if the child has not terminated or stopped or child_pid if it has.
Input and Output Redirection
We can use our knowledge of processes to alter the behavior of programs by exploiting the fact that open file descriptors are preserved across calls to fork and exec.
Try It Out - Redirection
1. Here's a very simple filter program, upper.c, to convert all characters to uppercase:
![]()
When we run this program, it reads our input and converts it:
![]()
We can, of course, use it to convert a file to uppercase by using the shell redirection:
$ cat file.txt
2. What if we want to use this filter fromwithin another program? This code, useupper.c, accepts a file name as an argument and will respond with an error if called incorrectly:
this is the file, file.txt, it is all lower case.
$ upper < file.txt
THIS IS THE FILE, FILE.TXT, IT IS ALL LOWER CASE.![]()
3. The done, we reopen the standard input, again checking for any errors as we do so, and then use execl to call upper:
![]()
4. don't forget that execl replaces the current process; provided there is no error, the remaining lines are not executed:
![]()
How It Works
when we run this program, we can give it a file to convert to uppercase. The job is done by the program upper. The program is executed by:
![]()
Because open file descriptors are preserved across the call to execl, the upper program runs exactly as it would have under the shell command:
![]()
Threads
UNIX processes can cooperate; they can send each other messages and they can interrupt one another.
There is a class of process known as a thread which are distinct from processes in that they are separate execution streams within a single process.
Signals
A signal is an event generated by the UNIX system in response to some condition, upon receipt of which a process may in turn take some action.
Signal names are defined in the header file signal.h. They all begin with SIG and include:
![]()
Additional signals include:
![]()
If the shell and terminal driver are configured normally, typing the interrupt character (Ctrl-C) at the keyboard will result in the SIGINT signal being sent to the foreground process. This will cause the program to terminate.
We can handle signals using the signal library function.
![]()
The signal function itself returns a function of the same type, which is the previous value of the function set up to handle this signal, or one of these tow special values:
![]()
Try It Out - Signal Handling
1. We'll start by writing the function which reacts to the signal which is passed in the parameter sig. Let's call it ouch:
![]()
2. The main function has to intercept the SIGINT signal generated when we type Ctrl-C.
For the rest of the time, it just sits in an infinite loop, printing a message once a second:
![]()
3. While the program is running, typing Ctrl-C causes it to react and then continue.
When we type Ctrl-C again, the program ends:
![]()
How It Works
The program arranges for the function ouch to be called when we type Ctrl-C, which gives the SIGINT signal.
![]()
Sending Signals
A process may send a signal to itself by calling raise.
![]()
A process may send a signal to another process, including itself, by calling kill.
![]()
Signals provide us with a useful alarm clock facility.
The alarm function call can be used by a process to schedule a SIGALRM signal at some time in the future.
![]()
Try It Out - An Alarm Clock
1. In alarm.c, the first function, ding, simulates an alarm clock:
![]()
2. In main, we tell the child process to wait for five seconds before sending a SIGALRM signal to its parent:
![]()
3. The parent process arranges to catch SIGALRM with a call to signal and then waits for the inevitable.
![]()
When we run this program, it pauses for five seconds while it waits for the simulated alarm clock.
![]()
This program introduces a new function, pause, which simply causes the program to suspend execution until a signal occurs.
It's declared as,
![]()
How It Works
The alarm clock simulation program starts a new process via fork. This child process sleeps for five seconds and then sends a SIGALRM to its parent.
A Robust Signals Interface
X/Open specification recommends a newer programming interface for signals that is more robust: sigaction.
![]()
The sigaction structure, used to define the actions to be taken on receipt of the signal specified by sig, is defined in signal.h and has at least the following members:
![]()
Try It Out - sigaction
Make the changes shown below so that SIGINT is intercepted by sigaction. Call the new program ctrlc2.c.
![]()
Running the program, we get a message when we type Ctrl-C because SIGINT is handled repeated;y by sigaction.
Type Ctrl-\ to terminate the program.
![]()
How It Works
The program calls sigaction instead of signal to set the signal handler for Ctrl-C (SIGINT) to the function ouch.
Signal Sets
The header file signal.h defines the type sigset_t and functions used to manipulate sets of signals.
![]()
The function sigismember determines whether the given signal is amember of a signal set.
![]()
The process signal mask is set or examined by calling the function sigprocmask.
![]()
sigprocmask can change the process signal mask in a number of ways according to the how argument.
The how argument can be one of:
![]()
If a signal is blocked by a process, it won't be delivered, but will remain pending.
A program can determine which of its blocked signals ar pending by calling the function sigpending.
![]()
A process can suspend execution until the delivery of one of a set of signals by calling sigsuspend.
This is a more general form of the pause function we met earlier.
![]()
sigaction Flags
The sa_flags field of the sigaction structure used in sigaction may contain the following values to modify signal behavior
![]()
Functions that are safe to call inside a signal handler, those guaranteed by the X/Open specification either to be re-entrant or not to raise signals themselves include:
![]()
Common Signal Reference
Here we list the signals that UNIX programs typically need to get involved with, including the default behaviors:
![]()
The default action signals is abnormal termination of the process.
![]()
By default, these signals also cause abnormal termination. Additionally, implementation-dependent actions, such as creation of a core file, may occur.
![]()
A process is stopped by default on receipt of one of the above signals.
![]()
SIGCONT restarts a stopped process and is ignored if received by a process which is not stopped.
![]()
The SIGCHLD signal is ignored by default.
Summary
We have seen how processes are a funcdamental part of the UNIX operation system.
We have also learned to start, terminate, and signal between processes.
CS 248 - UNIX Programming Web Site Menu
Information | Syllabus | Schedule | Online "Lectures" | Projects | Quizzes | Web Board
Copyright © 2001 by James L. Fuller, all rights reserved.