CSAPP Lab:Shell Lab

发布于 13 天前  25 次阅读


过年很常时间没有开 lab 了,这个 lab 做了两天(不包括写文档),第一天就是读文档和测试各种 demo,第二天前半天也是测试各种 demo,晚上才把 lab 写完。大概这就是做这个 lab 的体验吧。

做这个 lab 的时候 CSAPP 下载 lab 材料的网站还炸了,一度以为是不开放了再也上不去了

Shell Lab 介绍

Shell Lab 要求实现一个带有作业控制的 Unix Shell 程序,需要考虑基础的并发,进程控制以及信号和信号处理。做这个实验之前一定要将 CSAPP 的第八章(异常控制流)仔细研读。

📦 shlab-handout.tar

📄 shlab.pdf

执行下面的命令,开启 shell lab 之旅

tar xvf shlab-handout.tar
cd shlab-handout/
make

image-20230117100314256

接下来我花了一上午的时间来研读文档,了解实验的要求

Demo 与猜想

读完文档后,就开始尝试通过写代码来印证各种猜想。

首先需要注意区分进程的停止(stopped)和终止(terminated) 状态,其实停止这个词翻译得不太好,暂停表达的意思更合理,所以后文如果提到暂停或者 stopped 都表示停止(stopped)状态。

WNOHANG 与 WUNTRACED

WNOHANGWUNTRACED 信号的区别(CSAPP上关于 WUNTRACED 的解释有点难理解它想表达的意思)

image-20230117152944924

上面这个回答还比较正确,我也经过多次实验验证,WUNTRACED 参数下遇到等待集合中的一个子进程停止或终止状态就会返回。(有一些博客上说 WUNTRACED 是遇到停止但没有终止才会返回,这是错误的)

promask2.c 和 sigsuspend.c 的理解

这是书上的两个关于信号部分比较重要且值得理解的示例程序

  • promask2.c 在 P543,核心就是fork() 之前需要阻塞 SIGCHLD,然后做完 addjob(pid) 等创建子进程后需要做的有关的操作完成后,再解除阻塞 SIGCHLD
  • sigsuspend.c 在 P546,核心就是讲了显示地等待信号的正确方法,先将需要等待地信号屏蔽,然后使用 sigsuspend 让进程进入休眠状态,直到信号到达触发 handler,在 handler 中完成信号到达后应该做的任务,从 handler 返回之后程序苏醒继续运行,再解除信号屏蔽。(关于 sigsuspend 见书或上一篇文章)

pause 的第一个坑

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>

int main() {
    printf("Hello\n");
    pause();
    printf("After pause\n");
    return 0;
}

如上面的代码,在终端发送 SIGCONT 信号,没有反应

image-20230117164236421

如果想达到期望的效果,代码应该这样写:

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>

void sigcont_handler(int sig) {

}

int main() {
    signal(SIGCONT, sigcont_handler); 
    printf("Hello\n");
    pause();
    printf("After pause\n");
    return 0;
}

原因是这个,pause 返回当且仅当一个信号到达且从信号处理函数返回

image-20230117234939305

image-20230117164635089

僵尸进程的猜想验证

如下的情况为什么是仅有 2 个僵尸进程,而不是 3 个呢 ?

image-20230117170112326

image-20230117170725197

可以通过 printf("pid1 = %d pid2 = %d\n", pid1, pid2); 来验证,

image-20230117171427711

void sigcont_handler(int sig) {

}

int main() {
    int pid1 = fork();
    int pid2 = fork();
    signal(SIGCONT, sigcont_handler);
    printf("pid1 = %d  pid2 = %d\n", pid1, pid2);
    printf("Hello\n");
    if(pid1 && pid2) {
        pause();
    }
    return 0;
}

回收进程的 demo

下面是一个使用 sigsuspend 回收僵尸进程的实验

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

volatile sig_atomic_t pid, Cnt;

void sigchld_handler(int sig) {
    int olderrno = errno, status;
    while((pid = waitpid(-1, &status, 0)) > 0)
        printf("Reaped pid = %d\n", pid), ++Cnt;
    errno = olderrno;
}

