一文读懂CSAPP shell 微壳实验

 

第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测试... - 9 -

4.1 测试方法... - 9 -

4.2 测试结果评价... - 9 -

4.3 自测试结果... - 9 -

4.3.1测试用例trace01.txt的输出截图(1分)... - 9 -

4.3.2测试用例trace02.txt的输出截图(1分)... - 9 -

4.3.3测试用例trace03.txt的输出截图(1分)... - 10 -

4.3.4测试用例trace04.txt的输出截图(1分)... - 10 -

4.3.5测试用例trace05.txt的输出截图(1分)... - 10 -

4.3.6测试用例trace06.txt的输出截图(1分)... - 11 -

4.3.7测试用例trace07.txt的输出截图(1分)... - 11 -

4.3.8测试用例trace08.txt的输出截图(1分)... - 11 -

4.3.9测试用例trace09.txt的输出截图(1分)... - 12 -

4.3.10测试用例trace10.txt的输出截图(1分)... - 12 -

4.3.11测试用例trace11.txt的输出截图(1分)... - 12 -

4.3.12测试用例trace12.txt的输出截图(1分)... - 13 -

4.3.13测试用例trace13.txt的输出截图(1分)... - 13 -

4.3.14测试用例trace14.txt的输出截图(1分)... - 13 -

4.3.15测试用例trace15.txt的输出截图(1分)... - 14 -

4.4 自测试评分... - 14 -

 

第2章 实验预习

2.1 进程的概念、创建和回收方法

1、进程概念:

进程来源于操作系统对虚拟内存的定义,有了虚拟内存就有了进程的定义。进程定义为一个程序运行一次。一个进程中允许同时多个线程,也即一个进程就是一个线程组,每个进程有唯一的进程PID编号,每个进程有独立的地址空间,这样可以帮助程序规范且统一。

2、进程创建:进程的创建调用函数fork()

fork()

这个函数十分特别,其在父进程返回创建子进程的PID,在子进程返回0.也即不同位置返回2次。

进程的创建一般处于运行程序之前,会为这个程序建立一个子进程,子进程中的任何内容(内核,页表,用户栈,共享库,堆,代码区,数据区等)都和父进程一样,并保持只读和写时机制。只有当写入时,触发中断更改映射在物理内存中的地址后才可写入,也就是说,如果程序没有什么改变,则二者虽然是独立的虚拟地址,但映射的物理内存空间一致。

3、进程回收:进程回收父进程调用waitpid(pid_t pid, void* status,int options)这个函数。

首先了解一下进程为什么要回收,一个进程的状态分为3中,挂起(stopped),执行(execute),终止(killed),而对于终止的进程在操作系统中并未释放其内存,依然保留,此时这类进程称为僵尸进程(zombie),而回收制度保证了这类进程的回收,一般由父进程回收,如果父进程提前结束,则使用init进程回收,这个进程可以视为祖父进程,但一般是计算机启动时最初创建的进程。

进程的回收函数有点复杂,具体如下:

waitpid(pid_t pid, void* status,int options)

成功返回回收子进程的PID号,当是WNOHANG状态时返回0,出错返回-1

具体而言,

  1. pid有两种选择,第一个是固定需要回收子进程的PID,此时默认状态waitpid会挂起父进程,等待目标子进程终止后立刻返回;另一种是-1,此时waitpid的目标是父进程的所有子进程集,但凡该集合有子进程终止,就会立刻返回对应的pid
  2. 第二个参数是status,如果status为空,则waitpid会在返回时将回收的子进程的状态信息写入这里。具体而言有如下部分:

WIFEXITED(status)

子进程通过exit或者return正常返回,返回true,否则返回fasle

WEXITSTATUS(status)

只有当WIFEXITED(status)为真时,返回子进程正常返回的状态

WIFSIGNALED(status)

子进程通过未补货信号终止,返回true,否则返回fasle

WTERMSIG(status)

当WEXITSTATUS(status)为真时,返回这个终止状态,一般是SIGINT

WIFSTOPPED(status)

当子进程挂起时,返回true,否则返回false

WSTOPSIG(status)

当WIFSTOPPED(status)为真时,返回这个挂起状态,一般是SIGTSTP

WIFCONTINUED(status)

子进程收到一个SIGCONTINUE信号,则返回true,否则返回false

  1. 第三个参数是options,主要修改默认状态

默认状态是根据pid是-1还是具体pid,等待子进程组中目标进程终止,此时options为0。而如果options不为0,则有下面3个选项

WNOHANG

修改默认状态,当子进程组中没有任何进程修改状态,则立刻返回0.一般这个状态用于在等待回收子进程时还想干一些其他事

WUTRACED

