11.6 Background Processes in ush

Team-FLY

11.6 Background Processes in ush

The main operational properties of a background process are that the shell does not wait for it to complete and that it is not terminated by a SIGINT sent from the keyboard. A background process appears to run independently of the terminal. This section explores handling of signals for background processes. A correctly working shell must prevent terminal-generated signals and input from being delivered to a background process and must handle the problem of having a child divorced from its controlling terminal.

Program 11.11 shows a modification of ush4 that allows a command to be executed in the background. An ampersand ( & ) at the end of a command line specifies that ush5 should run the command in the background. The program assumes that there is at most one & on the line and that, if present, it is at the end. The shell determines whether the command is to be executed in the background before forking the child, since both parent and child both must know this information. If the command is executed in the background, the child calls setpgid so that it is no longer in the foreground process group of its session. The parent shell does not wait for background children.

Program 11.11 ush5.c

A shell that attempts to handle background processes by changing their process groups .

 #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 BACK_SYMBOL '&' #define PROMPT_STRING "ush5>>" #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) {     char *backp;     sigset_t blockmask;     pid_t childpid;     struct sigaction defhandler;     int inbackground;     char inbuf[MAX_CANON];     int len;     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;         okaytojump = 1;         printf("%d",(int)getpid());         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 ((backp = strchr(inbuf, BACK_SYMBOL)) == NULL)             inbackground = 0;         else {             inbackground = 1;             *backp = 0;         }         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 (inbackground && (setpgid(0, 0) == -1))                 return 1;             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");         if (!inbackground)    /* only wait for child not in background */             wait(NULL);     }     return 0; } 
Exercise 11.26

Execute the command ls & several times under ush5 . Then, execute ps -a (still under this shell). Observe that the previous ls processes still appear as < defunct > . Exit from the shell and execute ps -a again. Explain the status of these processes before and after the shell exits.

Answer:

Since no process has waited for them, the background processes become zombie processes. They stay in this state until the shell exits. At that time, init becomes the parent of these processes, and since init periodically waits for its children, the zombies eventually die.

The shell in Program 11.12 fixes the problem of zombie or defunct processes. When a command is to be run in the background, the shell does an extra call to fork . The first child exits immediately, leaving the background process as an orphan that can then be adopted by init . The shell now waits for all children, including background processes, since the background children exit immediately and the grandchildren are adopted by init .

Program 11.12 ush6.c

A shell that cleans up zombie background processes .

 #include <errno.h> #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 BACK_SYMBOL '&' #define PROMPT_STRING ">>" #define QUIT_STRING "q" void executecmd(char *incmd); int signalsetup(struct sigaction *def, struct sigaction *catch,                 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) {     char *backp;     sigset_t blockmask;     pid_t childpid;     struct sigaction defhandler, handler;     int inbackground;     char inbuf[MAX_CANON+1];     if (signalsetup(&defhandler, &handler, &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;         if (fputs(PROMPT_STRING, stdout) == EOF)             continue;         if (fgets(inbuf, MAX_CANON, stdin) == NULL)             continue;         if (*(inbuf + strlen(inbuf) - 1) == '\n')             *(inbuf + strlen(inbuf) - 1) = 0;         if (strcmp(inbuf, QUIT_STRING) == 0)             break;         if ((backp = strchr(inbuf, BACK_SYMBOL)) == NULL)             inbackground = 0;         else {             inbackground = 1;             *backp = 0;             if (sigprocmask(SIG_BLOCK, &blockmask, NULL) == -1)                 perror("Failed to block signals");             if ((childpid = fork()) == -1) {                 perror("Failed to fork child to execute command");                 return 1;             } else if (childpid == 0) {                 if (inbackground && (fork() != 0) && (setpgid(0, 0) == -1))                     return 1;                 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);                 perror("Failed to execute command");                 return 1;             }             if (sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)                 perror("Failed to unblock signals");             wait(NULL);         }         return 0;     } 
Exercise 11.27

Execute a long-running background process such as rusers & under the shell given in Program 11.12. What happens when you enter Ctrl-C?

Answer:

The background process is not interrupted because it is not part of the foreground process group. The parent shell catches SIGINT and jumps back to the main prompt.

Exercise 11.28

Use the showid function from Exercise 11.25 to determine which of three processes in a pipeline becomes the process group leader and which are children of the shell in ush6 . Do this for pipelines started both in the foreground and background.

Answer:

If the parent starts the pipeline in the foreground, all the processes have the same process group as the shell and the shell is the process group leader. The first process in the pipeline is a child of the shell and the others are grandchildren. If the shell starts the pipeline in the background, the first process in the pipeline is the process group leader. Its parent will eventually be init . The other processes are children or grandchildren of the first process in the pipeline.

The zombie child problem is more complicated if the shell does job control. In this case, the shell must be able to detect whether the background process is stopped because of a signal (e.g., SIGSTOP ). The waitpid function has an option for detecting children stopped by signals, but not for detecting grandchildren. The background process of Program 11.12 is a grandchild because of the extra fork call, so ush6 cannot detect it.

Program 11.13 shows a direct approach, using waitpid , for handling zombies. To detect whether background processes are stopped for a signal, ush7 uses waitpid with the WNOHANG for background processes rather than forking an extra child. The “1 for the first argument to waitpid means to wait for any process. If the command is not a background command, ush7 explicitly waits for the corresponding child to complete.

Program 11.13 ush7.c

A shell that handles zombie background processes by using waitpid .

 #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 BACK_SYMBOL '&' #define PROMPT_STRING "ush7>>" #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) {     char *backp;     sigset_t blockmask;     pid_t childpid;     struct sigaction defhandler;     int inbackground;     char inbuf[MAX_CANON];     int len;     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, newline */               (fputs("\n", stdout) == EOF) )             continue;         okaytojump = 1;         printf("%d",(int)getpid());         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 ((backp = strchr(inbuf, BACK_SYMBOL)) == NULL)             inbackground = 0;         else {             inbackground = 1;             *backp = 0;         }         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 (inbackground && (setpgid(0, 0) == -1))                 return 1;             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");         if (!inbackground)        /* wait explicitly for the foreground process */             waitpid(childpid, NULL, 0);         while (waitpid(-1, NULL, WNOHANG) > 0);    /* wait for background procs */     }     return 0; } 
Exercise 11.29

Repeat Exercise 11.28 for Program 11.13.

Answer:

The results are the same as for Exercise 11.28 except that when started in the background, the first process in the pipeline is a child of the shell.

Exercise 11.30

Compare the behavior of ush6 and ush7 under the following scenario. Start a foreground process that ignores SIGINT . While that process is executing, enter Ctrl-C.

Answer:

The shell of ush6 jumps back to the main loop before waiting for the process. If this shell executes another long-running command and the first command terminates, the shell waits for the wrong command and returns to the prompt before the second command completes. This difficulty does not arise in ush7 since the ush7 shell waits for a specific foreground process.

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