Shell Lab
Declaration
本文使用了 AIGC 来提高效率,其中可能存在谬误,我已尽力检查并校对,但仍不保证完全准确,欢迎指正。
Steps
要求实现一个带有作业控制的Unix Shell程序,考虑并发,进程控制以及信号和信号的处理。
eval:解析命令行 [约 70 行]builtin_cmd:检测是否为内置命令quit、fg、bg、jobs[约 25 行]do_bgfg:实现内置命令bg和fg[约 50 行]waitfg:等待前台作业执行完成 [约 20 行]sigchld_handler:处理SIGCHLD信号,即子进程停止或者终止 [约 80 行]sigint_handler:处理SIGINT信号,即来自键盘的中断ctrl-c[约 15 行]sigtstp_handler:处理SIGTSTP信号,即来自终端的停止信号 [约 15 行]
共计约300行代码

更加详细的要求在writeup中,我这里就不重复了。贴出本仓库放writeup的位置CMU-15213/resources at main · Deepcity/CMU-15213
在这里还包含了一个tshref的参考tsh,以及16个测试用例。通过跑trace可以测试自己的shell是否符合要求。详细方式是
# tshref 测试
make rtest<testid>
##tsh 测试
make test<testid>
对拍两者输出,详细请查看handout中Makefile文件
Code Policy
- 在处理信号时,使用
sigsuspend来等待信号的到来,避免忙等待 - 在调用unix系统调用时,检查返回值以处理可能的错误
TEST
下面仅列出几个容易出现错误的tsh测试用例以及修复过程
TEST04
这里调度了一个./myspin 1&的后台程序,旨在看shell能否正确invoke后台程序,并且正确打印出[jid] (pid) cmd &的信息
注意看eval函数中对后台作业的处理部分
TEST05
对内建命令jobs的测试,要求打印出所有后台作业的信息
注意看builtin_cmd函数中对jobs的处理部分
TEST06
看是否能够进行前台作业的控制,这里调度了一个./myspin 4的前台程序,要求等待其执行完毕,并重定向输入输出。
注意exec_cmd函数中对前台作业的处理部分
TEST14
该测试是对fg,bg的分支测试。详见do_bgfg函数中对bg和fg的处理部分。
QA
-
为什么在信号处理函数中要保存和恢复
errno?在信号处理函数中,可能会调用一些系统调用,这些调用可能会修改
errno的值。如果不保存和恢复errno,可能会导致主程序中的错误处理逻辑出现问题。因此,在进入信号处理函数时保存当前的errno值,在退出时恢复它,可以确保主程序的错误处理逻辑不受影响。 -
为什么在信号处理函数中要阻塞所有信号?
在信号处理函数中阻塞所有信号,可以防止在处理当前信号时被其他信号打断。这有助于确保信号处理函数的原子性,避免竞态条件和不一致状态的出现。通过使用
sigprocmask函数,可以临时阻塞其他信号,确保当前信号处理完成后再恢复信号的处理。 -
什么时候应该使用负的PID参数调用
kill函数?使用负的PID参数调用
kill函数时,表示向一个进程组发送信号。具体来说,kill(-pid, sig)会将信号s发送给进程组ID为pid的所有进程。这在实现作业控制时非常有用,因为一个作业可能包含多个进程,使用负的PID可以方便地向整个作业发送信号,例如终止或停止整个作业。 -
SIGCHLD, SIGINT, SIGTSTP 信号分别代表什么?
SIGCHLD:当子进程终止或停止时,父进程会收到这个信号。它通常用于通知父进程子进程的状态变化,以便父进程可以进行相应的处理,例如回收子进程资源。SIGINT:这是一个中断信号,通常由用户通过键盘输入Ctrl-C触发。它用于请求终止当前正在运行的前台进程。SIGTSTP:这是一个停止信号,通常由用户通过键盘输入Ctrl-Z触发。它用于请求暂停当前正在运行的前台进程,使其进入后台暂停状态。
-
sigprocmask 函数的作用是什么?
sigprocmask函数用于改变当前进程的信号屏蔽字。它可以用来阻塞或解除阻塞特定的信号,从而控制哪些信号可以被处理。通过使用sigprocmask,程序可以确保在关键代码段中不会被特定信号打断,从而避免竞态条件和不一致状态的出现。 -
sigprocmask 函数的参数含义是什么?
sigprocmask函数的参数通常包括三个部分:how:指定如何修改信号屏蔽字。常见的取值有SIG_BLOCK(阻塞指定的信号)、SIG_UNBLOCK(解除阻塞指定的信号)和SIG_SETMASK(设置新的信号屏蔽字)。set:一个指向sigset_t类型的指针,表示要阻塞或解除阻塞的信号集合。oldset:一个指向sigset_t类型的指针,用于保存调用前的信号屏蔽字。如果不需要保存,可以传递NULL。
-
为什么在信号处理函数中使用 sigfillset 函数?
sigfillset函数用于初始化一个信号集合,将所有信号都添加到该集合中。在信号处理函数中使用sigfillset,可以方便地阻塞所有信号,确保在处理当前信号时不会被其他信号打断。这有助于保持信号处理的原子性,避免竞态条件和不一致状态的出现。 -
shell中什么时候应该阻塞信号,原因是什么?
在shell中,通常在处理关键代码段时需要阻塞信号,例如在修改作业列表或等待前台作业完成时。阻塞信号的原因是为了防止在这些关键操作过程中被其他信号打断,从而导致竞态条件和不一致状态的出现。通过阻塞信号,可以确保关键操作的原子性,保证shell的稳定性和正确性。