11.4 Signal Handling in the Foreground

Team-FLY

Most shells support job control that allows users to terminate running processes and move processes between the foreground and the background. The ordinary user may not be explicitly aware that signals control these actions.

Suppose a user enters Ctrl-C to terminate a running process. The terminal device driver buffers and interprets characters as they are typed from the keyboard. If the driver encounters the intr character (usually Ctrl-C), it sends a SIGINT signal. In normal shell operation, Ctrl-C causes the executing command to be terminated but does not cause the shell to exit.

Program 11.8 ush3.c

A shell that does not exit on SIGINT or SIGQUIT .

 #include <limits.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define PROMPT_STRING "ush3>>" #define QUIT_STRING "q" void executecmd(char *incmd); int signalsetup(struct sigaction *def, sigset_t *mask, void (*handler)(int)); int main (void) {     sigset_t blockmask;     pid_t childpid;     struct sigaction defaction;     char inbuf[MAX_CANON];     int len;     if (signalsetup(&defaction, &blockmask, SIG_IGN) == -1) {         perror("Failed to set up shell signal handling");         return 1;     }     if (sigprocmask(SIG_BLOCK, &blockmask, NULL) == -1) {         perror("Failed to block signals");         return 1;     }     for( ; ; ) {         if (fputs(PROMPT_STRING, stdout) == EOF)             continue;         if (fgets(inbuf, MAX_CANON, stdin) == NULL)             continue;         len = strlen(inbuf);         if (inbuf[len - 1] == '\n')             inbuf[len - 1] = 0;         if (strcmp(inbuf, QUIT_STRING) == 0)             break;         if ((childpid = fork()) == -1) {             perror("Failed to fork child to execute command");         } else if (childpid == 0) {             if ((sigaction(SIGINT, &defaction, NULL) == -1)                    (sigaction(SIGQUIT, &defaction, NULL) == -1)                    (sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)) {                   perror("Failed to set signal handling for command ");                   return 1;             }             executecmd(inbuf);             return 1;         }         wait(NULL);     }     return 0; } 

If a user enters Ctrl-C with ush2 in Program 11.2, the shell takes the default action, which is to terminate the shell. The shell should not exit under these circumstances. Program 11.8 shows a modification of ush2 that ignores SIGINT and SIGQUIT .

After setting up various signal handling structures by calling signalsetup , ush3 ignores and blocks SIGINT and SIGQUIT . The ush3 shell forks a child as before. The key implementation point here is that the child must restore the handlers for SIGINT and SIGQUIT to their defaults before executing the command. Program 11.9 shows the signalsetup function that initializes various signal structures to block SIGINT and SIGQUIT .

Program 11.9 signalsetup.c

A function for setting up signal structures for ush3 .

 #include <signal.h> #include <stdio.h> int signalsetup(struct sigaction *def, sigset_t *mask, void (*handler)(int)) {     struct sigaction catch;     catch.sa_handler = handler;  /* Set up signal structures  */     def->sa_handler = SIG_DFL;     catch.sa_flags = 0;     def->sa_flags = 0;     if ((sigemptyset(&(def->sa_mask)) == -1)            (sigemptyset(&(catch.sa_mask)) == -1)            (sigaddset(&(catch.sa_mask), SIGINT) == -1)            (sigaddset(&(catch.sa_mask), SIGQUIT) == -1)            (sigaction(SIGINT, &catch, NULL) == -1)            (sigaction(SIGQUIT, &catch, NULL) == -1)            (sigemptyset(mask) == -1)            (sigaddset(mask, SIGINT) == -1)            (sigaddset(mask, SIGQUIT) == -1))         return -1;     return 0; } 
Exercise 11.15

If a user enters Ctrl-C while ush3 in Program 11.8 is executing fgets , nothing appears until the return key is pressed. What happens if the user enters Ctrl-C in the middle of a command line?

Answer:

When the user enters Ctrl-C in the middle of a command line, some systems display the symbols ^C . All the characters on the line before entry of Ctrl-C are ignored because the terminal driver empties the input buffer when Ctrl-C is entered (canonical input mode). These characters still appear on the current input line because ush3 does not redisplay the prompt.

Exercise 11.16

The parent process of ush3 ignores and blocks SIGINT and SIGQUIT . The child unblocks these signals after resetting their handlers to the default. Why is this necessary?

Answer:

Suppose the parent does not block SIGINT and the operating system delivers a SIGINT signal before ush3 restores the SIGINT handler to the default. Since the ush3 child ignores SIGINT , the child continues to execute the command after the user enters Ctrl-C.

The ush3 implementation isn't the final answer to correct shell signal handling. In fact, the shell should catch SIGINT rather than ignore it. Also, the parent in ush3 has SIGINT and SIGQUIT blocked at all times. In fact, the parent should have them unblocked and block them only during certain critical time periods. Remember that ignoring is different from blocking. Ignore a signal by setting the signal handler to be SIG_IGN , and block a signal by setting a flag in the signal mask. Blocked signals are not delivered to the process but are held for later delivery.

