CS449, Introduction to Systems Software, Spring 2007

TA: Ricardo Villamarín-Salomón

Lab #11

Part I  Signals

Signals are a way of informing a process that an event has occurred. For example, if you are running a program and press Ctrl+C, the process receives SIGINT, the interrupt signal. By default this causes the process to terminate. There are some states in which a signal can be:

·  Generated: when the event that causes the signal occurs

·  Delivered: when the process takes action based on that signal

·  Pending: Generated but not yet delivered

The lifetime of a signal is the interval between its generation and its delivery (there may be considerable time between both). The process must be running on a processor at the time of signal delivery.

Every signal has a symbolic name starting with SIG. The signal names are defined in signal.h, which must be included by any C program that uses signals. Normally, when a process receives a signal, the system will take action on it. This could mean just ignoring the signal, or it could mean terminating the process. If you need something else to occur, you register a handler for a particular signal. Table 1 describes some of the required POSIX signals and lists their default actions.

Table 1. Some POSIX required signals and their default action

signal / description / default action /
SIGCHLD / child terminated, stopped or continued / ignore
SIGCONT / execution continued if stopped / continue
SIGINT / interactive attention signal (usually Ctrl-C) / abnormal termination
SIGKILL / terminated (cannot be caught or ignored) / abnormal termination
SIGPIPE / write on a pipe with no readers / abnormal termination
SIGQUIT / interactive termination: core dump / implementation dependent
SIGSTOP / execution stopped (cannot be caught or ignored) / stop
SIGTERM / Termination / abnormal termination
SIGTTIN / background process attempting to read / stop
SIGTTOU / background process attempting to write / stop
SIGUSR1 / user-defined signal 1 / abnormal termination
SIGUSR2 / user-defined signal 2 / abnormal termination

A process can catch a signal by using a signal handler when the signal is delivered. A program installs a signal handler by calling sigaction (another way is using the signal function) with the name of a user-written function. The sigaction function may also be called with SIG_DFL or SIG_IGN instead of a handler. The SIG_DFL means take the default action, and SIG_IGN means ignore the signal. Neither of these last actions is considered to be "catching" the signal. If the process is set to ignore a signal, that signal is thrown away when delivered and has no effect on the process.

Catching Signals

A process can catch a signal with a handler. Receiving signals is straightforward with the following function:

·  void (*signal(int sig, void (*func)(int)))(int);
The function signal receives two parameters: an integer that represents the signal sig and a pointer to a function func that will handle sig. The function func returns void and accepts as parameter an integer (the signal that caused func to be invoked).
If the request can be honored, signal() shall return the value of func for the most recent call to signal() for the specified signal sig. Otherwise, SIG_ERR shall be returned and a positive value shall be stored in errno.

Listing 1 below detects when the user presses Ctrl+C, prints then a message informing about it and exits with a nonzero status code (follow the instructions to compile and run).

Listing 1

/* 1) Compile and Run: gcc -o r11 r11p1.c & ./r11
* 2) Enter some text and then press Enter
* 3) Press Ctrl+C and see what happens
*/
#include <stdio.h>
#include <signal.h>
#include <unistd.h> // for STDERR_FILENO
void signhandler(int signum);
int main(void)
{
char input[100];
if (signal(SIGINT, &signhandler) == SIG_ERR)
{
fprintf(stderr, "Could not register signal handler.\n");
exit(1);
}
printf("Type something, hit [Enter] and the program will echo it\n");
while (1)
{
gets(input); // used for simplicity, use fgets instead
printf("You entered: '%s'\n", input);
}
exit(0);
}
void signhandler(int signum)
{
char errmsg[] = "\nYou sent me a signal, I'll quit\n";
int msglen = sizeof(errmsg);
write(STDERR_FILENO, errmsg, msglen);
exit(2);
}

What happens when you compile and run the program in Listing 2 and try to press Ctrl+C?

Listing 2

/* 1) Compile and Run: gcc -o r11 r11p2.c & ./r11
* 2) Enter some text and then press Enter
* 3) Press Ctrl+C and see what happens
*/
#include <stdio.h>
#include <signal.h>
int main(void)
{
char input[100];
if (signal(SIGINT, SIG_IGN) == SIG_ERR)
{
fprintf(stderr, "Could not register signal handler.\n");
exit(1);
}
printf("Type something, hit [Enter] and the program will echo it\n");
while (1)
{
gets(input); // used for simplicity, use fgets instead
printf("You entered: '%s'\n", input);
if (strcmp(input, "quit")==0) break;
}
exit(0);
}

Generating signals[1]

The kill function is used to send signals:

-  int kill(int pid, int signal)
A system call that sends a signal to a process, pid. If pid is greater than zero, the signal is sent to the process whose process ID is equal to pid. If pid is 0, the signal is sent to all processes (excluding an unspecified set of system processes) whose process group ID is equal to the process group ID of the sender, and for which the process has permission to send a signal.

Here are some corresponding signals between UNIX and C:

UNIX Signal / Corresponding C signal / Description
TERM / SIGKILL / Terminates a process
STOP / SIGSTOP / Suspends a process
CONT / SIGCONT / Continue a process

Listing 3 shows a complete example of using kill and signal together.

Listing 3

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h> // for STDOUT_FILENO
void signal_interrupt(int signum);
void signal_quit(int signum);
int main()
{
int pid = fork();
if (pid==-1)
{
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
default:
perror("Another error occurred: ");
return;
}
}
else if( pid == 0 )
{
if (signal(SIGINT, signal_interrupt) == SIG_ERR)
perror("SIGINT: ");
if (signal(SIGQUIT,signal_quit) == SIG_ERR)
perror("SIGQUIT: ");
while(1);
}
else
{
printf("Parent is sending an interrupt signal to the child\n");
kill(pid, SIGINT);
sleep(1);
printf("Parent is sending a quit signal to the child\n");
kill(pid, SIGQUIT);
}
return 0;
}
void signal_interrupt(int signum)
{
char hndlrmsg[] = "The interrupt handler is handling the receiving signal\n";
int msglen = sizeof(hndlrmsg);
write(STDOUT_FILENO, hndlrmsg, msglen);
}
void signal_quit(int signum)
{
char hndlrmsg[] = "The quit handler is handling the received QUIT signal, I'll quit: BYE BYE\n";
int msglen = sizeof(hndlrmsg);
write(STDOUT_FILENO, hndlrmsg, msglen);
exit( 0 );
}

Advanced Signal Handlers

Another way to specify a signal handler is with the function sigaction[2].

·  int sigaction(int sig, const struct sigaction *restrict act,
struct sigaction *restrict oact);
The sig parameter of sigaction specifies the signal number for the action. The act parameter is a pointer to a struct sigaction structure that specifies the action to be taken. The oact parameter is a pointer to a struct sigaction structure that receives the previous action associated with the signal (pass NULL if you don’t care). If successful, sigaction returns 0. If unsuccessful, sigaction returns –1 and sets errno (see documentation for details).

The struct sigaction structure must have at least the following members.

struct sigaction {

void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */

sigset_t sa_mask; /* additional signals to be blocked

during execution of handler */

int sa_flags; /* special flags and options */

void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */

};

The storage for sa_handler and sa_sigaction may overlap, and an application should use only one of these members to specify the action. You can specify a standard signal handler as with signal() in the sa_handler field.

The sa_mask field is a “signal set” specifying which signals should be automatically blocked when the signal handler for this signal is executing. These are automatically unblocked when the signal handler returns. The function sigemptyset can be used to initialize the sa_mask with no signals to be blocked. Upon successful completion, sigemptyset returns 0. Otherwise, it returns -1 and sets errno to indicate the error.

Some possible values for the field sa_flags are listed in Table 2 (see documentation for other values).

Table 2. Flags and their meanings

Value / Meaning /
0 / Uses all the default options
SA_SIGINFO / Indicates that you will specify the signal handler with sa_sigaction instead of sa_handler.
SA_NOCLDSTOP / Indicates that, if the specified signal is SIGCHLD, the signal should only be delivered when a child process is terminated, not when one stops.
SA_NODEFER / Suppresses automatic blocking of the signal handler’s own signal while the signal handler is executing.

Listing 4 below is a modified version of Listing 1 that uses sigaction instead of signal to set the signal handler for SIGINT.

Listing 4

/* Compile and Run: gcc -o r11 r11p4.c & ./r11
* 2) Enter some text and then press Enter
* 3) Press Ctrl+C and see what happens
*/
#include <stdio.h>
#include <signal.h>
#include <unistd.h> // for STDERR_FILENO
void signhandler(int signum);
int main(void)
{
char input[100];
struct sigaction newact;
newact.sa_handler = signhandler; /* set the new handler */
newact.sa_flags = 0; /* no special options */
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(SIGINT, &newact, NULL) == -1))
{
perror("Failed to install SIGINT signal handler");
exit(1);
}
printf("Type something, hit [Enter] and the program will echo it\n");
while (1)
{
gets(input); // used for simplicity, use fgets instead
printf("You entered: '%s'\n", input);
}
exit(0);
}
void signhandler(int signum)
{
char errmsg[] = "\nYou sent me a signal, I'll quit\n";
int msglen = sizeof(errmsg);
write(STDERR_FILENO, errmsg, msglen);
exit(2);
}

As you can see from Table 2, if you set the SA_SIGINFO flag, you have to pass a handler in the field sa_sigaction. This handler is passed more information about the signal received. The second argument to the handler will point to an object of type siginfo_t explaining the reason why the signal was generated; the third argument can be cast to a pointer to an object of type ucontext_t to refer to the receiving process’ context that was interrupted when the signal was delivered. The struct siginfo_t includes at least the following members (not all of its fields will be set for every signal or for every method of sending a signal):

