时隔一年多重新开始写csapp的lap,这次回顾Shell Lab。

课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html

课程资料:https://github.com/EugeneLiu/translationCSAPP

课程视频:https://www.bilibili.com/video/av31289365/

参考资料:https://zhuanlan.zhihu.com/p/8922435

说明

为了完成该lab,可以先阅读官方的如下代码:

剩余参考资料:

编译命令:

make tsh

测试命令:

make test0$i > r1
make rtest0$i > r2
diff r1 r2

辅助函数

// 更新状态
int updatejob(struct job_t *jobs, pid_t pid, int state) {
    for (int i = 0; i < MAXJOBS; i++){
        if (jobs[i].pid == pid) {
            jobs[i].state = state;

            return 1;
        }
    }

    printf("Job %d does not find!\n", pid);
    return 0;
}

/* jid2pid - Map job ID to process ID */
pid_t jid2pid(int jid) {
    for (int i = 0; i < MAXJOBS; i++){
        if (jobs[i].jid == jid){
            return jobs[i].pid;
        }
    }

    return 0;
}

eval

执行命令的主程序,整体结构参考了procmask2:

/* 
 * 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 */
    // 参考procmask2: mask_all表示屏蔽所有信号, mask_one屏蔽某指定信号, prev_one保存之前的值
    sigset_t mask_all, mask_one, prev_one;

    // 复制cmdline
    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    // 如果不存在命令则返回
    if (argv[0] == NULL) {
        return;
    }

    // 判断是否为内置cmd, 如果是则直接运行, 否则fork & execve
    if (!builtin_cmd(argv)) {
        sigfillset(&mask_all);
        sigemptyset(&mask_one);
        sigaddset(&mask_one, SIGCHLD);

        // 阻塞SIGCHLD
        // 把set中的信号添加到blocked中(blocked = blocked | set), 父进程屏蔽SIGCHLD信号
        sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
        if((pid = fork()) == 0) {
            // child
            // 恢复信号
            // block = set
            sigprocmask(SIG_SETMASK, &prev_one, NULL);

            // 更改进程组id
            if (setpgid(0, 0) < 0) {
                unix_error("setpgid error!");
            }

            // 执行
            if (execve(argv[0], argv, environ) < 0) {
                printf("%s: Command not found\n", argv[0]);
                exit(0);
            }
        }
        // parent
        // 阻塞所有信号, 防止出现先删除后添加的情形 
        sigprocmask(SIG_BLOCK, &mask_all, NULL);
        // addjob
        if (bg) {
            addjob(jobs, pid, BG, buf);
        }else {
            addjob(jobs, pid, FG, buf);
        }
        // 恢复
        sigprocmask(SIG_SETMASK, &prev_one, NULL);   
        
        // 如果是FG, 则等待任务结束
        if (!bg) {
            waitfg(pid);
        }else{
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
        }
    }

    return;
}

builtin_cmd

比较容易实现的一个函数,主要是通过判断来调用其他函数:

/* 
 * 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")) {
        exit(0);
    }else if (!strcmp(argv[0], "fg")) {
        do_bgfg(argv);

        return 1;
    }else if (!strcmp(argv[0], "bg")) {
        do_bgfg(argv);

        return 1;
    }else if (!strcmp(argv[0], "jobs")) {
        listjobs(jobs);

        return 1;
    }
    
    return 0;     /* not a builtin command */
}

do_bgfg

整体思路为先判断是pid还是jid,然后发送SIGCONT给进程组中每个进程,如果是FG则需要等待对应的进程结束:

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
// 判断是否为数字
int is_number(char *str) {
    int n = strlen(str);
    for (int i = 0; i < n; i++) {
        if (!isdigit(str[i])) {
            return 0;
        }
    }

    return 1;
}

