CSAPP:lab7 shell


实验网站

课程网站:CSAPP

源码下载

源码下载

实验文档下载

我的实验环境:Ubuntu 20.04

lab7文档解读

? 查看 tsh.c (tiny shell) 文件,您会看到它包含一个简单的 Unix shell 的功能骨架。为了帮助您入门,我们已经实现了不太有趣的功能。你的任务是完成下面列出的剩余的空函数。作为对您的健全性检查,我们在参考解决方案中列出了每个函数的大致代码行数(其中包含大量注释)。

  • eval: 解析和解释命令行的主要例程。[70行]
  • builtin cmd:识别和解释内置命令:quit、fg、bg和jobs。[25行]
  • do bgfg:实现bg和fg内置命令。[50行]
  • waitfg: 等待前台作业完成。[20行]
  • sigchld handler: 捕获SIGCHILD信号。[80行]
  • sigint handler: 捕获SIGINT (ctrl-c) 信号。[15行]
  • sigtstp handler: 捕获SIGTSTP (ctrl-z) 信号。[15行]

Unix Shell 概述

shell 是一个交互式命令行解释器,它代表用户运行程序。 shell 反复打印提示,等待 stdin 上的命令行,然后按照命令行内容的指示执行一些操作。

命令行是由空格分隔的 ASCII 文本单词序列。命令行中的第一个单词要么是内置命令的名称,要么是可执行文件的路径名。剩下的词是命令行参数。如果第一个单词是内置命令,shell 会立即执行当前进程中的命令。否则,该词被假定为可执行程序的路径名。在这种情况下,shell 会派生一个子进程,然后在子进程的上下文中加载和运行程序。由于解释单个命令行而创建的子进程统称为作业。一般来说,一个作业可以由多个通过 Unix 管道连接的子进程组成。

如果命令行以 & 符号结尾,则作业在后台运行,这意味着 shell 在打印提示符并等待下一个命令行之前不会等待作业终止。否则,作业在前台运行,这意味着 shell 在等待下一个命令行之前等待作业终止。因此,在任何时间点,最多可以有一个作业在前台运行。但是,可以在后台运行任意数量的作业。例如,键入命令行

tsh> jobs

使shell执行内置的jobs命令。键入命令行T

tsh> /bin/ls -l -d

在前台运行ls程序。按照惯例,shell确保程序开始执行其主例程时

int main(int argc, char *argv[])

argc和argv参数具有以下值:

argc == 3,

argv[0] == ‘‘/bin/ls’’,

argv[1]== ‘‘-l’’,

argv[2]== ‘‘-d’’.

或者,键入命令行

tsh> /bin/ls -l -d &

在后台运行ls程序。

Unix shell 支持作业控制的概念,它允许用户在后台和前台之间来回移动作业,并更改作业中进程的进程状态(运行、停止或终止)。键入 ctrl-c 会导致将 SIGINT 信号传递给前台作业中的每个进程。 SIGINT 的默认操作是终止进程。同样,键入 ctrl-z 会导致将 SIGTSTP 信号传递给前台作业中的每个进程。 SIGTSTP 的默认操作是将进程置于停止状态,直到它被接收到 SIGCONT 信号唤醒为止。 Unix shell 还提供了各种支持作业控制的内置命令。例如:

? 作业:列出正在运行和已停止的后台作业。

? bg :将已停止的后台作业更改为正在运行的后台作业。

? fg :将已停止或正在运行的后台作业更改为在前台运行。

? kill :终止作业。

tsh规范

您的tsh-shell应具有以下功能:

  • 提示应该是字符串“tsh>”。
  • 用户键入的命令行应由名称和零个或多个参数组成,所有参数均由一个或多个空格分隔。如果name是内置命令,那么tsh应该立即处理它,并等待下一个命令行。否则,tsh应该假设name是可执行文件的路径,它在初始子进程的上下文中加载和运行 (在这种情况下,术语job指的是这个初始子进程)。
  • tsh 不需要支持管道 (|) 或 I/O 重定向(< 和 >)。
  • 键入 ctrl-c (ctrl-z) 应该会导致将 SIGINT (SIGTSTP) 信号发送到当前前台作业,以及该作业的任何后代(例如,它派生的任何子进程)。如果没有前台工作,那么信号应该没有效果。
  • 如果命令行以 & 符号结尾,那么 tsh 应该在后台运行该作业。否则,它应该在前台运行作业。
  • 每个作业都可以通过进程 ID (PID) 或作业 ID (JID) 来标识,这是一个由 tsh 分配的正整数。 JID 应该在命令行上用前缀 '%' 表示。例如,“%5”表示 JID 5,“5”表示 PID 5。(我们为您提供了操作作业列表所需的所有例程。)
  • tsh应该支持以下内置命令:
    • quit命令终止shell。
    • job命令列出所有后台运行的工作。
    • bg 命令重新启动<工作>通过发送SIGCONT,然后在后台运行。
    • 参数可以是一个PID或JID。
    • fg 命令重新启动<工作>通过发送SIGCONT,然后在前台运行它。
    • 参数可以是一个PID或JID。
    • tsh 应该收获它所有的僵尸孩子。如果任何作业因为接收到它没有捕获的信号而终止,则 tsh 应该识别此事件并打印一条带有作业 PID 和违规信号描述的消息。

Checking your work

我们提供了一些工具来帮助您检查您的工作。 在运行任何可执行程序之前,请确保它具有执行权限。 如果没有,使用“chmod +x”给它执行权限

**Reference solution. ** Linux 可执行文件 tshref 是 shell 的参考解决方案。 运行这个程序来解决你对 shell 应该如何工作的任何问题。 您的 shell 应该发出与参考解决方案相同的输出(当然,PID 除外,它在运行之间会发生变化)。

Shell driver sdriver.pl 程序将 shell 作为子进程执行,按照跟踪文件的指示向其发送命令和信号,并捕获并显示 shell 的输出。

unix> ./sdriver.pl -h
Usage: sdriver.pl [-hv] -t  -s  -a 

选项:

-h 打印此消息

-v 更详细

-t 跟踪文件

-s 用于测试的 Shell 程序

-a 读书笔记CSAPP:19[VB]ECF:信号和非本地跳转

进程

我们可通过调用以下函数来等待子进程的终止或停止,父进程会得到被回收的子进程PID,且内核会删除僵死进程

#include 
#include 
pid_t waitpid(pid_t pid, int *statusp, int options); 
  • 等待集合pid

    • 如果pid>0,则等待集合就是一个单独的子进程
      • 如果pid=-1,则等待集合就是该进程的所有子进程
      • 注意:当父进程创造了许多子进程,这里通过pid=-1进行回收时,子程序的回收顺序是不确定的,并不会按照父进程生成子进程的顺序进行回收。可通过按顺序保存子进程的PID,然后按顺序指定pid参数来消除这种不确定性。
  • 等待行为options

    • 0默认选项,则会挂起当前进程,直到等待集合中的一个子进程终止,则函数返回该子进程的PID。此时,已终止的子进程已被回收。
      • WNOHANG如果等待子进程终止的同时还向做其他工作,该选项会立即返回,如果子进程终止,则返回该子进程的PID,否则返回0。
      • WUNTRACED当子进程被终止或暂停时,都会返回。
      • WCONTINUED挂起当前进程,知道等待集合中一个正在运行的子进程被终止,或停止的子进程收到SIGCONT信号重新开始运行。
      • 注意:这些选项可通过|合并。
  • 如果statusp非空,则waitpid函数会将子进程的状态信息放在statusp中,可通过wait.h中定义的宏进行解析

    • WIFEXITED(statusp)如果子进程通过调用exitreturn正常终止,则返回真,。此时可通过WEXITSTATUS(statusp)获得退出状态。
      • WIFSIGNALED(status)如果子进程是因为一个未捕获的信号终止的,则返回真。此时可通过WTERMSIG(statusp)获得该信号的编号。
      • WIFSTOPPED(statusp)如果引起函数返回的子进程是停止的,则返回真。此时可通过WSTOPSIG(statusp)获得引起子进程停止的信号编号。
      • WIFCONTINUED(statusp)如果子进程收到SIGCONT信号重新运行,则返回真。
  • 如果当前进程没有子进程,则waitpid返回-1,并设置errnoECHILD,如果waitpid函数被信号中断,则返回-1,并设置errnoEINTR。否则返回被回收的子进程PID。

注意:waitpid通过设置options来决定是否回收停止的子进程。并且能通过statusp来判断进程终止或停止的原因。

有个简化的waitpid函数

#include 
#include 
pid_t wait(int *statusp);

调用wait(&status)等价于调用waitpid(-1, &status, 0)

注意:当调用waitpid函数之前,就有子进程被终止或停止,一调用waitpid函数就会马上将该子进程回收。

8.5.2发送信号

#include 
pid_t getpgrp(void); //返回所在的进程组
int setpgip(pid_t pid, pid_t pgid); //设置进程组
/* 
 * 如果pid大于零,就使用进程pid;如果pid等于0,就使用当前进程的PID。
 * 如果pgid大于0,就将对应的进程组ID设置为pgid;如果pgid等于0,就用pid指向的进程的PID作为进程组ID
 */ 
  • /bin/kill向进程发送任意信号
/bin/kill [-信号编号] id  

id>0时,表示将信号传递给PID为id的进程;当id<0时,表示将信号传递给进程组ID为|id|的所有进程。

  • 从键盘发送信号

通过键盘上输入Ctrl+C会使得内核发送一个SIGINT信号到前台进程组中的所有进程,终止前台作业;通过输入Ctrl+Z会发送一个SIGTSTP信号到前台进程组的所有进程,停止前台作业,直到该进程收到SIGCONT信号。

  • kill函数发送信号

可以在函数中调用kill函数来对目的进程发送信号

#include 
#include 
int kill(pid_t pid, int sig); 

pid>0时,会将信号sig发送给进程pid;当pid=0时,会将信号sig发送给当前进程所在进程组的所有进程;当pid<0时,会将信号sig发送给进程组ID为|pid|的所有进程

  • alarm函数发送SIGALARM信号
#include 
unsigned int alarm(unsigned int secs); 

alarm函数时,会取消待处理的闹钟,返回待处理闹钟剩下的时间,并在secs秒后发送一个SIGALARM信号给当前进程。

8.5.3接受信号

每种信号类型具有以下一种预定的默认行为:

  • 进程终止
  • 进程终止并dumps core
  • 进程挂起直到被SIGCONT信号重启
  • 进程忽略信号

可以通过signal函数来修改信号的默认行为,但是无法修改SIGSTOPSIGKILL信号的默认行为

#include 
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler);
  • signum为信号编号,可以直接输入信号名称
  • handler为我们想要对信号signum采取的行为
  • handlerSIG_IGN,表示要进程忽略该信号
  • handlerSIG_DFL,表示要恢复该信号的默认行为
  • handler为用户自定义的信号处理程序地址,则会调用该函数来处理该信号,该函数原型为void signal_handler(int sig);。调用信号处理程序称为捕获信号,置信信号处理程序称为处理信号。当信号处理程序返回时,会将控制传递回逻辑流中的下一条指令。注意:信号处理程序可以被别的信号处理程序中断。
  • signal函数执行成功,则返回之前signal handler的值,否则返回SIG_ERR

8.5.4 阻塞信号和解除阻塞信号P532

Linux提供阻塞信号的隐式和显示的机制:

  • 隐式阻塞机制:内核默认阻塞当前正在处理信号类型的待处理信号。
  • 显示阻塞机制:应用程序通过sigprocmask函数来显示阻塞和解阻塞选定的信号。
#include 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 通过how来决定如何改变阻塞的信号集合blocked

    • how=SIG_BLOCK时,blocked = blocked | set
      • how=SIG_UNBLOCK时,blocked = blocked & ~set
      • how=SETMASK时,block = set
  • 如果oldset非空,则会将原始的blocked值保存在oldset中,用于恢复原始的阻塞信号集合

这里还提供一些额外的函数来对set信号集合进行操作

#include 
int sigemptyset(sigset_t *set); //初始化set为空集合
int sigfillset(sigset_t *set); //把每个信号都添加到set中
int sigaddset(sigset_t *set, int signum); //将signum信号添加到set中
int sigdelset(sigset_t *set, int signum); //将signum从set中删除
int sigismember(const sigset_t *set, int signum); //如果signum是set中的成员,则返回1,否则返回0

以下是一个使用例子

img

以上执行内部函数时,就不会接收到SIGINT信号,即不会被Ctrl+C终止。

通过阻塞信号来消除函数冲突,或者保证程序运行逻辑正确。

显示等待信号

当我们想要主进程显示等待某个信号时,可以用以下代码

img

这里主进程会显示等待子进程被回收,这里使用了sigsuspend(&mask)函数,它等价于

sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL); 

但是它是这三条代码的原子版本,即第一行和第二行是一起调用的,则SIGCHLD信号不会出现在第一行和第二行之间,造成程序不会停止。

注意:第26行要先对SIGCHLD信号进行阻塞,防止过早发送给主进程,则pause函数就无法中断,就会使得程序不会停止。

Reference output:
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (42087) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (42087) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (42087) ./myspin 4 &
tsh> jobs
[1] (42087) Running ./myspin 4 &
Student's output:
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 4 &
[1] (42141) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (42141) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (42141) ./myspin 4 &
tsh> jobs
[1] (42141) Running ./myspin 4 &

Checking trace15.txt...
Reference output:
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (42182) terminated by signal 2
tsh> ./myspin 3 &
[1] (42210) ./myspin 3 &
tsh> ./myspin 4 &
[2] (42213) ./myspin 4 &
tsh> jobs
[1] (42210) Running ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> fg %1
Job [1] (42210) stopped by signal 20
tsh> jobs
[1] (42210) Stopped ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (42210) ./myspin 3 &
tsh> jobs
[1] (42210) Running ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> fg %1
tsh> quit
Student's output:
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 10
Job [1] (42255) terminated by signal 2
tsh> ./myspin 3 &
[1] (42269) ./myspin 3 &
tsh> ./myspin 4 &
[2] (42271) ./myspin 4 &
tsh> jobs
[1] (42269) Running ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> fg %1
Job [1] (42269) stopped by signal 20
tsh> jobs
[1] (42269) Stopped ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (42269) ./myspin 3 &
tsh> jobs
[1] (42269) Running ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> fg %1
tsh> quit

主要任务

需要实现的命令

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

按测试顺序实现

make test01//通过
make test02//需要实现quit
  1. 实现int builtin_cmd(char **argv)

    int builtin_cmd(char **argv)
    {   
        if(!strcmp(argv[0],"quit")){
            exit(0);
        }
        else if(!strcmp(argv[0],"jobs")){    /*需要防止冲突*/
            sigset_t mask, prev_mask;
            sigfillset(&mask);
            sigprocmask(SIG_BLOCK, &mask, &prev_mask);
            listjobs(jobs);
            sigprocmask(SIG_SETMASK, &prev_mask, NULL); 
            return 1;                   /*返回非0*/
            
        }
        else if(!strcmp(argv[0], "bg")){    /*需要防止冲突*/
           do_bgfg(argv);
            return 1;                   /*返回非0*/
            
        }
        else if(!strcmp(argv[0], "fg")){    /*需要防止冲突*/
           do_bgfg(argv);
            return 1;                   /*返回非0*/
            
        }
        else return 0;     /* not a builtin command */
    }
    
  2. make test03
    make test04
    make test05		//jobs指令执行失败
        			//需完善void eval(char *cmdline)
    
  3. 完善void eval(char *cmdline)

    //一开始直接参考书上P525简单shell,jobs指令失败
    //
    void eval(char *cmdline) 
    {
       char *argv[MAXARGS]; /* Argument list execve() */
       char buf[MAXLINE];   /* Holds modified command line */
       int bg;              /* Should the job run in bg or fg? */
       pid_t pid;           /* Process id */
    
       strcpy(buf,cmdline);
       bg = parseline(buf, argv);      /*后台执为true*/
       if (argv[0] == NULL)
           return;   /* Ignore empty lines */
       if (!builtin_cmd(argv)) {       /* 执行内置指令 */
           if ((pid = fork()) == 0) {      /* fork产生子进程执行指令 */
               if (execve(argv[0], argv, environ) < 0) {
                   printf("%s: Command not found.\n", argv[0]);
                   exit(0);
               }
           }
    
           /* Parent waits for foreground job to terminate */
    if (!bg) {
               int status;
               if (waitpid(pid, &status, 0) < 0)
                   unix_error("waitfg: waitpid error");
           }
           else
               printf("%d %s", pid, cmdline);
       }
       return;
    }
    

    检查知上面的eval缺少添加jobs,进一步添加信号阻塞和addjobs

    /* 
     * eval - Evaluate the command line that the user has just typed in
     * 
     * If the user has requested a built-in command (quit, jobs, bg or fg)
     * then execute it immediately. Otherwise, fork a child process and
     * run the job in the context of the child. If the job is running in
     * the foreground, wait for it to terminate and then return.  Note:
     * each child process must have a unique process group ID so that our
     * background children don't receive SIGINT (SIGTSTP) from the kernel
     * when we type ctrl-c (ctrl-z) at the keyboard.  
    */
    void eval(char *cmdline) 
    {
        char *argv[MAXARGS]; /* Argument list execve() */
        char buf[MAXLINE];   /* Holds modified command line */
        int bg;              /* Should the job run in bg or fg? */
        pid_t pid;           /* Process id */
        sigset_t mask_all, mask_one, prev_one;
        strcpy(buf,cmdline);
        bg = parseline(buf, argv);      /*结尾&,后台执,为true*/
        if (argv[0] == NULL) return;   /* Ignore empty lines */
        /*sigemptyset(sigset set)   初始化集合为空
          sigfillset(sigset set)    把每个信号都添加到set中
          sigaddset(sigset set,int signum)     函数把signum添加到集合set中
        */    
        Sigfillset(&mask_all);	
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one, SIGCHLD);	                //mask_one:SIGCHLD
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);   //阻塞SIGCHLD
        
        if (!builtin_cmd(argv)) {   /* 执行内置指令,不执行则往下执行 */
            if ((pid = Fork()) == 0) {      /* fork产生子进程执行指令 */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);      //解除阻塞
                setpgid(0, 0);              //确保前台进程组中只有一个进程,即shell
                if (execve(argv[0], argv, environ) < 0) {
                    // ref:./bogus:Commandnotfound      (无.)
                    printf("%s: Command not found\n", argv[0]);
                    exit(0);
                }
            }
    
            /* Parent waits for foreground job to terminate */
    	if (!bg) {          //fg执行
                Sigprocmask(SIG_BLOCK, &mask_all, NULL);
                addjob(jobs,pid,FG,cmdline);
                waitfg(pid);  //挂起父进程等待前台执行
                // if (waitpid(pid, &status, 0) < 0)
                //     unix_error("waitfg: waitpid error");
                Sigprocmask(SIG_SETMASK, &prev_one, NULL);
            }
            else{       //bg执行
                Sigprocmask(SIG_BLOCK, &mask_all, NULL);
                addjob(jobs,pid,BG,cmdline);
                Sigprocmask(SIG_SETMASK, &prev_one, NULL);
                struct job_t* bg_job = getjobpid(jobs, pid);
                printf("[%d] (%d) %s",bg_job->jid,bg_job->pid, cmdline);
            }
        }
        return;
    }
    
  4. make test05
    make test06//需要增加异常信号处理
    
    void sigchld_handler(int sig) //P543
    {   
        int olderrno=errno;
        sigset_t mask_all,prev_all;
        pid_t pid;
        struct job_t *job;
        int status;         //存储回收子进程的退出状态
    
        sigfillset(&mask_all);
        /*子进程都没有停止或终止,返回0;停止或终止返回该子进程pid*/
        while ((pid=waitpid(-1,&status,WNOHANG|WUNTRACED))>0){  //回收僵尸子进程  
            
            job=getjobpid(jobs,pid);
            int pid=job->pid;
            int jid=job->jid;
            if(!WIFSTOPPED(status)) deletejob(jobs,pid);
            sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
            
            //Job [1] (26263) terminated by signal 2
            if(WIFSIGNALED(status)){           //子进程停止因为未捕获的信号而终止,则WTERMSIG(status)返回引起子进程终止的编号
                printf("Job [%d] (%d) terminated by signal %d\n", jid, pid,WTERMSIG(status));
            }else if(WIFSTOPPED(status)){      //子进程停止,WSTOPSIG(status)返回引起子进程停止的进程编号
                job -> state = ST;
                printf("Job [%d] (%d) stopped by signal %d\n",jid ,pid, WSTOPSIG(status));
            }
            
        }
        
        if (errno != ECHILD) {
            unix_error("waitpid error");
        }
        return;
    }
    
    /* 
     * sigint_handler - The kernel sends a SIGINT to the shell whenver the
     *    user types ctrl-c at the keyboard.  Catch it and send it along
     *    to the foreground job.  
     */
    void sigint_handler(int sig) 
    {   int olderrno = errno;
        sig_t mask_all,prev_all;
    
        sigfillset(&mask_all);
        sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
        int fg_pid=fgpid(jobs);           //返回前台进程
        sigprocmask(SIG_SETMASK,&prev_all,NULL);
        if(fg_pid){
            kill(-fg_pid,sig);          //向进程组发送发送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) 
    {
        int olderrno = errno;
        sig_t mask_all,prev_all;
    
        Sigfillset(&mask_all);
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        int fg_pid = fgpid(jobs);
        Sigprocmask(SIG_SETMASK, &prev_all, NULL);
        if (fg_pid) {
            Kill(-fg_pid, sig);
        }
    
        errno=olderrno;
        return;
    }
    
  5. 实现do_fgbg

    ```c
    /* 
     * do_bgfg - Execute the builtin bg and fg commands
     */
    void do_bgfg(char **argv) 
    {   
        int bg=strcmp(argv[0],"bg");
        sigset_t mask_all,mask_one,prev_one;
        sigfillset(&mask_all);
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one,SIGCHLD);
    
        struct job_t *Job;
        if (!argv[1]) {             //缺少参数PID或JID
            //fg command requires PID or %jobid argument
            printf("%s command requires PID or %%jobid argument\n", (!bg) ? "bg":"fg");
            return;
        }
        else if((argv[1][0]<'0'||argv[1][0]>'9')&&argv[1][0]!='%')  //指令:fg %2  fg 2
        {   
            // fg: argument must be a PID or %jobid
            printf("%s: argument must be a PID or %%jobid\n", (!bg) ? "bg":"fg");
            return;
        }
        else if (argv[1][0] == '%') {
            int jid = 0;
            for (int i = 1; argv[1][i]; i++) {
                jid = jid * 10 + (argv[1][i] - '0');
            }
            Job = getjobjid(jobs, jid);
            if (!Job) {
                printf("%%%d: No such job\n",jid);
                return;
            }
        }
        else {
            pid_t pid = 0;
            for (int i = 0; argv[1][i]; i++) {
                pid = pid * 10 + (argv[1][i] - '0');
            }
            Job = getjobpid(jobs, pid);
            if (!Job) {
                printf("(%d): No such process\n",pid);
                return; 
            }
        }
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
        Kill( -Job -> pid, SIGCONT);
        int pid=Job->pid;
        int jid=Job->jid;
        if (bg) {
            Sigprocmask(SIG_BLOCK, &mask_all, NULL);
            Job -> state = FG;
            waitfg(pid);
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);        
        }
        else {
            Sigprocmask(SIG_BLOCK, &mask_all, NULL);
            Job -> state = BG;
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);
            printf("[%d] (%d) %s",jid ,pid,Job->cmdline);
        }
        return;
    }
    ```
    

Evaluation

分数将根据以下分布计算出最多90分:

80 Correctness: 16 trace ?les at 5 points each.
10 Style points. We expect you to have good comments (5 pts) and to check the return value of EVERY system call (5 pts).

您的解决方案 shell 将在 Linux 机器上测试正确性,使用包含在您的实验室目录中的相同 shell 驱动程序和跟踪文件。 您的 shell 应该在这些跟踪上产生与参考 shell 相同的输出,只有两个例外:

  • PID 可以(并且将会)不同。
  • trace11.txt、trace12.txt 和trace13.txt 中的/bin/ps 命令的输出将因运行而异。但是,/bin/ps 命令输出中任何 mysplit 进程的运行状态应该相同。

我们为您提供了一个名为grade shlab的测试。pl.以下是正确案例的示例:

unix> ./grade-shlab.pl -f tsh.c
CS:APP Shell Lab: Grading Sheet for tsh.c
Part 0: Compiling your shell
gcc -Wall -O2 tsh.c -o tsh
gcc -Wall -O2 myspin.c -o myspin
gcc -Wall -O2 mysplit.c -o mysplit
gcc -Wall -O2 mystop.c -o mystop
gcc -Wall -O2 myint.c -o myint
7
Part 1: Correctness Tests
Checking trace01.txt...
Checking trace02.txt...
Checking trace03.txt...
Checking trace04.txt...
Checking trace05.txt...
Checking trace06.txt...
Checking trace07.txt...
Checking trace08.txt...
Checking trace09.txt...
Checking trace10.txt...
Checking trace11.txt...
Checking trace12.txt...
Checking trace13.txt...
Checking trace14.txt...
Checking trace15.txt...
Checking trace16.txt...
Preliminary correctness score: 80