修改默认状态,当子进程组中有进程挂起或终止时就返回

WUCONTINUED

修改默认状态,当子进程组中有进程收到continue的信号或者终止,则返回。

2.2信号的机制、种类

2.2.1信号机制

信号和异常一样属于计算机对于异常控制流的解决方法,作为操作系统在软件上的处理例程,当触发异常控制流时,会首先更改进程的对应信号位,等待进程控制单元下一次获得该进程的控制权时,会首先检查信号,处理对应的信号函数,并之后调用中断处理函数。这就是基本的信号机制。

2.2.2信号种类

操作系统内核保有一张信号表,说是信号表,其实本质是一个跳转表。Signal函数规定了不同信号对应的处理函数地址,从而操作系统只需要查找这个表即可。

具体信号种类有:

SIGINT:中断进程(通常是由终端产生)。

SIGKILL:立即杀死进程,不能被捕获或忽略。

SIGTERM:请求终止进程,可以被捕获或忽略。

SIGQUIT:请求进程退出并产生 core 文件,可以被捕获或忽略。

SIGSTOP:暂停进程的执行,不能被捕获或忽略。

SIGCONT:恢复进程的执行,不能被捕获或忽略。

SIGUSR1 和 SIGUSR2:用户自定义信号。

具体而言,用的最多的是SIGINT,SIGSTOP,SIGCHLD。

2.3 信号的发送方法、阻塞方法、处理程序的设置方法

2.3.1信号发送

信号的发送有3种基本方法

  1. 通过基本的硬件发送,按动键盘上的ctrl-c向当前前台进程发送SIGINT信号;ctrl-z发送SIGTSTP等
  2. Kill函数,kill(pid_t pid,int signal)这个kill函数会想指定的pid进程发送sig信号,这是用的最多的一个方法。
  3. Alarm函数,还可以使用alarm函数发送信号

2.3.2信号阻塞

进程为信号机制保有信号位和阻塞位。信号的阻塞主要是分为两种,第一种是由于信号机制决定,在信号执行期间,同时可以保有的信号最多之有一个。其余的只能抛弃;第二种是人为阻塞,通过下面函数:

Sigadd(sig_t set,int signal):向set信号集合添加signal信号

Sigset(sig_t set,int signal):将set集合中的signal设置为1,其他为0

Sigprocmask(int option,sig_t set,sig_t oldset)

Options有两种,BLOCKED 和UNBLOCKED,第一种会将set和oldset做并集,并将原有的阻塞集换到oldset中;第二种会将阻塞集合中set位去除掉,做set补集和oldset的交集。

2.3.3信号处理程序设置

      信号处理程序有操作系统内核保管,但用户可以自己设置处理程序,使用

Signal(int sig,void* function)将sig对应的处理程序设置为function,这样当需要调用时,就可以执行function了

2.4 什么是shell,简述其功能和处理流程

Shell是操作系统与用户之间的桥梁,早期的计算机并没有shell,而是有用户直接操作操作系统内核控制硬件执行相关程序,但这样对于硬件的安全性极难保证,所以出现shell作为中间桥梁。Shell中的指令分为两部分,内置指令(build-in command)和企业级程序,本质上我们所有用到的指令都是一个一个的可执行路径保存在环境变量中,还记得execve在执行的时候有一个参数是环境变量吗,没错,这就是配置环境变量的重要性。回想一下,我们配置环境变量都是配置的路径对吧,这是因为操作系统会将可执行路径和一个名字对应起来,这样输入名字就像当于执行文件。

第3章 TinyShell的设计与实现

3.1 设计

3.1.1 void eval(char *cmdline)函数

函数功能:执行指令

参    数:参数为cmdline,作为输入指令的字符串

处理流程:eval会首先调用int parseline(const char *cmdline, char **argv)函数,这个函数会将cmdline这个字符串进行分割,并判断进程要在后台执行还是前台(有无&),之后判断是否为内置指令,如果是,直接执行即可,不用创建子进程,如果不是,则向下创建子进程处理(execve)

要点分析:具体代码和注释如下:

Eval函数

/*

 * 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;

}

3. 1.2 int builtin_cmd(char **argv)函数

函数功能:处理内置指令

参    数:argv作为参数变量,看看和那个内部指令一致,若一致则执行即可

处理流程:因为指令必定是在开头处,所以argv[0]是指令,先判断是否是null,如果是则报错,如果不是空,则一个一个用strcmp判断即可

要点分析:具体函数如下,比较简单

int builtin_cmd(char **argv)

/*

 * 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"))  //如果命令是quit,退出

        exit(0);

    else if(!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))  //如果是bg或者fg命令,执行do_fgbg函数

        do_bgfg(argv);

    else if(!strcmp(argv[0], "jobs"))  //如果命令是jobs,列出正在运行和停止的后台作业

        listjobs(jobs);

    else

        return 0;     /* not a builtin command */

    return 1;

}

