哈工大 計算機系統 實驗七 TinyShell


所有實驗文件可見github 計算機系統實驗整理

實驗報告

實 驗(七)

題 目 TinyShell
微殼  

計算機科學與技術學院

目 錄

第1章 實驗基本信息 - 4 -
1.1 實驗目的 - 4 -
1.2 實驗環境與工具 - 4 -
1.2.1 硬件環境 - 4 -
1.2.2 軟件環境 - 4 -
1.2.3 開發工具 - 4 -
1.3 實驗預習 - 4 -
第2章 實驗預習 - 6 -
2.1 進程的概念、創建和回收方法(5分) - 6 -
2.2信號的機制、種類(5分) - 6 -
2.3 信號的發送方法、阻塞方法、處理程序的設置方法(5分) - 6 -
2.4 什么是SHELL,功能和處理流程(5分) - 6 -
第3章 TINYSHELL的設計與實現 - 7 -
3.1.1 VOID EVAL(CHAR *CMDLINE)函數(10分) - 7 -
3. 1.2 INT BUILTIN_CMD(CHAR **ARGV)函數(5分) - 7 -
3. 1.3 VOID DO_BGFG(CHAR **ARGV) 函數(5分) - 7 -
3. 1.4 VOID WAITFG(PID_T PID) 函數(5分) - 7 -
3. 1.5 VOID SIGCHLD_HANDLER(INT SIG) 函數(10分) - 8 -
第4章 TINYSHELL測試 - 10 -
4.1 測試方法 - 10 -
4.2 測試結果評價 - 10 -
4.3 自測試結果 - 10 -
4.3.1測試用例trace01.txt - 10 -
4.3.2測試用例trace02.txt - 11 -
4.3.3測試用例trace03.txt - 11 -
4.3.4測試用例trace04.txt - 11 -
4.3.5測試用例trace05.txt - 11 -
4.3.6測試用例trace06.txt - 12 -
4.3.7測試用例trace07.txt - 12 -
4.3.8測試用例trace08.txt - 12 -
4.3.9測試用例trace09.txt - 13 -
4.3.10測試用例trace10.txt - 13 -
4.3.11測試用例trace11.txt - 13 -
4.3.12測試用例trace12.txt - 14 -
4.3.13測試用例trace13.txt - 14 -
4.3.14測試用例trace14.txt - 14 -
4.3.15測試用例trace15.txt - 15 -
4.4 自測試評分 - 15 -
第5章 總結 - 16 -
5.1 請總結本次實驗的收獲 - 16 -
5.2 請給出對本次實驗內容的建議 - 16 -
參考文獻 - 18 -

第1章 實驗基本信息

1.1 實驗目的
理解現代計算機系統進程與並發的基本知識
掌握linux 異常控制流和信號機制的基本原理和相關系統函數
掌握shell的基本原理和實現方法
深入理解Linux信號響應可能導致的並發沖突及解決方法
培養Linux下的軟件系統開發與測試能力
1.2 實驗環境與工具
1.2.1 硬件環境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 軟件環境
Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/優麒麟 64位
1.2.3 開發工具
Ubuntu,gcc
1.3 實驗預習
上實驗課前,必須認真預習實驗指導書(PPT或PDF)
了解實驗的目的、實驗環境與軟硬件工具、實驗操作步驟,復習與實驗有關的理論知識。
了解進程、作業、信號的基本概念和原理
了解shell的基本原理
熟知進程創建、回收的方法和相關系統函數
熟知信號機制和信號處理相關的系統函數
第2章 實驗預習
總分20分

