7

csapp之lab:shell lab

 2 years ago
source link: https://segmentfault.com/a/1190000041362187
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

csapp之lab:shell lab

发布于 2 月 2 日

shell lab主要目的是为了熟悉进程控制和信号。具体来说需要比对16个test和rtest文件的输出,实现五个函数:

void eval(char *cmdline):分析命令,并派生子进程执行 主要功能是解析cmdline并运行
int builtin_cmd(char **argv):解析和执行bulidin命令,包括 quit, fg, bg, and jobs
void do_bgfg(char **argv) 执行bg和fg命令
void waitfg(pid_t pid):实现阻塞等待前台程序运行结束
void sigchld_handler(int sig):SIGCHID信号处理函数
void sigint_handler(int sig):信号处理函数,响应 SIGINT (ctrl-c) 信号 
void sigtstp_handler(int sig):信号处理函数,响应 SIGTSTP (ctrl-z) 信号

可用辅助函数:

  • int parseline(const char *cmdline,char **argv):获取参数列表,返回是否为后台运行命令
  • void clearjob(struct job_t *job):清除job结构体。
  • void initjobs(struct job_t *jobs):初始化jobs链表。
  • void maxjid(struct job_t *jobs):返回jobs链表中最大的jid号。
  • int addjob(struct job_t *jobs,pid_t pid,int state,char *cmdline):在jobs链表中添加job
  • int deletejob(struct job_t *jobs,pid_t pid):在jobs链表中删除pidjob
  • pid_t fgpid(struct job_t *jobs):返回当前前台运行jobpid号。
  • struct job_t *getjobpid(struct job_t *jobs,pid_t pid):返回pid号的job
  • struct job_t *getjobjid(struct job_t *jobs,int jid):返回jid号的job
  • int pid2jid(pid_t pid):将pid号转化为jid
  • void listjobs(struct job_t *jobs):打印jobs
  • void sigquit_handler(int sig):处理SIGQUIT信号。

shell是交互式的命令行解释器,打印提示符并在stdin上等待输入命令,并按照命令行的内容执行。命令行是ASCII单词组成的命令和参数序列。若首个单词是内置命令,shell会立即在当前进程中执行。否则是可执行文件路径,shell派生出子进程,然后在该子进程的上下文中加载和运行程序。解释单个命令行而创建的子进程叫作业,通常由Unix管道连接的多个子进程组成。若命令行以&号“&”结束,则作业将在后台运行且不会等待作业结束。否则作业将在前台运行且等待作业终止。故在任何时间点最多仅一个作业在前台运行。但可在后台运行任意数量的作业。

例如:tsh> /bin/ls -l -d

在前台运行程序,程序的入口是:int main(int argc,char *argv[])

则argc==3,argv[0] == ‘‘/bin/ls’’,argv[1]== ‘‘-l’’,argv[2]== ‘‘-d’’。若在命令行后加上&,则在后台运行ls程序。shell支持作业控制,允许用户在后台和前台移动作业,并更改作业中进程的状态(运行、停止或终止)。输入ctrl-c会向前台作业中的每个进程发送SIGINT信号,默认操作是终止进程。类似地,键入ctrl-z将向前台作业中的每个进程发送SIGTSTP信号,默认操作是将进程置于停止状态,直到收到SIGCONT信号将其唤醒。当然shell也提供内置命令支持作业控制:

  • jobs:列出运行和终止的后台作业
  • bg <job>:将终止的后台作业改为运行
  • fg <job>:将终止或运行的后台作业改为前台运行
  • kill <job>:发送特定信号给特定进程和进程组,默认动作是终止进程
  • quit:终止shell

