当前位置: 首页 > Linux

自己实现一个UnixShell

时间:2023-04-06 23:08:20 Linux

本次实验通过实现一个支持作业控制的UnixShell,让我们更加熟悉进程控制和信号控制。课程Lab帮助我们搭建了Shell的整体框架,实现了与本次实验关系不大的代码。核心部分需要自己完成。整体框架Shell从标准输入(stdin)中读取用户输入的命令,然后对命令进行解析。Shell支持两种类型的命令:如果用户输入了内置命令(如quit、jobs等),则直接执行该命令;如果用户输入一个可执行文件的路径,则派生一个子进程,并在子进程中加载??并执行命令。Shell将用户输入的每条命令抽象为一个作业,一个作业可以包含多个进程(如管道)。每个作业都有两种运行模式。如果用户输入的命令以'&'结尾,作业将在后台(background)运行,否则,作业将在前台(foreground)运行。在任何时候,只允许0或1个前台作业,但可以有0个或多个后台作业在运行。最后,为了让用户能够向Shell发送信号,我们还需要实现三个信号处理器,分别处理信号SIGCHLD、SIGINT、SIGTSTP。注意事项默认情况下,子进程与其父进程属于同一个进程组,Unix系统提供的大量向进程发送信号的机制都是基于进程组的概念。当我们输入Ctrl+C时,内核会向前台进程组中的每个进程发送一个SIGINT信号。同样,键入Ctrl+Z将导致内核向前台进程组中的每个进程发送一个SIGTSTP信号。这里的“前台进程组”是指Shell进程所属的进程组。在实验中,我们不希望信号直接作用于Shell进程本身(否则Shell在收到SIGINT信号后会终止),而是需要让Shell将信号转发给Shell前台作业中的子进程以及它所属的进程组中的所有进程。过程。因此,我们不能让子进程和Shell进程属于同一个进程组。具体方法是通过setpgid函数改变子进程的进程组。当调用setpgid(0,0)时,内核会创建一个新的进程组,其进程组ID为调用者进程的PID,并将调用者进程添加到这个进程组中。当Shell接收到一个信号时,具体的工作需要交由信号处理函数来完成。例如,如果接收到SIGINT信号,信号处理函数会将信号发送给前台作业中的进程及其所属进程组中的所有进程。在实验中,我们通过kill(pid_tpid,intsig)发送信号,注意我们不只是向PID=pid的进程发送信号。kill函数帮我们实现了:如果pid小于0,kill向进程组|pid|中的每个进程发送Signalsig(pid的绝对值)。我们可以意识到以上几点需要注意为这一点做铺垫。父进程(Shell)fork出一个子进程后,父进程需要将这个进程作为一个作业添加到作业队列(addjob)中。当子进程终止时,内核会向父进程发送SIGCHLD信号,然后在相应的信号处理程序中,从作业队列中删除终止的子进程对应的作业(deletejob)。考虑一种情况:当父进程fork一个子进程时,子进程被调度在父进程之前,而在父进程执行addjob之前,子进程已经终止并向父进程发送了一个SIGCHLD信号。此时deletejob不会在signalhandler中做任何事情,因为此时父进程还没有将job添加到job队列中。这个问题的根本原因是在addjob之前调用了deletejob。这个问题的解决方法是:在父进程fork子进程之前,阻塞SIGCHLD信号,addjob完成后解除阻塞SIGCHLD信号,这样子进程加入job队列后可以回收子进程.过程。请注意,子进程会继承其父进程的一组阻塞信号,因此我们必须在调用execve之前解除对子进程中SIGCHLD信号的阻塞。CodeShellLab的代码在这里。