Computer system experiment 7 of Harbin Institute of Technology

Posted by Allenport on Tue, 01 Feb 2022 12:43:45 +0100

All experimental files can be seen in github Experimental arrangement of computer system

Experimental report

Experiment (7)

Title TinyShell
Micro shell
Professional computer
Student No.: 119030321
Class 1936602
Student Zheng Shenghe
Instructor Liu Hongwei
Test site G709
Test date: June 3, 2021

School of computer science and technology

catalogue

Chapter 1 basic experimental information - 4 -
1.1 purpose of the experiment - 4 -
1.2 experimental environment and tools - 4 -
1.2.1 hardware environment - 4 -
1.2.2 software environment - 4 -
1.2.3 development tools - 4 -
1.3 experiment Preview - 4 -
Chapter 2 experimental Preview - 6 -
2.1 process concept, creation and recycling method (5 points) - 6 -
2.2 signal mechanism and type (5 points) - 6 -
2.3 signal sending method, blocking method and setting method of processing program (5 points) - 6 -
2.4 what is SHELL, function and processing flow (5 points) - 6 -
Chapter 3 design and implementation of TINYSHELL - 7 -
3.1.1 VOID EVAL(CHAR *CMDLINE) function (10 points) - 7 -
3. 1.2 INT BUILTIN_CMD(CHAR **ARGV) function (5 points) - 7 -
3. 1.3 VOID DO_BGFG(CHAR **ARGV) function (5 points) - 7 -
3.1.4 voice waitfg (pid_t PID) function (5 points) - 7 -
3. 1.5 VOID SIGCHLD_HANDLER(INT SIG) function (10 points) - 8 -
Chapter 4 TINYSHELL test - 10 -
4.1 test method - 10 -
4.2 evaluation of test results - 10 -
4.3 self test results - 10 -
4.3.1 test case trace01 txt - 10 -
4.3.2 test case trace02 txt - 11 -
4.3.3 test case trace03 txt - 11 -
4.3.4 test case trace04 txt - 11 -
4.3.5 test case trace05 txt - 11 -
4.3.6 test case trace06 txt - 12 -
4.3.7 test case trace07 txt - 12 -
4.3.8 test case trace08 txt - 12 -
4.3.9 test case trace09 txt - 13 -
4.3.10 test case trace10 txt - 13 -
4.3.11 test case trace11 txt - 13 -
4.3.12 test case trace12 txt - 14 -
4.3.13 test case trace13 txt - 14 -
4.3.14 test case trace14 txt - 14 -
4.3.15 test case trace15 txt - 15 -
4.4 self test score - 15 -
Chapter 5 Summary - 16 -
5.1 please summarize the results of this experiment - 16 -
5.2 please give suggestions on the contents of this experiment - 16 -
reference - 18 -

Chapter 1 basic experimental information

1.1 purpose of the experiment
Understand the basic knowledge of modern computer system process and concurrency
Master the basic principles and related system functions of linux exception control flow and signal mechanism
Master the basic principle and implementation method of shell
Deeply understand the possible concurrency conflicts caused by Linux signal response and their solutions
Cultivate the ability of software system development and testing under Linux
1.2 experimental environment and tools
1.2.1 hardware environment
X64 CPU; 2GHz; 2G RAM; 256GHD Disk up
1.2.2 software environment
Windows7 64 bit or more; VirtualBox/Vmware 11 or above; Ubuntu 16.04 LTS 64 bit / youqilin 64 bit
1.2.3 development tools
Ubuntu,gcc
1.3 experiment Preview
Before the experimental class, you must carefully preview the experimental instruction (PPT or PDF)
Understand the purpose of the experiment, experimental environment, software and hardware tools, experimental operation steps, and review the theoretical knowledge related to the experiment.
Understand the basic concepts and principles of process, operation and signal
Understand the basic principles of shell
Familiar with process creation, recycling methods and related system functions
Familiar with signal mechanism and system functions related to signal processing
Chapter 2 experimental Preview
Total score: 20 points

2.1 process concept, creation and recycling method (5 points)
Concept of Process: a Process is a running activity of a computer program on a data set. It is the basic unit for resource allocation and scheduling of the system and the basis of the structure of the operating system. In the early computer structure of Process oriented design, Process is the basic executive entity of program; In the contemporary computer architecture of thread oriented design, Process is the container of thread. Program is the description of instructions, data and their organizational form, and Process is the entity of program.
Process creation: as like as two peas in unix, the system calls fork, fork will establish a child process which is basically the same as the parent process, and in windows, the system call is cresteprocess, which is responsible for creating the process and loading the correct program into the new process. It can be divided into the following steps:
1. System initialization
2. A process starts a child process during operation
3. Create a new process based on the user's interactive request (such as double clicking the application icon)
4. Initialization of a batch job (only applied in the batch system of mainframe)
About process creation, UNIX and WINDOWS
Process recycling: due to the imperfect coding, the performance of the application will be lower and lower over time, sometimes trapped in a cycle, resulting in unnecessary CPU load. These applications can also lead to memory leaks, when applications no longer release unwanted memory back to the operating system. These applications may cause the server to stop running, so you need to restart the server. Process recycling is created to solve these problems. The child process has the parent process recycling, the orphan process has init recycling, and the zombie process appears if it is not recycled in time.
2.2 signal mechanism and type (5 points)
Signals can be classified from two different classification perspectives:
Reliability: reliable signal and unreliable signal;
Relationship with time: real-time signal and non real-time signal.
① Reliable signal and unreliable signal
Linux signaling mechanism is basically inherited from Unix system. The signal mechanism in the early Unix system is relatively simple and primitive. Signals with signal value less than SIGRTMIN are unreliable signals. This is the source of "unreliable signals". Its main problem is that the signal may be lost.
With the development of time, practice has proved that it is necessary to improve and expand the original mechanism of signal. Since the originally defined signals have many applications and are not easy to change, they have to add some new signals and define them as reliable signals from the beginning. These signals support queuing and will not be lost.
The signals with signal values between SIGRTMIN and SIGRTMAX are reliable signals, which overcomes the problem of possible signal loss. While Linux supports the new version of signal installation function signal() and signal transmission function sigqueue(), it still supports the early signal() signal installation function and signal transmission function kill().
The reliability and unreliability of the signal are only related to the signal value, not to the signal transmission and installation function. At present, signal() in linux is implemented through the signal() function. Therefore, even if the signal installed through signal() does not need to call the signal installation function again at the end of the signal processing function. At the same time, the real-time signal installed by signal() supports queuing and will not be lost.
② Real time signal and non real time signal
In the early Unix system, only 32 kinds of signals were defined. The first 32 kinds of signals had predefined values. Each signal had a definite purpose and meaning, and each signal had its own default action. If you press CTRL ^C on the keyboard, a SIGINT signal will be generated. The default response to this signal is process termination. The last 32 signals represent real-time signals, which are equivalent to the reliable signals described above. This ensures that multiple real-time signals transmitted are received.
Non real-time signals do not support queuing and are unreliable signals; All signals are real-time and reliable.
2.3 signal sending method, blocking method and setting method of processing program (5 points)
A process can send a signal to other processes including itself through the kill function. If the program does not have the permission to send this signal, the call to the kill function will fail. The common reason for failure is that the target process is owned by another user.
Blocking method: implicit blocking mechanism: the kernel blocks any signal class being processed by the current handler by default
And pending signals. Display blocking base: the application can call sigprocmask function and its auxiliary functions to explicitly block and unblock the selected signal.
How to set the handler:
1) Call signal function and call signal(SIG,handler). Sig represents signal type and handler
Represents the corresponding processing program after receiving the SIG signal.
2) Because the semantics of signal are different, we need a portable signal processing function
The Posix standard defines the sigaction function, which allows the user to set the signal
When processing, they clearly specify the signal processing semantics they want
2.4 what is shell, function and processing flow (5 points)
What is a Shell: Shell is a program written in C language. It is the user who uses Linux
Bridge. Shell is both a command language and a programming language. Shell refers to an application program.
Function: Shell application provides an interface through which users can access the operating system
Nuclear services.
Processing flow:
1) Read in the input command from the terminal.
2) Cut the input string to get all the parameters
3) If it is a built-in command, execute it immediately
4. Otherwise, call the corresponding program for execution
5) The shell should accept keyboard input signals and process them accordingly.

Chapter 3 design and implementation of TinyShell
Total score: 45 points
3.1 design
3.1.1 void eval(char *cmdline) function (10 points)
Function function: judge whether there is a built-in command on the command line just entered by the user. If so, execute it immediately.
Parameter: char *cmdline
Processing flow: 1 After initializing some variables, first call the parseline function to parse the output and judge whether it is a built-in command or a program.
2. If it is a built-in command, enter build_ CMD function to execute built-in commands. Otherwise, fork a child process and add it to the jobs list.

Key points analysis:
Signal blocking is required before addjob to prevent non-existent child processes from being added to the list

3.1.2 int builtin_cmd(char **argv) function (5 points)
Function function: determine which built-in command needs to be executed by comparison
Parameter: char **argv
Processing flow: use several if statements to match which built-in command needs to be executed according to the input command line instruction, call the function of the built-in command, and return after execution. For example, if the first command on the command line is quit, exit the function directly. Other situations are similar.
Key point analysis: it should be noted that the signal needs to be blocked before listjobs to prevent jobs from being modified by the signal and ensure the accuracy of the function.

3.1.3 void do_bgfg(char **argv) function (5 points)
Function function: implement built-in commands bg and fg
Parameter: char **argv
Processing flow:
Step 1: first judge whether there are parameters after fg and bg commands. If not, ignore this command.
Step 2: if fg or bg is only followed by a number, it means that the process number is taken. After obtaining the process number, use getjobpid(jobs, pid) to get the job; If fg or bg is followed by% plus a number, it means that% is followed by the task number. At this time, after obtaining jid, you can use getjobjid(jobs, jid) to obtain job. So you get a job after the second step.
Step 3: according to the difference between fg and bg commands, obtain the pid value that this function needs to return according to the job obtained in step 2.
Key points analysis:

  1. Note that you need to use atoi to convert the string to an integer for calling.
  2. The function needs to judge whether the command needs to be executed first. If it needs to be executed, it also needs to obtain the job and the final pid value in two different ways according to two different commands.
  3. If SIGCONT is encountered, all we need to do is continue the stopped process.

3.1.4 void waitfg(pid_t pid) function (5 points)
Function: wait for the end of the foreground program
Parameter: pid_t pid
Processing flow: judge whether the foreground program ends. If not, continue to wait until the signal of process termination is received, jump out of the loop and return.
Key point analysis: in while, if only the pause() function is used, the program must wait for a long time before checking the termination condition of the loop again. If using a high-precision sleep function such as nanosleep is also unacceptable, because there is no good way to determine the sleep interval.

3.1.5 void sigchld_handler(int sig) function (10 points)
Function: capture sigshield signal
Parameter: int sig
Processing flow: first store all errnos to recover after obtaining the required signals, then add all signals to the mask and enter a while loop to prepare for the loop, and recover the sub process in the loop. If no process in the waiting set is stopped or stopped, return 0, otherwise the child will return to the pid of the process. Next, block the signal in the loop and use the getjobpid() function to find the job through pid. Judge which output form and return form to adopt according to different conditions. After the determination, the blocking signal is released, the errno is restored, and the error is returned.
Key points analysis:

  1. The while loop is used to avoid the problem of signal blocking. The waitpid() function is used in the loop to recycle as many zombie processes as possible.
  2. When deleting jobs, you need to block the signal first.

3.2 program realization (all contents of tsh.c) (10 points)
Focus on code style:
(1) Explain with good code comments - 5 points
(2) Check the return value of each system call - 5 points

/*
 * tsh - A tiny shell program with job control
 *
 * <Put your name and login ID here>
 */
#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); //Clear buffer and output
        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)
{
    /* $begin handout */
    char* argv[MAXARGS]; /* argv for execve() */
    int bg;              /* should the job run in bg or fg? */
    pid_t pid;           /* process id */
    sigset_t mask;       /* signal mask */

    /* Parse command line */
    bg = parseline(cmdline, argv);
    if (argv[0] == NULL)
        return;   /* ignore empty lines */

    if (!builtin_cmd(argv)) {

        /*
     * This is a little tricky. Block SIGCHLD, SIGINT, and SIGTSTP
     * signals until we can add the job to the job list. This
     * eliminates some nasty races between adding a job to the job
     * list and the arrival of SIGCHLD, SIGINT, and SIGTSTP signals.
     */

        if (sigemptyset(&mask) < 0)
            unix_error("sigemptyset error");
        if (sigaddset(&mask, SIGCHLD))
            unix_error("sigaddset error");
        if (sigaddset(&mask, SIGINT))
            unix_error("sigaddset error");
        if (sigaddset(&mask, SIGTSTP))
            unix_error("sigaddset error");
        if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
            unix_error("sigprocmask error");

        /* Create a child process */
        if ((pid = fork()) < 0)
            unix_error("fork error");

        /*
         * Child  process
         */

        if (pid == 0) {
            /* Child unblocks signals Unblock*/
            sigprocmask(SIG_UNBLOCK, &mask, NULL);

            /* Each new job must get a new process group ID
               so that the kernel doesn't send ctrl-c and ctrl-z
               signals to all of the shell's jobs */
            if (setpgid(0, 0) < 0)
                unix_error("setpgid error");

            /* Now load and run the program in the new job */
            if (execve(argv[0], argv, environ) < 0) {
                printf("%s: Command not found\n", argv[0]);
                exit(0);
            }
        }

        /*
         * Parent process
         */

         /* Parent adds the job, and then unblocks signals so that
            the signals handlers can run again */
        addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);
        sigprocmask(SIG_UNBLOCK, &mask, NULL);

        if (!bg)
            waitfg(pid);
        else
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
    }
    /* $end handout */
    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.
   builtin_cmd  - If the user types a built-in command, it is executed immediately.
 */
int builtin_cmd(char** argv)
{
    sigset_t mask, prev_mask;
    sigfillset(&mask);
    if (!strcmp(argv[0], "quit"))   //If the first command on the command line is quit, exit the process directly
        exit(0);
    else if (!strcmp(argv[0], "&"))     //Ignore this command when the first character of the command line is &
        return 1;
    else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))//Handling bg and fg
    {
        do_bgfg(argv);
        return 1;
    }
    else if (!strcmp(argv[0], "jobs"))  //Output all job information
    {
        sigprocmask(SIG_BLOCK, &mask, &prev_mask); //Since jobs is a global variable, blocking signals are required
        listjobs(jobs);
        sigprocmask(SIG_SETMASK, &prev_mask, NULL);
        return 1;
    }
   
    return 0;     /* not a builtin command */
}

/*
 * do_bgfg - Execute the builtin bg and fg commands
    Execute built-in bg and fg commands
 */
void do_bgfg(char** argv)
{
    /* $begin handout */
    struct job_t* jobp = NULL;

    /* Ignore command if no argument
    If there are no parameters, the command is ignored*/
    if (argv[1] == NULL) {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    /* Parse the required PID or %JID arg */
    if (isdigit(argv[1][0])) //Judge whether the 0th bit of string 1 is a number
    {
        pid_t pid = atoi(argv[1]);  //atoi converts a string to an integer
        if (!(jobp = getjobpid(jobs, pid))) {
            printf("(%d): No such process\n", pid);
            return;
        }
    }
    else if (argv[1][0] == '%') {
        int jid = atoi(&argv[1][1]);
        if (!(jobp = getjobjid(jobs, jid))) {
            printf("%s: No such job\n", argv[1]);
            return;
        }
    }
    else {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    /* bg command */
    if (!strcmp(argv[0], "bg")) {
        if (kill(-(jobp->pid), SIGCONT) < 0)
            unix_error("kill (bg) error");
        jobp->state = BG;
        printf("[%d] (%d) %s", jobp->jid, jobp->pid, jobp->cmdline);
    }

    /* fg command */
    else if (!strcmp(argv[0], "fg")) {
        if (kill(-(jobp->pid), SIGCONT) < 0)
            unix_error("kill (fg) error");
        jobp->state = FG;
        waitfg(jobp->pid);
    }
    else {
        printf("do_bgfg: Internal error\n");
        exit(0);
    }
    /* $end handout */
    return;
}

/*
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid) //The pid of a foreground process is passed in
{
    sigset_t mask;
    sigemptyset(&mask);  //Initialize mask to empty set
    while (pid == fgpid(jobs))
    {
        sigsuspend(&mask);  //Suspending the process temporarily is more accurate than the pause method
    }
}

/*****************
 * 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)
{
    struct job_t* job1;
    int olderrno = errno, status;
    sigset_t mask, prev_mask;
    pid_t pid;
    sigfillset(&mask);
    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
    {
        //Through this loop, you can recycle as many subprocesses as possible
        sigprocmask(SIG_BLOCK, &mask, &prev_mask);  //Since jobs is a global variable, it is necessary to block all signals when deleting
        job1 = getjobpid(jobs, pid);  //Find job through pid
        if (WIFSTOPPED(status)) //The waitpid function returns when the child process stops
        {
            job1->state = ST;
            printf("Job [%d] (%d) terminated by signal %d\n", job1->jid, job1->pid, WSTOPSIG(status));
        }
        else
        {
            if (WIFSIGNALED(status)) //Return caused by child process termination
                printf("Job [%d] (%d) terminated by signal %d\n", job1->jid, job1->pid, WTERMSIG(status));
            deletejob(jobs, pid);  //Direct recycling process
        }
        fflush(stdout);
        sigprocmask(SIG_SETMASK, &prev_mask, NULL);
    }
    errno = olderrno;
}

/*
 * 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)
{
    pid_t pid;
    sigset_t mask, prev_mask;
    int olderrno = errno;
    sigfillset(&mask);
    sigprocmask(SIG_BLOCK, &mask, &prev_mask);  //Blocking signal
    pid = fgpid(jobs);  //Get the pid of the job
    sigprocmask(SIG_SETMASK, &prev_mask, NULL);
    if (pid != 0)  //Only foreground job s are processed
        kill(pid, SIGINT);
    errno = olderrno;
    return;
}

/*
 * 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)
{
    pid_t pid;
    sigset_t mask, prev_mask;
    int olderrno = errno;
    sigfillset(&mask);
    sigprocmask(SIG_BLOCK, &mask, &prev_mask);  //Blocking signal
    pid = fgpid(jobs);
    sigprocmask(SIG_SETMASK, &prev_mask, NULL);
    if (pid != 0)
        kill(-pid, SIGTSTP);
    errno = olderrno;
    return;
}

/*********************
 * 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);
}

Chapter 4 TinyShell test
Total score: 15 points
4.1 test method
For tsh and the reference shell program tshhref, complete the comparison test of test items 4.1-4.15, and save the screenshot of the test results or redirect to a text file (for example,. / sdriver.pl - t trace01.txt - S. / tsh - a "- P" > tshresult01.txt), and fill in the corresponding table in section 4.3.
4.2 evaluation of test results
The output of tsh and tshhref can be different in the following two aspects:
(1)pid
(2) Test document trace11 txt, trace12. Txt and trace13 Txt, the output of each run will be different, but the running state of each mysplit process should be the same.
In addition to the allowable differences in the above two aspects, if the output of tsh and tshhref is the same, it is judged to be correct. If it is different, the cause analysis is given.
4.3 self test results
Fill in the test results of the following test cases, 1 point for each test case.
4.3.1 test case trace01 txt

4.3.2 test case trace02 txt

4.3.3 test case trace03 txt

4.3.4 test case trace04 txt

4.3.5 test case trace05 txt

4.3.6 test case trace06 txt

4.3.7 test case trace07 txt

4.3.8 test case trace08 txt

4.3.9 test case trace09 txt

4.3.10 test case trace10 txt

4.3.11 test case trace11 txt
There are many output contents of ps instruction in the test. Only part of the information of tsh, mysplit and other processes closely related to this experiment can be recorded.

4.3.12 test case trace12 txt
There are many output contents of ps instruction in the test. Only part of the information of tsh, mysplit and other processes closely related to this experiment can be recorded.

4.3.13 test case trace13 txt
There are many output contents of ps instruction in the test. Only part of the information of tsh, mysplit and other processes closely related to this experiment can be recorded.

4.3.14 test case trace14 txt

4.3.15 test case trace15 txt

Chapter 5 evaluation score
Total score: 20 points
Score of unified test of experimental procedure (teacher evaluation):
(1) Accuracy score: (out of 10)
(2) Performance weighted score: (out of 10)

Chapter 6 Summary
5.1 please summarize the results of this experiment
Have a deeper understanding of some basic shell instructions and the basic working principle of the shell, and learn to call some shell instructions.

5.2 please give suggestions on the contents of this experiment
Suggest more teaching.

Note: this chapter is to add sub items as appropriate.

reference

The books and websites you read to complete this experiment