CSAPP:shell lab

这次的实验要求我们实现一个简化版的shell,需要能够实现子程序的前台与后台运行以及切换,同时实现几条内建命令。
需要我们实现的内建命令包括以下4个:

  • quit: 退出shell
  • jobs: 打印子进程列表与运行状态
  • fg: 切换进程到前台运行
  • bg: 切换进程到后台运行


**实验之前一定要认真读[手册](http://csapp.cs.cmu.edu/3e/shlab.pdf)和课本**,在进行实验的时候也要时刻翻看课本,想清楚每一步之的前后关联。下面放上代码与思路。

eval()

void eval(char *cmdline)
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;
    pid_t pid;
    sigset_t mask_one, mask_all, pre_mask;
    sigfillset(&mask_all);
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD);
    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    if (argv[0] == NULL)
        return;
    if (!builtin_cmd(argv))
    {
        sigprocmask(SIG_BLOCK, &mask_one, &pre_mask);//阻断SIGCHLD
        if ((pid = fork()) == 0)
        {
            setpgid(0, 0); //*******很重要
            sigprocmask(SIG_SETMASK, &pre_mask, NULL);
            if (execve(argv[0], argv, environ) < 0)
            {
                printf("%s: Command not found
", argv[0]);
                exit(0);
            }
        }
        sigprocmask(SIG_BLOCK, &mask_all, NULL);//addjob涉及全局变量访问
        if (bg)
            addjob(jobs, pid, BG, buf);
        else
            addjob(jobs, pid, FG, buf);
        if (!bg)
            waitfg(pid);
        else
        {
            struct job_t *curr_bgmask = getjobpid(jobs, pid);
            printf("[%d] (%d) %s", curr_bgmask->jid, curr_bgmask->pid, curr_bgmask->cmdline);
        }
        sigprocmask(SIG_SETMASK, &pre_mask, NULL);
    }
    return;
}

这个函数本身比较简单,书上的代码拿来改改就能用,但是有一个特别重要的问题需要注意

问题

大体意思就是在Unix shell上运行我们自己的shell时,需要在创建出一个子进程之后调用setpgid(0, 0) 将他们放到一个新的进程组,否则我们的ctrl-cctrl-z 会把信号发送到包括我们的shell在内的全部进程中,就是因为没注意这个使得我的shell产生的很多迷幻的错误,以至于调试很久却不知道原因(其实只能怪自己没认真看手册)。

waitfg()

对于waitfg()函数,我想过几个实行方案:

waitfg1()
# This is a buggy code.
void waitfg(pid_t pid)
{
    int status;
    if (waitpid(pid,&status,0) < 0)
        unix_error("waitfg: waitpid error
");
    printf("wait %d ok
",pid);
    return;
}

void sigchld_handler(int sig)
{
    pid_t pid;  
    int old_errno = errno;
    while ((pid = waitpid(-1, NULL, 0)) > 0) //这里必须是while,书上讲的很明白了
	    printf("Handler reaped child %d
", (int)pid);
    if (errno != ECHILD)
	    unix_error("waitpid error");
    errno = old_errno;
    return;
}

代码是之前的,省略了一些不相关的细节。
我们直接在waitfg里面调用 waitpid 函数,但是这样做有个很明显的问题,就是在回收后台进程时,如果在sigchld_handler 函数的执行阶段前台进程结束了,会被一并回收掉,从而导致waitfg 中的waitpid 回收不到前台进程,从而产生一个错误。

waitfg2()
# This is a buggy code.
void waitfg(pid_t pid)
{
    int status;
    sigset_t mask_chld,pre_mask;
    sigemptyset(&mask_chld);
    sigaddset(&mask_chld,SIGCHLD);
    sigprocmask(SIG_SETMASK,&mask_chld,&pre_mask);
    if (waitpid(pid,&status,0) < 0)
        unix_error("waitfg: waitpid error
");
    printf("wait %d ok
",pid);
    sigprocmask(SIG_SETMASK,&pre_mask,NULL);
    return;
}

加入我们再waitfg中加入对SIGCHLD信号的阻塞,这样可以保证前台进程能被正确回收,但是问题在于我们执行前台进程时不会处理SIGCHLD信号,这样子进程就不能被回收,如果前台进程一直运行,就会导致后台进程退出后全部变成僵死进程,浪费系统资源。
所以我们最终还是要按照书上的思路来实现

waitfg()
void waitfg(pid_t pid)
{
    sigset_t mask_empty, mask_all, mask_pre;
    sigemptyset(&mask_empty);
    sigfillset(&mask_all);
    sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //fgpid涉及全局变量的访问
    while (pid == fgpid(jobs))
        sigsuspend(&mask_empty);
    sigprocmask(SIG_SETMASK, &mask_pre, NULL);
    return;
}

sigchld_handler()

void sigchld_handler(int sig)
{
    pid_t pid;
    int status;
    int old_errno = errno;
    sigset_t mask_empty, mask_all, mask_pre;
    sigemptyset(&mask_empty);
    sigfillset(&mask_all);
    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
    {
        sigprocmask(SIG_BLOCK, &mask_all, &mask_pre);
        if (WIFSTOPPED(status)) //暂停
        {
            struct job_t *job = getjobpid(jobs, pid);
            job->state = ST;
            printf("Job [%d] (%d) stopped by signal %d
",job->jid, pid, WSTOPSIG(status));
        }
        else
        {
            if (WIFSIGNALED(status)) //被信号退出
            {
                struct job_t *job = getjobpid(jobs, pid);
                printf("Job [%d] (%d) terminated by signal %d
", job->jid, job->pid, WTERMSIG(status));
            } 
            deletejob(jobs, pid);
        }
        sigprocmask(SIG_SETMASK, &mask_pre, NULL);
    }
    errno = old_errno;
    return;
}

有了前面的基础,这个函数也很简单了。

sigint_handler() 与 sigtstp_handler()

void sigint_handler(int sig)
{
    pid_t pid;
    int old_errno = errno;
    if ((pid = fgpid(jobs)) != 0)
    {
        kill(-pid, SIGINT);
    }
    errno = old_errno;
    return;
}

void sigtstp_handler(int sig)
{
    pid_t pid;
    int old_errno = errno;
    pid = fgpid(jobs);
    if (pid)
    {
        kill(-pid, sig);
    }
    errno = old_errno;
    return;
}

这两个也很简单

builtin_cmd()

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)
{
    struct job_t *job;
    if(argv[1] == NULL){
        printf("%s command requires PID or %%jobid argument
",argv[0]);
        return;
    }
    char * idstring = argv[1];
    int isjid = 0;
    int id;
    if(idstring[0]=='%'){   //以jobid作为目标
        isjid = 1;
        id = atoi(idstring+1);
        if(!id){
            printf("%s: argument must be a PID or %%jobid
",argv[0]);
            return;
        }
    }
    else{   //以pid作为目标
        id = atoi(idstring);
        if(!id){
            printf("%s: argument must be a PID or %%jobid
",argv[0]);
            return;
        }
    }
    sigset_t mask_empty, mask_all, mask_pre;
    sigemptyset(&mask_empty);
    sigfillset(&mask_all);
    sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //与job相关操作涉及全局变量的访问
    if (isjid)  //寻找目标job
    {
        job = getjobjid(jobs, id);
        if (!job){
            printf("%%%d: No such job
", id);
            sigprocmask(SIG_SETMASK, &mask_pre, NULL);
            return;
        }
    }
    else 
    {
        job = getjobpid(jobs, id);
        if (!job){
            printf("(%d): No such process
", id);
            sigprocmask(SIG_SETMASK, &mask_pre, NULL);
            return;
        }
    }
    pid_t pid = job->pid;
    if (!strcmp(argv[0], "bg")) //bg fg切换
    {
        job->state = BG;
        printf("[%d] (%d) %s",job->jid,job->pid,job->cmdline);
        kill(-pid, SIGCONT);
    }
    else if (!strcmp(argv[0], "fg"))
    {
        job->state = FG;
        kill(-pid, SIGCONT);
        sigprocmask(SIG_SETMASK, &mask_pre, NULL);
        waitfg(pid);
        return;
    }
    sigprocmask(SIG_SETMASK, &mask_pre, NULL);
    return;
}

主要是要对参数是否符合要求进行判断。

完整代码

tsh.c

原博文地址,欢迎大家访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值