目标

补全tsh.c中剩余的代码:

  • void eval(char *cmdline):解析并执行命令。
  • int builtin_cmd(char **argv):检测命令是否为内置命令quitfgbgjobs
  • void do_bgfg(char **argv):实现bgfg命令。
  • void waitfg(pid_t pid):等待前台命令执行完成。
  • void sigchld_handler(int sig):处理SIGCHLD信号,即子进程停止或终止。
  • void sigint_handler(int sig):处理SIGINT信号,即来自键盘的中断ctrl-c
  • void sigtstp_handler(int sig):处理SIGTSTP信号,即终端停止信号ctrl-z

使用make testn用来测试你编写的shell执行第n组测试数据的输出。

使用make rtestn用来测试参考shell程序第n组测试数据的输出(共16组测试数据)。

tshref.out包含参考shell程序的所有测试数据的输出结果,先看完该文件了解命令格式在开始编码。

说明

可用辅助函数:

  • int parseline(const char *cmdline,char **argv):获取参数列表char **argv,返回是否为后台运行命令(true)。
  • 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信号。

eval

  • 为避免子进程在未加入到jobs链表中就发送信号(SIGINTSIGCHLDSIGTSTP)去处理jobs链表(竞争),在子进程创建前需要将SIGCHLD阻塞,在加入jobs链表后解锁该阻塞。
  • fork后的子进程会继承父进程的信号屏蔽字并且在exec后仍会继承,所以需要在执行可执行文件前复原信号屏蔽字。
  • fork后的子进程会继承父进程的信号处理设置,而exec后不会继承。
  • 参考《深入理解计算机第三版》p543,如何解决竞争问题。
void eval(char *cmdline) 
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg; 
    pid_t pid;

    strcpy(buf,cmdline);
    bg = parseline(buf,argv);
    if(argv[0] == NULL)
        return;
    if(!builtin_cmd(argv)){
        sigset_t mask_all,mask_one,prev;
        Sigfillset(&mask_all);
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one,SIGCHLD);

        Sigprocmask(SIG_BLOCK,&mask_one,&prev);
        if((pid=Fork()) == 0){ 
            setpgid(0,0);
            Sigprocmask(SIG_SETMASK,&prev,NULL);
            if(execve(argv[0],argv,environ) < 0){ 
                printf("%s: Command not found.\n",argv[0]);
                exit(0);
            }   
        }   
        int state = bg ? BG:FG;
        //parent
        Sigprocmask(SIG_BLOCK,&mask_all,NULL);
        addjob(jobs,pid,state,cmdline);
        Sigprocmask(SIG_SETMASK,&prev,NULL);

        if(!bg){
            waitfg(pid);
        }else{
            printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
        }   
    }      
    return;
}

builtin_cmd

  • 参考《深入理解计算机第三版》p525
int builtin_cmd(char **argv)
{
    if(!strcmp(argv[0],"quit"))
        exit(0);
    else if(!strcmp(argv[0],"jobs")){
        listjobs(jobs);
        return 1;
    }else if(!strcmp(argv[0],"bg") || !strcmp(argv[0],"fg")){
        do_bgfg(argv);
        return 1;
    }
    return 0;     /* not a builtin command */
}

do_bgfg

void do_bgfg(char **argv)
{
    if(argv[1] == NULL){
        printf("%s command requires PID or %%jobid argument\n",argv[0]);
        return;
    }
    int bg = !strcmp(argv[0],"bg");
    struct job_t *job_ptr;
    pid_t pid;
    int jid;
    if(sscanf(argv[1],"%d",&pid) > 0){
        // pid
        job_ptr = getjobpid(jobs,pid);
        if(job_ptr == NULL || job_ptr->state == UNDEF){
            printf("(%d): No such process\n",pid);
            return;
        }
    }else if(sscanf(argv[1],"%%%d",&jid) > 0){
        // jid
        job_ptr = getjobjid(jobs,jid);
        if(job_ptr == NULL || job_ptr->state == UNDEF){
            printf("%%%d: No such job\n",jid);
            return;
        }
    }else{
        printf("%s: argument must be a PID or %%jobid\n",argv[0]);
        return;
    }
    // get the job_ptr;
    if(bg){
        printf("[%d] (%d) %s",job_ptr->jid,job_ptr->pid,job_ptr->cmdline);
        job_ptr->state = BG;
        kill(-job_ptr->pid,SIGCONT);
    }else{
        // "fg"
        job_ptr->state = FG;
        kill(-job_ptr->pid,SIGCONT);
        waitfg(job_ptr->pid);
    }
    return;
}

waitfg

  • 使用循环检查,简单实现。
  • 参考《深入理解计算机第三版》p545,显示等待信号。
void waitfg(pid_t pid)
{
    while(pid == fgpid(jobs))
        sleep(1);
    return;
}

sigchld_handler

  • 参考《深入理解计算机第三版》p539,如何回收尽可能多的僵尸子进程。
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){
        if(WIFEXITED(status)){
            // normally exit
            Sigprocmask(SIG_BLOCK,&mask_all,&prev);
            deletejob(jobs,pid);
            Sigprocmask(SIG_SETMASK,&prev,NULL);
        }else if(WIFSIGNALED(status)){
            // exit by signal
            struct job_t *job_ptr = getjobpid(jobs,pid);
            Sigprocmask(SIG_BLOCK,&mask_all,&prev);
            printf("Job [%d] (%d) terminated by signal %d\n",job_ptr->jid,job_ptr->pid,WTERMSIG(status));
            deletejob(jobs,pid);
            Sigprocmask(SIG_SETMASK,&prev,NULL);
        }else{ // stop
            struct job_t *job_ptr = getjobpid(jobs,pid);
            Sigprocmask(SIG_BLOCK,&mask_all,&prev);
            printf("Job [%d] (%d) stopped by signal %d\n",job_ptr->jid,job_ptr->pid,WSTOPSIG(status));
            job_ptr->state= ST;
            Sigprocmask(SIG_SETMASK,&prev,NULL);
        }
    }
    errno = olderrno;
    return;
}

sigint_handler

  • 最后两个函数,均向子进程发送信号。
void sigint_handler(int sig)
{
    int olderrno = errno;
    pid_t pid = fgpid(jobs);
    if(pid != 0){
        kill(-pid,SIGINT);
    }
    errno = olderrno;
    return;
}

sigtstp_handler

void sigtstp_handler(int sig)
{
    int olderrno = errno;
    pid_t pid = fgpid(jobs);
    if(pid != 0){
        kill(-pid,SIGTSTP);
    }
    errno = olderrno;
    return;
}
最后修改:2022 年 04 月 09 日
如果觉得我的文章对你有用,请随意赞赏