3. 1.3 void do_bgfg(char **argv) 函数

函数功能:处理前台和后台程序

参    数:argv仍然是参数

处理流程:判断argv[]是否带%,若为整数则传入pid,若带%则传入jid。接着调用getjobjid函数来获得对应的job结构体,如果返回为空,说明列表中并不存在jid的job,要输出提示。

使用strcmp函数判断是bg命令还是fg命令

若是bg,使目标进程重新开始工作,设置状态为BG(后台),打印进程信息

若是fg,使目标进程重新开始工作,设置状态为FG(前台),等待进程结束

要点分析:

void do_bgfg(char **argv)

/*

 * do_bgfg - Execute the builtin bg and fg commands

 */

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])) {

       pid_t pid = atoi(argv[1]);

       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;

}

3. 1.4 void waitfg(pid_t pid) 函数

函数功能:等待前台程序执行完成

参    数:pid是前台程序的PID

处理流程:首先会在eval中判断进程是否是在前台执行,如果是,则必须等待前台进程执行完成后才可以执行后台进程。这里使用的是sleep函数和while循环,但这种时间消耗会高,最好的方式是使用sigsuspend函数

要点分析:

void waitfg(pid_t pid)

void waitfg(pid_t pid)

{

    struct job_t *job = getjobpid(jobs, pid);

    if(!job) return;

    // 如果当前子进程的状态没有发生改变,则tsh继续休眠

    while(job->state == FG)

        // 使用sleep的这段代码会比较慢,最好使用sigsuspend

        sleep(1);

    return;

}

3. 1.5 void sigchld_handler(int sig) 函数

函数功能:父进程回收子进程函数

参    数:

处理流程:我们在前面已经说过了子进程的终止会演变为僵尸进程,只有父进程回收后才算结束。而在本实验中你会发现,还有一个信号也要处理,就是挂起,那么就可使用waitpid获得子进程的pid并根据getjobpid获得对应的job结构体,这样根据不同情况,使用status获得对应的信号,如果是SIGTSTP则挂起即可,如果是SIGINT,则直接在调用waitpid的时候就已经回收了。

要点分析:

void sigchld_handler(int sig)

void sigchld_handler(int sig)

{

    int status, jid;

    pid_t pid;

    struct job_t *job;

    if(verbose)

        puts("sigchld_handler: entering");

    while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){

        // 如果当前这个子进程的job已经删除了,则表示有错误发生

        if((job = getjobpid(jobs, pid)) == NULL){

            printf("Lost track of (%d)\n", pid);

            return;

        }

        jid = job->jid;

        //接下来判断三种状态

        // 如果这个子进程收到了一个暂停信号(还没退出)

        if(WIFSTOPPED(status)){

            printf("Job [%d] (%d) stopped by signal %d\n", jid, job->pid, WSTOPSIG(status));

            job->state = ST;  //状态设为挂起

        }

        // 如果子进程通过调用 exit 或者一个返回 (return) 正常终止

        else if(WIFEXITED(status)){

            if(deletejob(jobs, pid))

                if(verbose){

                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);

                    printf("sigchld_handler: Job [%d] (%d) terminates OK (status %d)\n", jid, pid, WEXITSTATUS(status));

                }

        }

        // 如果子进程是因为一个未被捕获的信号终止的,例如SIGKILL

        else {

            if(deletejob(jobs, pid)){  //清除进程

                if(verbose)

                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);

            }

            printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, WTERMSIG(status));  //返回导致子进程终止的信号的数量

        }

    }

    if(verbose)

        puts("sigchld_handler: exiting");

    return;

}

3.2 程序实现(tsh.c的全部内容)

Tsh.c

/*

 * 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.  

 */

int builtin_cmd(char **argv)

{

    if(!strcmp(argv[0], "quit"))  //如果命令是quit,退出

        exit(0);

    else if(!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))  //如果是bg或者fg命令,执行do_fgbg函数

        do_bgfg(argv);

    else if(!strcmp(argv[0], "jobs"))  //如果命令是jobs,列出正在运行和停止的后台作业

        listjobs(jobs);

    else

        return 0;     /* not a builtin command */

    return 1;

}

/*

 * do_bgfg - Execute the builtin bg and fg commands

 */

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])) {

    pid_t pid = atoi(argv[1]);

    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)

