• shell lab


    实现几个函数,使得该shell能处理前后台运行程序、能够处理ctrl+z、ctrl+c等信号。
    实现这几个函数:
    eval: Main routine that parses and interprets the command line. [70 lines] builtin cmd: Recognizes and interprets the built-in commands: quit, fg, bg, and jobs. [25 lines] do bgfg: Implements the bg and fg built-in commands. [50 lines] waitfg: Waits for a foreground job to complete. [20 lines] sigchld handler: Catches SIGCHILD signals. 80 lines] sigint handler: Catches SIGINT (ctrl-c) signals. [15 lines] sigtstp handler: Catches SIGTSTP (ctrl-z) signals. [15 lines]

      

    eval: 主要功能是解析cmdline,并且运行. [70 lines]
    builtin_cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs. [25lines]
    do_bgfg: 实现bg和fg命令. [50 lines] 
    waitfg: 实现等待前台程序运行结束. [20 lines]
    sigchld_handler: 响应SIGCHLD. 80 lines]
    sigint_handler: 响应 SIGINT (ctrl-c) 信号. [15 lines] 
    sigtstp_handler: 响应 SIGTSTP (ctrl-z) 信号. [15 lines]

    注意:

    使用跟踪文件来指导shell的开发。从trace01开始,确保shell生成与参考shell相同的输出。然后转到文件trace02.txt,这样继续往下。

    waitpid、kill、fork、execve、setpgid和sigprocmask函数非常有用。

    wuntracking和WNOHANG选项对于waitpid也很有用。

    在实现信号处理程序时,确保将SIGINT和SIGTSTP信号发送到整个前台进程组,在kill函数的参数中使用“-pid”而不是“pid”。

    sdriver.pl程序测试这个错误。

    任务中比较棘手的部分之一是决定waitfg之间的工作分配和sigchld处理函数。采用以下方法:

      -在waitfg中,使用sleep函数的一个繁忙循环。

      -在sigchld处理程序中,只使用一个对waitpid的调用。

    虽然可以使用其他解决方案,例如在waitfg和sigchld处理程序中调用waitpid。

    在eval中,父进程必须先使用sigprocmask来阻止SIGCHLD信号,然后再派生子进程,

    然后解压这些信号,再次使用sigprocmask,在它将子节点添加到作业列表by之后

    调用addjob。由于孩子继承了他们父母的阻塞向量,孩子必须在SIGCHLD执行新程序之前解除它的信号阻塞。父进程需要以这种方式阻塞SIGCHLD信号,以避免其中的竞态条件

    sigchld_handler在父进程调用addjob之前捕获子进程(因此从作业列表中删除)。

    诸如more、less、vi和emacs之类的程序,不考虑从shell中运行这些程序。专注简单的基于文本的程序,如/bin/ls,/bin/ps,/bin/echo.

    当从标准Unix shell运行你的shell时,您的shell正在前台进程组中运行如果你的shell随后创建了一个子进程,默认情况下,该子进程也是前台进程组。

     因为按ctrl-c会向前台组的每个进程发送一个SIGINT,键入ctrl-c将向你的shell以及你的shell的每个子进程发送一个SIGINT。这显然是不正确的。

    下面是解决方案:

      在fork之后,但在execve之前,子进程应该调用setpgid(0,0),它将子进程放入一个新的进程组中,该进程组的组ID与孩子的PID相同。

    这确保前台进程组中只有一个进程,即你的shell。

    当您键入ctrl-c时,shell应该捕获生成的SIGINT,然后转发它到适当的前台作业

     

    1 eval()函数的原型可以在P503找到

    主要功能是解析cmdline,并且运行。

     1 void eval(char *cmdline)
     2 {
     3     char *argv[MAXLINE];    /*argument list of execve()*/
     4     char buf[MAXLINE];      /*hold modified commend line*/
     5     int bg;                 /*should the job run in bg or fg?*/
     6     pid_t pid;
     7     sigset_t mask;          /*mask for signal*/
     8 
     9     stpcpy(buf,cmdline);
    10     bg = parseline(buf,argv);
    11 
    12     if(argv[0]==NULL){
    13         return;     /*ignore empty line*/
    14     }
    15 
    16     if(!builtin_cmd(argv)){                         /*not a build in cmd*/
    17         Sigemptyset(&mask);
    18         Sigaddset(&mask,SIGCHLD);
    19         Sigprocmask(SIG_BLOCK,&mask,NULL);           /*block the SIGCHLD signal*/
    20 
    21         if((pid = Fork())==0)
    22         {
    23             Sigprocmask(SIG_UNBLOCK,&mask,NULL);     /*unblock the SIGCHLD signal in child*/
    24             Setpgid(0,0);                            /*puts the child in a new process group*/
    25 
    26             if(execve(argv[0],argv,environ)<0){
    27                 printf("%s: Command not found
    ",argv[0]);
    28                 exit(0);
    29             }
    30         }
    31 
    32         addjob(jobs, pid, bg?BG:FG,cmdline);        /*add job into jobs*/
    33         Sigprocmask(SIG_UNBLOCK,&mask,NULL);        /*unblock the SIGCHLD signal in parent*/
    34 
    35         bg ? printf("[%d] (%d) %s", pid2jid(pid), pid,cmdline):waitfg(pid); /*do in background or foreground*/
    36     }
    37     return;
    38 }

    2 builtin_cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs.

    辨别是否是builtin命令,是就执行;否则就返回。

    参考p503

    int builtin_cmd(char **argv)
    {
        if(!strcmp(argv[0],"quit")){
            exit(0);
        }
        if(!strcmp(argv[0],"&")){
            return 1;
        }
        if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg"){
            do_bgfg(argv);
            return 1;
        }
        if(!strcmp(argv[0],"jobs")){
            listjobs(jobs);
            return 1;
        }
        return 0;     //非builtin命令
    }

    3 do_bgfg: 实现bg和fg命令. 

      

    void do_bgfg(char **argv)
    {
        pid_t pid;
        struct job_t *job;
        char *id = argv[1];
    
        if(id==NULL){       //没有参数
            printf("%s command requires PID or %%jobid argument
    ",argv[0]);
            return;
        }
    
        if(id[0]=='%'){     //%number,number表示任务id
            int jid = atoi(&id[1]);
            job = getjobjid(jobs,jid);//取到job
            if(job==NULL){
                printf("%%%d: No such job
    ",jid);
                return;
            }
        }else if(isdigit(id[0])){               //number,表示进程id
            pid = atoi(id);
            job = getjobpid(jobs,pid);//取到job
            if(job==NULL){
                printf("(%d): No such process
    ",pid);
                return ;
            }
        }else{
            printf("%s: argument must be a PID or %%jobid
    ", argv[0]);
            return;
        }
    
        Kill(-(job->pid),SIGCONT); //发送SIGCONT 
    
        if(!strcmp(argv[0],"bg")){ //设置状态,做出操作
            job->state = BG;
            printf("[%d] (%d) %s", job->jid, job->pid,job->cmdline);
        }else{
            job->state = FG;
            waitfg(job->pid);
        }
        return;
    }

    4 waitfg: 实现等待前台程序运行结束.

    void waitfg(pid_t pid){
        while(pid == fgpid(jobs)){//只要当前进程在前台就等待
            sleep(0);
        }
        return ;
    }

    5 sigchld_handler: 响应SIGCHLD. 80 lines]

    当子进程停止或终止时向父进程发送信号的处理函数,要循环进行waitpid操作,尽量多回收子进程

    void sigchld_handler(int sig)
    {
        pid_t pid;
        int status;
        while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED))>0){
            if(WIFEXITED(status)){  //正常结束,删掉
                deletejob(jobs,pid);
            }
            if(WIFSIGNALED(status)){//僵尸进程,删掉
                printf("Job [%d] (%d) terminated by signal %d
    ",pid2jid(pid),pid,WTERMSIG(status));
                deletejob(jobs,pid);
            }
            if(WIFSTOPPED(status)){//是个停止进程,不需要回收
                printf("Job [%d] (%d) stopped by signal %d
    ",pid2jid(pid),pid,WSTOPSIG(status));
                struct job_t *job = getjobpid(jobs,pid);
                if(job !=NULL )job->state = ST;//设置状态为ST
            }
        }
        return;
    }

    6 sigint_handler: 响应 SIGINT (ctrl-c) 信号. 

    接收到应强行终止前台的进程。

    void sigint_handler(int sig)
    {
        pid_t pid = fgpid(jobs);//获取前台进程pid
        if(pid != 0){
            Kill(-pid,SIGINT);//删掉前台进程组
        }
        return;
    }

    7 sigtstp_handler: 响应 SIGTSTP (ctrl-z) 信号. 

    void sigtstp_handler(int sig)
    {
        pid_t pid = fgpid(jobs);//获取前台进程pid
    
        if(pid != 0 ){
            struct job_t *job = getjobpid(jobs,pid);//得到作业
            if(job->state == ST){  //已经终止状态,返回
                return;
            }else{
                Kill(-pid,SIGTSTP);
            }
        }
        return;
    }
  • 相关阅读:
    操作系统--精髓与设计原理(第八版)第六章复习题答案
    操作系统--精髓与设计原理(第八版)第五章复习题答案
    操作系统--精髓与设计原理(第八版)第四章复习题答案
    操作系统--精髓与设计原理(第八版)第三章复习题答案
    操作系统--精髓与设计原理(第八版)第二章复习题答案
    操作系统--精髓与设计原理(第八版)第一章复习题答案
    跟着webbench学习C++网络编程(二)
    跟着webbench学C++网络编程(一)
    每日一问11:C++程序的内存格局
    Redis系列三:redis支持的数据类型
  • 原文地址:https://www.cnblogs.com/gudygudy/p/10459433.html
Copyright © 2020-2023  润新知