void do_bgfg(char **argv) 
{
    // 判断是否有参数
    if (argv[1] == NULL) {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    // 判断是否为jid
    int isjid = (argv[1][0] == '%');
    int id = -1;
    // 解析id
    if (isjid){
        sscanf(argv[1], "%%%d", &id);
    }else{
        sscanf(argv[1], "%d", &id);
    }
    // 如果无法解析, 则报错
    if (id == -1) {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    // jid转换为pid
    pid_t pid;
    if (isjid){
        pid = jid2pid(id);
    }else{
        pid = id;
    }

    if (isjid){
        // 查找jid对应的job
        struct job_t *job = getjobjid(jobs, id);
        if (job == NULL){
            printf("%%%d: No such job\n", id);

            return;
        }
    }else{
        // 查找pid对应的job
        struct job_t *job = getjobpid(jobs, id);
        if (job == NULL){
            printf("(%d): No such process\n", id);

            return;
        }
    }

    // 发送信号sig给进程组|pid|中的每个进程
    if (!strcmp(argv[0], "fg")) {
        // 发送信号
        kill(-pid, SIGCONT);
        // 更改状态
        updatejob(jobs, pid, FG);
        // 等待pid结束
        waitfg(pid);
    }else {
        // 发送信号
        kill(-pid, SIGCONT);
        // 更改状态
        updatejob(jobs, pid, BG);
        struct job_t *job = getjobpid(jobs, pid);
        
        printf("[%d] (%d) %s", pid2jid(pid), pid, job->cmdline);
    }

    return;
}

waitfg

参考8.5.7,以及sigsuspend.c文件:

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
    sigset_t mask;
    // 初始化为空集合
    sigemptyset(&mask);
    // 如果存在fg进程
    while (fgpid(jobs)) {
        // 则等待直至进程结束
        sigsuspend(&mask);
    }

    return;
}

sigchld_handler

利用waitpid获得状态,WNOHANG | WUNTRACED表示:立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID:

/* 
 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
 *     a child job terminates (becomes a zombie), or stops because it
 *     received a SIGSTOP or SIGTSTP signal. The handler reaps all
 *     available zombie children, but doesn't wait for any other
 *     currently running children to terminate.  
 */
// 一个子进程停止或者终止
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    int status;
    sigset_t mask_all, prev_all;
    pid_t pid;

    sigfillset(&mask_all);
    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
        if (WIFEXITED(status)){
            // 正常终止
            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev_all, NULL);
        }else if (WIFSIGNALED(status)){
            // 因为一个未捕获的信号终止
            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
            // 返回信号编号
            int sig = WTERMSIG(status);
            // 先输出后删除
            printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, sig);
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev_all, NULL);
        }else if (WIFSTOPPED(status)) {
            // 如果引起返回的子进程当前是停止的
            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
            int sig = WSTOPSIG(status);
            // 先输出后更新
            printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, sig);
            updatejob(jobs, pid, ST);
            sigprocmask(SIG_SETMASK, &prev_all, NULL);
        } else {
            // 剩余情形直接删除
            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev_all, NULL);
        }
    }

    errno = olderrno;

    return;
}

sigint_handler

终止前台进程所在进程组:

