(diagram 1)
First and foremost, each process is given a unique process id (PID) in order to identify the process. PID's are usually issues sequentially, each process is given the next available PID number.
The actual program code and shared libraries
is shared between the two processes. There are two major advantages to
this:
Note: In an MS Windows environment shared libraries are commonly referred to as dynamically linked libraries (dll's).
Note that the program code and the shared libraries
are
shared between various processes. However, not all resources are shared.
Certain process resources are unique to the individual process. Among these
are:
This discussion is summarized in Diagram 1.
Much information about processes can be derived via the /proc filesystem. According to the man page(s) descriptor /proc is described as a pseudo-filesystem which is used to interface kernel data structures. The following diagram describes the layout of the /proc filesystem:
Each sub directory under /proc corresponds to a running process (PID #). Thus for example /proc/205 corresponds to the process with PID 205. Much info is found within /proc/205 , and the information is held within the various files, links, and sub directories within each process sub directory. Some of this info is represented in the diagram above. Further details regarding /proc can be found by viewing the proc man pages.
A simple way to view the processes running on the system at any given time is to use the ps command:
$ ps(See the man or info pages for ps for a description of the command, options, etc.)
By default ps specifically shows processes which communicate with certain devices, i.e. terminals, consoles, etc. In addition it's default behavior it shows processes belonging to the user running the command. If we use ps with the a option:
$ ps awill show processes belonging to all users. If we apply the x option ps will show all processes running, not just those which communicate with specific devices. Thus we can view all processes running by using both the a and the x option.
Typical output of ps looks as follows:
| PID | TTY | STAT | TIME | COMMAND |
| 3823 | 3 | SW | 0:00 | (login) |
| 3866 | 3 | SW | 0:00 | (bash) |
| 3877 | 3 | SW | 0:00 | (startx) |
| . | . | . | . | . |
| 6912 | p7 | R | 0:00 | ps |
The columns refer to the following:
The second field can contain the following entry:R - Runnable S - Sleeping D - Un-interuptable sleep T - Stopped or Traced Z - Zombie Process
And the third field:W - if the process has no residual pages
N - if the process has a positive nice value
Therefore a line in the process table such as:
3823 3 SW 0:00 (login): implies that the login process, which is used to log in to the system was started from TTY 3. It has a PID of 3823, it has thus far used 0:00 of CPU time and it is currently sleeping.
6912 p7 R 0:00 ps: implies that PID 6912 refers to the ps command which was started from p7 (probably an xterm), It's current status is runnable and it has thus far used 0:00 of CPU time.
Extended process info can be gotten by running ps with the l (long format) option:
$ ps l ==> process table in long formatSee the man pages for the ps command to learn about all the various options and the information displayed.
$ ps axand examine the resulting process table we will find an entry which looks something like this:
This entry refers to the init process. The init process is essentially the grand daddy of all other processes. It is the very first process run upon startup. It's role is to start additional processes which are vital to normal system operation at the various runlevels. When init runs it reads a file called /etc/inittab which tells init how to set up the system, what processes it should start with respect to specific runlevels. One very important process which init usually starts is the getty program. A getty process is usually started for each terminal upon which a user can log into the system. The getty program produces the login: prompt on each terminal and then waits for activity. Once a getty process detects activity (at a user attempts to log in to the system), the getty program passes control over to the login program.
PID TTY STAT TIME COMMAND 1 ? S 0:03 init
For more information regarding init , /etc/inittab, and runlevels check out the init man page(s).
Unix uses a process scheduler to decide what process to allocate the next time slice to. It is based on process priority. Higher priority processes get to run more often than lower priority ones. In Unix, processes cannot overrun their allocated time slice. They are preemptively multitasked , that is, they are started and stopped without the cooperation of the process.
2 commands we can use to set process priority are the commands nice and renice. Priorities follow a scale which looks like:
highest
(-20)
-------------
0
----------------
(+20)
lowest
default
By default, the nice command starts a program with a priority of +10
We can view the priorities of active processes by using the -l option for ps., i.e. $ ps
We use the nice command as:
$ nice oclock &
And be default this will start the oclock
program
at a priority level of +10.
Let's say we want to change the priority to +15.
We can use the renice command as follows:
$ renice 15 255
In which 15 is the desired priority and 255 is the PID for the process
int system(const char *string);
ex: we can use system() to run the ps command as follows:
/* system.c */
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Running ps with system\n");
system("ps -ax");
printf("Done \n");
exit(0);
}
Since the system( ) function uses a shell to start the program we could then consider putting it into the background. Let's go back to the listing for system.c and change the function call to system("ps -ax &");
When we use the command system("ps -ax"); the ps program is executed. The program returns from the call to system once the ps command has finished. The system call is somewhat limited because the program has to wait untill the process started by the call to system finishes before we get on with other tasks.
Using system("ps -ax &"); the call to system() returns
as sooon as the shell command finishes. Since it is a request to run a
program in the background the shell returns as soon as the ps program
is started. This is the same thing that would happen as if we types ps
-ax & on the command line. The output from ps follows
the shell prompt put out after system exits.
Let'snow look at another type of interface which provides us finer
control over the processes.
Back
to Index
#include <unistd.h>
char **environ;
int execl(const char *path, const char *arg0,
..., (char *)0 );
int execlp(const char *path, const char *arg0,
..., (char *)0 );
int execle(const char *path, const char *arg0,
... , (char *)0, const char *envp[] );
int execv( const char *path, const char *argv[]
);
int execvp( const char *path, const char *argv[]
);
int execve( const char *path, const char *argv[],
const char *envp[] );
The program given by the path argument is used as the program code to execute in place of what is currently running. In the case of the execl the new program is passed arguments arg0, arg1, arg2,... up to a null pointer. By convention, the first argument supplied (i.e. arg0) should point to the file name of the file being executed. In the case of the execv programs the arguments can be given in the form of a pointer to an array of strings, i.e. the argv array. The new program starts with the given arguments appearing in the argv array passed to main. Again, by convention, the first argument listed should point to the file name of the file being executed..
The function name suffixed with a p differ in that they will search the PATH environment variable to find the new program executeable file. If the executeable isn;t on the path, and absolute file name, including directories, will need to be passed to the function as a parameter.
The global variable environ is available to pass a value for the new program environment. In addition, an additional argument to the exec functions execle and execve is available for passing an array of strings to be used as the new program environment.
We can use the exec functions to run the ps program as follows:
#include <unistd.h>
/* example of an argument list */
/* note that we need a program name for argv[0] */
const char *ps_argv[] = ("ps", "-ax", 0);
/* Example environment, not terribly useful */
const char *ps_envp[] = {"PATH=/bin:/usr/bin", "TERM=console", 0};
/* Possible calls to exec functions */
execl("/bin/ps", "ps", "-ax", 0); /* assumes ps is in bin */
execlp("ps", "ps", "-ax", 0); /* assumes
"/bin" is in PATH */
execle("/bin/ps", "ps", "-ax", 0, ps_envp);
/* passes own environment */
execv("/bin/ps", ps_argv);
execvp("ps", ps_argv);
execve("/bin/ps", ps_argv, ps_envp);
Examples:
1) Let's use execl to run the command ls -l. We can accomplish this as follows:
#include <unistd.h>Note that for the path argument we supplied the full path "/bin/ls". Also, for our first argument arg0 we supply the file name ls which is the command being executed. When we compile and run this program the output will be the listing for the directory from which the command is run.
#include <stdio.h>int main()
{
printf("Using execl to run ls -l.\n");
execl("/bin/ps", "ls" , "-l", 0);
exit(0);
}
2) Lets do the same thing only using the execlp
function:
#include <unistd.h>The only difference here is that for the path argument it suffices to use the name of the function being executed without supplying the entire path to that file. This is because the execlp function will search our entire PATH environment variable to find ls assuming that it is in our path. If ihowever ls were not in out path then we would have had to supply the entire absolute file name /bin/ls.
#include <stdio.h>int main()
{
printf("Using execlp to run ls -l \n");
execlp("ls", "ls", "-l", 0);
exit(0);
}
3) Let's now do the same using execle:
#include <unistd.h>In this case we are passing a new program environment to the program.
#include <stdio.h>int main()
{
const char *ls_envp[]= {"PATH=/bin:/usr/bin", "TERM=console");
printf("Using execle to run ls -l \n");
execle("/bin/ls", "ls", "-l", 0, le_envp);
exit(0);
}
4) Now using execv is basically the same
as for the first example suing execl except that we suppy
our arguments in the form of an arrtay. So, the program would look as follows:
#include <unistd.h>The array ls_argv[] = { "ls", "-l", 0}; is an array of null terminated strings. The first array element, (i.e. the first argument) should point to the file name associated with the file being executed, in this case ls . The list of arguments in the array must be terminated with a null pointer.
#include <stdio.h>int main()
{
char *ls_argv[] = { "ls", "-l", 0};printf("Running ls -l with execv\n");
execv("/bin/ls", ls_argv);
exit(0);
}
5) Using execvp is exactly the same as
for execv except, that execvp will automatically search the
PATH
environment
variable. Since ls is in the path defined by this environment variable
we don't have to specify the full pathname fot the file being executed
in the *path argument. Thus to use execvp
we can use the
same program as in example 4, only changing the command line
execv("/bin/ls", ls_argv); with:
execvp( "ls", ls_argv );6) Using execve() function we pass an additional array of null terminated strings which act as the new program environment:
#include <unistd.h>Note that similar to the argument array ls_argv, the environment array ls_envp is again an array of null terminated strings and, the final element of the list must be a null pointer.
#include <stdio.h>int main()
{
char *ls_argv[] = { "ls", "-l", 0};
char *ls_envp[] = { "PATH=/bin:/usr/bin", "TERM=xterm", 0 };printf("Using execve to run ls -l \n");
execve("/bin/ls", ls_argv, ls_envp);
exit(0);
}
Let's look at a similar program which uses an
execlp
call
to run ps:
/* pexec.c */
#include <unistd.h>This abbreviated output looks as follows:
#include <stdio.h>int main()
{
printf("Running ps with execlp\n");
execlp("ps", "ps", "-ax", 0);
exit(0);
}
[holotko@landreau UNIX-Programming]$ ./pexecNote that the program prints the first message (first printf() statement) then calls execlp which then searches the directories given in the PATH environment variable for a new program called ps. It then executes ps in place of our pexec program starting it just as if we had entered the command
Running ps with execlp
warning: `-' deprecated; use `ps ax', not `ps -ax'
PID TTY STAT TIME COMMAND
1 ? S 0:03 (init)
2 ? SW 0:00 (kflushd)
3 ? SW< 0:00 (kswapd)1012 p1 S 0:00 script photo.log
1013 p6 S 0:00 bash -i
1014 p6 R 0:00 ps -ax
Note thar once ps finishes we arrive at a new shell prompt, pexec does not resume and thus the second printf() statement does not get executed. The PID of the new process has the same PID as the origional, and similarly are the parent PID and nice values. Essentially, what has occurred is that the running program has started to execute mew code from a new executeable file specified in the call to exec.
There is a limit on the combined size of the argument list and environment for a process started by the exec functions. This is given by ARG_MAX and on most Linux systems is 128k bytes. POSIXspecs require that ARG_MAX be at least 4096 bytes.
The exec funcions generally don't return values except when an error occurrs in which case the error variable errno is set and the exec function returns -1.
New processes started by exec inherit many features from the origional. Particularly, open file descriptors remain open in the new process unless their "close on exec" flag has been set. Any open directory streams in the origional process are closed.
We can use the function fork() to actually create a new process.
This system call duplicates the current process, creating a new entry in
the process table with many of the same attributes as the origiional. The
new process will be almost identical to the origional, it will execute
the same code, but it will have it''s own data space, environment, and
file descriptors. When we combine exec witth fork we can
create new processes. The systax of fork is:
The fork() system call creates a new child process. It is identical to the calling process except that the new process has a unique process ID, and has the calling process as it's parent PID. The new child process inherits a copy of the parents data space (variables), open file descriptors, and directory streams.#include <sys/types.h>
#include <unistd.h>pid_t fork( void );
The call to fork() in the parent returns the PIDof the new child process. The new process continues to execute just as the origional however, in the child process the call to fork() returns 0. This allows both parent and child process to determine which is which.
fork() returns -1 on failure. Offten this occurrs when we try to exceed a limit (CHILD_MAX) on the number of child processes which a parent may have, in which case errno will be set to EAGAIN.
Typical code segment using fork looks as follows:
The following example demonstrates a simple useage of fork():pid_t = new_pid;new_pid = fork();
switch(new_pid) {
case -1 : /* error */
break;
case 0 : /* child process */
break;
default: /* parent process */
break;
}
/* fork1.c */This program actually runs as 2 processes. A child process is created and pronts a message 5 times. The origional process prints a message only 3x. The output looks as follows:#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;
char *message;
int n;printf("Fork program starts here...\n");
pid = fork();
switch(pid) {case -1:
exit(1);case 0:
message = "This is the child process";
n = 5;
break;default:
message = "This is the parent process";
n = 3;
break;
}for (; n > 0; n--) {
puts(message);
sleep(5);
}exit(0);
}
[holotko@landreau UNIX-Programming]$ ./fork1Note that the parent process has finished before the child process. Thus output from the child process appears after the shell prompt.
Fork program starts here...
This is the parent process
This is the child process
This is the parent process
This is the child process
This is the parent process
This is the child process
[holotko@landreau UNIX-Programming]$ This is the child process
This is the child process
wait() causes a parent process to pause untill one of the child processes dies or is stopped. The call returns the PID of the child process for which status information is available. This will usually be a child process which has terminated. The status information allows the parent process to determine the exit status of the child process, the value returned from main or passed to exit. If it is not a null pointer the status information will be written to the location pointed to by stat_loc.#include <sys/types.h>
#include <sys/wait.h>pid_t wait( int *stat_loc );
We can interrogate the status information using
macros defined in sys/wait.h. These are listed in the following
table:
| Macro | Definition |
| WIFEXITED(stat_val); | Nonzero if the child is terminated normally |
| WEXITSTATUS(stat_val); | If WIFEXITED is nonzero, this returns child exit code. |
| WIFSIGNALLED(stat_val); | Nonzero if the child is terminated on an uncaught signal. |
| WTERMSIG(stat_val); | If WIFSIGNALLED is nonzero, this returns a signal number. |
| WIFSTOPPED(stat_val); | Nonzero if the child stopped on a signal. |
| WSTOPSIG(stat_val); | If WIFSTOPPED is nonzero, this returns a signal number. |
/* wait.c */The output of this program looks like this:#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;
char *message;
int n;
int exit_code;printf("Fork program starting...\n");
pid = fork();
switch(pid) {case -1:
exit(1);
case 0:
message = "This is the child";
n = 5;
exit_code = 37;
break;
default:
message = "this is the parent";
n = 3;
exit_code = 0;
break;
}for (; n > 0; n--) {
puts(message);
sleep(1);
}/* This section of the program waits for the child process to finish */
if (pid) {
int stat_val;
pid_t child_pid;child_pid = wait(&stat_val);
printf("Child has finished: PID = %d\n", child_pid);
if (WIFEXITED(stat_val))
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
else
printf("Child terminated abnormally.\n");
}
exit(exit_code);}
The parent process, which gets a nonzero return from fork(), uses wait() to suspend it's own execution untill status information becomes available for the child process. This occurrs when the child process calls exit(exit_code), The parent process then resumes and extracts and displays the exit code and information.[holotko@landreau UNIX-Programming]$ ./wait
Fork program starting...
this is the parent
This is the child
This is the child
this is the parent
this is the parent
This is the child
This is the child
This is the child
Child has finished: PID = 825
Child exited with code 37
In the next program start a parent process which runs through
a loop several times. It then starts a child process. The parent
process is then suspended via wait untill status information becomes
available for the child process. While the parent process is waiting, the
child process is running through several repetitve counts of a loop. Once
the status information for the child process is received the parent
process again resumes running, it runs through several repititions of a
for loop and then exits extracting and displaying information about the
child process. The program looks as follows:
Back
to Index
/* fork4.c */And the output from this program looks as follows:#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>int main()
{pid_t pid, child_pid;
int x;
int stat;for (x = 0; x < 5; x++) {
printf("%d Parent process running...\n", x);
sleep(1);
}printf("Starting a new child process...\n");
pid = fork();if (pid) {
child_pid = wait(&stat);
for (x = 0; x < 5; x++) {
printf("%d Parent process running...\n", x);
sleep(1);
}
if (WIFEXITED(stat)) {
printf("The child process exited normally...\n");
printf("Exit status was...%d\n", WEXITSTATUS(stat));
printf("The PID of the child process was %d\n", child_pid);
}
else
printf("Child process terminated abnormally...\n");
}else if (!pid) {
for (x = 0; x <= 5; x++) {
printf("%d Child process running...\n", x);
sleep(1);
}
}
exit(0);
}
Back to Index[holotko@landreau UNIX-Programming]$ ./fork4
0 Parent process running...
1 Parent process running...
2 Parent process running...
3 Parent process running...
4 Parent process running...
Starting a new child process...
0 Child process running...
1 Child process running...
2 Child process running...
3 Child process running...
4 Child process running...
5 Child process running...
0 Parent process running...
1 Parent process running...
2 Parent process running...
3 Parent process running...
4 Parent process running...
The child process exited normally...
Exit status was...0
The PID of the child process was 853
Consider the fork1.c program . If we change the program so that the child process prints fewer messages than the parent process, the child process will therefore finish before the parent, and will exist as a zombie untill the parent finishes and exits normally.
As an alternate example consider the following program:
If we compile and run the above program, and then examine the process table at the point where the child process finishes printing, we will see an entry in the table for a zombie process. However, once the parent calls wait() the zombie process will dissapear from the table. A zombie exists when a child process dies and the parent process is still running. It dissapears when the parent program either calls wait or terminates normally.
/* zombie1.c */#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>int main()
{pid_t pid, child_pid;
int i;
int stat_loc;printf("Starting child process...\n");
pid = fork();if (pid) {
for (i = 0; i < 10; i++) {
printf("Parent process running...\n");
sleep(2);
}
child_pid = wait( &stat_loc);
if (WIFEXITED(stat_loc)) {
printf("Child process exited normally...\n");
printf("Terminated with exit code %d\n", WEXITSTATUS(stat_loc));
printf("PID == %d\n", child_pid);
}
}
else if (!pid) {
for (i = 0; i < 5; i++) {
printf("Child process running...\n");
sleep(1);
}
}exit(0);
}
Note, if the parent terminates abnormally then the child process gets the process with PID 1, (init) as parent. (such a child process is often referred to as an orphan). The child process is now a zombie. It is no longer running, it's origional parent process is gone, and it has been inherited by init. It will remain in the process table as a zombie untill the next time the table is processed. If the process table is long this may take a while. till init cleans them up.
As a general rule, program wisely and try to avoid zombie processes. When zobbies accumulate they eat up valuable resources.
#include <sys/types.h>The pid argument specifies the PID of the particular child process to wait for. If it is a -1 then waitpid() will return information to the child process. Status information will be written to the location pointed to by stat_loc. The options argument enables us to change the behavior of waitpid() . A very usefull oprion is WNOHANG which prevents the call to waitpid() from suspending the execution of the caller. we can therby use it to find out whether any child process has terminated and, if not, to continue. See the man pages for other options.
#include <sys/wait.h>pit_t waitpid(pid_t pid, int *stat_loc, int options);
Lets say we want to have a parent process regularly
check whether or not a specific child process has terminated. We could
use a call such as:
waitpid( child_pid, (int *) 0, WNOHANG);will return 0 if the child has not terminated nor stopped. or child_pid if it has. waitpid() will return -1 on error and ser errno.
/* waitpid1.c */#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid, child_pid;
int i, status;printf("Starting child process...\n");
if ( (pid = fork()) == -1 ) {
fprintf(stderr, "Fork process did not work, no child created, aborting...\n");
exit(1);
}if (pid) {
while( (child_pid = waitpid(-1, &status, WNOHANG)) == 0) {
printf("This is the parent process, pid = %ld \n", (long)getpid() );
sleep(1);
}
if (WIFEXITED(status)) {
printf("The child exited normally...\n");
printf("Exit status = %d\n", WEXITSTATUS(status));
printf("Exit code = %ld \n", (long)child_pid);
}
else
printf("The child exited abnormally...\n");
}
else if (!pid) {
for (i = 0; i < 10; i++) {
printf("This is the child process...\n");
sleep(1);
}
}exit(0);
}
/* upper.c */When this program is run it reads our input and converts it to uppercase. for example:#include <stdio.h>
#include <ctype.h>int main()
{int ch;
while ((ch = getchar()) != EOF) {
putchar(toupper(ch));
}exit(0);
}
We can convert an entire file to uppercase using shell redirection. For example:$ ./upper
hello
HELLO
Nice Day TODAY
NICE DAY TODAY
^C
Let's say that we would like to use this filter from within another program. The following program accepts a filename as an argument and runs the program upper on that file to produce uppercase output:$ upper < file.txt
/* useupper1.c */When we run this program we give it a file to convert to uppercase: ex:#include <unistd.h>
#include <stdio.h>int main( int argc, char *argv[])
{char *filename;
if (argc != 2) {
fprintf(stderr, "useage: useupper1 file \n");
exit(1);
}filename = argv[1];
if (!freopen(filename, "r", stdin)) {
fprintf(stderr, "could not redirect stdin to file %s \n", filename);
exit(2);
}execl("/home/holtko/UNIX-Programming/upper", "upper", 0);
/* execl repaces the current process provided there is no error the *
* following lines will not be executed. */fprintf(stderr, "could not exec upper. \n");
exit(3);}
The program uses freopen() to close the standard input and associate the file stream stdin with the file given as a program argument. It then calls execl() to replace the running process code with that of the upper program. Since the open file descriptors are preserved across the call to execl() the upper program runs exactly as it would havve under the shell command:$ ./useupper1 file.txt
$ ./upper < file.txt
Untill recently there has not been a standard for using multiple threads of execution within a Unix environment. This has changed as a POSIX extension was approved in June of 1995. Since the recent adoption of a Posix standard for threads, threaded applications should be becoming more commonplace within the Unix environment.
For simplicities sake threads will not be covered in this document at present. Perhaps they will be added at a later time, or perhaps a entire document devoted to threading will be provided. Meanwhile you may turn to other Unix programming sources for detailed information concerning Posix threads. For example see Chapter 9 "Posix Threads" of the book "Practical Unix Programming" by "kay A. Robbins & Steven Robbins", Published by Prentice Hall PTR, ISBN 0-13-443706-3. See: Prentice hall for book and bookstore information.
Signal names are defined in the header file signal.h. They all begin with the prefix SIG. The following chart lists some important signals and their meanings:
| Signal Name | Description | Implementation dependent action? |
| SIGABORT | Process Abort | Yes |
| SIGALARM | Alarm Clock | No |
| SIGFPE | Floating point Exception | Yes |
| SIGHUP | Hangup | No |
| SIGILL | Illegal Instruction | Yes |
| SIGINT | Terminal Interrupt | No |
| SIGKILL | Kill (Can't be caught or ignored) | No |
| SIGPIPE | Write on a pipe with no reader | No |
| SIGQUIT | Terminal Quit | No |
| SIGSEGV | Invalid memory segment Access | Yes |
| SIGTERM | Termination | No |
| SIGUSR1 | User-defined signal 1 | No |
| SIGUSR2 | User-defined signal 2 | No |
There are some additional signals including:
| Signal name | Description |
| SIGCHLD | Child process has stopped or exited |
| SIGCONT | Continue exiting, if stopped |
| SIGSTOP | Stop executing (can't be caught or ignored) |
| SIGTSTP | Terminal stop signal |
| SIGTTIN | Background process trying to read |
| SIGTTOU | Background process trying to write |
If the shell & terminal driver are properly configured typing the
interrupt charachter (often CNTL-C) on the keyboard will result
in the signal SIGINT being sent to the foreground process which
will cause the program to terminate unless it has arranged to catch the
signal. Signals can be handled via the signal library function.
It's syntax is as follows:
#include <signal.h>signal requires 2 parameters.void (*signal(int sig, void (*func)(int)))(int);
To make this all a bit clearer lets make a short program that reacts to Cntl-C by printing the appropriate message rather than terminating. A second Cntl-C will terminate the program. Ex:
/* ctrl1.c */This program enables ouch to be called when we hit Ctrl-C while it is running. Ctrl-C gives it the SIGNINT signal. After that the default signal handling action is restored and the next time we hit Ctrl-C and the program receives a SIGINT the program terminates.#include <signal.h>
#include <stdio.h>
#include <unistd.h>void ouch( int sig )
{printf("OUCH!! -- I got the signal %d\n", sig);
signal( SIGINT, SIG_DFL );
}int main()
{
(void) signal(SIGINT, ouch);while(1) {
printf("Hello Ball! \n");
sleep(1);
}
}
Note! : This behavior is different for different Unix system. For instance, if we removed the line signal(SIGINT, SIG_DFL), from the function ouch the default behavior would not return after we hit Ctrl-c and the program receives SIGINT. Thus, subsequent hits of Ctrl-C would be handled by ouch. On other systems the default behavior is restored after the first signal is received.
The signal function returns the previous value of the signal handler for the specified signal, if there is one, or SIG_ERR otherwize, in which case errno will be set to a positive value. errno will be set to EINVAL if an invalid signal is specified or an attempt is made to handle a signal that may not be caught or ignored, such as SIGKILL.
int sigaction( int signo, const struct sigaction *act, struct sigaction *oact);
struct sigaction {
void (*sa_handler)()l
/* SIG_DFL, SIG_IGN, or a pointer to a function */
sigset_t sa_mask;
/* additional signals to be blocked during execution of the handler */
int sa_flags;
/* special flags and options */
};
sa_mask: gives a mask of signals which should be blocked during execution of the signal handler. In addition, the signal which triggered the handler will be blocked unless the SA_NODEFER, or SA_NOMASK flags are used.
sa_flags: specifies a set of flags which
modifies the behavior of the signal handling process. It is formed by the
bitwise OR of zero, or one or more of the following:
SA_ONESHOT or SA_RESETHAND: Restore the signal action to the default state once the signal handler has been called.
SA_RESTART: Makes behavior compatable with BSD semantics.
SA_NOMASK or SA_NODEFER:
Don't
prevent the signal from being received from within it's own signal handler.
Normally when a signal handling function is executed, the signal received is added to the process signal mask, for the duration of the handling function. This will prevent a subsequent occurrence of the same signal causing the signal handling function to run again. If the function is not re-entrant, it can sometimes cause problems. setting the SA_NODEFER flag results in the signal mask no being altered when it receives it's signal.
It is possible that a signal handling function might be interrupted in the middle, and then called again by something else. When we come back to the first call it is important that the signal handling function still operates properly. It must not be just recursive, in which it calls itself, but it should be re-entrant, where it can be executed again with no problems. For ex: interrupt service routined that deal with more than 1 device at a time must be re-entrant.
Check the following list of functions that are safe to call from within a signal handler.These are guaranteed by the X/OPEN specifications to be re-entrant and not raising signals themselves:
The following code sample sets the signal handler for SIGINT
to
mysighand.
struct sigaction newact;
newact.sa_handler = mysighand; /* sets the new signal
handler */
sigemptyset(&newact.sa_mask); /* no other signals blocked
*/
newact.sa_flags = 0;
/* no special options */
if (sigaction(SIGINT, &newact, NULL) == -1 )
perror("Could not install SIGINT signal handler.");
The following program causes the process to ignore SIGINT the first time it is delivered to the process. After that, the default signal handler is restored:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
/* Simple Signal handling Function */
void foo( int sig )
{
printf("I got signal %d\n", sig);
}
int main()
{
struct sigaction newaction;
newaction.sa_handler = foo;
newaction.sa_flags = SA_ONESHOT;
if ( sigaction( SIGINT, &newaction, NULL ) == -1)
perror("Could not install the new signal
handler");
while (1) {
printf("Hello Ball!! \n");
sleep(1);
}
}
#include <signal.h>Any established signal handler for the specified signal sig will be executed. raise returns 0 on success, non-zero with errno set to EINVAL if the argument is not a valid signal number.int raise( int sig );
#include <sys/types.h>kill sends the specified signal sig to the process who's identifier is given by pid. If successfull it returns 0.
#include <signal.h>int kill(pid_t pid, int sig);
kill will fail, return -1 and set errno to EINVAL if the signal given is not a valid one. If it does not have permisson errno will be set to EPERM and if the specified process does not exist it will be set to ESRCH.
Signals also provuide us with a useful alarm clock utility. The alarm
function can be used by a process to schedule a SIGALRM signal
at some time in the future. It's syntax is as follows:
#include <unistd.h>This call schedules the delivery of a SIGALRM signal in seconds second's time. a value of 0 will cancel any outstanding alarm request. Calling alarm before the signal is received will cause the alarm to be rescheduled. Each process can have only 1 outstanding alarm.unsigned int alarm(unsigned int seconds);
alarm returns the number of seconds to go before any outstanding alarm call would be sent.
This next program simulates the effect of alarm using fork, sleep, and signal. Thie following program starts a new process for the purpose of sending a signal at a later time.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
/* the function "dingding" simulates an alarm clock */
void dingding( int sig )
{
printf("Alarm has gone off.\n");
}
/* Here in "main()" we start a child process and tell it to wait
5 seconds *
* before sending a SIGALRM signal to the parent.
*/
int main()
{
int pid;
struct sigaction newact;
newact.sa_handler = dingding;
printf("alarm application starting.\n");
if ((pid = fork()) == 0) {
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* the parent process arranged to catch SIGALRM wit
a call to signal *
* and then waits for the signal to be received */
printf("Waiting for the alarm to go off.\n");
(void) sigaction(SIGALRM, &newact, NULL);
pause();
printf("done...\n");
exit(0);
}
Note that the above program introduced a new function called pause()
which
simply causes a program to suspend execution untill a signal actually occurrs.
Once it received a signal, any established handler is run and executtion
will continue as normal. The pause() function has the following
syntax:
int pause( void );
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void stop( int sig )
{
printf("received alarm...\n");
raise( SIGTERM );
}
int main()
{
struct sigaction newact;
unsigned int sec = 60;
newact.sa_handler = stop;
if ( sigaction( SIGALRM, &newact, NULL) == -1)
perror("Could not install the new signal handler..");
alarm( sec );
while (1) {
printf("Hello Ball...\n");
sleep(1);
}
}
Blocking of a signal is quite different than ignoring a signal. When a process ignores a signal the process throws the signal away upon it's delivery. However, when a process blocks a signal an occurence of the signal is held untill the process unblocks the signal. A process blocks a signal by modifying it's signal mask with sigprocmask.
We can specify operations such as blocking or unblocking on groups of signals using signal sets of type sigset_t along with the following signal set functions:
|
|
|
| int sigemptyset(sigset_t *set) | Initialized a signal set to contain no signals |
| int sigfillset(sigset_t *set) | initializes a signal set to contain all signals |
| int sigaddset(sigset_t *set, int signo); | Puts a specified signal into the set |
| int sigdelset(sigset_t *set, int signo); | Removes a specified signal from the set. |
| int sigismember(const sigset_t *set, int signo); | Checks is the specified signal is a member of the set. |
sigismember() - returns 1 if the specified signal is a
member of the set, otherwise it returns 0.
All the other functions listed in the chart above return 0 on
success and -1 on error.
The following code segment shows how a signal set called twosigs
would be initialized to contain exactly two signals SIGINT
and SIGQUIT.
#include <signal.h>A process can examine or modify its process signal mask woth the sigprocmask() function. It has the following syntax:sigset_t twosigs;
sigemptyset(&twosigs);
sigaddset(&twosigs, SIGINT);
sigaddset(&twosigs, SIGQUIT);
#include <signal.h>The how parameter is an integer which indicates how the signal mask is to be modified. It can take on one of the following 3 values:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
|
|
|
|
|
Add a collection of signals to those already blocked. |
|
|
Delete a collection of signals from those currently blocked. |
|
|
Set the collection of signals being blocked to the collection given. |
The set parameter points to the set of signals to be used for modification.
The oset parameter is the address of a sigset_t variable used for holding the set of signals that were blocked before the call to sigprocmask(). Note, the second or third parameters may be null.
If the second parameter const sigset_t *set is null the third parameter will provide the current signal mask without modification.
If the 3rd parameter, sigset_t *oset is null then sigprocmask does not return the old value of the process signal mask in oset.
Return values: sigprocmask() returns -1 on error and sets errno. On succedd it returns 0.
Example: The following code example adds SIGINT to the set of
signals that the process has blocked.
#include <stdio.h>If SIGINT is already blocked the sigprocmask() example above has no effect.
#include <signal.h>sigset_t newsigset;
sigemptyset(&newsigset); // start with a new empty set of signals
sigaddset(&newsigset, SIGINT); // adds SIGINT to the set
if (sigprocmask(SIG_BLOCK, &newsigset, NULL) < 0)
perror("Could not block the signal");
The following program displays a message and blocks and unblockes SIGINT:
/* sigs1.c */If the user enters Cntl-c while the SIGINT is blocked the program finishes it;'s calculation and doesn't end untill the SIGINT is unblocked. However, if we hit Ctrl-C while the signal is unblocked then the program will terminate immediately.#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <signal.h>void main( int argc, char *argv[] )
{double y;
sigset_t intmask;
int i, repeat_factor;if (argc != 2) {
fprintf(stderr, "Useage: %s repeat_factor\n", argv[0]);
exit(1);
}repeat_factor = atoi(argv[1]);
sigemptyset(&intmask); /* Initialize set with no signals */
sigaddset(&intmask, SIGINT); /* add SIGINT to set */
for ( ; ; ) {
sigprocmask(SIG_BLOCK, &intmask, NULL);
fprintf(stderr, "SIGINT signal blocked...\n");
for (i = 0; i < repeat_factor; i++) {
y = sin((double)i);
sleep(1);
}
fprintf(stderr, "Blocked calculation is finished...\n");
sigprocmask(SIG_UNBLOCK, &intmask, NULL);
fprintf(stderr, "SIGINT signal unblocked.\n");
for (i = 0; i < repeat_factor; i++) {
y = sin((double)i);
sleep(1);
}
fprintf(stderr, "Unblocked calculation is finished.\n");
}
}
When we use the function sigaction() we can use the sigset_t sa_mask parameter of struct sigaction to define the signals which are to be blocked during the execution of the signal handler. The following code segment demonstrates this;
#include <signal.h>
#include <stdio.h>struct sigaction newact; /* define struct */
newact.sa_handler = mysighandler; /* set the new signal handler */
sigaddset(&newacct.sa_mask, SIGPIPE); /* add SIGPIPE signal to lset */
newact.sa_flags = 0; /* no special flags or options */
if (sigaction(SIGINT, &newact, NULL) == -1)
perror("Could not intstall the SIGINT signal handler");
#include <sigpending.h>This will write the set of signals that are currently blocked from delivery into the signal set pointed to by *set. If successfull it returns 0. On error it returns -1 and sets errno.int sigpending(sigset_t *set);
#include <signal.h>The sigsuspend() function replaces the process signal mask with the signal set given by sigmask and then it suspends execution.. It will resume after the execution of a signal handling function. if the received signal terminates the program then sigsuspend() will never return. If a received signal doesn't terminate the program sigsuspend returns -1 with errno set to EINVAL.int sigsuspend(const sigset_t *sigmask);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Generated by the timer set by the alarm function. |
|
|
Sent to the controlling process by a disconnecting terminal. Or by the controlling process on termination of each foreground process. |
|
|
Raised from the terminal by typing CTRL-C, or the configured interrupt charachter. |
|
|
Mainly used from the shell to force termination of an errant process. Cannot be caught or ignored. |
|
|
Generated if a pipe with no associated reader is written to. |
|
|
Sent as a request for processes to finish. Used by the system when shutting down to tell system daemons (services) to stop. Default signal from the kill() command. |
|
|
May be used by processes to communicate with each other, possibly to cause them to report status information. |
|
|
Same as for SIGUSR. |
Default action for these signals is abnormal process termination with all the consequences of _exit,( _exit is like exit except that it performs no cleanup before returning to the kernel. ), except that the status is made available to wait and waitpid indicating abnormal termination by the specified signal.
|
|
Genrated by a floating point arithmetic exception. |
|
|
An illegal instruction has been executed by the processor. Often caused by a corrupt program of an invalid shared memory module. |
|
|
Usually raised from the terminal by the CTRL-\ or the configured quit charachter. |
|
|
Segmentation violation, usually caused by reading or writing to an illegal location of memory often due to programming errors like exceeding array bounds or dereferencing an invalid pointer. Overriding a local array variable and corrupting the stack can cause this signal to be raised when a function returns an illegal address. |
By default, these signals also cause abnormal termination. Additional implementation dependent actions, such as creating a core file, may also occurr.
|
|
Stop executing (can't be caught or ignored) |
|
|
Terminal stop signal, often raised by CTRL-Z |
|
|
Used by the shell to indicate that background processes have stopped because they need to be read from the terminal, or produce output. |
|
|
same as for SIGTTIN |
By default, a process is stopped upon receipt of one of the above
signals.
|
|
Continue executing, if stopped. |
Restarts a stopped process and is ignored when received by a process
that is not stopped.
|
|
Raised when a child process stops or exits. |
By default, the SIGCHLD process is ignored.
For footnote information see Footnotes
Page
Bibliography Information see: Bibliography
Info