Experiment PPT explanation
An interrupt is an asynchronous exception
Interrupt is a process that is interrupted by an external event. Interrupt is accomplished by setting the interrupt pin of the processor. After the interrupt is complete, it will return to the next instruction to continue execution
Example:
- IO interruption
- Keyboard hit CTRL+C, this is the KEYBOARD IO caused by the interrupt
- The arrival of a network packet triggers an I/O interrupt to announce the completion of the transmission
- The arrival of data on a disk also triggers an I/O interrupt to inform you that the transfer is complete
- Hardware restart interruption
- Hit the reset button
- Software restart interruption
- Hitting CTRL ALT – DELETE
There are four basic states of a process
-
Running — Running
-
Runnable — Can run
-
Blocked – block
-
Zombie — Terminates but is not reclaimed
(There are no terminated and reclaimed processes here, presumably because the occupied resources have been completely reclaimed, i.e. the process never existed)
A signal is a message that informs a process that an event has occurred
- Akin to exceptions and interrupts
- This message is sent from the kernel to a process (sometimes at the request of another process)
- The signal type is numbered from 1 to 30
- The only information a signal transmits is its signal ID and the fact that it arrived.
-
The kernel sends (passes) a signal to the target process by updating some state in the target process context
-
Reasons for sending signals:
- The kernel detects a system event, such as CtrL-C (SIGINT), division by zero (SIGFPE), or the termination of a child process (SIGCHLD).
- Another process called the kill function.
- A target process receives a signal when it is forced by the kernel to react in a certain way to the passing of a signal
- The receiving signal is not queued
- Signal receiving time: context switch (During context switch, the kernel mode is ready to switch from process A to process B to check whether there is A signal received and process it)
- Three responses to signals:
- Ignore the signal
- Terminate the process
- Catch this signal and execute user-level signal handlers (similar to hardware exception handlers called in response to asynchronous interrupts)
-
A target process receives a signal when it is forced by the kernel to react in a certain way to the passing of a signal
-
Blocking signal
- Sometimes code needs to run in a part that can’t be broken
- with
sigprocmask()
function
-
Waiting for the signal
- Sometimes we want to pause execution until we get a specific signal
- Implement it with sigsuspend(). (Block signal, pause, start signal)
-
SIGKILL and SIGSTOP cannot be altered
Signal handler:
- Can be installed and run on signal
- Void handler(int signum){… }
- The separation of control flow in the same process to form the control flow that processes signals and the logic flow that the program originally executes
- Return to normal control flow (continue with original logical flow)
- When the appropriate signal is triggered, it can be executed at any time
int sigsuspend(const sigset_t *mask)"Equivalent to ": blocking signal -pause(a)- UnblockCopy the code
- Temporarily replace the signal mask of the calling process with the given mask
- Pauses the pause process until a signal is received
The experimental goal
The Lab requires us to implement a simple Unix shell program that relies on the exception control flow of CSAPP Chapter 8. This LAB is implemented bit by bit based on the TRACE file. It’s important to read WRITES UP, and it’s important to understand every piece of code in the textbook.
There is a general framework in the given file. What we need to do is to complete the following functions of tsh.c:
void eval(char *cmdline) // Parses the command and derives child processes to execute it
int builtin_cmd(char **argv) // Execute the built-in command
void do_bgfg(char **argv) // Run bg and fg commands
void waitfg(pid_t pid) Block until a process is not running in the foreground
void sigchld_handler(int sig) //SIGCHID signal handler function
void sigint_handler(int sig) //SIGINT signal handler function
void sigtstp_handler(int sig) //SIGTSTP signal processing function
Copy the code
The shell we want to implement has the following features:
-
TSH should reap all zombie processes. If a job is terminated because it received a job it did not capture (without following the signal handler function), TSH should output the JOB’S PID and a description of the signal.
-
Shell does not need to support pipe | or I/O redirection < and >
-
Typing CtrL-C (Ctrl-Z) should cause the SIGINT (SIGTSTP) signal to be sent to the current foreground job and any descendants of the job, if there is no foreground job then the signal should have no effect.
-
If a command ends in “&”, TSH should run them in the background, otherwise in the foreground (and wait for it to end)
-
The shell supports the following built-in commands: quit, jobs, bg
, and fg
.
-- The quit command terminates The shell. -- The jobs command lists all background jobs. -- The BG <job> command restarts <job> by sending it a SIGCONT signal, The <job> argument can be either a PID or a JID. -- the fg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in the foreground. The <job> argument can be either a PID or a JID.Copy the code
The experiment began
All right, here we go. First of all, according to WriteUp we know that we need to test from the test file in turn. Make rtest0x = make rtest0x = make rtest0x
Here we see a problem with test02. Take a look at trace02 here to see what’s going on.
There is a quit directive. The reason for this failure is that our TSH program has not implemented the processing of this command. So it’s not going to quit it’s going to stop here. Now let’s start our experiment
Because we have no way to predict the order of arrival. Therefore, the handling of concurrent programming becomes very important.
Consider the following case
So we have to think about that. Block SIGCHLD signal before parent addJob.
1. eval
Function implementation
void eval(char *cmdline)
{
char *argv[MAXLINE]; /*Argument list execve()*/
char buf[MAXLINE]; /*Hold modified commend line*/
int bg; /*Should the job run in bg or fg? * /
pid_t pid;
int state;
sigset_t mask_all, mask_one, prev_one;
strcpy(buf,cmdline); // Copy the string from cmdline to buf
bg=parseline(cmdline,argv); // parse cmdline into argv[]
if(argv[0] = =0) {return ;//ignore empty line
}
if(! builtin_cmd(argv)){ sigfillset(&mask_all); sigemptyset(&mask_one); sigaddset(&mask_one, SIGCHLD);//mask_one:SIGCHLD
// Here you can block SIGCHLD directly
sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //Block SIGCHLD
if((pid=fork())==0) {/* Child process :*/
sigprocmask(SIG_SETMASK,&prev_one,NULL);//UnBlock SIGCHLD
if (setpgid(0.0) < 0) Create a process group with pgid=pid
{
perror("SETPGID ERROR");
exit(0);
}
if (execve(argv[0], argv, environ) < 0) //if execve error return 0
{
printf("%s: Command not found\n", argv[0]);
exit(0); }}else{
state = bg ? BG : FG;
sigprocmask(SIG_BLOCK, &mask_all, NULL); //parent process
addjob(jobs, pid, state, cmdline);
sigprocmask(SIG_SETMASK, &prev_one, NULL); //unblock SIGCHLD
}
bg?printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline):waitfg(pid);
// Background execution: output jID, PID and execution instructions
// Foreground execution :waitfg(pid)
}
return;
}
Copy the code
Basic reference to the book P525 shell code and P543 code. Consider the concurrent access problem mentioned above.
2. builtin_cmd
The implementation of the
If the user enters built-in command, the command is executed immediately; otherwise, 0 is returned
There are four built-in commands to support
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0]."quit")) {// If the first argument is quit, exit directly
exit(0);
}
if(!strcmp(argv[0]."&"))
return 1;
if(!strcmp(argv[0]."bg") | |!strcmp(argv[0]."fg"))
{
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0]."jobs")) {// If the first argument is Jobs, the current jobs are listed
listjobs(jobs);
return 1;
}
return 0; /* not a builtin command */
}
Copy the code
* * 3.do_bgfg
The realization of the * *
This function handles the two built-in commands bg and fg separately.
Each job can be identified by a process ID (PID) or job ID (JID), which is a positive integer TSH assigned. JID should be represented on the command line with the prefix “%”.
For example, % 5 stands for JID 5 and 5 stands for PID5.
tsh> fgThe % 1
Job [1] (9723) stopped by signal 20
Copy the code
void do_bgfg(char **argv)
{
struct job_t *job;
int id;
if(argv[1] = =NULL) {// If argv[0] is the only argument, then print it out with pid or %jid
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return ;
}
if (sscanf(argv[1]."%%%d", &id) > 0)
{
job = getjobjid(jobs, id);
if (job == NULL)
{
printf("%%%d: No such job\n", id);
return; }}else if (sscanf(argv[1]."%d", &id) > 0)
{
job = getjobpid(jobs, id);
if (job == NULL)
{
printf("(%d): No such process\n", id);
return; }}else
{
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
if(!strcmp(argv[0]."bg"))
{
kill(-(job->pid), SIGCONT);
job->state = BG;
printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
}
else
{
kill(-(job->pid), SIGCONT);
job->state = FG;
waitfg(job->pid);
}
return;
}
Copy the code
In the bg command, we kill a SIGCONT command to continue the stalled process, then change the state of the job to BG, print it, and let it execute in the background. Our kill SIGCONT is paused by CTRL+Z
Fg command, we kill a SIGCONT command first, let the suspended process continue, then change the state of the job to FG, and wait for the foreground execution to complete, as shown in the following example
4.waitfg
function
In fact, we don’t need parameters for waitFG here, we use the Jobs array to check if there are still foreground tasks running, and if there are foreground tasks running, we spin. The parameter here is the function prototype provided by CSApp, so WE won’t modify it, but we know that this parameter is not actually needed.
void waitfg(pid_t pid) // block until a process is no longer running in the foreground and removed from JOBS. The role here is a synchronization semantics, and our shell must wait for the foreground to finish executing before it can continue.
Copy the code
void waitfg(pid_t pid)
{
// In fact, sigsuspend should have the same effect as pause
sigset_t mask;
sigemptyset(&mask);
/* fgpid - Return PID of current foreground job, 0 if no such job */
while (fgpid(jobs) > 0) //fgpid Can obtain the PID of the current job running in the foreground. 0 indicates that no job is running
sigsuspend(&mask);
return;
}
Copy the code
Our WaitFD is used in do_bGFG, and if we make a process a foreground, we need to synchronize it with WaitFD and wait until the foreground is finished before giving control to the shell.
Waitfd’s while loop repeatedly checks to see if the current child is still executing in the foreground. This is a very important synchronization, because waitfd means that our shell process will wait until the foreground job is deleted before continuing with the next line of command interaction. For example, if we execute the child process, CTRL + C, we terminate the child process, or CTRL + Z, we pause the child process, so we check the stop or stop information of the child process by waitPID to determine whether the child process is terminated by CTRL + C, paused by CTRL + Z, or exits normally by itself.
The code for the FDPID function is as follows (this is provided by the experiment, so we don’t need to write it ourselves) :
pid_t fgpid(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}
Copy the code
5.sigint_handler
andsigstp_handler
Starting here, there are three signal handler functions.
Sigint is the termination of CTRL+C.
Sigstp is CTRL+Z to bring stop.
/* * sigint_handler - The kernel sends a SIGINT to the shell whenver the * user types ctrl-c at the keyboard. Catch it and send it along * to the foreground job. */
void sigint_handler(int sig)
{
int old_errno = errno; // First you need to save the original errno
pid_t pid = fgpid(jobs);
if(pid ! =0) // If there is no foreground executing, return. If there is a foreground executing, send SIGINT to the process group
kill(-pid,sig);
errno = old_errno;
}
/* * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever * the user types ctrl-z at the keyboard. Catch it and suspend the * foreground job by sending it a SIGTSTP. */
void sigtstp_handler(int sig)
{
int old_errno = errno; // First you need to save the original errno
pid_t pid = fgpid(jobs);
if(pid ! =0) // If there is no foreground executing, return directly. If there is a foreground executing, send SIGTSTP pause signal to the process group
kill(-pid,sig);
errno = old_errno;
}
Copy the code
When we typeCtrl+C
, the kernel will sendSIGINT
Signals to the shell, and the shell just needs to pass throughkill
The function forwards it to the child process when we typeCtrl+Z
The same is true of.
Note :(reference here from CSAPP+SICP – zhihu.com)
-
We need to save errno and reassign it on return to prevent it from being changed.
-
When I wrote this code, I had a suspicion that we caught both signals with Signal on the outermost side, so the new child of fork should also process the signals with both Signal handlers. So when the shell sends SIGINT or SIGTSTP signals to the foreground process, doesn’t the foreground process also run into those two signal handlers? But I found that it went into SIGCHLD’s signal processing program. Here is due to:
-
- When a process calls
fork
The address of the signal handler is meaningful in the child process because the child process copies the parent process’s storage image at the beginning, soThe child inherits the signal handler defined by the parent. - butWhen the child process calls
execve
After, because **execve
Running the new program overwrites the storage image inherited from the parent process, so the signal handler is meaningless in the new program, soexecve
Signals that were set to capture are changed to the default action **. So when the shell sendsSIGINT
andSIGTSTP
When the signal is given to the child process, they do the default action, which is to terminate the process, so the kernel sends when the child process terminatesSIGCHLD
Signals are sent to the parent process so that it can jump to the shellSIGCHLD
Is in the signal processing program.
- When a process calls
-
The kill(-pid,sig) of the foreground process group does not continue to call the signal handler instead of going into an infinite loop.
The reason is that when we reset the state machine with execve, the signal handler in the child process is also reset, so the signal handler in the child process is the default, and then calling kill(-pid,sig) really tells it to stop or terminate.
6. * *sigchld_handler
The realization of the * *
Sigchld_handler, which means that when a child terminates or terminates, the child sends a SIGCHLD to the parent and then recycles it.
Our child process can end in three ways:
- The normal exit
- CTRL+C forcibly terminates the process
- CTRL+Z suspends the task, but the task is not finished, it is still in progress, it just remains suspended. The user can use fg/bg to continue foreground or background tasks,fg command to restart foreground interrupted tasks,bg command to put interrupted tasks in background execution.
When the child stops or terminates, it sends SIGCHLD signals to the parent, so we need to handle all three cases
void sigchld_handler(int sig)
{
int olderrno = errno; // Keep the original errno
pid_t pid;
int status;
sigset_t mask_all, prev;
sigfillset(&mask_all);
while((pid = waitpid(- 1, &status, WNOHANG | WUNTRACED)) > 0)
{
if (WIFEXITED(status)) // If the child exits properly, then true, delete job
{
sigprocmask(SIG_BLOCK, &mask_all, &prev);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else if (WIFSIGNALED(status)) // If the child process terminates after receiving a SIGNAL, print SIGNAL before deleting it
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else // If the child process is paused, it is not deleted. Instead, it changes the state of the process to ST(stop) and prints
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
job->state= ST;
sigprocmask(SIG_SETMASK, &prev, NULL); // Resume signal reception
}
}
errno = olderrno;// Restore errno, keeping the error message unchanged before and after the child process is reclaimed.
return;
}
Copy the code
- The normal exit
- deletejob
- CTRL + C to terminate
- Deletejob + Prints the termination information
- CTRL + Z to suspend
- The job becomes the STATE of ST paused + The pause information is printed
The processing procedure of SIGCHLD signal is complicated. First, since there is no queue for the signal, we need to assume that an unprocessed signal indicates that at least one signal has arrived, so we need to use a while loop instead of an if. Second, in the use of waitpid function recovery, we need to set options for WNOHANG | WUNTRACED, WNOHANG said when there is no child process terminates, the parent will not be suspended, but waitpid () function returns 0, thus preventing when there is the child in the shell, Stuck in loop due to while; WUNTRACED is guaranteed to return the PID of any terminated or stopped sub-processes. As a result, the kernel will send SIGCHLD signals to the shell when the sub-processes receive SIGINT and SIGTSTP signals to terminate or stop. You get stuck in the loop.
In addition, we need to note:
- The execution signal handler is in the same process as the main program
- The signal is sent by the inner core to the parent process, such as typing
Ctrl+C
orCtrl+Z
, the kernel will sendSIGINT
orSIGTSTP
Signals are sent to the parent process and are sent by the kernel when the child process stops or terminatesSIGCHLD
Signal to the parent process, and then execute the corresponding signal handler in the parent process. Keep an eye on what process is currently executing.
7. To summarize
Briefly summarize the shell’s behavior.
As a process, the shell determines whether a command line is a built-in one after receiving it
- If the command is a built-in command, the shell executes it directly
- If it is not a built-in command, the shell will pass
fork
Create a new child process, assign it a separate process group ID, separate from the shell process, and passexecve
Function to execute the executable object file. If it is a foreground job, the shell waits for the job to complete; if it is a background job, the shell reads the next command directly.Note:execve
Function will eliminate the signal handler we defined.
When the program we are executing exits normally, or CTRL+C terminates, or CTRL+ Z pauses, Sigchld is triggered by both the termination and pause of the child process, and then we enter the Sigchld handler.
- Exit normally – delete job
- Ctrl+ C Terminate -delete job + Print
- Ctrl+ Z Pause – Change the state of this foreground process to ST(stop) + Print
After that, we either delete the foreground job or change the foreground job to stop, so our parent process was stuck in waitFG, and now, since there is no foreground job, the parent process enters the next TSH interaction.
When we type Ctrl+C or Ctrl+Z, since we are executing a shell process, the kernel sends SIGINT or SIGTSTP to the parent TSH process, and TSH needs to send the signal to the foreground process via the kill function. Since the foreground process execve, the default behavior is either to terminate or stop the child process, and then the kernel sends a SIGCHLD signal to the shell to indicate that a child process has been terminated or stopped. The shell then uses the corresponding signal handler to recycle or modify the job status of the child process.
8. Several examples analyze the execution process
When we execute./myspin 2, a child process is created to execute it, and the parent waits with waitFG without going to the next ZSH interaction. Our waitFG keeps checking to see if there are any foreground programs running in jobs until after the child process exit, our SIGchld deletes the foreground job with waitPID, so the TSH process returns from waitFG and enters the next TSH interaction.
Ctrl+C, we will signal Ctrl+C to our TSH process. The TSH process creates a child process to perform tasks. When the TSH process forks, its pGID is copied from TSH, so it is TSH’s PID. Since we need to separate them into different process groups, make this subprocess a separate process group, as shown in the PPT
In fact, the basic execution flow is like this: our terminal receives the keyboard interrupt Ctrl+ C, and sends this signal to our TSH process, which in turn sends the kill to the foreground child process group.
Ctrl+Z pauses similarly:
Let’s look at background execution:
In the background, the TSH process does not have a WAITFG wait and instead goes straight to the next round of TSH interaction. And since we added the job to Jobs in the eval function, we can still display the process information by typing Jobs.
Let’s look at BG and FG
As you can see, we move a background process to the foreground, and it’s actually stuck waiting for the foreground to finish, because we wrote waitfg in our do_bGFg function, as follows
Let’s try again to bring a suspended process to the foreground:
Test of 9.
kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± college ~/csapp/ LABS /05-shell-lab kkbabe@ubuntu master ± make test01 ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"#
# trace01.txt - Properly terminate on EOF.
#kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test02
./sdriver.pl -t trace02.txt -s ./tsh -a "-p"
#
# trace02.txt - Process builtin quit command.
#kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test03
./sdriver.pl -t trace03.txt -s ./tsh -a "-p"
#
# trace03.txt - Run a foreground job.
#
tsh> quitkkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test04./sdriver.pl -t trace04.txt -s./ TSH -a "-p"#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (47246) ./myspin 1 &
kkbabe@ubuntu ~/csapp/labs/05-shell-lab master ± make test05
./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
#
# trace05.txt - Process jobs builtin command.
#
tsh> ./myspin 2 &
[1] (47325) ./myspin 2 &
tsh> ./myspin 3 &
[2] (47327) ./myspin 3 &
tsh> jobs[1] (47325) Running./ MySpin 2 & [2] (47327) Running./ mySpin 3 & kkbabe@ubuntu ~/ csApp/LABS /05-shell- Lab Master ± make test06./sdriver.pl -t trace06.txt -s./ TSH -a "-p"#
# trace06.txt - Forward SIGINT to foreground job.
#
tsh> ./myspin 4Job [1] (47420) terminated by signal 2 kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test07./sdriver.pl -t trace07.txt -s ./tsh -a "-p"#
# trace07.txt - Forward SIGINT only to foreground job.
#
tsh> ./myspin 4 &
[1] (47535) ./myspin 4 &
tsh> ./myspin 5
Job [2] (47537) terminated by signal 2
tsh> jobs
[1] (47535) Running ./myspin 4 &
kkbabe@ubuntu ~/csapp/labs/05-shell-lab master ± make test08
./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
tsh> ./myspin 4 &
[1] (47621) ./myspin 4 &
tsh> ./myspin 5
Job [2] (47623) stopped by signal 20
tsh> jobs[1] (47621) Running./ Myspin 4 & [2] (47623) Stopped./ Myspin 5 kkbabe@ubuntu ~/ CSapp/LABS /05-shell-lab master ± make test09 ./sdriver.pl -t trace09.txt -s ./tsh -a "-p"#
# trace09.txt - Process bg builtin command
#
tsh> ./myspin 4 &
[1] (47715) ./myspin 4 &
tsh> ./myspin 5
Job [2] (47717) stopped by signal 20
tsh> jobs
[1] (47715) Running ./myspin 4 &
[2] (47717) Stopped ./myspin 5
tsh> bg% 2
[2] (47717) ./myspin 5
tsh> jobs[1] (47715) Running./ MySpin 4 & [2] (47717) Running./myspin 5 kkbabe@ubuntu ~/ csApp/LABS /05-shell-lab master ± make test10 ./sdriver.pl -t trace10.txt -s ./tsh -a "-p"#
# trace10.txt - Process fg builtin command.
#
tsh> ./myspin 4 &
[1] (47809) ./myspin 4 &
tsh> fgThe % 1
Job [1] (47809) stopped by signal 20
tsh> jobs
[1] (47809) Stopped ./myspin 4 &
tsh> fgThe % 1
tsh> jobskkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test11./sdriver.pl -t trace11.txt -s./ TSH -a "-p"#
# trace11.txt - Forward SIGINT to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (47935) terminated by signal 2
tsh> /bin/ps aPID TTY STAT TIME COMMAND 1760 tty2 Ssl+ 0:00 /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu 1762 tty2 Sl+ 6:57 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3 1813 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --systemd --session=ubuntu 1887 tty2 Z+ 0:00 [fcitx] <defunct> 46847 pts/6 Ss 0:00 /usr/bin/zsh 46893 pts/7 Ss+ 0:00 /usr/bin/zsh 47930 pts/6 S+ 0:00 make test11 47931 pts/6 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tsh -a "-p" 47932 pts/6 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p 47933 PTS /6 S+ 0:00./ TSH -p 47943 PTS /6 R 0:00 /bin/ps a kkbabe@ubuntu ~/ CSapp/LABS /05-shell-lab master ± make test12 ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"#
# trace12.txt - Forward SIGTSTP to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (48070) stopped by signal 20
tsh> jobs
[1] (48070) Stopped ./mysplit 4
tsh> /bin/ps aPID TTY STAT TIME COMMAND 1760 tty2 Ssl+ 0:00 /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu 1762 tty2 Sl+ 6:57 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3 1813 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --systemd --session=ubuntu 1887 tty2 Z+ 0:00 [fcitx] <defunct> 46847 pts/6 Ss 0:00 /usr/bin/zsh 46893 pts/7 Ss+ 0:00 /usr/bin/zsh 48065 pts/6 S+ 0:00 make test12 48066 pts/6 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tsh -a "-p" 48067 pts/6 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p 48068 pts/6 S+ 0:00 ./tsh -p 48070 pts/6 T 0:00 ./mysplit 4 48071 pts/6 T 0:00 ./mysplit 4 48074 pts/6 R 0:00 /bin/ps A kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test13./sdriver.pl -t trace13.txt -s./ TSH -a "-p"#
# trace13.txt - Restart every stopped process in process group
#
tsh> ./mysplit 4
Job [1] (48162) stopped by signal 20
tsh> jobs
[1] (48162) Stopped ./mysplit 4
tsh> /bin/ps aPID TTY STAT TIME COMMAND 1760 tty2 Ssl+ 0:00 /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu 1762 tty2 Sl+ 6:58 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3 1813 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --systemd --session=ubuntu 1887 tty2 Z+ 0:00 [fcitx] <defunct> 46847 pts/6 Ss 0:00 /usr/bin/zsh 46893 pts/7 Ss+ 0:00 /usr/bin/zsh 48157 pts/6 S+ 0:00 make test13 48158 pts/6 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p" 48159 pts/6 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p 48160 pts/6 S+ 0:00 ./tsh -p 48162 pts/6 T 0:00 ./mysplit 4 48163 pts/6 T 0:00 ./mysplit 4 48171 pts/6 R 0:00 /bin/ps atsh> fgThe % 1
tsh> /bin/ps aPID TTY STAT TIME COMMAND 1760 tty2 Ssl+ 0:00 /usr/lib/gdm3/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu 1762 tty2 Sl+ 6:58 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3 1813 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --systemd --session=ubuntu 1887 tty2 Z+ 0:00 [fcitx] <defunct> 46847 pts/6 Ss 0:00 /usr/bin/zsh 46893 pts/7 Ss+ 0:00 /usr/bin/zsh 48157 pts/6 S+ 0:00 make test13 48158 pts/6 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p" 48159 pts/6 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p 48160 PTS /6 S+ 0:00./ TSH -p 48179 PTS /6 R 0:00 /bin/ps a kkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test14 ./sdriver.pl -t trace14.txt -s ./tsh -a "-p"#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (48311) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg% 2
%2: No such job
tsh> fgThe % 1
Job [1] (48311) stopped by signal 20
tsh> bg% 2
%2: No such job
tsh> bgThe % 1
[1] (48311) ./myspin 4 &
tsh> jobs
[1] (48311) Running ./myspin 4 &
kkbabe@ubuntu ~/csapp/labs/05-shell-lab master ± make test15
./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (48397) terminated by signal 2
tsh> ./myspin 3 &
[1] (48399) ./myspin 3 &
tsh> ./myspin 4 &
[2] (48401) ./myspin 4 &
tsh> jobs
[1] (48399) Running ./myspin 3 &
[2] (48401) Running ./myspin 4 &
tsh> fgThe % 1
Job [1] (48399) stopped by signal 20
tsh> jobs
[1] (48399) Stopped ./myspin 3 &
[2] (48401) Running ./myspin 4 &
tsh> bg% 3
%3: No such job
tsh> bgThe % 1
[1] (48399) ./myspin 3 &
tsh> jobs
[1] (48399) Running ./myspin 3 &
[2] (48401) Running ./myspin 4 &
tsh> fgThe % 1
tsh> quitkkbabe@ubuntu ~/csapp/ LABS /05-shell-lab master ± make test16./sdriver.pl -t trace16.txt -s./ TSH -a "-p"#
# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
# signals that come from other processes instead of the terminal.
#
tsh> ./mystop 2
Job [1] (48517) stopped by signal 20
tsh> jobs
[1] (48517) Stopped ./mystop 2
tsh> ./myint 2
Job [2] (48525) terminated by signal 2
Copy the code
We did pass all the tests
10. All code
/* * tsh - A tiny shell program with job control * *
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
/* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */
/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */
/* * Jobs states: FG (foreground), BG (background), ST (stopped) * Job state transitions and enabling actions: * FG -> ST : ctrl-z * ST -> FG : fg command * ST -> BG : bg command * BG -> FG : fg command * At most 1 job can be in the FG state. */
/* Global variables */
extern char **environ; /* defined in libc */
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
int verbose = 0; /* if true, print additional output */
int nextjid = 1; /* next job ID to allocate */
char sbuf[MAXLINE]; /* for composing sprintf messages */
struct job_t { /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] * /
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */
/* Function prototypes */
/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);
void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);
/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv);
void sigquit_handler(int sig);
void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs);
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void listjobs(struct job_t *jobs);
void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
/* * main - The shell's main routine */
int main(int argc, char **argv)
{
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* emit prompt (default) */
/* Redirect stderr to stdout (so that driver will get all output * on the pipe connected to stdout) */
dup2(1.2);
/* Parse the command line */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* print help message */
usage();
break;
case 'v': /* emit additional diagnostic info */
verbose = 1;
break;
case 'p': /* don't print a prompt */
emit_prompt = 0; /* handy for automatic testing */
break;
default: usage(); }}/* Install the signal handlers */
/* These are the ones you will need to implement */
Signal(SIGINT, sigint_handler); /* ctrl-c */
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
/* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler);
/* Initialize the job list */
initjobs(jobs);
/* Execute the shell's read/eval loop */
while (1) {
/* Read command line */
if (emit_prompt) {
printf("%s", prompt);
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) = =NULL) && ferror(stdin))
app_error("fgets error");
if (feof(stdin)) { /* End of file (ctrl-d) */
fflush(stdout);
exit(0);
}
/* Evaluate the command line */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}
exit(0); /* control never reaches here */
}
/* * eval - Evaluate the command line that the user has just typed in * * If the user has requested a built-in command (quit, jobs, bg or fg) * then execute it immediately. Otherwise, fork a child process and * run the job in the context of the child. If the job is running in * the foreground, wait for it to terminate and then return. Note: * each child process must have a unique process group ID so that our * background children don't receive SIGINT (SIGTSTP) from the kernel * when we type ctrl-c (ctrl-z) at the keyboard. */
void eval(char *cmdline)
{
char *argv[MAXLINE]; /*Argument list execve()*/
char buf[MAXLINE]; /*Hold modified commend line*/
int bg; /*Should the job run in bg or fg? * /
pid_t pid;
int state;
sigset_t mask_all, mask_one, prev_one;
strcpy(buf,cmdline);
bg=parseline(cmdline,argv);
if(argv[0] = =0) {return ;//ignore empty line
}
if(! builtin_cmd(argv)){ sigfillset(&mask_all); sigemptyset(&mask_one); sigaddset(&mask_one, SIGCHLD); sigprocmask(SIG_BLOCK, &mask_one, &prev_one);//Block SIGCHLD
if((pid=fork())==0){
sigprocmask(SIG_SETMASK,&prev_one,NULL);//UnBlock SIGCHLD
if (setpgid(0.0) < 0)
{
perror("SETPGID ERROR");
exit(0);
}
if (execve(argv[0], argv, environ) < 0) //if execve error return 0
{
printf("%s: Command not found\n", argv[0]);
exit(0); }}else{
state = bg ? BG : FG;
sigprocmask(SIG_BLOCK, &mask_all, NULL); //parent process
addjob(jobs, pid, state, cmdline);
sigprocmask(SIG_SETMASK, &prev_one, NULL); //unblock SIGCHLD
}
bg?printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline):waitfg(pid);
}
return;
}
/* * parseline - Parse the command line and build the argv array. * * Characters enclosed in single quotes are treated as a single * argument. Return true if the user has requested a BG job, false if * the user has requested a FG job. */
int parseline(const char *cmdline, char **argv)
{
static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that traverses command line */
char *delim; /* points to first space delimiter */
int argc; /* number of args */
int bg; /* background job? * /
strcpy(buf, cmdline);
buf[strlen(buf)- 1] = ' '; /* replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* ignore leading spaces */
buf++;
/* Build the argv list */
argc = 0;
if (*buf == '\' ') {
buf++;
delim = strchr(buf, '\' ');
}
else {
delim = strchr(buf, ' ');
}
while (delim) {
argv[argc++] = buf;
*delim = '\ 0';
buf = delim + 1;
while (*buf && (*buf == ' ')) /* ignore spaces */
buf++;
if (*buf == '\' ') {
buf++;
delim = strchr(buf, '\' ');
}
else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL;
if (argc == 0) /* ignore blank line */
return 1;
/* should the job run in the background? * /
if ((bg = (*argv[argc- 1] = ='&')) != 0) {
argv[--argc] = NULL;
}
return bg;
}
/* * builtin_cmd - If the user has typed a built-in command then execute * it immediately. */
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0]."quit")) {// If the first argument is quit, exit directly
exit(0);
}
if(!strcmp(argv[0]."&"))
return 1;
if(!strcmp(argv[0]."bg") | |!strcmp(argv[0]."fg"))
{
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0]."jobs")) {// If the first argument is Jobs, the current jobs are listed
listjobs(jobs);
return 1;
}
return 0; /* not a builtin command */
}
/* * do_bgfg - Execute the builtin bg and fg commands */
void do_bgfg(char **argv)
{
struct job_t *job;
int id;
if(argv[1] = =NULL) {// If argv[0] is the only argument, then print it out with pid or %jid
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return ;
}
if (sscanf(argv[1]."%%%d", &id) > 0)
{
job = getjobjid(jobs, id);
if (job == NULL)
{
printf("%%%d: No such job\n", id);
return; }}else if (sscanf(argv[1]."%d", &id) > 0)
{
job = getjobpid(jobs, id);
if (job == NULL)
{
printf("(%d): No such process\n", id);
return; }}else
{
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
if(!strcmp(argv[0]."bg"))
{
kill(-(job->pid), SIGCONT); // Let the child process continue
job->state = BG;
printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
}
else
{
kill(-(job->pid), SIGCONT); // Let the child process continue
job->state = FG;
waitfg(job->pid);
}
return;
}
/* * waitfg - Block until process pid is no longer the foreground process */
void waitfg(pid_t pid)
{
// In fact, sigsuspend should have the same effect as pause
sigset_t mask;
sigemptyset(&mask);
while (fgpid(jobs) > 0) // If the foreground is still running, pause.
sigsuspend(&mask);
return;
}
/***************** * Signal handlers *****************/
/* * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever * a child job terminates (becomes a zombie), or stops because it * received a SIGSTOP or SIGTSTP signal. The handler reaps all * available zombie children, but doesn't wait for any other * currently running children to terminate. */
void sigchld_handler(int sig)
{
int olderrno = errno; // Keep the original errno
pid_t pid;
int status;
sigset_t mask_all, prev;
sigfillset(&mask_all);
while((pid = waitpid(- 1, &status, WNOHANG | WUNTRACED)) > 0) // Wait until the collection succeeds
{
if (WIFEXITED(status)) // If the child exit, then true, delete job
{
sigprocmask(SIG_BLOCK, &mask_all, &prev);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else if (WIFSIGNALED(status)) // If the child process terminates after receiving a SIGNAL (CTRL + C), it returns true, prints SIGNAL and deletes it
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else // If the child process is paused (CTRL +Z), instead of deleting it, change the state of the process to ST(stop) and print
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
job->state= ST;
sigprocmask(SIG_SETMASK, &prev, NULL);
}
}
errno = olderrno;// Restore errno, keeping the error message unchanged before and after the child process is reclaimed.
return;
}
/* * sigint_handler - The kernel sends a SIGINT to the shell whenver the * user types ctrl-c at the keyboard. Catch it and send it along * to the foreground job. */
void sigint_handler(int sig)
{
int old_errno = errno; // First you need to save the original errno
pid_t pid = fgpid(jobs);
if(pid ! =0) // If there is no foreground executing, return. If there is a foreground executing, send SIGINT to the process group
kill(-pid,sig);
errno = old_errno;
}
/* * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever * the user types ctrl-z at the keyboard. Catch it and suspend the * foreground job by sending it a SIGTSTP. */
void sigtstp_handler(int sig)
{
int old_errno = errno; // First you need to save the original errno
pid_t pid = fgpid(jobs);
if(pid ! =0) // If there is no foreground executing, return directly. If there is a foreground executing, send SIGTSTP pause signal to the process group
kill(-pid,sig);
errno = old_errno;
}
/********************* * End signal handlers *********************/
/*********************************************** * Helper routines that manipulate the job list * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\ 0';
}
/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}
/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs)
{
int i, max=0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return max;
}
/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == 0) {
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++;
if (nextjid > MAXJOBS)
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline);
if(verbose){
printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
}
return 1; }}printf("Tried to create too many jobs\n");
return 0;
}
/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) {
clearjob(&jobs[i]);
nextjid = maxjid(jobs)+1;
return 1; }}return 0;
}
/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}
/* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
return &jobs[i];
return NULL;
}
/* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid)
{
int i;
if (jid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid == jid)
return &jobs[i];
return NULL;
}
/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid) {
return jobs[i].jid;
}
return 0;
}
/* listjobs - Print the job list */
void listjobs(struct job_t *jobs)
{
int i;
for (i = 0; i < MAXJOBS; i++) {
if(jobs[i].pid ! =0) {
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
switch (jobs[i].state) {
case BG:
printf("Running ");
break;
case FG:
printf("Foreground ");
break;
case ST:
printf("Stopped ");
break;
default:
printf("listjobs: Internal error: job[%d].state=%d ",
i, jobs[i].state);
}
printf("%s", jobs[i].cmdline); }}}/****************************** * end job list helper routines ******************************/
/*********************** * Other helper routines ***********************/
/* * usage - print a help message */
void usage(void)
{
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit(1);
}
/* * unix_error - unix-style error routine */
void unix_error(char *msg)
{
fprintf(stdout."%s: %s\n", msg, strerror(errno));
exit(1);
}
/* * app_error - application-style error routine */
void app_error(char *msg)
{
fprintf(stdout."%s\n", msg);
exit(1);
}
/* * Signal - wrapper for the sigaction function */
handler_t *Signal(int signum, handler_t *handler)
{
struct sigaction action.old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
action.sa_flags = SA_RESTART; /* restart syscalls if possible */
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
/* * sigquit_handler - The driver program can gracefully terminate the * child shell by sending it a SIGQUIT signal. */
void sigquit_handler(int sig)
{
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}
Copy the code