这个实验经过实现一个支持做业控制的Unix Shell
,让咱们对进程控制和信号控制更加熟悉。课程Lab
已经帮助咱们搭建起了Shell
的总体框架,并实现了与本次实验不太相关的代码,核心部分须要咱们本身完成。git
Shell
从标准输入(stdin
)读取用户输入的命令,而后解析命令,Shell
支持两种类型的命令:若是用户输入的是的内置命令(如quit
、jobs
等),那么直接执行该命令;若是用户输入的是某个可执行文件的路径,那么经过fork
一个子进程,在子进程中加载并执行命令。Shell
把每次用户输入的命令抽象为一个job
,一个job
能够包含多个进程(例如管道)。每一个job
有两种运行方式,若是用户输入的命令以'&
'结尾,那么job
将会在后台(background
)运行,不然,job
运行在前台(foreground
)。在任意时刻,只容许存在0
或1
个前台job
,可是能够有0
或多个后台job
运行。最后,为了支持用户可以向Shell
发送信号,咱们还须要实现3
个信号处理程序,分别处理信号SIGCHLD
、SIGINT
和SIGTSTP
。github
Unix
系统提供的大量向进程发送信号的机制,都是基于进程组这个概念的。当咱们输入Ctrl + C
,内核会发送一个SIGINT
信号到前台进程组的每一个进程,相似的,输入Ctrl + Z
会致使内核发送一个SIGTSTP
信号给前台进程组中的每一个进程。这儿的“前台进程组”指的是Shell
进程所属的进程组。实验中,咱们并不指望信号直接做用于Shell
进程自己(不然Shell
收到SIGINT
信号就终止了),而是须要让Shell
将信号转发给Shell
前台做业中的子进程及其所属进程组中的全部进程。因此,咱们不能让子进程和Shell
进程同属一个进程组。具体作法是经过使用setpgid
函数来改变子进程的进程组,当调用setpgid(0, 0)
时,内核会建立一个新的进程组,其进程组ID
是调用者进程的PID
,而且会把调用者进程加入到这个进程组中。Shell
收到信号时,具体的工做须要信号处理函数来完成。例如收到SIGINT
信号,那么信号处理函数会把该信号发往前台job
中的进程及其所属进程组中的全部进程。实验中,咱们是经过kill(pid_t pid, int sig)
来发送信号,注意到咱们并不单单是向PID = pid
的进程发送信号,kill
函数帮助咱们实现了这一点:若是pid
小于0
,kill
发送信号sig
给进程组|pid|
(pid
的绝对值)中的每一个进程。咱们能够意识到,上一点须要注意的地方正是为这一点作铺垫的。Shell
)fork
了一个子进程后,父进程须要将这个进程做为一个job
添加到job
队列中去(addjob
),当子进程终止时,内核会发送一个SIGCHLD
信号给父进程,而后在相应的信号处理程序中,把终止的子进程对应的job
从job
队列中删除(deletejob
)。考虑一种状况:当父进程fork
了一个子进程以后,子进程先于父进程得到调度,而且在父进程执行addjob
前,子进程就已经终止了,并发送了SIGCHLD
信号给父进程。此时,在信号处理程序中deletejob
不会作任何操做,由于此时父进程尚未把job
加入到job
队列中。出现这个问题的根本缘由是在addjob
以前调用了deletejob
。解决这个问题的方法是:在父进程fork
子进程以前,将SIGCHLD
信号阻塞,当完成addjob
以后,才解除对SIGCHLD
信号的阻塞,这样就能保证在子进程被添加到job
队列以后再回收该子进程。注意,子进程继承了它们父进程的被阻塞信号集合,因此咱们必须在调用execve
以前,解除子进程中阻塞的SIGCHLD
信号。Shell Lab
的代码在这里。并发