{

    struct job_t *job = getjobpid(jobs, pid);

    if(!job) return;

    // 如果当前子进程的状态没有发生改变,则tsh继续休眠

    while(job->state == FG)

        // 使用sleep的这段代码会比较慢,最好使用sigsuspend

        sleep(1);

    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 status, jid;

    pid_t pid;

    struct job_t *job;

    if(verbose)

        puts("sigchld_handler: entering");

    /*

    以非阻塞方式等待所有子进程

    waitpid 参数3

        1.     0     执行waitpid时, 只有在子进程 **终止** 时才会返回。

        2. WNOHANG   : 若子进程仍然在运行,则返回0

                注意只有设置了这个标志,waitpid才有可能返回0

        3. WUNTRACED : 如果子进程由于传递信号而停止,则马上返回。

                只有设置了这个标志,waitpid返回时,其WIFSTOPPED(status)才有可能返回true

    */

    while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){

        // 如果当前这个子进程的job已经删除了,则表示有错误发生

        if((job = getjobpid(jobs, pid)) == NULL){

            printf("Lost track of (%d)\n", pid);

            return;

        }

        jid = job->jid;

        //接下来判断三种状态

        // 如果这个子进程收到了一个暂停信号(还没退出)

        if(WIFSTOPPED(status)){

            printf("Job [%d] (%d) stopped by signal %d\n", jid, job->pid, WSTOPSIG(status));

            job->state = ST;  //状态设为挂起

        }

        // 如果子进程通过调用 exit 或者一个返回 (return) 正常终止

        else if(WIFEXITED(status)){

            if(deletejob(jobs, pid))

                if(verbose){

                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);

                    printf("sigchld_handler: Job [%d] (%d) terminates OK (status %d)\n", jid, pid, WEXITSTATUS(status));

                }

        }

        // 如果子进程是因为一个未被捕获的信号终止的,例如SIGKILL

        else {

            if(deletejob(jobs, pid)){  //清除进程

                if(verbose)

                    printf("sigchld_handler: Job [%d] (%d) deleted\n", jid, pid);

            }

            printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, WTERMSIG(status));  //返回导致子进程终止的信号的数量

        }

    }

    if(verbose)

        puts("sigchld_handler: exiting");

    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)

{

    if(verbose)

        puts("sigint_handler: entering");

    pid_t pid = fgpid(jobs);

    if(pid){

        // 发送SIGINT给前台进程组里的所有进程

        // 需要注意的是,前台进程组内的进程除了当前前台进程以外,还包括前台进程的子进程。

        // 最多只能存在一个前台进程,但前台进程组内可以存在多个进程

        if(kill(-pid, SIGINT) < 0)

            unix_error("kill (sigint) error");

        if(verbose){

            printf("sigint_handler: Job (%d) killed\n", pid);

        }

    }

    if(verbose)

        puts("sigint_handler: exiting");

    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)

{

    if(verbose)

        puts("sigstp_handler: entering");

    pid_t pid = fgpid(jobs);

    struct job_t *job = getjobpid(jobs, pid);

    if(pid){

        if(kill(-pid, SIGTSTP) < 0)

            unix_error("kill (tstp) error");

        if(verbose){

            printf("sigstp_handler: Job [%d] (%d) stopped\n", job->jid, pid);

        }

    }

    if(verbose)

        puts("sigstp_handler: exiting");

    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.2 测试结果评价

tsh与tshref的输出在以下两个方面可以不同:

(1)PID

(2)测试文件trace11.txt, trace12.txt和trace13.txt中的/bin/ps命令,每次运行的输出都会不同,但每个mysplit进程的运行状态应该相同。

除了上述两方面允许的差异,tsh与tshref的输出相同则判为正确,如不同则给出原因分析。

4.3 自测试结果

4.3.1测试用例trace01.txt的输出截图(1分)

4.3.2测试用例trace02.txt的输出截图(1分)

4.3.3测试用例trace03.txt的输出截图(1分)

4.3.4测试用例trace04.txt的输出截图(1分)

4.3.5测试用例trace05.txt的输出截图(1分)

4.3.6测试用例trace06.txt的输出截图(1分)

4.3.7测试用例trace07.txt的输出截图(1分)

4.3.8测试用例trace08.txt的输出截图(1分)

4.3.9测试用例trace09.txt的输出截图(1分)

4.3.10测试用例trace10.txt的输出截图(1分)

4.3.11测试用例trace11.txt的输出截图(1分)

Test测试

Rtest:

4.3.12测试用例trace12.txt的输出截图(1分)

Test

Rtest

4.3.13测试用例trace13.txt的输出截图(1分)

Test

Rtest

4.3.14测试用例trace14.txt的输出截图(1分)

Rtest

Test

4.3.15测试用例trace15.txt的输出截图(1分)

rtest

Test:

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值