实验准备
仔细阅读 CS:APP 第八章,大部分代码都能在书上找到
当然,你还要学会去查手册与
Write up
先写一个 check.zsh
文件来自动执行与判分:
for x in 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 make test$x >> tsh.out
wait
可以仔细看
tshref.out
的输出,一些错误处理应该输出的语句就是test14
eval
在 CS:APP 课本上实际上已经给出了蓝本,我们将此框架照写下来:
void eval(char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int bg; pid_t pid; sigset_t mask_all, mask_one, prev_one;
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, &prev_one); if ((pid = fork()) == 0) { sigprocmask(SIG_SETMASK, &prev_one, NULL); setpgid(0, 0); if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found\n", argv[0]); } exit(0); } sigprocmask(SIG_BLOCK, &mask_all, NULL); addjob(jobs, pid, bg ? BG : FG, cmdline); sigprocmask(SIG_SETMASK, &mask_one, NULL); if (!bg) { waitfg(pid); } else printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline); sigprocmask(SIG_SETMASK, &prev_one, NULL); }
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; } else if (!strcmp(argv[0], "&")) { return 1; } return 0; /* not a builtin command */}
没有什么值得注意的地方
do_fgbg
void do_bgfg(char **argv) { if (argv[1] == NULL) { printf("%s command requires PID or %d", &id) > 0) { job = getjobjid(jobs, id); if (job == NULL) { printf("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;}
这里,注意我们不需要加锁(或者说阻塞信号)来同步,需要关注的就是:
- 错误处理
- 我们使用
kill
函数来发送信号SIGCONT
,在这里,如果kill
的第一个参数是负数,那么表示发给整个进程组,关于这点,可以输入man 3 kill
来查看
waitfg
这里有两种写法
- 我们可以通过
sigsuspend
来发送信号进行等待(书上有这种方式) - 也可以通过
sleep
的方式来显示的阻塞
void waitfg(pid_t pid) { sigset_t mask; sigemptyset(&mask); while (fgpid(jobs) != 0) sigsuspend(&mask); return;}
void waitfg(pid_t pid) { struct job_t *job = getjobpid(jobs, pid); while (job->state == FG) { sleep(1); } return;}
handler
错误处理函数,我们先写两个简单的:
void sigint_handler(int sig) { int olderrno = errno; pid_t pid = fgpid(jobs); if (pid) kill(-(pid), SIGINT); errno = olderrno;}
void sigtstp_handler(int sig) { int olderrno = errno; pid_t pid = fgpid(jobs); if (pid) kill(-(pid), SIGTSTP); errno = olderrno;}
注意,我们需要保存 errno
的旧值,最后再进行恢复(Write up
中有写,如果你不知道,请仔细阅读)
不要对
errno
有任何处理,例如判断其等不等于某个信号,不等于就error
,这可能会导致你的程序出错
对于最后的 sigchld_handler
函数,其蓝本在课本上也有写,但我们需要注意,不能使用 while ((pid = waitpid(-1, &status, 0)) > 0)
的条件来进行回收僵死子进程了,我们需要立即返回而不是等待子进程结束才返回。
并且,我们还需要修改全局数据 jobs
,因此需要加锁进行保护。
于是,代码为:
void sigchld_handler(int sig) { int olderrno = errno; sigset_t mask_all, prev_all; pid_t pid; sigfillset(&mask_all); int status; while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if (WIFEXITED(status)) deletejob(jobs, pid); else if (WIFSIGNALED(status)) { struct job_t *job = getjobpid(jobs, pid); printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status)); deletejob(job, pid); } else { struct job_t *job = getjobpid(jobs, pid); printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status)); job->state = ST; }
sigprocmask(SIG_SETMASK, &prev_all, NULL); } errno = olderrno;}
如果你对
waitpid
有任何疑问,请打开终端,输入man 2 waitpid
,自行RTFM
Result
运行:
zsh check.zsh
进行检查即可,注意 ps
的进程可能会不一样,进程的 pid
也可能不一样,但形式与顺序应当是一致的。
如果你看不明白
diff
工具的输出的话,可以自行写一个.py
脚本来检测