ICS shelllab

发布于 2019-09-12  1183 次阅读


实验分析

你要模拟一个shell(程序名为tsh),已经给了一个shell的框架,包含struct job_t和jobs的维护函数.

目标:

1.tsh能够正确执行内建指令(quit,fg,bg, jobs);

2.tsh能够运行前台进程和后台进程,当输入ctrl-c或ctrl-z时能够停止/中断前台进程;

3.tsh能够监控前后台进程,出现各种特殊情况时(如后台任务异常退出)输出调试信息。

方法:

1.判断是否为内建指令并做相应处理;

2.若指令声明为前台,tsh fork子进程并execve;主进程调用waitfg函数等待其结束或暂停。输入ctrl-c和ctrl-z的处理逻辑为:

ctrl-c:
tsh: 输入ctrl-c => tsh.SIGINT_handler => waitfg

     (case 1) SIGCHLD signal => tsh.SIGCHLD_handler

foreground: SIGINT signal  =>  (case 1) terminate => send SIGCHLD to tsh

                               (case 2) ignore / ...

经测试知,只有当子进程真正被中断(case 1)时tsh才需要输出调试信息,和SIGINT是否调用无关.因此输出信息应放在SIGCHLD_handler里,同时在这里deletejob、reap fg process.

waitfg的退出设定为是否已经删除job.

ctrl-z:
tsh: 输出ctrl-z => tsh.SIGSTOP_handler => waitfg

     (case 1) ?signal by waitpid(WUNTRACED)

foreground: SIGSTOP signal => (case 1) stop => send signal?

                              (case 2) ignore / ...

由于SIGSTOP不能产生SIGCHLD,需要在waitfg中放waitpid(WUNTRACED) 同时追踪中断信号;由于可能是terminate,用WIFSTOPPED(state)加以确定.

waitfg的写法如下:

void waitfg(pid_t pid)
{
    pid_t q;
    int state = 0;
    struct job_t *p;
    while((p = getjobpid(jobs, pid)) != NULL && p->state != ST)
    {
        if((q = waitpid(pid, &state, WNOHANG | WUNTRACED)) > 0)
        {
            if(WIFSTOPPED(state)) printf("Job [%d] (%d) stopped by signal 20\n", pid2jid(pid), pid), p->state = ST;
            return;
        }
        sleep(1);
    }
}

3.具体操作在2已经大致介绍完了. 当子进程自行停止或中断时tsh也能输出信息.

重点&难点

1.tsh如何正确分析前台进程出现异常,以及怎样将处理好输出调试信息的逻辑;(见上)

一个keypoint是:前台进程正常结束和异常结束有区别,需要用WIFSIGNALED(state)或WIFEXITED(state)进一步分析这种区别.

另一个keypoint: 为避免不确定的等待将所有waitpid的options 加上WNOHANG.

2.sigmask:有可能在fork后tsh还未addjob而子进程已经执行完并调用deletejob了,而这显然是不合法的,应在fork附近加上sigprocmask相关函数,对SIGCHLD、SIGINT和SIGSTOP进行屏蔽。

具体写法如下:

        sigset_t mask, old_mask;
        sigemptyset(&mask);
        sigaddset(&mask, SIGINT);
        sigaddset(&mask, SIGSTOP);
        sigaddset(&mask, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask, &old_mask);
        ...
        sigprocmask(SIG_SETMASK, &old_mask, NULL);  

3.子进程调用execve函数的方法(from CSAPP):

environ为tsh执行开始就定义了的全局环境变量,直接使用。

            char* name = argv[0];
            if(execve(argv[0], argv, environ) < 0)
            {
                printf("%s: Command not found\n", name);
                exit(0);
            }

遇到的问题

trace**.txt文件中是什么:

以trace04为例,文本如下:

#
# trace04.txt - Run a background job.
#
/bin/echo -e tsh> ./myspin 1 \046
./myspin 1 &

\046就是&,第一句话实际是借助tsh打印出下一句要执行的话,-e参数表示将 \ +字符变成转义符; make test04执行效果如下(最后一句为调试信息):

./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (4114) ./myspin 1 &

这里有趣的地方在于:当把echo这一句话用shell执行时,shell会把>识别为重定向管道,从而将"tsh 1"输出到myspin文件,从而使得./myspin失效。本人初次尝试后对tshref不能正常运行迷惑不解,之后才明白其中原因。

tsh没有将其视为管道的原因在于tsh没有设置识别管道的能力,而传入的参数已经是字符串了("tsh>"),故bin/echo自动将其视为字符串处理。从这里也可以看出,管道逻辑的处理实际上是shell负责的工作.


WRYYYYYYYYY!