CSAPP Shell Lab

Shell Lab 的内容是实现一个具有基本作业控制和信号处理功能的 Unix Shell。

系统调用包装函数

依照书上的写法定义一些系统调用的包装函数。

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
pid_t Fork()
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}

int Sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{
if (sigprocmask(how, set, oldset) < 0)
unix_error("Sigprocmask error");
return 0;
}

int Sigemptyset(sigset_t *set)
{
if (sigemptyset(set) < 0)
unix_error("Sigemptyset error");
return 0;
}

int Sigfillset(sigset_t *set)
{
if (sigfillset(set) < 0)
unix_error("Sigfillset error");
return 0;
}

int Sigaddset(sigset_t *set, int signum)
{
if (sigaddset(set, signum) < 0)
unix_error("Sigaddset error");
return 0;
}

int Setpgid(pid_t pid, pid_t pgid)
{
if (setpgid(pid, pgid) < 0)
unix_error("Setpgid error");
return 0;
}

eval

书上给出了只考虑前台作业的代码框架,在其基础上修改一下即可,主要是注意访问jobs的时候一定要阻塞所有信号以避免竞争。

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
47
48
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); /* 把子进程放到新的进程组中,这样<Ctrl-C>发出的 SIGINT 信号就不会直接发送给它。 */
if (execve(argv[0], argv, environ) < 0)
{
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}

if (!bg) /* 前台 */
{
Sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, FG, cmdline);
Sigprocmask(SIG_SETMASK, &prev_one, NULL);
waitfg(pid);
}
else /* 后台 */
{
Sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, BG, cmdline);
Sigprocmask(SIG_SETMASK, &prev_one, NULL);
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
}
return;
}

builtin_cmd

对于几种内置指令调用各自的处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int builtin_cmd(char **argv)
{
if (!strcmp(argv[0], "quit"))
exit(0);
if (!strcmp(argv[0], "jobs"))
{
listjobs(jobs);
return 1;
}
if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
{
do_bgfg(argv);
return 1;
}
if (!strcmp(argv[0], "&"))
return 1;
return 0;
}

waitfg

参考书上 8.5.7 节,等待前台作业。

1
2
3
4
5
6
7
8
9
10
void waitfg(pid_t pid)
{
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
while (fgpid(jobs))
sigsuspend(&prev_all);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}

sigchld_handler

理论上说信号处理函数不能用printf这个非线程安全的函数,但是比较懒所以就直接用了这个。

这个函数回收已经终止的进程,同时改变已经停止的进程的状态。

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
void sigchld_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if (WIFEXITED(status)) /* exit 或者 return 终止 */
deletejob(jobs, pid);
else if (WIFSIGNALED(status)) /* 被信号终止 */
{
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
deletejob(jobs, pid); /* 删除作业 */
}
else if (WIFSTOPPED(status)) /* 被信号停止 */
{
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
getjobpid(jobs, pid)->state = ST; /* 改变作业状态为停止 */
}
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
if (pid < 0 && errno != ECHILD)
unix_error("waitpid error");
errno = olderrno;
return;
}

sigint_handler & sigtstp_handler

只需要负责把信号转发到前台作业。

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
void sigint_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if (pid = fgpid(jobs))
kill(-pid, SIGINT);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
errno = olderrno;
return;
}
void sigtstp_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
if (pid = fgpid(jobs))
kill(-pid, SIGTSTP);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
errno = olderrno;
return;
}

do_bgfg

首先是对于指令格式错误的处理,占了比较大的一部分。

其他的就是分情况更改作业的状态、给进程发送SIGCONT信号,注意返回之前或者前台进程在调用waitfg之前要把信号阻塞恢复。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void do_bgfg(char **argv)
{
sigset_t mask_all, prev_all;
Sigfillset(&mask_all);
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);

if (argv[1] == NULL)
printf("%s: command requires PID or %%jobid argument\n", argv[0]);
else if (isdigit(argv[1][0])) /* process */
{
int pid = atoi(argv[1]);
struct job_t *job = getjobpid(jobs, pid);
if (job == NULL)
printf("(%d): No such process\n", pid);
else if (argv[0][0] == 'b') /* ST->BG */
{
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
job->state = BG;
kill(-pid, SIGCONT);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}
else /* ST->FG */
{
kill(-pid, SIGCONT);
job->state = FG;
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
waitfg(pid);
return;
}
}
else if (argv[1][0] == '%') /* job */
{
int jid = atoi(argv[1] + 1);
struct job_t *job = getjobjid(jobs, jid);
if (job == NULL)
printf("%%%d: No such job\n", jid);
else if (argv[0][0] == 'b') /* ST->BG */
{
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
kill(-job->pid, SIGCONT);
job->state = BG;
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}
else /* ST->FG */
{
kill(-job->pid, SIGCONT);
job->state = FG;
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
waitfg(job->pid);
return;
}
}
else
printf("%s: argument must be a PID or %%jobid\n", argv[0]);

Sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}

总结

刚开始这个实验还是很懵逼的,但是照着 trace 一个一个慢慢实现起来还是不算很困难,主要把书上的几个系统调用和例子都认真读明白了就没什么问题,对信号的理解更加深刻了!

作者

xqmmcqs

发布于

2022-02-07

更新于

2022-06-09

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×