本身动手实现一个Unix Shell

这个实验经过实现一个支持做业控制的Unix Shell,让咱们对进程控制和信号控制更加熟悉。课程Lab已经帮助咱们搭建起了Shell的总体框架,并实现了与本次实验不太相关的代码,核心部分须要咱们本身完成。git

总体框架

Shell从标准输入(stdin)读取用户输入的命令,而后解析命令,Shell支持两种类型的命令:若是用户输入的是的内置命令(如quitjobs等),那么直接执行该命令;若是用户输入的是某个可执行文件的路径,那么经过fork一个子进程,在子进程中加载并执行命令。Shell把每次用户输入的命令抽象为一个job,一个job能够包含多个进程(例如管道)。每一个job有两种运行方式,若是用户输入的命令以'&'结尾,那么job将会在后台(background)运行,不然,job运行在前台(foreground)。在任意时刻,只容许存在01个前台job,可是能够有0或多个后台job运行。最后,为了支持用户可以向Shell发送信号,咱们还须要实现3个信号处理程序,分别处理信号SIGCHLDSIGINTSIGTSTPgithub

须要注意的地方

  • 默认的,一个子进程和它的父进程同属于一个进程组,而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小于0kill发送信号sig给进程组|pid|pid的绝对值)中的每一个进程。咱们能够意识到,上一点须要注意的地方正是为这一点作铺垫的。
  • 父进程(Shellfork了一个子进程后,父进程须要将这个进程做为一个job添加到job队列中去(addjob),当子进程终止时,内核会发送一个SIGCHLD信号给父进程,而后在相应的信号处理程序中,把终止的子进程对应的jobjob队列中删除(deletejob)。考虑一种状况:当父进程fork了一个子进程以后,子进程先于父进程得到调度,而且在父进程执行addjob前,子进程就已经终止了,并发送了SIGCHLD信号给父进程。此时,在信号处理程序中deletejob不会作任何操做,由于此时父进程尚未把job加入到job队列中。出现这个问题的根本缘由是在addjob以前调用了deletejob。解决这个问题的方法是:在父进程fork子进程以前,将SIGCHLD信号阻塞,当完成addjob以后,才解除对SIGCHLD信号的阻塞,这样就能保证在子进程被添加到job队列以后再回收该子进程。注意,子进程继承了它们父进程的被阻塞信号集合,因此咱们必须在调用execve以前,解除子进程中阻塞的SIGCHLD信号。

代码

Shell Lab的代码在这里并发

相关文章
相关标签/搜索