/* 
 * 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 = fgpid(jobs);
    if (pid != 0){
        kill(-pid, sig);
    }
    
    return;
}

sigtstp_handler

和sigint_handler类似:

/*
 * 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 = fgpid(jobs);
    if (pid != 0){
        kill(-pid, sig);
    }

    return;
}

测试

编写脚本test.sh:

#! /bin/bash

for i in {1..9}
do
    make test0$i > r1
    make rtest0$i > r2
    echo test$i >> res
    diff r1 r2 >> res
    echo -e "\n" >> res
done

for i in {10..16}
do
    make test$i > r1
    make rtest$i > r2
    echo test$i >> res
    diff r1 r2 >> res
    echo -e "\n" >> res
done

查看res的结果:

test1
1c1
< ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace01.txt -s ./tshref -a "-p"


test2
1c1
< ./sdriver.pl -t trace02.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace02.txt -s ./tshref -a "-p"


test3
1c1
< ./sdriver.pl -t trace03.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace03.txt -s ./tshref -a "-p"


test4
1c1
< ./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace04.txt -s ./tshref -a "-p"
6c6
< [1] (5661) ./myspin 1 &
---
> [1] (5681) ./myspin 1 &


test5
1c1
< ./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace05.txt -s ./tshref -a "-p"
6c6
< [1] (5697) ./myspin 2 &
---
> [1] (5733) ./myspin 2 &
8c8
< [2] (5699) ./myspin 3 &
---
> [2] (5735) ./myspin 3 &
10,11c10,11
< [1] (5697) Running ./myspin 2 &
< [2] (5699) Running ./myspin 3 &
---
> [1] (5733) Running ./myspin 2 &
> [2] (5735) Running ./myspin 3 &


test6
1c1
< ./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace06.txt -s ./tshref -a "-p"
6c6
< Job [1] (5744) terminated by signal 2
---
> Job [1] (5750) terminated by signal 2


test7
1c1
< ./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace07.txt -s ./tshref -a "-p"
6c6
< [1] (5758) ./myspin 4 &
---
> [1] (5767) ./myspin 4 &
8c8
< Job [2] (5760) terminated by signal 2
---
> Job [2] (5769) terminated by signal 2
10c10
< [1] (5758) Running ./myspin 4 &
---
> [1] (5767) Running ./myspin 4 &


test8
1c1
< ./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace08.txt -s ./tshref -a "-p"
6c6
< [1] (5777) ./myspin 4 &
---
> [1] (5786) ./myspin 4 &
8c8
< Job [2] (5779) stopped by signal 20
---
> Job [2] (5788) stopped by signal 20
10,11c10,11
< [1] (5777) Running ./myspin 4 &
< [2] (5779) Stopped ./myspin 5 
---
> [1] (5786) Running ./myspin 4 &
> [2] (5788) Stopped ./myspin 5 


test9
1c1
< ./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace09.txt -s ./tshref -a "-p"
6c6
< [1] (5796) ./myspin 4 &
---
> [1] (5807) ./myspin 4 &
8c8
< Job [2] (5798) stopped by signal 20
---
> Job [2] (5809) stopped by signal 20
10,11c10,11
< [1] (5796) Running ./myspin 4 &
< [2] (5798) Stopped ./myspin 5 
---
> [1] (5807) Running ./myspin 4 &
> [2] (5809) Stopped ./myspin 5 
13c13
< [2] (5798) ./myspin 5 
---
> [2] (5809) ./myspin 5 
15,16c15,16
< [1] (5796) Running ./myspin 4 &
< [2] (5798) Running ./myspin 5 
---
> [1] (5807) Running ./myspin 4 &
> [2] (5809) Running ./myspin 5 


test10
1c1
< ./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace10.txt -s ./tshref -a "-p"
6c6
< [1] (5820) ./myspin 4 &
---
> [1] (5830) ./myspin 4 &
8c8
< Job [1] (5820) stopped by signal 20
---
> Job [1] (5830) stopped by signal 20
10c10
< [1] (5820) Stopped ./myspin 4 &
---
> [1] (5830) Stopped ./myspin 4 &


test11
1c1
< ./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace11.txt -s ./tshref -a "-p"
6c6
< Job [1] (5841) terminated by signal 2
---
> Job [1] (5850) terminated by signal 2
30,34c30,34
<  5836 pts/1    S+     0:00 make test11
<  5837 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
<  5838 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p
<  5839 pts/1    S+     0:00 ./tsh -p
<  5844 pts/1    R      0:00 /bin/ps a
---
>  5845 pts/1    S+     0:00 make rtest11
>  5846 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tshref -a "-p"
>  5847 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tshref -a -p
>  5848 pts/1    S+     0:00 ./tshref -p
>  5853 pts/1    R      0:00 /bin/ps a


test12
1c1
< ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace12.txt -s ./tshref -a "-p"
6c6
< Job [1] (5860) stopped by signal 20
---
> Job [1] (5871) stopped by signal 20
8c8
< [1] (5860) Stopped ./mysplit 4 
---
> [1] (5871) Stopped ./mysplit 4 
32,38c32,38
<  5855 pts/1    S+     0:00 make test12
<  5856 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
<  5857 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p
<  5858 pts/1    S+     0:00 ./tsh -p
<  5860 pts/1    T      0:00 ./mysplit 4
<  5861 pts/1    T      0:00 ./mysplit 4
<  5865 pts/1    R      0:00 /bin/ps a
---
>  5866 pts/1    S+     0:00 make rtest12
>  5867 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tshref -a "-p"
>  5868 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tshref -a -p
>  5869 pts/1    S+     0:00 ./tshref -p
>  5871 pts/1    T      0:00 ./mysplit 4
>  5872 pts/1    T      0:00 ./mysplit 4
>  5876 pts/1    R      0:00 /bin/ps a


test13
1c1
< ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
6c6
< Job [1] (5883) stopped by signal 20
---
> Job [1] (5896) stopped by signal 20
8c8
< [1] (5883) Stopped ./mysplit 4 
---
> [1] (5896) Stopped ./mysplit 4 
32,38c32,38
<  5878 pts/1    S+     0:00 make test13
<  5879 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
<  5880 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
<  5881 pts/1    S+     0:00 ./tsh -p
<  5883 pts/1    T      0:00 ./mysplit 4
<  5884 pts/1    T      0:00 ./mysplit 4
<  5887 pts/1    R      0:00 /bin/ps a
---
>  5891 pts/1    S+     0:00 make rtest13
>  5892 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
>  5893 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
>  5894 pts/1    S+     0:00 ./tshref -p
>  5896 pts/1    T      0:00 ./mysplit 4
>  5897 pts/1    T      0:00 ./mysplit 4
>  5900 pts/1    R      0:00 /bin/ps a
47c47
<    67 pts/0    Sl+    0:22 /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/node /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/out/bootstrap-fork --type=watcherService
---
>    67 pts/0    Sl+    0:23 /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/node /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/out/bootstrap-fork --type=watcherService
63,67c63,67
<  5878 pts/1    S+     0:00 make test13
<  5879 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
<  5880 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
<  5881 pts/1    S+     0:00 ./tsh -p
<  5890 pts/1    R      0:00 /bin/ps a
---
>  5891 pts/1    S+     0:00 make rtest13
>  5892 pts/1    S+     0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
>  5893 pts/1    S+     0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
>  5894 pts/1    S+     0:00 ./tshref -p
>  5903 pts/1    R      0:00 /bin/ps a


test14
1c1
< ./sdriver.pl -t trace14.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace14.txt -s ./tshref -a "-p"
8c8
< [1] (5912) ./myspin 4 &
---
> [1] (5931) ./myspin 4 &
24c24
< Job [1] (5912) stopped by signal 20
---
> Job [1] (5931) stopped by signal 20
28c28
< [1] (5912) ./myspin 4 &
---
> [1] (5931) ./myspin 4 &
30c30
< [1] (5912) Running ./myspin 4 &
---
> [1] (5931) Running ./myspin 4 &


test15
1c1
< ./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace15.txt -s ./tshref -a "-p"
8c8
< Job [1] (5951) terminated by signal 2
---
> Job [1] (5971) terminated by signal 2
10c10
< [1] (5953) ./myspin 3 &
---
> [1] (5973) ./myspin 3 &
12c12
< [2] (5955) ./myspin 4 &
---
> [2] (5975) ./myspin 4 &
14,15c14,15
< [1] (5953) Running ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Running ./myspin 3 &
> [2] (5975) Running ./myspin 4 &
17c17
< Job [1] (5953) stopped by signal 20
---
> Job [1] (5973) stopped by signal 20
19,20c19,20
< [1] (5953) Stopped ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Stopped ./myspin 3 &
> [2] (5975) Running ./myspin 4 &
24c24
< [1] (5953) ./myspin 3 &
---
> [1] (5973) ./myspin 3 &
26,27c26,27
< [1] (5953) Running ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Running ./myspin 3 &
> [2] (5975) Running ./myspin 4 &