实验分析
你要模拟一个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负责的工作.




Comments | NOTHING