CSAPP Lab6 Writing Your Own Unix Shell

CSAPP Lab6 Writing Your Own Unix Shell

准备

除了看 shlab.dvi (cmu.edu)Introduction to Computer Systems 15-213/18-243, spring 2009 (cmu.edu) 外,这个实验还需要仔细阅读书上第八章异常控制流部分,而书中未补全代码可在 csapp.cs.cmu.edu/3e/ics3/code/src/csapp.c 搜索(比如 Kill)。

由于我基本是按照书上介绍内容照葫芦画瓢,实际的个人思考很少,个人认为比较重要的点是对照 tshref.out(也可以用 make rtestx 输出)差异去不断修改。具体来说,我们按照原代码框架顺序编写 eval、builtin_cmd、do_bgfg、waitfg 和另外 3 个 handler 即可。

另外值得注意的是很多特殊变量不需要我们定义,比如 SIGCONT,我的错误示范:

1
2
/* signal.h 中未定义信号 */
#define SIGCONT 18 /* 用在 bg 和 fg 指令 */

eval、builtin_cmd、do_bgfg、waitfg

eval 部分代码直接参照 8-24,而其中锁的部分参考 8.5.4 和 8-40,lab 在 Hints 中也有提到需要在 fork 子进程前用 sigprocmask 锁住 SIGCHLD 信号,防止子进程提前终止导致父进程添加僵尸进程。当然,这里一开始我是完全没考虑并发冲突之类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void eval(char *cmdline) {
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;

strcpy(buf, cmdline);
bg = parseline(buf, argv);
// 输入空命令行
if (argv[0] == NULL)
return;

sigset_t mask_all, mask_one, prev_one;

Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one, SIGCHLD);

if (!builtin_cmd(argv)) {
Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
if ((pid = Fork()) == 0) {
Sigprocmask(SIG_SETMASK, &prev_one, NULL);
Setpgid(0, 0);
Execve(argv[0], argv, environ);
}

// 添加子任务到 jobs
Sigprocmask(SIG_BLOCK, &mask_all, NULL);
int state = bg ? BG : FG;
addjob(jobs, pid, state, cmdline);
Sigprocmask(SIG_SETMASK, &prev_one, NULL); // 解锁

// 父进程等待前台 job 终止
if (!bg) {
waitfg(pid);
} else {
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
}
return;
}

然后,编写 buildtin_cmd,注意这个方法属于主线程,因此我们用 exit (0) 可以跳出循环安全退出,当然具体代码参照 8-24 编写即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int builtin_cmd(char **argv) {
// quit
if (!strcmp(argv[0], "quit")) {
exit(0);
}

// jobs
if (!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
}

// bg | fg <job>
if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
do_bgfg(argv);
return 1;
}

return 0; /* not a builtin command */
}

在编写 do_bgfg 的时候,要注意和参考输出做对比,这里我推荐 test15,基本囊括了所有情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void do_bgfg(char **argv) {
int bg = !strcmp(argv[0], "bg");
if (argv[1] == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}

pid_t pid;
struct job_t *job;
if (argv[1][0] == '%') {
// job_id
int jid = atoi(argv[1] + 1);
if (jid == 0) {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}

if ((job = getjobjid(jobs, jid)) == NULL) {
printf("%s: No such job\n", argv[1]);
return;
}
pid = job->pid;
} else {
// pid
pid = atoi(argv[1]);
if (pid == 0) {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}

if ((job = getjobpid(jobs, pid)) == NULL) {
printf("(%s): No such process\n", argv[1]);
return;
}
}

Kill(-pid, SIGCONT);
if (bg) {
job->state = BG;
printf("[%d] (%d) %s", job->jid, pid, job->cmdline);
} else {
job->state = FG;
waitfg(pid);
}
}

waitfg 我是参考 8.5.7,循环阻塞式检测是否还有前台任务。

1
2
3
4
5
6
7
void waitfg(pid_t pid) {
sigset_t mask;
Sigemptyset(&mask);
while (fgpid(jobs) != 0) {
sigsuspend(&mask);
}
}

Signal handlers

我们创建的任务会有多种运行场景,收到信号 SIGINT 强制进程结束(键入 ctrl+c 也可以发出相同信号),收到 SIGSTOP 或者 SIGTSTP(等效于 ctrl+z)而暂停进程,后两者本质差不多,但还是有区别 unix - What is the difference between SIGSTOP and SIGTSTP? - Stack Overflow。当然我们需要发送的另一个指令 SIGCONT 可以让暂停进程继续运行,通常和 SIGSTOP 成对出现。

理解了信号的使用后,再借助 8.4.3 的内容,我们可以用信号去结束、回收、重启我们的子进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void sigchld_handler(int sig) {
int olderrno = errno, status;
pid_t pid;
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
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)) {
// TSTP
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
deletejob(jobs, pid);
} else if (WIFSTOPPED(status)) {
// STOP
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
struct job_t *job = getjobpid(jobs, pid);
job->state = ST;
}
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
errno = olderrno;
}

void sigint_handler(int sig) {
pid_t pid;
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if ((pid = fgpid(jobs)) > 0) {
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
Kill(-pid, SIGINT);
}
}

void sigtstp_handler(int sig) {
pid_t pid;
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if ((pid = fgpid(jobs)) > 0) {
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
Kill(-pid, SIGTSTP);
}
}

收官~

CSAPP Lab6 Writing Your Own Unix Shell

https://zion4h.github.io/2022/06/06/CSAPP-LAB-6/

作者

zion h4

发布于

2022-06-06

更新于

2025-04-19

许可协议

评论