《深入理解计算机系统》实验六Shell Lab

前言

《深入理解计算机系统》实验六Shell Lab下载和官方文档机翻请看:
https://blog.youkuaiyun.com/weixin_43362650/article/details/122331406

我觉得这个文档对整个实验很有帮助。

这个实验我是按照文档的这个提示来做的,本文也是这样写的,16个文件+1个代码总结。

使用跟踪文件来指导shell的开发。从trace01.txt开始,确保您的shell产生与参考shell相同的输出。然后继续跟踪文件trace02.txt,等等。

还有一个提示

阅读课本第八章(异常控制流)的每一个字。

实验任务

打开tsh.c文件可以看到里面有我们需要实现的函数(有些注释非自带,自己整理的)

/* 下面是您将实现的函数 */
void eval(char *cmdline);               /* eval:解析和解释命令行的主例程。[70行] */
int builtin_cmd(char **argv);           /* 识别并解释内置命令:quit、fg、bg和jobs。[25行] */
void do_bgfg(char **argv);              /* 实现bg和fg内置命令. [50行] */
void waitfg(pid_t pid);                 /* 等待前台任务完成。[20行] */

void sigchld_handler(int sig);          /* 捕获SIGCHILD信号。[80行] */
void sigtstp_handler(int sig);          /* 捕获SIGINT (ctrl-c)信号。[15行] */
void sigint_handler(int sig);           /* 捕获SIGTSTP (ctrl-z)信号。[15行] */

还有以下的辅助函数

/* 下面是我们为您提供的辅助程序 */
int parseline(const char *cmdline, char **argv);            /* 解析命令行并构建argv数组。 */
void sigquit_handler(int sig);                              /*  */
            
void clearjob(struct job_t *job);                           /* 清除job结构中的条目 */
void initjobs(struct job_t *jobs);                          /* 初始化作业列表 */
int maxjid(struct job_t *jobs);                             /* 返回已分配的最大作业ID */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);        /* 向job列表中添加job */
int deletejob(struct job_t *jobs, pid_t pid);               /* 从job列表中删除PID= pid的作业 */
pid_t fgpid(struct job_t *jobs);                            /* 返回当前前台job的PID,如果没有该job则返回0 */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);     /* 在job列表中找到一个job(通过PID) */
struct job_t *getjobjid(struct job_t *jobs, int jid);       /* 在job列表中找到一个job(通过JID) */
int pid2jid(pid_t pid);                                     /* 将进程ID映射到job ID */
void listjobs(struct job_t *jobs);                          /* 打印job列表 */
        
void usage(void);                                           /* 打印帮助信息 */
void unix_error(char *msg);                                 /* unix-style 错误程序 */
void app_error(char *msg);                                  /* application-style 错误程序 */
typedef void handler_t(int);                                /*  */
handler_t *Signal(int signum, handler_t *handler);          /* sigaction函数的包装器 */

过程

trace01 - 正确地终止EOF。

先熟悉一下环境
正确的输出如下:
在这里插入图片描述
我们编译tsh.c文件运行看看
在这里插入图片描述
会发现一模一样,第一个文件就这样啥都没动就解决了,熟悉了环境。

trace02 - 进程内置quit命令。

主要修改的就是eval()函数和builtin_cmd()函数。
这里《CS:APP3e》P525页有段代码和P543页有关于job的操作的代码。

trace03 - 运行一个前台job。

在前台运行job,要等待job运行完毕。可以在waitfg()方法中循环fgpid()方法

trace04 - 运行后台job。

结合trace2-4编写出以下代码。

/* 
 * eval - 计算用户刚刚输入的命令行
 * 
 * 如果用户请求了一个内置命令(quit, jobs, bg或fg),
 * 然后立即执行它。否则,fork一个子进程并在该子进程的上下文中运行该作业。
 * 如果作业正在前台运行,则等待它终止然后返回。
 * 注意:每个子进程必须有一个唯一的进程组ID,这样当我们在键盘上输入ctrl-c (ctrl-z)时,
 * 我们的后台子进程就不会从内核接收到SIGINT (SIGTSTP)。
*/
void eval(char *cmdline) 
{
    char *argv[MAXARGS];        /* 参数列表execve() */
    char buf[MAXLINE];          /* 保存修改的命令行 */
    int bg;                     /* 这个作业应该在后台进行? */
    pid_t pid;                  /* 进程id*/
    sigset_t mask_all,mask_one,prev_one;

    strcpy(buf,cmdline);
    bg = parseline(buf,argv);
    if(argv[0] == NULL)
        return;                 /* 忽略空命令 */

    if(!builtin_cmd(argv)){
        sigfillset(&mask_all);                              /* 保存当前的信号集合(blocked位向量) */
        sigemptyset(&mask_one);
        sigaddset(&mask_one,SIGCHLD);

        sigprocmask(SIG_BLOCK,&mask_one,&prev_one);         /* 父进程保持SIGCHLD的阻塞 */
        if((pid = fork()) == 0){    /* 子程序运行用户作业 */
            sigprocmask(SIG_SETMASK,&prev_one,NULL);        /* 解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/
            if(execve(argv[0],argv,environ) < 0){
                printf("%s: Command not found.\n",argv[0]);
                exit(0);
            }
        }
        
        sigprocmask(SIG_BLOCK,&mask_all,NULL);              /* 恢复信号集合(blocked位向量) */
        addjob(jobs,pid,bg==1 ? BG : FG,cmdline);           /* 将子任务添加到任务列表中 */
        sigprocmask(SIG_SETMASK,&prev_one,NULL);            /* 解除子进程对SIGCHLD的阻塞 */
        /* 这样子sigchld_handler处理程序在我们将其添加到工作队列
            中之前是不会运行的。因为直到addjob()之后,我们才解除对SIGCHLD的阻塞
         */

        /* 父任务等待前台任务结束 */
        if (!bg){
            waitfg(pid);
        }else{  /* 后台工作 */
            printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
        }

    }

    return;
}

/* 
 * builtin_cmd - 如果用户输入了一个内置的命令,那么立即执行它。
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0],"quit"))     /* 退出命令 */
        exit(0);
    if(!strcmp(argv[0],"&"))        /* 忽略单& */
        return 1;

    return 0;     /* 不是一个内置命令 */
}

/* 
 * waitfg - 阻塞,直到进程pid不再是前台进程
 */
void waitfg(pid_t pid)
{
    /* 唯一的前台作业结束后,被sigchld_handler回收,deletejob(..)后,jobs列表中就没有前台作业,
        循环fpgid(..)
    */
    while(pid==fgpid(jobs)){
        sleep(0);
    }
    return;
}

/* 
 * sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
 * 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
 * 但不等待任何其他当前运行的子进程终止。
 */
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev_all;
    pid_t pid;

    sigfillset(&mask_all);                          /* 保存当前的信号集合(blocked位向量) */
    while((pid = waitpid(-1,NULL,WNOHANG)) > 0){	/* WNOHANG:非阻塞的 */
        sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
        deletejob(jobs,pid);                        
        sigprocmask(SIG_SETMASK,&prev_all,NULL);
    }
    errno = olderrno;

    return;
}

编译运行(左边我们编写的tsh,右边标准答案tshref,下同)
trace02
在这里插入图片描述
trace03
在这里插入图片描述
trace04
在这里插入图片描述

trace05 - 处理jobs内置命令

这个简单,直接调用自带的listjobs()方法

/* 
 * builtin_cmd - 如果用户输入了一个内置的命令,那么立即执行它。
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0],"quit")){    /* 退出命令 */
        exit(0);
    }
    if(!strcmp(argv[0],"jobs")){    /* jobs内置指令 */
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0],"&")){       /* 忽略单& */
        return 1;
    }
    return 0;     /* 不是一个内置命令 */
}

在这里插入图片描述

trace06 - 将SIGINT转发到前台作业。

trace07 - 仅将SIGINT转发给前台作业。

SIGINT信号是终止。
文档中有这么一个提示。
在这里插入图片描述
大概就是下图所示
请添加图片描述
所以要更改eval()函数

sigprocmask(SIG_BLOCK,&mask_one,&prev_one);         /* 父进程保持SIGCHLD的阻塞 */
if((pid = Fork()) == 0){    /* 子程序运行用户作业 */
    sigprocmask(SIG_SETMASK,&prev_one,NULL);        /* 解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/
    if(setpgid(0,0) < 0){               /* 把子进程放到一个新进程组中,该进程组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即shell进程。*/
        printf("setpgid error");
        exit(0);
    }

    if(execve(argv[0],argv,environ) < 0){
        printf("%s: Command not found.\n",argv[0]);
        exit(0);
    }
}

sigprocmask(SIG_BLOCK,&mask_all,NULL);              /* 恢复信号集合(blocked位向量) */
addjob(jobs,pid,bg==1 ? BG : FG,cmdline);           /* 将子任务添加到任务列表中 */
sigprocmask(SIG_SETMASK,&prev_one,NULL);            /* 解除子进程对SIGCHLD的阻塞 */

修改sigint_handler()函数,转发到前台作业(包含前台作业的进程组)。

/* 
 * sigint_handler - 当用户在键盘上键入ctrl-c时,内核向shell发送一个SIGINT。抓住它并把它发送到前台工作。
 */
void sigint_handler(int sig) 
{
    pid_t pid = fgpid(jobs);	/* 获取前台进程id */
    if(pid > 0){
        kill(-pid,sig);     	/* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}

还要修改sigchld_handler()函数。区分终止原因

  • 调用exit或者一个返回(return)正常终止。(原本的写法)
  • 接收到其他信号如:SIGINT。终止的。(修改后要加上的)
/* 
 * sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
 * 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
 * 但不等待任何其他当前运行的子进程终止。
 */
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev_all;
    pid_t pid;
    int status;

    sigfillset(&mask_all);                          /* 保存当前的信号集合(blocked位向量) */
    while((pid = waitpid(-1,&status,WNOHANG)) > 0){    /* WNOHANG:非阻塞的 */
        /* 通过调用exit或者一个返回(return)正常终止 */
        if(WIFEXITED(status)){
            sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
            deletejob(jobs,pid);     
            sigprocmask(SIG_SETMASK,&prev_all,NULL);                   
        }
        /* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
        if(WIFSIGNALED(status)){
            int jid = pid2jid(pid);
            printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
            deletejob(jobs,pid);  /* 终止就删除pid的job */
        }
        
    }
    errno = olderrno;

    return;
}

编译运行
trace06
在这里插入图片描述

trace07
在这里插入图片描述

trace08 - 仅将SIGTSTP转发到前台作业。

SIGTSTP信号是停止直到下一个SIGCONT。
和singint_handler()差不多


/*
 * sigtstp_handler - 每当用户在键盘上键入ctrl-z时,内核就向shell发送一个SIGTSTP。捕获它并通过向它发送SIGTSTP来挂起前台作业。
 */
void sigtstp_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    /* 获取前台进程id */

    if(pid > 0){
        kill(-pid,sig);         /* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}

还要修改sigchld_handler()函数。区分终止/停止

  • 调用exit或者一个返回(return)正常终止。(原本的写法)
  • 接收到其他信号如:SIGINT。终止的。(原本的写法)
  • 引起返回的子进程当前是停止的如:SIGTSTP。(修改后要加上的)

还要加多一个WUNTRACED,变成WNOHANG | WUNTRACED
WNOHANG:挂起调用进程,直到有子进程终止。
WUNTRACED:挂起调用进程,直到等待集合中的一个进程变成已终止或者被停止。
WNOHANG | WUNTRACED:等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。
可以理解为WNOHANG接收终止,WUNTRACED接收停止。两个合在一起就是接收终止和停止。

/* 
 * sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
 * 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
 * 但不等待任何其他当前运行的子进程终止。
 */
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev_all;
    pid_t pid;
    int status;

    sigfillset(&mask_all);                          /* 保存当前的信号集合(blocked位向量) */
    while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED)) > 0){    /* WNOHANG:非阻塞的 */
        /* 通过调用exit或者一个返回(return)正常终止 */
        if(WIFEXITED(status)){
            sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
            deletejob(jobs,pid);     
            sigprocmask(SIG_SETMASK,&prev_all,NULL);                   
        }
        /* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
        if(WIFSIGNALED(status)){
            int jid = pid2jid(pid);
            printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
            deletejob(jobs,pid);		/* 终止就删除pid的job */
        }
        /* 引起返回的子进程当前是停止的(SIGTSTP) */
        if(WIFSTOPPED(status)){
            struct job_t * job = getjobpid(jobs,pid);
            int jid = pid2jid(pid);
            printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
            job->state = ST;			/* 状态设为停止(ST) */
        }
        
    }
    errno = olderrno;

    return;
}

编译运行
trace08
在这里插入图片描述

trace09 - 进程bg内置命令

bg <job>:将一个已停止的后台作业修改为正在运行的后台作业。

往builtin_cmd()方法添加内容

if(!strcmp(argv[0],"bg")){      /* bg内置指令 */
    do_bgfg(argv);
    return 1;
}

修改do_bgfg()方法

void do_bgfg(char **argv) 
{

    pid_t pid;                      /* 进程id */
    int jid;                        /* job的id */
    struct job_t * job;
    if (argv[1][0] == '%'){   /* 给的是%x,即jid */
        jid = atoi(argv[1]+1);
        job = getjobjid(jobs,jid);
        pid = job->pid;
    }else{                              /* 给的是pid */
        pid = atoi(argv[1]);
        job = getjobpid(jobs,pid);
        jid = job->jid;
    }
    if(pid > 0){
        if(!strcmp(argv[0],"bg")){			/* bg内置指令 */
            printf("[%d] (%d) %s",jid,pid,job->cmdline);
            job->state = BG;                /* 更改状态 */
            kill(pid,SIGCONT);              /* 传递SIGCONT信号 */
            
        }
    }

    return;
}

编译运行
trace09
在这里插入图片描述

trace10 - 进程fg内置命令

fg <job>:将一个已停止或正在运行的后台作业更改为正在前台运行的作业。

往builtin_cmd()方法添加内容

if(!strcmp(argv[0],"fg")){      /* bg内置指令 */
    do_bgfg(argv);
    return 1;
}

修改do_bgfg()方法

if(pid > 0){
    if(!strcmp(argv[0],"bg")){          /* bg内置指令 */
        printf("[%d] (%d) %s",jid,pid,job->cmdline);
        job->state = BG;                /* 更改状态 */
        kill(pid,SIGCONT);              /* 传递SIGCONT信号 */
        
    }else
    if(!strcmp(argv[0],"fg")){          /* fg内置指令 */
        if(job->state == ST){			/* ST:已停止 */
            kill(pid,SIGCONT);          /* 传递SIGCONT信号 */
        }
        job->state = FG;                /* 更改状态 */

        waitfg(pid);                    /* 等待前台job完成 */
    }
}

编译运行
trace10
在这里插入图片描述

trace11 - 将SIGINT转发给前台进程组中的每个进程

在trace06-trace07中写的就是转发给进程组

void sigint_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    /* 获取前台进程id */
    if(pid > 0){
        kill(-pid,sig);         /* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}

运行结果(太长了,就不放全了,一个一个对着看就好)
trace11
在这里插入图片描述

trace12 - 将SIGTSTP转发到前台进程组中的每个进程

在trace08中写的就是转发给进程组

void sigtstp_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    /* 获取前台进程id */

    if(pid > 0){
        kill(-pid,sig);         /* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}

运行结果(太长了,就不放全了,一个一个对着看就好)
trace12
在这里插入图片描述

trace13 - 重新启动进程组中的每个已停止的进程

运行标准答案可以发现主要就是这两个进程在一组,先给停止,然后重新启动。
在这里插入图片描述
在这里插入图片描述
修改do_bgfg()方法

  • 传递给kill的pid修改为-pid,传递给整个进程组。
  • 去掉job->state == ST才传递SIGCONT信号的判断,因为当前进程可能没有停止(ST),但是进程组有停止的,要重新启动。
if(pid > 0){
    if(!strcmp(argv[0],"bg")){          /* bg内置指令 */
        printf("[%d] (%d) %s",jid,pid,job->cmdline);
        job->state = BG;                /* 更改状态 */
        kill(-pid,SIGCONT);              /* 传递SIGCONT信号给进程组中的所有进程 */
        
    }else
    if(!strcmp(argv[0],"fg")){          /* fg内置指令 */
        job->state = FG;                /* 更改状态 */
        kill(-pid,SIGCONT);             /* 传递SIGCONT信号给进程组中的所有进程 */
        waitfg(pid);                    /* 等待前台job完成 */
    }
}

运行结果(太长了,就不放全了,一个一个对着看就好)
trace13
在这里插入图片描述

trace14 - 简单的错误处理

运行看看效果,就是处理一些错误的命令。
在这里插入图片描述

void do_bgfg(char **argv) 
{

    pid_t pid;                      /* 进程id */
    int jid;                        /* job的id */
    struct job_t * job;
    if (argv[1] == NULL){
        printf("%s command requires PID or %%jobid argument\n",argv[0]);
        return;
    }
    if (argv[1][0] == '%'){   /* 给的是%x,即jid */
        if(argv[1][1] < '0' || argv[1][1] >'9'){
            printf("fg: argument must be a PID or %%jobid\n");
            return;
        }

        jid = atoi(argv[1]+1);
        job = getjobjid(jobs,jid);
        if(job == NULL){
            printf("%%%d: No such job\n",jid);
            return;
        }
        pid = job->pid;
    }else{                              /* 给的是pid */
        if(argv[1][0] < '0' || argv[1][0] >'9'){
            printf("bg: argument must be a PID or %%jobid\n");
            return;
        }
        pid = atoi(argv[1]);
        job = getjobpid(jobs,pid);
        if(job == NULL){
            printf("(%d): No such process\n",pid);
            return;
        }
        jid = job->jid;
    }
    if(pid > 0){
        if(!strcmp(argv[0],"bg")){          /* bg内置指令 */
            printf("[%d] (%d) %s",jid,pid,job->cmdline);
            job->state = BG;                /* 更改状态 */
            kill(-pid,SIGCONT);              /* 传递SIGCONT信号给进程组中的所有进程 */
            
        }else
        if(!strcmp(argv[0],"fg")){          /* fg内置指令 */
            job->state = FG;                /* 更改状态 */
            kill(-pid,SIGCONT);             /* 传递SIGCONT信号给进程组中的所有进程 */
            waitfg(pid);                    /* 等待前台job完成 */
        }
    }

    return;
}

编译运行
trace14
在这里插入图片描述

trace15 - 把它们放在一起

这个就是把命令放在一起测试。
在这里插入图片描述

trace16 - 测试shell是否能够处理来自其他进程而不是终端的SIGTSTP和SIGINT信号。

测试一波,也可以了。
在这里插入图片描述

总结

想说的都在注解和上面的过程中了。

void eval(char *cmdline);

/* 
 * eval - 计算用户刚刚输入的命令行
 * 
 * 如果用户请求了一个内置命令(quit, jobs, bg或fg),
 * 然后立即执行它。否则,fork一个子进程并在该子进程的上下文中运行该作业。
 * 如果作业正在前台运行,则等待它终止然后返回。
 * 注意:每个子进程必须有一个唯一的进程组ID,这样当我们在键盘上输入ctrl-c (ctrl-z)时,
 * 我们的后台子进程就不会从内核接收到SIGINT (SIGTSTP)。
*/
void eval(char *cmdline) 
{
    char *argv[MAXARGS];        /* 参数列表execve() */
    char buf[MAXLINE];          /* 保存修改的命令行 */
    int bg;                     /* 这个作业应该在后台进行? */
    pid_t pid;                  /* 进程id*/
    sigset_t mask_all,mask_one,prev_one;

    strcpy(buf,cmdline);
    bg = parseline(buf,argv);
    if(argv[0] == NULL)
        return;                 /* 忽略空命令 */

    if(!builtin_cmd(argv)){
        sigfillset(&mask_all);                              /* 保存当前的信号集合(blocked位向量) */
        sigemptyset(&mask_one);
        sigaddset(&mask_one,SIGCHLD);

        sigprocmask(SIG_BLOCK,&mask_one,&prev_one);         /* 父进程保持SIGCHLD的阻塞 */
        if((pid = fork()) == 0){    /* 子程序运行用户作业 */
            sigprocmask(SIG_SETMASK,&prev_one,NULL);        /* 解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/
            if(setpgid(0,0) < 0){               /* 把子进程放到一个新进程组中,该进程组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即shell进程。*/
                printf("setpgid error");
                exit(0);
            }

            if(execve(argv[0],argv,environ) < 0){
                printf("%s: Command not found.\n",argv[0]);
                exit(0);
            }
        }
        
        sigprocmask(SIG_BLOCK,&mask_all,NULL);              /* 恢复信号集合(blocked位向量) */
        addjob(jobs,pid,bg==1 ? BG : FG,cmdline);           /* 将子任务添加到任务列表中 */
        sigprocmask(SIG_SETMASK,&prev_one,NULL);            /* 解除子进程对SIGCHLD的阻塞 */
        /* 这样子sigchld_handler处理程序在我们将其添加到工作队列
            中之前是不会运行的。因为直到addjob()之后,我们才解除对SIGCHLD的阻塞
         */

        /* 父任务等待前台任务结束 */
        if (!bg){
            waitfg(pid);
        }else{  /* 后台工作 */
            printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
        }

    }

    return;
}

int builtin_cmd(char **argv);