In ush4 , the parent shell and the child command handle the SIGINT in different ways. The parent shell clears the input line and goes back to the prompt, which the shell accomplishes with calls to sigsetjmp and siglongjmp .

The strategy for the child is different. When the child is forked, it inherits the signal mask and has a copy of the signal handler from the parent. The child should not go to the prompt if a signal occurs. Instead, the child should take the default action, which is to exit. To accomplish this, the parent blocks the signal before the fork. The child then installs the default action before unblocking the signal. When the child executes execvp , the default action is automatically installed since execvp restores any signals being caught to have their default actions. The program cannot afford to wait until execvp automatically installs the default action. The reason is that the child needs to unblock the signal before it executes execvp and a signal may come in between unblocking the signal and the execvp .

The parent shell in Program 11.10 uses sigsetjmp , discussed in Section 8.7, to return to the prompt when it receives Ctrl-C. The sigsetjmp function stores the signal mask and current environment in a designated jump buffer. When the signal handler calls siglongjmp with that jump buffer, the environment is restored and control is transferred to the point of the sigsetjmp call. Program 11.10 sets the jumptoprompt point just above the shell prompt. When called directly, sigsetjmp returns 0. When called through siglongjmp , sigsetjmp returns a nonzero value. This distinction allows the shell to output a newline when a signal has occurred. The siglongjmp call pops the stack and restores the register values to those at the point from which the sigsetjmp was originally called.

In the shells discussed in this chapter we do not need to worry about function calls that are interrupted by a signal. No signal handler in any of these shells returns. Instead, the shells call siglongjmp , so no function has an opportunity to set errno to EINTR . Notice also that ush4 executes the command, even if it could not successfully block SIGINT and SIGQUIT .

Program 11.10 ush4.c

A shell that uses siglongjmp to handle Ctrl-C .

 #include <limits.h> #include <setjmp.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define PROMPT_STRING "ush4>>" #define QUIT_STRING "q" void executecmd(char *incmd); int signalsetup(struct sigaction *def, sigset_t *mask, void (*handler)(int)); static sigjmp_buf jumptoprompt; static volatile sig_atomic_t okaytojump = 0; /* ARGSUSED */ static void jumphd(int signalnum) {     if (!okaytojump) return;     okaytojump = 0;     siglongjmp(jumptoprompt, 1); } int main (void) {     sigset_t blockmask;     pid_t childpid;     struct sigaction defhandler;     int len;     char inbuf[MAX_CANON];     if (signalsetup(&defhandler, &blockmask, jumphd) == -1) {         perror("Failed to set up shell signal handling");         return 1;     }     for( ; ; ) {         if ((sigsetjmp(jumptoprompt, 1)) &&   /* if return from signal, \n */               (fputs("\n", stdout) == EOF) )             continue;         wait(NULL);         okaytojump = 1;         if (fputs(PROMPT_STRING, stdout) == EOF)             continue;         if (fgets(inbuf, MAX_CANON, stdin) == NULL)             continue;         len = strlen(inbuf);         if (inbuf[len - 1] == '\n')             inbuf[len - 1] = 0;         if (strcmp(inbuf, QUIT_STRING) == 0)             break;         if (sigprocmask(SIG_BLOCK, &blockmask, NULL) == -1)             perror("Failed to block signals");         if ((childpid = fork()) == -1)             perror("Failed to fork");         else if (childpid == 0) {             if ((sigaction(SIGINT, &defhandler, NULL) == -1)                    (sigaction(SIGQUIT, &defhandler, NULL) == -1)                    (sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)) {                 perror("Failed to set signal handling for command ");                 return 1;             }             executecmd(inbuf);             return 1;         }         if (sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)             perror("Failed to unblock signals");     }     return 0; } 

Compilers sometimes allocate local variables in registers for efficiency. It is important that variables that should not be changed when siglongjmp is executed are not stored in registers. Use the volatile qualifier from ISO C to suppress this type of assignment.

Program 11.10 uses the same signal handler for both SIGINT and SIGQUIT . Therefore, signalsetup sets the signals to block both of them when they are caught. It wasn't necessary to block these signals in ush3 , but it did not hurt to do so. The child of Program 11.10 installs the default action before unblocking the signal after fork . The parent shell only blocks SIGINT and SIGQUIT when it is creating a child to run the command.

Exercise 11.17

Why did we move wait in ush4 from the bottom of the loop to the top of the loop?

Answer:

If wait is at the bottom of the loop and you kill a child with Ctrl-C, the shell jumps back to the start of the loop without waiting for the child. When a new command is entered, the shell will wait for the child that was killed instead of waiting for the new command to complete.

Exercise 11.18

Why can't we fix the problem described in Exercise 11.17 by restarting wait when errno is EINTR ?

Answer:

When a function like wait is interrupted by the signal, it returns only when the signal handler returns. In this case, the signal handler is executing a siglongjmp , so wait does not return when the signal is caught.

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net