Section 15.2. Job Control in ladsh


15.2. Job Control in ladsh

Adding job control to ladsh is the last addition to the simple shell, the final source code to which appears in Appendix B. The first step is to add a member to each of struct childProgram, struct job, and struct jobSet. As ladsh has not been discussed for a while, it may help to refer back to page 149, where these data structures were first introduced. Here is the final definition of struct childProgram:

 35: struct childProgram { 36:     pid_t pid;              /* 0 if exited */ 37:     char ** argv;           /* program name and arguments */ 38:     int numRedirections;    /* elements in redirection array */ 39:     struct redirectionSpecifier * redirections;  /* I/O redirs */ 40:     glob_t globResult;      /* result of parameter globbing */ 41:     int freeGlob;           /* should we free globResult? */ 42:     int isStopped;          /* is the program currently running? */ 43: }; 

We already differentiate between running children and terminated children through the pid member of struct childProgram it is zero if the child has terminated and it contains a valid pid otherwise. The new member, isStopped, is nonzero if the process has been stopped and zero otherwise. Note that its value is meaningless if the pid member is zero.

An analogous change needs to be made to struct job. It previously kept track of the number of programs in a job and how many of those processes were still running. Its new member, stoppedProgs, records how many of the job's processes are currently stopped. It could be calculated from the isStopped members of the children that comprise the job, but it is convenient to track it separately. This change gives the final form for struct job:

 45: struct job { 46:     int jobId;              /* job number */ 47:     int numProgs;           /* number of programs in job */ 48:     int runningProgs;       /* number of programs running */ 49:     char * text;            /* name of job */ 50:     char * cmdBuf;          /* buffer various argv's point to */ 51:     pid_t pgrp;             /* process group ID for the job */ 52:     struct childProgram * progs; /* array of programs in job */ 53:     struct job * next;      /* to track background commands */ 54:     int stoppedProgs;      /* num of programs alive, but stopped */ 55: }; 

Like previous versions of ladsh, ladsh4.c ignores SIGTTOU. It does this to allow tcsetpgrp() to be used even when the shell is not a foreground process. As the shell will have proper job control now, however, we do not want our children to ignore the signal. As soon as a new process is fork() ed by runCommand(), it sets the handler for SIGTTOU to SIG_DFL. This allows the terminal driver to suspend background processes that attempt to write to (or otherwise manipulate) the terminal. Here is the code that begins creating the child process, where SIGTTOU is reset and some additional synchronization work is performed:

 514:       pipe(controlfds); 515: 516:       if (!(newJob.progs[i].pid = fork())) { 517:           signal(SIGTTOU, SIG_DFL); 518: 519:           close(controlfds[1]); 520:          /* this read will return 0 when the write side closes */ 521:           read(controlfds[0], &len, 1); 522:           close(controlfds[0]); 

The controlfds pipe is used to suspend the child process until after the shell has moved the child into the proper process group. By closing the write side of the pipe and reading from the read side, the child process stops until the parent closes the write side of the pipe, which happens after the setpgid() call on line 546. This type of mechanism is necessary to ensure that the child gets moved into the process group before the exec() occurs. If we waited until after the exec(), we are not assured that the process would be in the right process group before it starts accessing the terminal (which may not be allowed).

ladsh checks for terminated children in two places. The primary place is when it wait()s for processes in the foreground process group. When the foreground process has terminated or been stopped, ladsh checks for changes in the states of its background processes through the checkJobs() function. Both of these code paths need to be modified to handle stopped children, as well as terminated ones.

Adding the WUNTRACED flag to the waitpid() call, which waits on foreground processes, allows it to notice stopped processes, as well. When a process has been stopped rather than terminated, the child's isStopped flag is set and the job's stoppedProgs count is incremented. If all the programs in the job have been stopped, ladsh moves itself back to the foreground and waits for a user's command. Here is how the portion of ladsh's main loop that waits on the foreground process now looks:

 708:         /* a job is running in the foreground; wait for it */ 709:         i = 0; 710:         while (!jobList.fg->progs[i].pid || 711:                jobList.fg->progs[i].isStopped) i++; 712: 713:         waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED); 714: 715:         if (WIFSIGNALED(status) && 716:                 (WTERMSIG(status) != SIGINT)) { 717:             printf("%s\n", strsignal(status)); 718:         } 719: 720:         if (WIFEXITED(status) || WIFSIGNALED(status)) { 721:             /* the child exited */ 722:             jobList.fg->runningProgs--; 723:             jobList.fg->progs[i].pid = 0; 724: 725:             if (!jobList.fg->runningProgs) { 726:                 /* child exited */ 727: 728:                 removeJob(&jobList, jobList.fg); 729:                 jobList.fg = NULL; 730: 731:                 /* move the shell to the foreground */ 732:                 if (tcsetpgrp(0, getpid())) 733:                     perror("tcsetpgrp"); 734:             } 735:         } else { 736:             /* the child was stopped */ 737:             jobList.fg->stoppedProgs++; 738:             jobList.fg->progs[i].isStopped = 1; 739: 740:             if (jobList.fg->stoppedProgs == 741:                                 jobList.fg->runningProgs) { 742:                 printf("\n" JOB_STATUS_FORMAT, 743:                        jobList.fg->jobId, 744:                        "Stopped", jobList.fg->text); 745:                 jobList.fg = NULL; 746:             } 747:         } 748: 749:             if (!jobList.fg) { 750:                 /* move the shell to the foreground */ 751:                 if (tcsetpgrp(0, getpid())) 752:                     perror("tcsetpgrp"); 753:             } 754:         } 