int main() {
    pid_t pid;
    sigset_t mask, prev;

    signal(SIGCHLD, sigchld_handler);
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &prev);

    for(int i = 1; i <= 4; ++i)
        if((pid = fork()) == 0) {
            //子进程
            sigprocmask(SIG_SETMASK, &prev, NULL);
            printf("child process %d\n", getpid());
            sleep(getpid() % 5 + 1);
            _exit(0);
        }
    while(Cnt < 4)
        sigsuspend(&prev);
    sigprocmask(SIG_SETMASK, &prev, NULL);
    return 0;
}

image-20230117233230940

waitpid 会浪费 CPU 资源吗

下面是 ChatGPT 的回答,

image-20230118110105967

image-20230118110320375

我也经过多次实验证明,waitpid 阻塞的过程中,进程处于 可中断睡眠状态(S),和 pause 的效果是一样的,不会浪费 CPU 资源

pause 的第二个坑 与 Linux 进程状态

下图是当时遇到的一个问题,

image-20230118114203869

这是一个值得注意的坑, pause() 不会将进程变成 stopped 状态,而是变成可中断的睡眠状态(S),T 才代表暂停或跟踪状态,具体见 Linux进程状态说明

R (task_running) : 可执行状态
S (task_interruptible): 可中断的睡眠状态
D (task_uninterruptible): 不可中断的睡眠状态
T(task_stopped or task_traced):暂停状态或跟踪状态
Z (task_dead - exit_zombie):退出状态,进程成为僵尸进程
X (task_dead - exit_dead):退出状态,进程即将被销毁

下面这样就达到了想要的结果了,成功测试了 WUNTRACED 这个可选参数,当然 WNOHANG 我也测试过了

image-20230118191706812

遗留的 waitpid 的困惑与猜想