2.1 進程的概念、創建和回收方法(5分)
進程的概念:進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。
進程的創建:在unix中系統調用的是:fork,fork會建立一個和父進程基本一模一樣的子進程,在windows中該系統調用是:cresteprocess,既負責處理進程的創建,也負責把正確的程序裝入新進程。具體可以分為如下步驟:
1.系統的初始化
2.一個進程在運行過程中開啟了子進程
3.用戶的交互式請求,而創建一個新進程(如雙擊應用圖標)
4.一個批處理作業的初始化(只在大型機的批處理系統中應用)
關於進程的創建,UNIX和WINDOWS
進程的回收:由於代碼編寫的不完善,隨着時間的推移,應用程序的性能會越來越低,有時會陷於某一循環中,導致不必要的 CPU 負載。這些應用程序還可能導致內存泄漏,這時應用程序不再將不需要的內存釋放回操作系統。這些應用程序可能會導致服務器停止運行,因此需要重新啟動服務器。進程回收就是為解決這些問題而創建的。子進程有父進程回收,孤兒進程有init回收,沒有及時回收則出現僵屍進程。
2.2信號的機制、種類(5分)
可以從兩個不同的分類角度對信號進行分類:
可靠性方面:可靠信號與不可靠信號;
與時間的關系上:實時信號與非實時信號。
①可靠信號與不可靠信號
Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,信號值小於SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是信號可能丟失。
隨着時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。由於原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,並在一開始就把它們定義為可靠信號,這些信號支持排隊,不會丟失。
信號值位於SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在 支持新版本的信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送 函數kill()。
信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。
②實時信號與非實時信號
早期Unix系統只定義了32種信號,前32種信號已經有了預定義值,每個信號有了確定的用途及含義,並且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同於前面闡述的可靠信號。這保證了發送的多個實時信號都被接收。
非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
2.3 信號的發送方法、阻塞方法、處理程序的設置方法(5分)
進程可以通過kill函數向包括它本身在內的其他進程發送一個信號,如果程序沒有發送這個信號的權限,對kill函數的調用就將失敗,而失敗的常見原因是目標進程由另一個用戶所擁有。
阻塞方法:隱式阻塞機制:內核默認阻塞任何當前處理程序正在處理信號類
和待處理信號。顯示阻塞進制:應用程序可以調用 sigprocmask 函數和它的輔助函數,明確地阻塞和解除阻塞選定的信號。
處理程序的設置方法:
1) 調用 signal 函數,調用 signal(SIG,handler),SIG 代表信號類型,handler
代表接收到 SIG 信號之后對應的處理程序。
2) 因為 signal 的語義各有不同,所以我們需要一個可移植的信號處理函
數設置方法,Posix 標准定義了 sigaction 函數,它允許用戶在設置信號
處理時明確指定他們想要的信號處理語義
2.4 什么是shell,功能和處理流程(5分)
什么是 shell:Shell 是一個用 C 語言編寫的程序,他是用戶使用 Linux 的
橋梁。Shell 既是一種命令語言,又是一種程序設計語言,Shell 是指一種應用程序。
功能:Shell 應用程序提供了一個界面,用戶通過這個界面訪問操作系統內
核的服務。
處理流程:
1)從終端讀入輸入的命令。
2)將輸入字符串切分獲得所有的參數
3)如果是內置命令則立即執行
4否則調用相應的程序執行
5)shell 應該接受鍵盤輸入信號,並對這些信號進行相應處理。

第3章 TinyShell的設計與實現
總分45分
3.1 設計
3.1.1 void eval(char *cmdline)函數(10分)
函數功能:判斷用戶剛輸入的命令行是否存在內置命令,如果是的話立即執行。
參 數:char *cmdline
處理流程:1.在初始化一些變量之后,首先調用parseline函數解析輸出后,判斷這是一個內置命令還是一個程序。
2.如果是內置命令,進入built_cmd函數,執行內置命令。否則fork一個子進程並添加在jobs列表里。

要點分析:
在addjob之前需要進行信號阻塞,防止把不存在的子進程添加到列表里

3.1.2 int builtin_cmd(char **argv)函數(5分)
函數功能:通過比較確定需要執行哪個內置命令
參 數:char **argv
處理流程:通過幾個if語句來匹配根據輸入的命令行的指令,需要執行哪一個內置命令,並調用該內置命令的函數,執行結束后返回。例如:如果命令行的第一個命令是quit,則直接退出函數,其余情況類似。
要點分析:需要注意在listjobs之前需要對信號進行阻塞,防止jobs被信號修改,保證函數的准確性。