Similarly, background tasks may be stopped by signals. We again add WUNTRACED to the waitpid(), which checks the states of background processes. When a background process has been stopped, the isStopped flag and stoppedProgs counter are updated, and if the entire job has been stopped, a message is printed.

The final ability that ladsh requires is to be able to move jobs between running in the foreground, running in the background, and being stopped. Two built-in commands allow this: fg and bg. They are limited versions of the normal shell commands that go by the same name. Both take a single parameter, which is a job number preceded by a % (for compatibility with standard shells). The fg command moves the specified job to the foreground; bg sets it running in the background.

Both chores are done by sending SIGCONT to every process in the process group being activated. Although it could send the signal to each process through separate kill() calls, it is slightly simpler to send it to the entire process group using a single kill(). Here is the implementation of the fg and bg built-in commands:

 461:     } else if (!strcmp(newJob.progs[0].argv[0], "fg") || 462:                !strcmp(newJob.progs[0].argv[0], "bg")) { 463:         if (!newJob.progs[0].argv[1] || newJob.progs[0].argv[2]) { 464:             fprintf(stderr, 465:                     "%s: exactly one argument is expected\n", 466:                     newJob.progs[0].argv[0]); 467:             return 1; 468:         } 469: 470:         if (sscanf(newJob.progs[0].argv[1], "%%%d", &jobNum) != 1) { 471:              fprintf(stderr, "%s: bad argument '%s'\n", 472:                      newJob.progs[0].argv[0], 473:                      newJob.progs[0].argv[1]); 474:              return 1; 475:         } 476: 477:         for (job = jobList->head; job; job = job->next) 478:             if (job->jobId == jobNum) break; 479: 480:         if (!job) { 481:             fprintf(stderr, "%s: unknown job %d\n", 482:                     newJob.progs[0].argv[0], jobNum); 483:             return 1; 484:         } 485: 486:         if (*newJob.progs[0].argv[0] == 'f') { 487:             /* Make this job the foreground job */ 488: 489:             if (tcsetpgrp(0, job->pgrp)) 490:                 perror("tcsetpgrp"); 491:             jobList->fg = job; 492:         } 493: 494:         /* Restart the processes in the job */ 495:         for (i = 0; i < job->numProgs; i++) 496:             job->progs[i].isStopped = 0; 497: 498:         kill(-job->pgrp, SIGCONT); 499: 500:         job->stoppedProgs = 0; 501: 502:         return 0; 503:     } 

Job control was the final ability that ladsh required in order to be usable. It is still missing many features present in regular shells, such as shell and environment variables, but it illustrates all the low-level tasks that shells perform. The complete source code to the final version of ladsh appears in Appendix B.


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168 © 2008-2017.
    If you may any questions please contact us: