This experiment makes us more familiar with process control and signal control by implementing a Unix Shell that supports job control. The course Lab has helped us build the overall framework of Shell and realize the code that is not very relevant to this experiment. We need to complete the core part by ourselves.

The overall framework

The Shell reads the commands entered by the user from the standard input (stdin) and then parses the commands. The Shell supports two types of commands: if the user enters a built-in command (quit, jobs, etc.), the command is executed directly. If the user entered a path to an executable file, fork a child process in which the command is loaded and executed. The Shell abstracts each command entered by a user into a single job, which can contain multiple processes (such as pipes). Each job can be run in two ways. If the command entered by the user ends with ‘&’, the job will be run in background; otherwise, the job will be run in foreground. At any time, only 0 or 1 foreground jobs can exist, but 0 or more background jobs can run. Finally, to enable the user to send signals to the Shell, we need to implement three signal handlers, SIGCHLD, SIGINT, and SIGTSTP.

Something to watch out for

  • By default, a child process and its parent process belong to the same process group, whileUnixMany of the mechanisms provided by the system to send signals to processes are based on the concept of process groups. When we type inCtrl + C, the kernel will send oneSIGINTSignals to each process in the foreground process group, similarly, inputCtrl + ZCauses the kernel to send oneSIGTSTPSignals to each process in the foreground process group. The foreground process group here refers toShellThe process group to which the process belongs. In the experiment, we don’t expect the signal to act directly on phiShellThe process itself (otherwiseShellreceivedSIGINTThe signal stops), but needs to letShellTo forward the signal toShellThe child processes in the foreground job and all the processes in the process group to which they belong. So, we can’t have the child process sumShellAll processes belong to the same process group. The way to do this is through usesetpgidFunction to change the subprocess’s process group when calledsetpgid(0, 0)“, the kernel creates a new process group, its process groupIDBelongs to the caller processPIDAnd adds the caller process to the process group.
  • whenShellWhen a signal is received, the specific work needs to be done by the signal processing function. Such as receivedSIGINTSignal, then the signal processing function will send that signal to the front stationjobThe process and all processes in the process group to which it belongs. In the experiment, we were throughkill(pid_t pid, int sig)To send a signal, notice that we’re not just sending a signalPID = pidThe process sends a signal,killThe function helps us achieve this: ifpidLess than0.killSend a signalsigTo process group|pid|(pidAbsolute value of). We can realize that the previous caveat sets the stage for this.
  • The parent process (Shell)forkWhen a child process is created, the parent process needs to treat that process as a child processjobAdded to thejobIn the queue (addjob), the kernel sends one when the child process terminatesSIGCHLDSignal to the parent process, and then, in the corresponding signal handler, to the terminated child processjobfromjobDelete from the queue (deletejob). Consider one case: being a parent processforkAfter a child process is created, the child process gets the schedule before the parent and executes in the parent processaddjobThe child process has already terminated and sentSIGCHLDSignal to the parent process. At this point, in the signal handlerdeletejobNothing will be done because the parent process has not yet calledjobTo join thejobIn the queue. The root cause of this problem is inaddjobI calleddeletejob. The solution to this problem is in the parent processforkBefore the child process willSIGCHLDSignal block when finishedaddjobAfter that, it was unpairedSIGCHLDSignal to block, so that the child process is guaranteed to be added tojobThe child process is then reclaimed from the queue. Note that child processes inherit the set of blocked signals from their parent, so we have to callexecveBefore unblocking the child processSIGCHLDThe signal.

code

The code for Shell Lab is here.