3.1.3 void do_bgfg(char **argv) 函數(5分)
函數功能:實現內置命令bg和fg.
參 數:char **argv
處理流程:
第一步:先判斷fg和bg命令后是否有參數,若沒有忽略此條命令。
第二步:如果fg或bg后面只是數字,說明取的是進程號,獲取該進程號后,使用getjobpid(jobs, pid)得到job;如果fg或bg后面是%加上數字的形式,說明%后面是任務號,此時獲取jid后,可以使用getjobjid(jobs, jid)得到job。因此第二步之后得到了一個job。
第三步:根據fg和bg兩種命令的不同根據第二步獲得的job獲得這個函數需要返回的pid的值。
要點分析:

  1. 注意需要使用atoi來把字符串轉化為一個整數以供調用。
  2. 函數需要先判斷是否需要執行命令,如果需要執行的話還需要根據兩個不同的命令使用兩種不同的方式獲得job並獲得最終的pid的值。
  3. 如果遇到了SIGCONT我們需要做的就是繼續停止的進程。

3.1.4 void waitfg(pid_t pid) 函數(5分)
函數功能:等待前台程序結束
參 數:pid_t pid
處理流程:判斷前台程序是否結束,如果還沒結束的話繼續等待直到收到進程終止的信號跳出循環,返回。
要點分析:在while內部,如果使用的只是pause()函數,那么程序必須等待相當長的一段時間才會再次檢查循環的終止條件,如果使用像nanosleep這樣的高精度休眠函數也是不可接受的,因為沒有很好的辦法來確定休眠的間隔。

3.1.5 void sigchld_handler(int sig) 函數(10分)
函數功能:捕獲SIGCHILD信號.
參 數:int sig
處理流程:首先將所有的errno存儲,以便在獲得需要的信號之后恢復,接着將所有的信號加入mask,並進入一個while循環,准備進行循環,並在循環中回收子進程,如果等待集合中沒有進程被中止或停止返回0,否則孩子返回進程的pid。接着,在循環中阻塞信號,並且使用getjobpid()函數,通過pid找到job。根據不同的條件判斷需要采用哪一種輸出形式與返回形式。在確定之后解除阻塞信號,恢復errno,並返回。
要點分析:

  1. while循環來避免信號阻塞的問題,循環中使用waitpid()函數,以盡可能多的回收僵屍進程。
  2. deletejobs時需要先阻塞信號。

3.2 程序實現(tsh.c的全部內容)(10分)
重點檢查代碼風格:
(1) 用較好的代碼注釋說明——5分
(2) 檢查每個系統調用的返回值——5分


/*
 * 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); //清空緩沖區並輸出
        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 解除阻塞*/
            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  - 如果用戶鍵入了內置命令,則立即執行。
 */
int builtin_cmd(char** argv)
{
    sigset_t mask, prev_mask;
    sigfillset(&mask);
    if (!strcmp(argv[0], "quit"))   //如果命令行的首個命令是quit直接退出進程
        exit(0);
    else if (!strcmp(argv[0], "&"))     //命令行的第一個字符是&的時候忽略這一條命令
        return 1;
    else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))//處理bg和fg
    {
        do_bgfg(argv);
        return 1;
    }
    else if (!strcmp(argv[0], "jobs"))  //輸出所有job信息
    {
        sigprocmask(SIG_BLOCK, &mask, &prev_mask); //由於jobs是全局變量,需要阻塞信號
        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
    執行內置bg和fg命令
 */
void do_bgfg(char** argv)
{
    /* $begin handout */
    struct job_t* jobp = NULL;

    /* Ignore command if no argument
    如果沒有參數,則忽略命令*/
    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])) //判斷串1的第0位是否為數字
    {
        pid_t pid = atoi(argv[1]);  //atoi把字符串轉化為整型數
        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) //傳入的是一個前台進程的pid
{
    sigset_t mask;
    sigemptyset(&mask);  //初始化mask為空集合
    while (pid == fgpid(jobs))
    {
        sigsuspend(&mask);  //暫時掛起進程,比pause方法更准確一些
    }
}

/*****************
 * 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)
    {
        //通過這個循環能實現盡可能多地回收子進程
        sigprocmask(SIG_BLOCK, &mask, &prev_mask);  //由於jobs是全局變量,因此delete的時候需要阻塞所有的信號
        job1 = getjobpid(jobs, pid);  //通過pid找到job
        if (WIFSTOPPED(status)) //子進程停止引起的waitpid函數返回
        {
            job1->state = ST;
            printf("Job [%d] (%d) terminated by signal %d\n", job1->jid, job1->pid, WSTOPSIG(status));
        }
        else
        {
            if (WIFSIGNALED(status)) //子進程終止引起返回
                printf("Job [%d] (%d) terminated by signal %d\n", job1->jid, job1->pid, WTERMSIG(status));
            deletejob(jobs, pid);  //直接回收進程
        }
        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);  //阻塞信號
    pid = fgpid(jobs);  //獲取job的pid
    sigprocmask(SIG_SETMASK, &prev_mask, NULL);
    if (pid != 0)  //只處理前台job
        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);  //阻塞信號
    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);
}

第4章 TinyShell測試
總分15分
4.1 測試方法
針對tsh和參考shell程序tshref,完成測試項目4.1-4.15的對比測試,並將測試結果截圖或者通過重定向保存到文本文件(例如:./sdriver.pl -t trace01.txt -s ./tsh -a "-p" > tshresult01.txt),並填寫完成4.3節的相應表格。
4.2 測試結果評價
tsh與tshref的輸出在以下兩個方面可以不同:
(1)pid
(2)測試文件trace11.txt, trace12.txt和trace13.txt中的/bin/ps命令,每次運行的輸出都會不同,但每個mysplit進程的運行狀態應該相同。
除了上述兩方面允許的差異,tsh與tshref的輸出相同則判為正確,如不同則給出原因分析。
4.3 自測試結果
填寫以下各個測試用例的測試結果,每個測試用例1分。
4.3.1測試用例trace01.txt
在這里插入圖片描述

4.3.2測試用例trace02.txt
在這里插入圖片描述

4.3.3測試用例trace03.txt
在這里插入圖片描述

4.3.4測試用例trace04.txt
在這里插入圖片描述

4.3.5測試用例trace05.txt
在這里插入圖片描述

4.3.6測試用例trace06.txt
在這里插入圖片描述

4.3.7測試用例trace07.txt
在這里插入圖片描述

4.3.8測試用例trace08.txt
在這里插入圖片描述

4.3.9測試用例trace09.txt
在這里插入圖片描述

4.3.10測試用例trace10.txt
在這里插入圖片描述

4.3.11測試用例trace11.txt
測試中ps指令的輸出內容較多,僅記錄和本實驗密切相關的tsh、mysplit等進程的部分信息即可。

4.3.12測試用例trace12.txt
測試中ps指令的輸出內容較多,僅記錄和本實驗密切相關的tsh、mysplit等進程的部分信息即可。
在這里插入圖片描述

4.3.13測試用例trace13.txt
測試中ps指令的輸出內容較多,僅記錄和本實驗密切相關的tsh、mysplit等進程的部分信息即可。
在這里插入圖片描述

4.3.14測試用例trace14.txt
在這里插入圖片描述

4.3.15測試用例trace15.txt
在這里插入圖片描述

第5章 評測得分
總分20分
實驗程序統一測試的評分(教師評價):
(1)正確性得分: (滿分10)
(2)性能加權得分: (滿分10)

第6章 總結
5.1 請總結本次實驗的收獲
對於shell的一些基本指令和shell的基本工作原理有了更加深入的了解,學會了調用一些shell指令的調用。

5.2 請給出對本次實驗內容的建議
建議可以多一些教學。

注:本章為酌情加分項。

參考文獻

為完成本次實驗你翻閱的書籍與網站等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM