这里是实验要用到的文件
这里是实验的 writeup
开始
这次的实验呢,要求我们写一个简单的 shell。像之前一样,在正式开始写代码之前,我们先来看一看下下来的 shlab-handout 里都有些什么:
首先是 “my” 开头的四个 .c 文件——这些是测试 shell 时要用到的;还有 16 个 “trace” 开头的文件、一个 .pl 文件以及 Makefile,是测试 shell 用的。我们的代码要写在 tsh.c 里面。当然了,tsh.c 也不是一片空白,作者已经给我们定义好了用来描述作业的结构(job),用来描述作业状态的宏(UNDEF、FG、BG、ST),放作业的全局数组(jobs),以及一大堆乱七八糟的帮助函数。我们需要实现的函数有 7 个——三个是信号处理函数,余下四个是处理用户输入的函数。
好了,知道了文件夹里都有什么,那我们就开敲吧。
sigint_handler 和 sigtstp_handler
我们先从比较简单的两个信号处理函数开始。
这两个函数的主要任务,是在收到 shell 传来的信号时,将这个信号“转发”给在 shell 中运行的进程。这个过程很好办——先用 fgpid(作者提供的帮助函数)获取前台进程(为啥只有前台进程嘞?因为 tstp 和 int 信号是只发给前台进程的)的 pid,之后走 kill 调用,向这个子进程发对应的信号。
好办是好办,但是其中还是有一些需要注意的点的。fgpid 是不是访问了全局数组呢?那么我们是否要在它执行的时候暂时阻塞所有信号呢?为了保证信号处理函数之外依赖于 errno 的代码正常运行,我们是不是应该暂存 errno 呢?
其实还有一个比较隐蔽的点哦——不过作者已经在 writeup 中提示给我们了——就是 kill 的第一个参数应该填 “-pid” (“负的 pid”),而不是 “pid”,从而保证“转发”的信号能够被传递给整个子进程组。为什么是“子进程组”呢?如果 shell fork 出来的子进程,没有再 fork 它自己的子进程的话,填 “pid” 没有任何问题;但是,如果它 fork 了的话(shell 就有孙进程了),这时候子进程和孙进程的 pid 是不一样的。填正的 pid,只能保证子进程能被结束;但是孙进程么……就没那么幸运了——它会“丧父”(变成孤儿进程),直到操作系统“收养”它。所以为了避免这些麻烦事,填成负的 pid 就好了。
所以,我们写出来的代码是这样的:
void sigint_handler(int sig)
{
int olderrno = errno;
// 暂存 errno
sigset_t mask_all, prev_all;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
// 阻塞所有信号
pid_t pid = fgpid(jobs);
if (pid == 0)
{
// 没有前台进程么?收拾收拾返回
errno = olderrno;
sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}
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;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
pid_t pid = fgpid(jobs);
if (pid == 0)
{
errno = olderrno;
sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
}
kill(-pid, SIGTSTP);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
errno = olderrno;
}
sigchld_handler
上面我们实现了处理 SIGINT 和 SIGTSTP 信号的函数对不对。那么,当子进程收到信号之后停止了,又会发生什么呢?我们知道,这时候内核会发 SIGCHLD 信号给 shell,让它处理子进程的停止(或者是“终止”)事件。我们要写的 sigchld_handler 就是做这件事的。
先上代码:
void sigchld_handler(int sig)
{
int olderrno = errno;
// 存储 errno ——编写安全的信号处理函数要求之一
int status;
pid_t pid;
sigset_t mask_all, prev_all;
sigfillset(&mask_all)