有三点值得注意:

  • tsh不支持管道和I/O重定向
  • 每个作业要么被process ID识别,要么被job ID识别,jid因该在命令行中用前缀“%”表示,“%5”表示jid 5,5表示PID 5
  • shell因该回收所有僵尸进程,若任何一个作业因为接收到它没有捕捉到的信号而终止,那么tsh应该识别该事件,并打印PID和错误描述消息
  • 仔细阅读CSAPP第八章的异常控制流和lab的writeup
  • make testn测试shell执行第n组测试数据的输出,make rtestn打印shell预期输出,tshref.out包含shell所有预期输出结果,先看文件输出,了解命令格式再编码,修改makefile文件中CFLAGS字段,加-g参数并去掉-O2参数
  • waitpid, kill, fork, execve, setpgid, sigprocmask 很常用,可通过命令手册查看使用细节,WUNTRACEDWNOHANG选项对waitpid也很有用
  • 实现信息处理函数,确保发送SIGINTSIGTSTP信号给整个前台进程组,用-pid代替pid作为kill参数
  • 建议在waitfg的循环中用sleep函数,在sigchld_handler中对waitpid只调用一次
  • eval中进程在fork之前用sigprocmask阻塞SIGCHLD信号,之后在解除信号阻塞,之后在调用addjob添加孩子到作业列表用sigprocmask阻塞信号,因为子继承继承父进程的阻塞集合,所以子程序必须确保在执行新进程前解除阻塞SIGCHLD信号。父进程需以这种方式阻塞SIGCHLD信号,避免在父进程调用addjob之前,SIGCHLD处理器获取子进程(从而从任务列表中删除)的竞争状态。
  • 不要直接调用常用命令,而应输入完整路径,如/bin/ls
  • 当在标准Unix shell运行tsh时,tsh运行在前台进程组中。若tsh随后创建子进程,默认情况下,该子进程也是前台进程组的成员。因为按下ctrl-c会向前台组中的每个进程发送SIGINT信号,按下ctrl-c会向tsh及Unix shell创建的每个子进程,显然不正确。应该在fork后,但在execve前,子进程调用setpgid(0,0),把子进程放到新进程组中,该进程组ID与子进程的PID相同。确保前台进程组中只有一个进程,即tsh进程。当按下ctrl-c时,tsh应捕获生成的SIGINT,然后将其转发给包含前台作业的进程组。

实验前环境配置

由于csapp都是运行在32位系统,即使安装32位系统所需的库,仍然无法运行tsh,在网上找到有人配置好的csapp的docker镜像,因此直接使用docker,环境配置如下:

  1. 安装docker,并配置加速
  2. 安装vscode和ssh插件
  3. 命令行中运行systemctl start docker启动docker和docker run --privileged -d -p 1221:22 --name shell yansongsongsong/csapp:shelllabshell lab的实验环境
  4. 通过ssh输入密码登录实验环境

在vscode中打开shlab-handout文件夹,并打开tsh.c文件,可以看到在main函数中调用eval函数,而在书P525或20-ecf-sigs的P19可找到eval函数的整体代码框架:

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);
    if (argv[0] == NULL)
        return;/* Ignore empty lines */

    if (!builtin_cmd(argv)) {
        if ((pid = Fork()) == 0) {/* Child runs user job */
            Execve(argv[0], argv, environ);
        }/* 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;
}

尽管ppt上说有bug,暂时先不管,先搞好整体框架,完成简单的函数,到后面在考虑。另外值得一提的是这里将forkexecve都进行封装以处理错误情况。
运行make rtest01make test01可以看到输出一样,已经达到要求。同样操作,可以看到test02未按照预期退出tsh,分析知需要实现builtin_cmd函数。同样在书上P525能找到基础代码,只需加上jobsfgbg3种情况即可。代码如下:

int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0],"quit")) /* quit command */
        exit(0);

    if (!strcmp(argv[0], "&")) /* Ignore singleton & */
        return 1; 
    
    if(!strcmp((argv[0]),"jobs"))/* jobs command */
    {
        listjobs(jobs);
        return 1;
    }

    if(!strcmp((argv[0]),"fg") || !strcmp((argv[0]),"bg"))/* bg/fg command */
    {
        do_bgfg(argv);
        return 1;
    }
    return 0;     /* not a builtin command */
}

这样就过了test02test03,经过比较test04rtest04的输出,确定只需修改输出格式即可:

printf("[%d] (%d) %s", pid2jid(pid),pid, cmdline);

接着发现test05是执行内部命令:jobs,打印job list,比对rtest05发现没有打印出job,参考上面的提示第6条,知应同步避免父子竞争,具体来说:父进程在fork前屏蔽信号,子进程在execve前还原信号,因为子进程回继承原来的屏蔽信号。同时前台job需要调用waitfg进行等待。如果不阻塞会出现子进程先结束从jobs中删除,然后再执行到主进程addjob的竞争问题。在书上P542和PPT P57页都有对应的参考代码:

int main(int argc, char **argv)
{
    int pid;
    sigset_t mask_all, mask_one, prev_one;
    int n = N; /* N = 5 */
    Sigfillset(&mask_all);
    Sigemptyset(&mask_one);
    Sigaddset(&mask_one, SIGCHLD);
    Signal(SIGCHLD, handler);
    initjobs(); /* Initialize the job list */
    
    while (n--) {
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
        if ((pid = Fork()) == 0) { /* Child process */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
            Execve("/bin/date", argv, NULL);
        }
        Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Parent process */
        addjob(pid); /* Add the child to the job list */
        Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
    }
    exit(0);
}

加上图中对应代码,同时若子进程结束,需要delete job,在sigchld_handler中加上非阻塞循环等待子进程的代码:

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);
    if (argv[0] == NULL)
        return;/* Ignore empty lines */

    if (!builtin_cmd(argv)) {
        Sigfillset(&mask_all);/* add every signal number to set */
        Sigemptyset(&mask_one);/* create empty set */
        Sigaddset(&mask_one, SIGCHLD);/* add signal number to set */

        /*  block SIGINT and save previous blocked set */
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
        if ((pid = Fork()) == 0) {/* Child runs user job */
            /* restore previous blocked set,unblocking SIGINT */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
            //Setpgid(0,0);
            Execve(argv[0], argv, environ);
        }/* Parent waits for foreground job to terminate */

        Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Block SIGCHLD */
        int st = (bg==0) ? FG : BG;
        addjob(jobs,pid,st,cmdline);
        Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
        if (!bg) {
            //由于sigchld_handler上面被调用,而上面回调用waitpid,因此这里不用调用只需循环等待即可
            waitfg(pid);
        }
        else
            printf("[%d] (%d) %s", pid2jid(pid),pid, cmdline);
    }
    return;
}
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev_all;
    pid_t pid;
    Sigfillset(&mask_all);
    /*改成非阻塞,否则test05中运行到此处,前端进程执行jobs会阻塞直到所有子进程都被回收,即两个后端进程都执行并delete才会离开,则jobs命令什么也没有打印*/
    while((pid = waitpid(-1,NULL,WNOHANG | WUNTRACED))>0){
        Sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
        deletejob(jobs,pid);
        Sigprocmask(SIG_SETMASK,&prev_all,NULL);
    }
    errno = olderrno;
    return;
}

如果是前台命令,则调用waitfg循环等待,在注释中看到最好不要用waitpid(pid,NULL,0),其次根据上面的提示,不要同时在sigchld_handlerwaitfg函数中使用waitpid,因为在同一个程序的两个地方都回收僵死进程,虽然也行,但容易让人迷惑:

void waitfg(pid_t pid)
{
    while(fgpid(jobs))
        usleep(1000);//一秒
    return;
}

这样就完成test05,接下来test06test07test08就是实现SIGINTSIGSTOP信号处理函数,注意前面提示的第4条用-pid作为kill的参数,同时最后一条在forkexecve前子进程应调用setpgid(0,0),否则回报错No such process,注意sigint_handlersigtstp_handler只需调用kill即可,将输出留到sigchld_handler中,这样就需修改前面的sigchld_handler以处理不同子进程退出状态:

void sigint_handler(int sig) 
{
    int olderrno = errno;
    pid_t fg = fgpid(jobs);
    if(fg){
        Kill(-fg,sig);
    }
    errno = olderrno;
    return;
}
void sigtstp_handler(int sig) 
{
    int olderrno = errno;
    pid_t fg = fgpid(jobs);
    if(fg){
        Kill(-fg,sig);
    }
    errno = olderrno;
    return;
}
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    sigset_t mask_all,prev;
    pid_t pid;
    int status;
    Sigfillset(&mask_all);
    /*改成非阻塞,否则test05中运行到此处,前端进程执行jobs会阻塞直到所有子进程都被回收,即两个后端进程都执行并delete才会离开,则jobs命令什么也没有打印*/
    while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED))>0){
        // WNOHANG | WUNTRACED 是立即返回
        // 用WIFEXITED(status),WIFSIGNALED(status),WIFSTOPPED(status)等来补获终止或者
        // 被停止的子进程的退出状态。
        if (WIFEXITED(status))  // 正常退出 delete
        {
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        else if (WIFSIGNALED(status))  // 信号退出 delete
        {
            struct job_t* job = getjobpid(jobs, pid);
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        else  // 停止 只修改状态就行
        {
            struct job_t* job = getjobpid(jobs, pid);
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
            job->state= ST;
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
    }
    errno = olderrno;  // 恢复
    return;
}

这样就完成test06test07test08。接下来test09test10是测试fgbg内置命令,先解析命令通过getjobjidgetjobpid获取job,再分情况对fgbg命令做不同处理,输入%num 代表任务id,num代表进程id,分情况讨论即可,但要注意各种异常情况:

void do_bgfg(char **argv) 
{
    if(!argv[1]){
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    if (!isdigit(argv[1][0]) && argv[1][0] != '%') {            // Checks if the second argument is valid
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    struct job_t* myjob;
    if(argv[1][0]=='%'){
        myjob = getjobjid(jobs,atoi(&argv[1][1]));
        if(!myjob){
            printf("%s: No such job\n", argv[1]);
            return;
        }
    }else{
        myjob = getjobpid(jobs,atoi(argv[1]));
        if (!myjob) {                                 // Checks if the given PID is there
            printf("(%d): No such process\n", atoi(argv[1]));
            return;
        }
    }

    Kill(-myjob->pid,SIGCONT);
    if(!strcmp(argv[0],"bg")){
        myjob->state = BG;
        printf("[%d] (%d) %s",myjob->jid,myjob->pid,myjob->cmdline);
    }else{
        myjob->state = FG;
        waitfg(myjob->pid);
    }

    return;
}

这样就过了test09test10。接下来test11test12test13分别测试Forward SIGINT Forward SIGTSTPRestart stopped process 都能正常通过,若每通过,因该是前面某些测试有问题,解决后即可。test14是测试JIDPID的错误输入的情况,较容易通过。test15将前面所有测试情况放一起,也顺利通过,而test16是测试tsh能否处理不是来自终端而是来自其他进程的SIGSTPSIGINT信号,顺利通过。

最终代码见下,该实验主要涉及加载、进程控制、信号等基础但很重要的知识,涉及到异常控制流、进程、系统调用、信号处理函数与非本地跳转等并发编程的知识。并发的同步问题是关键,利用信号屏蔽与还原就能解决。此外阅读 man 手册了解系统接口使用细节对完成实验很有帮助。

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

/*error handling function */
pid_t Fork(void);
void Execve(const char *filename, char *const argv[], char *const environ[]);
void Kill(pid_t pid, int signum);
void Sigemptyset(sigset_t *set);
void Sigaddset(sigset_t *set, int signum);
void Sigfillset(sigset_t *set);
void Setpgid(pid_t pid, pid_t pgid);
void Sigprocmask(int how, sigset_t *set, sigset_t *oldset);
/* 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 *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);
    if (argv[0] == NULL)
        return;/* Ignore empty lines */

    if (!builtin_cmd(argv)) {
        //blocking SIGCHLD in if status,otherewise it maybe has bugs
        Sigfillset(&mask_all);/* add every signal number to set */
        Sigemptyset(&mask_one);/* create empty set */
        Sigaddset(&mask_one, SIGCHLD);/* add signal number to set */

        /*  block SIGINT and save previous blocked set */
        /* avoid parent process run to addjob exited,before fork child process block sigchild signal,after call addjob unblock  */
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
        if ((pid = Fork()) == 0) {/* Child runs user job */
            /* restore previous blocked set,unblocking SIGINT */
            /* child process inherit parent process' blocking sets,avoid it can't receive itself child process signal,so we must unblock */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
            Setpgid(0,0);// set child's group to a new process group (this is identical to the child's PID)
            Execve(argv[0], argv, environ);//this function not return ,so must call exit,otherewise it will run forever
        }/* Parent waits for foreground job to terminate */

        Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Block SIGCHLD */
        int st = (bg==0) ? FG : BG;
        addjob(jobs,pid,st,cmdline);
        Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
        if (!bg) {
            //because sigchld_handler was called above,it call waitpid,so don't call and circular wait wait
            waitfg(pid);
        }
        else
            printf("[%d] (%d) %s", pid2jid(pid),pid, cmdline);
    }
    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 command */
        exit(0);

    if (!strcmp(argv[0], "&")) /* Ignore singleton & */
        return 1; 
    
    if(!strcmp((argv[0]),"jobs"))/* jobs command */
    {
        listjobs(jobs);
        return 1;
    }

    if(!strcmp((argv[0]),"fg") || !strcmp((argv[0]),"bg"))/* bg/fg command */
    {
        do_bgfg(argv);
        return 1;
    }
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
    if(!argv[1]){
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    if (!isdigit(argv[1][0]) && argv[1][0] != '%') {            // Checks if the second argument is valid
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    struct job_t* myjob;
    if(argv[1][0]=='%'){//jid
        myjob = getjobjid(jobs,atoi(&argv[1][1]));
        if(!myjob){
            printf("%s: No such job\n", argv[1]);
            return;
        }
    }else{//pid
        myjob = getjobpid(jobs,atoi(argv[1]));
        if (!myjob) {                                 // Checks if the given PID is there
            printf("(%d): No such process\n", atoi(argv[1]));
            return;
        }
    }

    Kill(-myjob->pid,SIGCONT);//send continue signal 
    if(!strcmp(argv[0],"bg")){
        myjob->state = BG;
        printf("[%d] (%d) %s",myjob->jid,myjob->pid,myjob->cmdline);
    }else{
        myjob->state = FG;
        waitfg(myjob->pid);
    }

    return;
}

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
    while(fgpid(jobs))
        usleep(1000);//sleep one second
    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;
    sigset_t mask_all,prev;
    pid_t pid;
    int status;
    Sigfillset(&mask_all);
    while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED))>0){
        // WNOHANG | WUNTRACED return immediately
        if (WIFEXITED(status))  // normally exited,delete job
        {
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        else if (WIFSIGNALED(status))  //terminated by signal, delete job and print message
        {
            struct job_t* job = getjobpid(jobs, pid);
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
            deletejob(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        else  //stopped,change the status
        {
            struct job_t* job = getjobpid(jobs, pid);
            sigprocmask(SIG_BLOCK, &mask_all, &prev);
            printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
            job->state= ST;
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        //actually there is WIFCONTINUED,but we don't care about
    }
    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;
    pid_t fg = fgpid(jobs);
    if(fg){
        Kill(-fg,sig);
    }
    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;
    pid_t fg = fgpid(jobs);
    if(fg){
        Kill(-fg,sig);
    }
    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);
}

/******************************
 * my functions with error handling
 ******************************/

/*
 * fork error handling
 */
pid_t Fork(void)
{
    pid_t pid;

    if ((pid = fork()) < 0)
        unix_error("Fork error");
    return pid;
}

/*
 * execve error handling
 */
void Execve(const char *filename, char *const argv[], char *const environ[])
{
    if (execve(filename, argv, environ) < 0) {
        printf("%s: Command not found.\n", argv[0]);
        exit(0);
    }
}

/*
 * kill error handling
 */
void Kill(pid_t pid, int signum) 
{
    int kr;

    if ((kr = kill(pid, signum)) < 0)
        unix_error("Kill error");
    return;
}

/*
 * sigemptyset error handling
 */
void Sigemptyset(sigset_t *set)
{
    if(sigemptyset(set)<0)
        unix_error("Sigemptyset error");
    return;
}
/*
 * sigaddset error handling
 */
void Sigaddset(sigset_t *set,int sign)
{
    if(sigaddset(set,sign)<0)
        unix_error("Sigaddset error");
    return;
}

/*
 * sigprocmask error handling
 */ 
void Sigprocmask(int how, sigset_t *set, sigset_t *oldset)
{
    if(sigprocmask(how,set,oldset)<0)
        unix_error("Sigprocmask error");
    return;
}

/*
 * sigfillset error handling
 */
void Sigfillset(sigset_t *set)
{
    if(sigfillset(set)<0)
        unix_error("Sigfillset error");
    return;
}

/*
 * setpgid error handling
 */
void Setpgid(pid_t pid, pid_t pgid) {
    int rc;

    if ((rc = setpgid(pid, pgid)) < 0)
        unix_error("Setpgid error");
    return;
}

本文由博客一文多发平台 OpenWrite 发布!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK