Thursday, August 25, 2011

POSIX Signal Tutorial - signal Function

The simplest interface to the signal features of the UNIX System is the signal function.

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);

Returns: previous disposition of signal (see following) if OK, SIG_ERR on error.

The signal function is defined by ISO C, which doesn't involve multiple processes, process groups, terminal I/O, and the like. Therefore, its definition of signals is vague enough to be
almost useless for UNIX systems. Implementations derived from UNIX System V support the signal function, but it provides the old unreliable-signal semantics. This function provides backward compatibility for applications that require the older semantics. New applications should not use these unreliable signals. 4.4BSD also provides the signal function, but it is defined in terms of the sigaction function, so using it under 4.4BSD provides the newer reliablesignal semantics. FreeBSD 5.2.1 and Mac OS X 10.3 follow this strategy. Solaris 9 has roots in both System V and BSD, but it chooses to follow the System V semantics for the signal function. On Linux 2.4.22, the semantic of signal can follow either the BSD or System V semantics, depending on the version of the C library and how you compile your application.

Because the semantics of signal differ among implementations, it is better to use the sigaction function instead. The value of func is

(a) the constant SIG_IGN,
(b) the constant SIG_DFL,
(c) the address of a function to be called when the signal occurs.

If we specify SIG_IGN, we are telling the system to ignore the signal. (Remember that we cannot ignore the two signals SIGKILL and SIGSTOP.) When we specify SIG_DFL, we are setting the action associated with the signal to its default value (see the final column in my first post of this tutorial). When we specify the address of a function to be called when the signal occurs, we are arranging to "catch" the signal. We call the function either the signal handler or the signal-catching function.

The prototype for the signal function states that the function requires two arguments and returns a pointer to a function that returns nothing (void). The signal function's first argument, signo, is an integer. The second argument is a pointer to a function that takes a single integer argument and returns nothing. The function whose address is returned as the value of signal takes a single integer argument (the final (int)). In plain English, this declaration says that the signal handler is passed a single integer argument (the signal number) and that it returns nothing. When we call signal to establish the signal handler, the second argument is a pointer to the function. The return value from signal is the pointer to the previous signal handler. Many systems call the signal handler with additional, implementation-dependent arguments.

The perplexing signal function prototype shown at the beginning of this section can be made much simpler through the use of the following typedef:

typedef void Sigfunc(int);

Then the prototype becomes

Sigfunc *signal(int, Sigfunc *);

If we examine the system's header <signal.h>, we probably find declarations of the form

#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1

These constants can be used in place of the "pointer to a function that takes an integer argument and returns nothing," the second argument to signal, and the return value from signal. The three values used for these constants need not be -1, 0, and 1. They must be three values that can never be the address of any declarable function. Most UNIX systems use the values shown.

Example
shows a simple signal handler that catches either of the two user-defined signals and prints the signal number. The pause function, which simply suspends the calling process until a signal is received. We invoke the program in the background and use the kill(1) command to send it signals. Note that the term kill in the UNIX System is a misnomer. The kill(1) command and the kill(2) function just send a signal to a process or process group. Whether or not that signal terminates the process depends on which signal is sent and whether the process has arranged to catch the signal.

$ ./a.out & start process in background
[1] 7216 job-control shell prints job number and process ID
$ kill -USR1 7216 send it SIGUSR1
received SIGUSR1
$ kill -USR2 7216 send it SIGUSR2
received SIGUSR2
$ kill 7216 now send it SIGTERM
[1]+ Terminated ./a.out

When we send the SIGTERM signal, the process is terminated, since it doesn't catch the signal, and the default action for the signal is termination.

Example: Simple program to catch SIGUSR1 and SIGUSR2

#include <signal.h>
#include <stdio.h>

void err_dump(const char *, int); 
void err_sys(const char *);

static void sig_usr(int); /* one handler for both signals */

int main(void{
   if (signal(SIGUSR1, sig_usr) == SIG_ERR) {
      err_sys("can't catch SIGUSR1");
   }
   if (signal(SIGUSR2, sig_usr) == SIG_ERR) {
      err_sys("can't catch SIGUSR2");
   }
   for ( ; ; ) {
      pause();
   }
}
static void sig_usr(int signo) {/* argument is signal number */ 
   if (signo == SIGUSR1)
      printf("received SIGUSR1\n");
   else if (signo == SIGUSR2)
      printf("received SIGUSR2\n");
   else
      err_dump("received signal %d\n", signo);
}


void err_dump (const char* err, int sig) {
   printf ("%s %d", err, sig);
}



void err_sys (const char* errg) {
   printf ("%s", err);
}


Program Start-Up
When a program is executed, the status of all signals is either default or ignore. Normally, all signals are set to their default action, unless the process that calls exec is ignoring the signal. Specifically, the exec functions change the disposition of any signals being caught to their default action and leave the status of all other signals alone. (Naturally, a signal that is being caught by a process that calls exec cannot be caught by the same function in the new program, since the address of the signalcatching function in the caller probably has no meaning in the new program file that is executed.)

One specific example is how an interactive shell treats the interrupt and quit signals for a background process. With a shell that doesn't support job control, when we execute a process in the background, as in

cc main.c &

the shell automatically sets the disposition of the interrupt and quit signals in the background process to be ignored. This is so that if we type the interrupt character, it doesn't affect the background process. If this weren't done and we typed the interrupt character, it would terminate not only the foreground process, but also all the background processes. Many interactive programs that catch these two signals have code that looks like

void sig_int(int), sig_quit(int);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
   signal(SIGINT, sig_int);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
   signal(SIGQUIT, sig_quit);

Doing this, the process catches the signal only if the signal is not currently being ignored. These two calls to signal also show a limitation of the signal function: we are not able to determine
the current disposition of a signal without changing the disposition. We'll see later in this chapter how the sigaction function allows us to determine a signal's disposition without changing it.

Process Creation

When a process calls fork, the child inherits the parent's signal dispositions. Here, since the child starts off with a copy of the parent's memory image, the address of a signal-catching function has meaning in the child.

No comments:

Post a Comment