shelllab的一些思路

首先是eval函数:

void eval(char *cmdline) 
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;
    pid_t pid;
    sigset_t mask;
 
    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    if(argv[0] == NULL)
         return;
   
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, NULL);    //阻塞SIGCHLD信号
 
    if(!builtin_cmd(argv)){
        if((pid = fork()) == 0){
            sigprocmask(SIG_UNBLOCK, &mask, NULL);
            if(setpgid(0, 0) < 0)   //将当前进程组ID改为当前进程ID
                unix_error("eval: setgpid failed.\n");
            if(execve(argv[0], argv, environ) < 0 ){
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
 
        if(!bg)
            addjob(jobs, pid, FG, cmdline);
        else
            addjob(jobs, pid, BG, cmdline);
        sigprocmask(SIG_UNBLOCK, &mask, NULL);  //如果不阻塞SIGCHLD信号,可能会发生先deletejob再addjob
 
        if(!bg)
            waitfg(pid);
        else
            printf("[%d] (%d) %s\n", pid2jid(pid), pid, cmdline);
    }
    return;
}

可以参考书本P525 eval函数,但不同的是这里加了一个addjob的操作,所以需要阻塞信号,因为如果不阻塞,可能发生系统先调度子进程执行,子进程执行完后发送信号给父进程,父进程收到信号先deletejob再addjob,这样就会出错。

然后是builtin_cmd函数,这个函数内容比较简单,主要是调用do_bgfg函数:

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

然后是do_bgfg函数:

void do_bgfg(char **argv) 
{
    char *id = argv[1];
    struct job_t *job;
 
    int jobid;
    if(id[0] == '%')
        jobid = atoi(id+1);
    else
    	jobid = pid2jid(atoi(id));
    if((job = getjobjid(jobs, jobid)) == NULL){
        printf("Job is not exist.\n");
        return;
    }
    if(!strcmp(argv[0], "bg")){
        job->state = BG;
        kill(-1* job->pid, SIGCONT);    //*-1是发送给整个进程组
    }
    if(!strcmp(argv[0], "fg")){
        job->state = FG;
        kill(-1 * job->pid, SIGCONT);
        waitfg(job->pid);   //前台运行就等待他结束
    }
    return;
}

这里作业要求有点乱,只能根据书本P555要求一条一条来,首先拆分命令,第一个是bg或者fg,第二个是JID或者PID,首先统一把前面的全部换成JID,寻找任务返回指针,根据fg判断是前台运行还是后台运行。

waitfg很简单,就是等待前台任务执行完:

void waitfg(pid_t pid)
{
    while(pid == fgpid(jobs));
    return;
}

然后是SIGCHLD信号的处理,即当一个子进程停止或终止时需要使用的handler函数:

void sigchld_handler(int sig) 
{
    pid_t pid;
    int status, child_sig;
    while((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0 ){  //等待集合中的子进程有一个返回或终止,返回他的pid,否则返回零
        printf("Handling child process %d\n", (int)pid);
        if( WIFSTOPPED(status) )
            sigtstp_handler( WSTOPSIG(status) );
        else if( WIFSIGNALED(status) ) {
            child_sig = WTERMSIG(status);   //是什么信号导致中止
            if(child_sig == SIGINT)     //如果SIGINT处理这个信号
                sigint_handler(child_sig);
        }
        else    
            deletejob(jobs, pid);
    }
    return;
}

WUNTRACED | WNOHANG 在书本P517,子进程有一个停止或者终止,返回他的PID,有三种可能,一种是ctrl-z,一种是ctrl-c,一种是完成后返回,那么分别处理,前两种主要是处理函数在做,最后一种直接删除任务即可。

针对前两种,有两个处理函数,首先是sigint_handler:

void sigint_handler(int sig) 
{
    pid_t pid = fgpid(jobs);    //前台任务是什么
    int jid = pid2jid(pid);
    if(pid != 0){
        printf("Job [%d] terminated by SIGINT.\n", jid);
        deletejob(jobs, pid);   //结束他
        kill(-pid, sig);
    }
    return;
}

按下ctrl-c就是要前台任务结束执行,那么只要deletejob,另外还要将信号转发给进程组中所有进程,所以要写一句kill(-pid, sig),-pid就是进程组中所有进程,因为他可能在执行过程中有子进程,要一起杀死。

然后是sigtstp_handler函数:

void sigtstp_handler(int sig) 
{
    pid_t  pid = fgpid(jobs);
    int jid = pid2jid(pid);
 
    if(pid != 0){
        printf("Job[%d] stopped by SIGTSTP", jid);
        (*getjobpid(jobs, pid)).state = ST;
        kill(-pid, sig);
    }
    return;
}

是类似的,只不过不能deletejob,而是把状态改为ST。

这么做下来,test到08的时候就出现了问题,问题大致是前台的还没有结束就有新的命令输入进来这样的问题,另外有时候jobs指令也会出错,因此我参考了博客:https://blog.csdn.net/KopM1/article/details/73307213?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control

前三个函数不做改动与博客中的大致相同,不做改动。

后面函数改动如下:

void waitfg(pid_t pid)
{
    int status;
    sigset_t mask_all,prev_all;

    sigfillset(&mask_all);

    waitpid(pid,&status,WUNTRACED);  
    struct job_t *job = getjobpid(jobs,pid);

    if(WIFSIGNALED(status)){        
        printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
    }
    if(WIFSTOPPED(status)){         
        printf("Job [%d] (%d) stopped by signal 20\n",job->jid,job->pid);
        job->state = ST;
        return;
    }

    if(!WIFSTOPPED(status)) {
        sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        deletejob(jobs, pid);        
        sigprocmask(SIG_SETMASK, &prev_all, NULL);  //在deletejob时阻塞信号
        return;
    }
}

void sigchld_handler(int sig)
{
    sigset_t mask_all,prev_all;

    sigfillset(&mask_all);
    pid_t pid;

    while((pid = waitpid(-1,NULL,WNOHANG))!=0){
        if(pid == -1)
            break;      
        sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
        deletejob(jobs,pid);
        sigprocmask(SIG_SETMASK,&prev_all,NULL);    
    }
    return;
}

void sigint_handler(int sig)
{
    pid_t pid;

    pid = fgpid(jobs);

    if(pid!=0) {
        kill(-pid, SIGINT);
    }

    return;
}


void sigtstp_handler(int sig)
{
    pid_t pid;

    pid = fgpid(jobs);

    if(pid!=0) {
        kill(-pid,SIGTSTP);
    }
    return;
}

sigtstp_handler和sigint_handler里面的内容变少了,这符合handler函数要尽量短小的要求,原先的函数中有printf,delete这样的函数存在,有风险!这些操作全都应该交给waitfg做,他不是一个handler函数,所以安全。另外,由于在子进程终止时候deletejob工作必须交给sigchld_handler做,所以这里deletejob使用了阻塞信号保证他在执行过程中不会受到干扰。

这样所有的make test 都和make rtest 一样了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值