Loading

[已完成+附代码]CS:APP:Lab6-ShellLab

由于我的第五个实验的partB部分一直出问题。而且修了好久没解决先不管了

这个实验建议一定要认真读完csapp全书的第八章。不然可能会毫无思路。千万不要上来直接做。

0. 环境配置和实验下载

利用docker配置Linux环境。无论你是mac还是windows都可以轻松搞定 https://www.cnblogs.com/JayL-zxl/p/14286789.html

1. 实验目的

你的任务为补全tsh.c里面的一些空缺的函数。

eval: 解析命令行 [70 lines]

builtin cmd: 识别命令是否为内置命令: quit, fg, bg, and jobs. [25 lines]

do bgfg:实现bgfg命令. [50 lines]

waitfg: 等待前台程序完成 [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]

每一次你修改tsh.c函数之后都要重新编译它,然后运行你的shell

make
./tsh

writeup里面给了非常多的提示。这里简单给大家列举一下

  1. 要详细阅读书本的第八章内容
  2. 如果用户输入ctrl-c (ctrl-z),那么SIGINT (SIGTSTP)信号应该被送给每一个在前台进程组中的进程,如果没有进程,那么这两个信号应该不起作用。
  3. 如果一个命令以“&”结尾,那么tsh应该将它们放在后台运行,否则就放在前台运行(并等待它的结束)
  4. tsh应该回收(reap)所有僵尸进程,如果一个工作是因为收到了一个它没有捕获的(没有按照信号处理函数)而终止的,那么tsh应该输出这个工作的PID和这个信号的相关描述。
  5. tsh应该支持如下的内置命令
– The quit command terminates the shell.
– The jobs command lists all background jobs.
– The bg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
the background. The <job> argument can be either a PID or a JID.
– The fg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
the foreground. The <job> argument can be either a PID or a JID.

书上有展示过一个非常简单的Shell程序这里先复习一下。



当然上面的程序并没有回收后台运行的程序。而且它非常不完整,但足够帮助我们理解整个shell的基本逻辑了

1.1 信号简介



当一个进程收到信号之后就会调用信号处理程序。这个也是我们任务之一。

信号--->进程---->信号处理程序---执行------结果

当然关于信号有很多可以说的。比如信号不会排队啊。信号处理程序的阻塞,以及并发情况如何处理啊。这些请大家仔细阅读课本第八章。

2. 实验开始

好了下面开始实验了。首先根据writeup我们知道我们要依次根据test文件进行测试。就是说我们的make test0x的执行结果要和make rtest0x的参考结果一样就可以了。

这里我们发现对于test02的测试就出现了问题。这里去看一下trace02看看到底发生了什么。

这里有一条quit指令。不通过的原因就是我们的tsh程序还没有实现对于这个命令的处理。所以它不会退出而会停在这里。
下面正式开始我们的实验啦

由于我们没有办法预估信号到达的顺序。因此对于并发编程的处理就变得非常的重要。

考虑下面这种情况


因此我们要考虑这种情况。在父进程addjob之前先把SIGCHLD信号阻塞掉。

1. eval函数的实现

void eval(char *cmdline) 
{
    char *argv[MAXLINE];    /*Argument list execve()*/
    char buf[MAXLINE];      /*Hold modified commend line*/
    int bg;                 /*Should the job run in bg or fg?*/
    pid_t pid;
    int state;
    sigset_t mask_all, mask_one, prev_one;
    strcpy(buf,cmdline);
    bg=parseline(cmdline,argv);
    if(argv[0]==0){
        return ;//ignore empty line
    }
    if(!builtin_cmd(argv)){
        sigfillset(&mask_all);
        sigemptyset(&mask_one);
        sigaddset(&mask_one, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //Block SIGCHLD

        if((pid==fork())==0){
            sigprocmask(SIG_SETMASK,&prev_one,NULL);//UnBlock SIGCHLD
            if (setpgid(0, 0) < 0)
            {
                perror("SETPGID ERROR");
                exit(0);
            }
            if (execve(argv[0], argv, environ) < 0) //if execve error return 0
            {
                printf("%s: Command not found\n", argv[0]);
                exit(0);
            }
        }
      else{
        state = bg ? BG : FG;
        sigprocmask(SIG_BLOCK, &mask_all, NULL); //parent process
        addjob(jobs, pid, state, cmdline);
        sigprocmask(SIG_SETMASK, &prev_one, NULL); //unblock SIGCHLD
      }
        bg?printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline):waitfg(pid);
    }
    return;
}

基本参考了书上p525shell代码和p543的代码。考虑了上述提到的并发访问问题。

2. builtin_cmd的实现

如果用户输入的是built-in command则立即执行否则返回0

一共需要支持四个内置命令

分别处理即可

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;     /* not a builtin command */
}

3. do_bgfg的实现

Execute the builtin bg and fg commands

每个job都可以由进程ID(PID)或job ID(JID)标识,该ID是一个正整数tsh分配。 JID应该在命令行上以前缀“%”表示。 例如,“%5”
表示JID 5,“ 5”表示PID5。(我们已为您提供了所需的所有例程处理工作清单。)

tsh> fg %1
Job [1] (9723) stopped by signal 20
kill(pid,signal)的规则
if (pid < 0) 则向|pid|的组中全部发送信号
if(pid > 0) 则就向单个进程发送
if(pid=0) 信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
void do_bgfg(char **argv) 
{
    struct  job_t *job;
    int id;
    if(argv[1]==NULL){
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return ;
    }
    if (sscanf(argv[1], "%%%d", &id) > 0)
    {
        job = getjobjid(jobs, id);
        if (job == NULL)
        {
            printf("%%%d: No such job\n", id);
            return ;
        }
    }
    else if (sscanf(argv[1], "%d", &id) > 0)
    {
        job = getjobpid(jobs, id);
        if (job == NULL)
        {
            printf("(%d): No such process\n", id);
            return ;
        }
    }
    else
    {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    if(!strcmp(argv[0], "bg"))
    {
        kill(-(job->pid), SIGCONT);
        job->state = BG;
        printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
    }
    else
    {
        kill(-(job->pid), SIGCONT);
        job->state = FG;
        waitfg(job->pid);
    }


    return;
}

3. waitfg 函数实现

Block until process pid is no longer the foreground process
void waitfg(pid_t pid)
{
    sigset_t mask;
    sigemptyset(&mask);
    while (fgpid(jobs) > 0)
        sigsuspend(&mask);
    return;
}

这里注意一下

4. sigchld_handler的实现

The kernel sends a SIGCHLD to the shell whenever
*     a child job terminates (becomes a zombie), 
*     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.  

关于waitpid参数的全介绍

void sigchld_handler(int sig) 
{

        int olderrno = errno;
        pid_t pid;
        int status;
        sigset_t mask_all, prev;

        sigfillset(&mask_all);
        while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
        {
            if (WIFEXITED(status)) //正常
            {
                sigprocmask(SIG_BLOCK, &mask_all, &prev);
                deletejob(jobs, pid);
                sigprocmask(SIG_SETMASK, &prev, NULL);
            }
            else if (WIFSIGNALED(status)) 
            {
                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;

}

5. 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 pid=fgpid(jobs);
    int jid=pid2jid(pid);
    sigset_t mask_all, prev;
    sigfillset(&mask_all);
    if(pid!=0){
        sigprocmask(SIG_BLOCK, &mask_all, &prev);
        printf("Job [%d] terminated by SIGINT.\n",jid);
        deletejob(jobs,pid);
        sigprocmask(SIG_SETMASK, &prev, NULL);
        kill(-pid,sig);
    }
    return;
}

6. sigstp_handler的实现

The kernel sends a SIGSTP to the shell whenver 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 pid=fgpid(jobs);
    int jid=pid2jid(pid);
    sigset_t mask_all, prev;
    sigfillset(&mask_all);
    if(pid!=0){
        sigprocmask(SIG_BLOCK, &mask_all, &prev);
        printf("Job [%d] stopped by SIGSTP.\n",jid);
        (*getjobpid(jobs,pid)).state = ST;;
        sigprocmask(SIG_SETMASK, &prev, NULL);
        kill(-pid,sig);
    }
    return;
}
posted @ 2021-02-07 19:07  周小伦  阅读(518)  评论(0编辑  收藏  举报