typedef struct {

int si_signo; // Signal number being delivered. This field is always set.

int si_code; // Signal code. This field is always set.

int si_errno; // If non-zero, an errno value associated with this signal.

pid_t si_pid; // Process ID of sending process.

uid_t si_uid; // Real user ID of sending process.

void *si_addr; // Address at which fault occurred.

int si_status; // Exit value or signal for process termination.

int si_band; // band event for SIGPOLL

union sigval si_value; // Signal value [3].

} siginfo_t;

Possible values for si_code related to signal SIGCHLD are listed in Table 3.

Table 3. Values for field si_code related to signal SIGCHLD

Signal / Code / Reason /
SIGCHLD / CLD_EXITED / child has exited
CLD_KILLED / child has terminated abnormally and did not create a core file
CLD_DUMPED / child has terminated abnormally and created a core file
CLD_TRAPPED / traced child has trapped
CLD_STOPPED / child has stopped
CLD_CONTINUED / stopped child has continued

The program in Listing 5 shows an example of using a signal handler set with sigaction to intercept the signal SIGCHLD. The handler detects when the child finishes normally (calling exit(0)).

A more advanced example is shown in Listing 6. The handler detects when the child is paused (signal SIGSTOP, or suspend command), resumed (signal SIGCONT) and terminated somehow (signals SIGKILL, SIGTERM, etc.)

What to use? signal() or sigaction()? A useful advice from [1]:

Legacy programs sometimes use signal instead of sigaction to specify signal handlers. Although signal is part of ISO C, it is unreliable even when used in a program with a single thread. Always use sigaction to set up your handlers.

Listing 5

/* Compile and Run: gcc -o r11 r11p5.c & ./r11 */
#include <stdio.h>
#include <signal.h>
#include <unistd.h> // for STDOUT_FILENO
#include <sys/wait.h> // for WEXITSTATUS
void signhandler(int signo, siginfo_t *info, void *context);
int main(){
pid_t pid;
if ((pid = fork()) == -1)
{
perror("fork() failed\n");
exit(-1);
}
else if( pid == 0 )
{
printf("\nI am the child and I am exiting\n\n");
exit(0);
}
else
{
struct sigaction newact;
newact.sa_flags = SA_SIGINFO; /* will use sa_sigaction */
newact.sa_sigaction = signhandler; /* set the new handler */
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(SIGCHLD, &newact, NULL) == -1))
{
perror("Failed to install SIGCHLD signal handler");
exit(-2);
}
printf("Parent: Successfully created child: %d\n", pid);
wait(NULL);
}
return 0; // exists with status code == 0
}
void signhandler(int signo, siginfo_t *info, void *context)
{
char errmsg[60];
if (info->si_errno != 0)
strcpy(errmsg, "An error occurred with this signal\n");
else
{
switch (info->si_code)
{
case CLD_EXITED:
sprintf(errmsg, "Child '%d' exited normally with status: %d\n",
info->si_pid, WEXITSTATUS(info->si_status));
break;
default:
sprintf(errmsg, "A signal was sent to child '%d'\n",
info->si_pid);
break;
}
}
int msglen = strlen(errmsg);
if (write(STDOUT_FILENO, errmsg, msglen)==-1)
perror("Error calling function 'write'\n");
}

Listing 6

/* 1) Compile and Run: gcc -o r11 r11p6.c & ./r11 */
#include <stdio.h>
#include <signal.h>
#include <unistd.h> // for STDOUT_FILENO
void signhandler(int signo, siginfo_t *info, void *context);
int main(){
pid_t pid;
if ((pid = fork()) == -1)
{
perror("fork() failed\n");
exit(-1);
}
else if( pid == 0 )
{
// I am the lazy child who
// only knows how to waste resources
while(1);
}
else
{
struct sigaction newact;
newact.sa_flags = SA_SIGINFO; /* will use sa_sigaction */
newact.sa_sigaction = signhandler; /* set the new handler */
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(SIGCHLD, &newact, NULL) == -1))
{
perror("Failed to install SIGCHLD signal handler");
kill(pid, SIGKILL);
exit(-2);
}
printf("Parent created child pid: %d\n", pid);
//Parent sending SIGSTOP to pause the child
kill(pid, SIGSTOP);
sleep(1);
//Parent sending SIGCONT to resume the child
kill(pid, SIGCONT);
sleep(1);
//Parent sending SIGTERM to normally exit the child
kill(pid, SIGTERM);
sleep(1);
}
return 0; // exists with status code == 0
}
void signhandler(int signo, siginfo_t *info, void *context)
{
char errmsg[60];
if (info->si_errno != 0)
sprintf(errmsg, "An error occurred with this signal\n");
else
{
switch (info->si_code)
{
case CLD_STOPPED:
sprintf(errmsg, "Parent detected that child '%d' was paused\n",
info->si_pid);
break;
case CLD_CONTINUED:
sprintf(errmsg, "Parent detected resumption of child '%d'\n",
info->si_pid);
break;
default:
sprintf(errmsg, "Parent detected termination of '%d'\n",
info->si_pid);
break;
}
}
int msglen = strlen(errmsg);
write(STDOUT_FILENO, errmsg, msglen);
}

References