背景#
动手实现一个简单的 Lab,主要依赖于课本第八章的内容。 感觉主要是 05 比较难:一开始发现执行的顺序不太对,原因是 SIGCHLD 里 waitpid 的参数没写对,后面就相对简单了。 累计大概花了 10 个小时的样子。
实现细节#
built-in command#
built-in command 指的是 shell 自身的命令,因此只有少数几个,比如 pwd。在 shell 上使用 which pwd 的时候,会提示 pwd: shell built-in command。
测试文件的构成#
以 trace04.txt 举例:
1#
2# trace04.txt - Run a background job.
3#
4/bin/echo -e tsh> ./myspin 1 \046
5./myspin 1 &这是两条测试命令。。第一条通过 “/bin/echo -e” 来执行,而且这条命令是一个 fg job;\046 是 ‘&’ 的 ASCII 码,是输出字符串的一部分。
子进程中的 log 打印不正确#
有些执行路径的 log 没有打印出来,可以调用 fflush(stdout) 确保打印。
在执行 fg job 的时候,会等待上一个未结束的 bg job 执行#
这是因为 waitpid 的 option 写成了默认的 0,没有传入正确的 option 导致的。
SIGCHLD 的实现#
感觉这个函数是整个 Lab 的难点。
注意这里除了要对正常结束的 process 处理以外,也要处理因为其他原因导致的进程退出,可以根据下图判断进程是哪种结束原因。

这段调了好久。。虽然书上已经讲了比较多的情况,不过还是觉得略难。
1void sigchld_handler(int sig) {
2 int olderrno = errno;
3 sigset_t mask_all, prev_all;
4 pid_t pid;
5 int status;
6 Sigfillset(&mask_all);
7
8 while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
9 if (WIFEXITED(status)) {
10 Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
11 deletejob(jobs, pid);
12 Sigprocmask(SIG_SETMASK, &prev_all, NULL);
13 } else if (WIFSIGNALED(status)) {
14 printf("Job (%d) [%d] terminated by signal %d\n",
15 pid2jid(pid), pid, WTERMSIG(status));
16 Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
17
18 deletejob(jobs, pid);
19 Sigprocmask(SIG_SETMASK, &prev_all, NULL);
20 } else if (WIFSTOPPED(status)) {
21 printf("Job (%d) [%d] stopped by signal %d\n",
22 pid2jid(pid), pid, WSTOPSIG(status));
23 struct job_t* job = getjobpid(jobs, pid);
24 if (job != NULL) {
25 job->state = ST;
26 }
27 }
28 }
29 errno = olderrno;
30}do_bgfg#
一开始可能会有些纠结:怎么转换 bg 和 fg 呢?其实直接改变 state 就好了。 所谓 bg job 还是 fg job,区别只在于当前 process 要不要等其结束,并没有什么本质区别。
job 在 FG/BG/ST 之间的状态转换如下:
1void do_bgfg(char** argv) {
2 char* cmd = argv[0];
3 char* id = argv[1];
4 if (id == NULL) {
5 printf("%s command requires PID or %%jobid argument\n", cmd);
6 return;
7 }
8
9 struct job_t* job;
10 if (id[0] == '%') {
11 int jid = atoi(&id[1]);
12 if (jid == 0) {
13 printf("%s: argument must be a PID or %%jobid\n", cmd);
14 return;
15 }
16 job = getjobjid(jobs, jid);
17 if (job == NULL) {
18 printf("%%%d: No such job\n", jid);
19 return;
20 }
21
22 } else {
23 pid_t pid = atoi(id);
24 if (pid == 0) {
25 printf("%s: argument must be a PID or %%jobid\n", cmd);
26 return;
27 }
28 job = getjobpid(jobs, pid);
29 if (job == NULL) {
30 printf("(%d): No such process\n", pid);
31 return;
32 }
33 }
34
35 Kill(-job->pid, SIGCONT);
36 if (cmd[0] == 'b') {
37 job->state = BG;
38 printf("[%d] (%d) %s", pid2jid(job->pid), job->pid,
39 job->cmdline);
40
41 } else {
42 job->state = FG;
43 // 变为fg后,需要一直等到结束
44 waitfg(job->pid);
45 }
46}eval#
直接写可能确实比较有难度,好在课本以及 lab 的说明给了足够多的示例代码和 hint,所以难度其实还好。
详细代码
1
2void eval(char* cmdline) {
3 char* parsed_args[MAXARGS];
4 char buf[MAXLINE];
5
6 strcpy(buf, cmdline);
7 int bg = parseline(cmdline, &parsed_args[0]);
8 if (parsed_args[0] == NULL) {
9 // ignore empty lines
10 return;
11 }
12 sigset_t mask_all, mask_one, prev_one;
13 // mask_all 是屏蔽全部信号
14 // mask_one 是屏蔽SIGCHLD信号
15 Sigfillset(&mask_all);
16 Sigemptyset(&mask_one);
17 Sigaddset(&mask_one, SIGCHLD);
18
19 if (builtin_cmd(parsed_args) == 0) {
20 pid_t pid;
21 // printf("bg:%d cmdline :%s\n", bg, cmdline);
22
23 // block SIGCHLD
24 Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
25 if ((pid = Fork()) == 0) {
26 // child process
27 // printf("in child process\n");
28 Sigprocmask(SIG_SETMASK, &prev_one,
29 NULL); // unblock SIGCHLD
30 // printf("before setpgid\n");
31 setpgid(0, 0);
32 // printf("before execve\n");
33
34 // 打log记得调用fflush,不然可能还没来得及输出到屏幕上就exit了
35 fflush(stdout);
36 fflush(stdout);
37 // 现在的background是虚假的。。其实还是会等待。。
38 // 如果后面都是bg指令,就不会等待,但是如果有fg指令,就会等待。
39 Execve(parsed_args[0], parsed_args, environ);
40 }
41 // printf("pid =%d\n", pid);
42 // printf("before add job block all signals\n");
43 Sigprocmask(SIG_BLOCK, &mask_all, NULL);
44 addjob(jobs, pid, bg ? BG : FG, cmdline);
45 Sigprocmask(SIG_SETMASK, &prev_one, NULL);
46
47 // parent wait child
48 if (!bg) {
49 waitfg(pid);
50 } else {
51 printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
52 }
53 }
54 return;
55}完整代码参考:这里