我写了 3 个 waitpid{1-3}.c ,这里就懒得放出来了,大致总结出两个猜想:

  • waitpid 返回值为 0 的话 status 的结果是不可信的(对应 waitpid1.c,waitpid2.c

  • waitpid 只在子进程状态改变时才能正常返回,返回的结果才有效,或者说对同一子进程调用两次 waitpid 可能会出问题(waitpid3.c 是两次对一个已经 stopped 进程调用 waitpid,选项为 WUNTRACED,按理说 WUNTRACED 选项的情况下遇到 stopped 进程是什么都没做吧,也不会回收它,但第二次调用它就阻塞了)

代码实现

在进行了诸多猜想验证和 demo 实验后,那么完成这个 shell lab 问题不是太大了。但写的时候还发现了一个新知识:

/*

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

从 lab 上的注释中我发现了 CSAPP 上一个漏讲的东西,就是在子进程收到 SIGSTOP 等停止相关的信号时也会向父进程发送 SIGCHLD

此外写代码的时候还有一个很烦的就是得多次查看文档要求,因为要求实在太多了,容易漏掉😭

下面直接上最后的代码:

/* 
 * 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) 
{
    char buf[MAXLINE];
    char *argv[MAXARGS];
    int bg;
    pid_t pid;
    sigset_t mask, prev, mask_all;

    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    if(argv[0] == NULL) return;
    int is_builtin_cmd = builtin_cmd(argv);
    if(is_builtin_cmd) return; //在当前进程中执行内置命令

    //fork之前先 block SIGCHLD
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask, &prev);

    //fork一个新进程执行非内置命令
    if((pid = fork()) == 0) {
        setpgid(0, 0); //将子进程的进程组ID改成子进程ID
        sigprocmask(SIG_SETMASK, &prev, NULL);
        if(execve(argv[0], argv, environ) < 0) {
            printf("%s: Command not found\n", argv[0]);
            exit(0);
        }
    }
    sigprocmask(SIG_BLOCK, &mask_all, NULL); //访问全局数据结构前阻塞所有信号
    addjob(jobs, pid, (bg ? BG : FG), cmdline);
    sigprocmask(SIG_SETMASK, &prev, NULL);
    if(bg) printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline);
    else waitfg(pid);
    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")) exit(0);
    if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) {
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0], "jobs")) {
        listjobs(jobs);
        return 1;
    }
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
    pid_t pid = 0;
    int jid = 0, bg = 0;
    struct job_t *job;
    sigset_t mask_all, prev;

    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask_all, &prev);
    if(argv[1] == NULL) {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        goto END;
    }
    if(argv[1][0] == '%') {
        sscanf(&argv[1][1], "%d", &jid);
        job = getjobjid(jobs, jid);
        if(job == NULL) {
            printf("%%%d: No such job\n", jid);
            goto END;
        }
    } else
    if(isdigit(argv[1][0]))  {
        sscanf(&argv[1][0], "%d", &pid);
        job = getjobpid(jobs, pid);
        if(job == NULL) {
            printf("(%d): No such process\n", pid);
            goto END;
        }
    } else {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        goto END;
    }
    if(!strcmp(argv[0], "bg")) bg = 1;
    kill(-job->pid, SIGCONT);
    job->state = (bg ? BG : FG);
    sigprocmask(SIG_SETMASK, &prev, NULL); //调用waitfg之前解除信号屏蔽
    if(bg) printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
    else waitfg(pid);
    return;

    END:
    sigprocmask(SIG_SETMASK, &prev, NULL);
    return;
}

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
    // int status;
    // waitpid(pid, &status, 0); //感觉返回值是啥都行,没有必要检查
    sigset_t mask, prev;
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &prev);

    while(fgpid(jobs) > 0)
        sigsuspend(&prev);

    sigprocmask(SIG_SETMASK, &prev, NULL);
    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 olderrno = errno, status;
    sigset_t mask_all, prev;
    pid_t pid;
    struct job_t *job;

    sigfillset(&mask_all);
    while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
        sigprocmask(SIG_BLOCK, &mask_all, &prev); //阻塞所有信号,保护对共享全局数据结构的访问
        if(WIFEXITED(status)) {
            deletejob(jobs, pid);
        }
        if(WIFSIGNALED(status)) {
            printf ("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
            deletejob(jobs, pid);
        }
        if(WIFSTOPPED(status)) {
            job = getjobpid(jobs, pid);
            job->state = ST;
            printf ("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
        }
        sigprocmask(SIG_SETMASK, &prev, NULL);
    }
    errno = olderrno;
    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;
    sigset_t mask_all, prev;
    pid_t pid;

    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask_all, &prev);
    if((pid = fgpid(jobs)) > 0) {
        kill(-pid, SIGINT); //发给整个进程组,为因子进程可能fork了新进程,新进程和子进程都属于子进程的进程组
    }
    sigprocmask(SIG_SETMASK, &prev, NULL);
    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;
    sigset_t mask_all, prev;
    pid_t pid;
    // struct job_t *job;

    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask_all, &prev);
    if((pid = fgpid(jobs)) > 0) {
        kill(-pid, SIGTSTP); //发给整个进程组,因为子进程可能fork了新进程,新进程和子进程都属于子进程的进程组
        //下面可以省略,因为子进程收到SIGTSTP后kernel会给父进程发SIGCHLD,父进程在sigchld_handler完成了下面的修改
        // job = getjobpid(jobs, pid);
        // job->state = ST; 
    }
    sigprocmask(SIG_SETMASK, &prev, NULL);
    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);
}




测试

image-20230118235718027

image-20230118235927382

image-20230119000044103

image-20230119000340607

image-20230119000451043

image-20230119001126980

image-20230119001213022

image-20230119001252592

总结

  • 通过学习 Linux 信号以及信号程序编写后,我不再能像以前那样能够自信地确保程序的正确性,任何时候都可能插入一个中断,插入一个信号处理,使得编写程序的时候得格外小心,我也没有什么异步程序编写经验,还需要更多地学习和练习
  • 上面的程序一些不足的地方,比如没有使用包装函数(wrapper),可以像 CSAPP 那样为那些系统函数写个包装函数,不过我懒得写了。还有包括在信号处理程序中使用了 printf 异步不安全函数。还有在对共享全局数据结构访问的时候需要阻塞所有信号,我在很多处都这样做了,但还有少数地方我没有这样做。这也看出使用全局变量真是麻烦,最好在信号处理程序中少用吧
  • 这次 lab 的测试程序竟然没有实现自动化测试,还得一个一个人工比对。这或许也是因为信号处理程序的错误很难发现和复现。

Reference

CSAPP-Lab07 Shell Lab 深入解析

Linux进程状态说明

pause(3p) — Linux manual page


昨日的月光悄然退场,曦阳已经渐亮