/* 
 * builtin_cmd - 如果用户输入了一个内置的命令,那么立即执行它。
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0],"quit")){    /* 退出命令 */
        exit(0);
    }
    if(!strcmp(argv[0],"jobs")){    /* jobs内置指令 */
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0],"bg")){      /* bg内置指令 */
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0],"fg")){      /* bg内置指令 */
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0],"&")){       /* 忽略单& */
        return 1;
    }

    return 0;     /* 不是一个内置命令 */
}

void do_bgfg(char **argv);

/* 
 * do_bgfg - 执行bg和fg命令
 */
void do_bgfg(char **argv) 
{

    pid_t pid;                      /* 进程id */
    int jid;                        /* job的id */
    struct job_t * job;
    if (argv[1] == NULL){
        printf("%s command requires PID or %%jobid argument\n",argv[0]);
        return;
    }
    if (argv[1][0] == '%'){   /* 给的是%x,即jid */
        if(argv[1][1] < '0' || argv[1][1] >'9'){
            printf("fg: argument must be a PID or %%jobid\n");
            return;
        }

        jid = atoi(argv[1]+1);
        job = getjobjid(jobs,jid);
        if(job == NULL){
            printf("%%%d: No such job\n",jid);
            return;
        }
        pid = job->pid;
    }else{                              /* 给的是pid */
        if(argv[1][0] < '0' || argv[1][0] >'9'){
            printf("bg: argument must be a PID or %%jobid\n");
            return;
        }
        pid = atoi(argv[1]);
        job = getjobpid(jobs,pid);
        if(job == NULL){
            printf("(%d): No such process\n",pid);
            return;
        }
        jid = job->jid;
    }
    if(pid > 0){
        if(!strcmp(argv[0],"bg")){          /* bg内置指令 */
            printf("[%d] (%d) %s",jid,pid,job->cmdline);
            job->state = BG;                /* 更改状态 */
            kill(-pid,SIGCONT);              /* 传递SIGCONT信号给进程组中的所有进程 */
            
        }else
        if(!strcmp(argv[0],"fg")){          /* fg内置指令 */
            job->state = FG;                /* 更改状态 */
            kill(-pid,SIGCONT);             /* 传递SIGCONT信号给进程组中的所有进程 */
            waitfg(pid);                    /* 等待前台job完成 */
        }
    }

    return;
}

void waitfg(pid_t pid);

/* 
 * waitfg - 阻塞,直到进程pid不再是前台进程
 */
void waitfg(pid_t pid)
{
    /* 唯一的前台作业结束后,被sigchld_handler回收,deletejob(..)后,jobs列表中就没有前台作业,
        循环fpgid(..)
    */
    while(pid==fgpid(jobs)){
        sleep(0);
    }
    return;
}

void sigchld_handler(int sig);

/* 
 * sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
 * 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
 * 但不等待任何其他当前运行的子进程终止。
 */
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev_all;
    pid_t pid;
    int status;

    sigfillset(&mask_all);                          /* 保存当前的信号集合(blocked位向量) */
    while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED)) > 0){    /* WNOHANG:非阻塞的 WUNTRACED:等待集合的停止*/
        /* 通过调用exit或者一个返回(return)正常终止 */
        if(WIFEXITED(status)){
            sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
            deletejob(jobs,pid);     
            sigprocmask(SIG_SETMASK,&prev_all,NULL);                   
        }
        /* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
        if(WIFSIGNALED(status)){
            int jid = pid2jid(pid);
            printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
            deletejob(jobs,pid);            /* 终止就删除pid的job */
        }
        /* 引起返回的子进程当前是停止的(SIGTSTP) */
        if(WIFSTOPPED(status)){
            struct job_t * job = getjobpid(jobs,pid);
            int jid = pid2jid(pid);
            printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
            job->state = ST;                /* 状态设为停止(ST) */
        }
        
    }
    errno = olderrno;

    return;
}

void sigint_handler(int sig)

/* 
 * sigint_handler - 当用户在键盘上键入ctrl-c时,内核向shell发送一个SIGINT。抓住它并把它发送到前台工作。
 */
void sigint_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    /* 获取前台进程id */
    if(pid > 0){
        kill(-pid,sig);         /* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}

void sigint_handler(int sig);

/*
 * sigtstp_handler - 每当用户在键盘上键入ctrl-z时,内核就向shell发送一个SIGTSTP。捕获它并通过向它发送SIGTSTP来挂起前台作业。
 */
void sigtstp_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    /* 获取前台进程id */

    if(pid > 0){
        kill(-pid,sig);         /* 转发信号sig给进程组|pid|中的每个进程 */
    }
    return;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值