HIT CSAPP LAB7

TinyShell

微壳

实验日常需要查重,所以不要照搬代码!!!!!!!!!看看思路就好

目 录

第1章 实验基本信息 - 3 -

1.1 实验目的 - 3 -
1.2 实验环境与工具 - 3 -
1.2.1 硬件环境 - 3 -
1.2.2 软件环境 - 3 -
1.2.3 开发工具 - 3 -

1.3 实验预习 - 3 -

第2章 实验预习 - 4 -

2.1 进程的概念、创建和回收方法(5分) - 4 -
2.2信号的机制、种类(5分) - 4 -
2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分) - 4 -
2.4 什么是SHELL,功能和处理流程(5分) - 4 -

第3章 TINYSHELL测试 - 5 -

3.1 TINYSHELL设计 - 5 -

第4章 总结 - 5 -

4.1 请总结本次实验的收获 - 5 -
4.2 请给出对本次实验内容的建议 - 5 -

参考文献 - 7 -

第1章 实验基本信息

1.1 实验目的

理解现代计算机系统进程与并发的基本知识
掌握linux 异常控制流和信号机制的基本原理和相关系统函数
掌握shell的基本原理和实现方法
深入理解Linux信号响应可能导致的并发冲突及解决方法
培养Linux下的软件系统开发与测试能力

1.2 实验环境与工具

1.2.1 硬件环境

X64 CPU;2GHz;2G RAM;256GHD Disk 以上

1.2.2 软件环境

Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位

1.2.3 开发工具

GCC CodeBlocks

1.3 实验预习

上实验课前,必须认真预习实验指导书(PPT或PDF)
了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。
了解进程、作业、信号的基本概念和原理
了解shell的基本原理
熟知进程创建、回收的方法和相关系统函数
熟知信号机制和信号处理相关的系统函数

第2章 实验预习

总分20分

2.1 进程的概念、创建和回收方法(5分)

1.进程的概念

进程是一个执行中程序的示例。是OS对CPU执行的程序的运行过程的一种抽象。是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

2.进程的创建

每次用户通过向shell输入一个可执行文件目标的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也可以创建新进程,并且在这个新进程的上下文运行它们自己的代码或其他应用程序。
一般来说父进程同通过调用fork函数创建一个新的运行的子进程。
而内核里面只有fork 和exec两种可以创建进程。其他方式都是使用的这两种方式,如system()封装了exec。
通常,在系统调用 fork() 之后,有个进程使用系统调用 exec(),以用新程序来取代进程的内存空间。
具体实现与如下函数有关:
2.1.fork函数,创建的进程拥有各自的文件表项。
2.2.exec系列函数,如execl()、execlp()、execle()、execv()、execvp()、execvpe(),这种方法创建的进程会直接用新进程覆盖原有进程的代码段。
2.3.system函数

#include <stdlib.h>  
int main(){  
    system("ls -l"); //创建了一个ls -l的进程。system("clear")表示清屏。  
    return 0;  
}  

2.4.popen函数

3.进程的回收

如果一个父进程终止了,内核会安排init进程称为它的孤儿进程的养父。孤儿进程退出后,它的清理工作有祖先进程init自动处理。但是init进程对僵死进程的清理并没有没那么及时。
如果父进程没有回收它的僵死子进程就终止了,那么内核会安排init进程去回收它们。一个进程可以调用waitpid函数来等待它的子进程终止或者停止。
其中wait函数是waitpid函数的简单版本,调用wait(&status)等价于调用waitpid(-1,&status,0)。
如果子进程先行退出,系统将不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作。如果父进程不做清理工作,则已经退出的子进程将成为僵死进程,在系统中如果存在的僵死进程过多,将会影响系统的性能,所以必须对僵死进程进行处理。

2.2信号的机制、种类(5分)

1.信号的机制

1.1. Linux信号是一种更高层的软件形式的异常,它通知进程系统中发生某个类型的事件,它允许进程和内核中断其他进程。
1.2. 信号机制指的是内核如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对信号的反应、内核在什么时机处理和怎样处理进程收到的信号。
1.3.[1]内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。
在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设定的对该信号的处理例程的地址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程 在调用signal之前又得到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢 出。为了避免出现上述情况。在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。
第二个要引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注意的是,BSD系统中内核可以自动地重新开始系统调用。
第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。
第四个要注意的地方:内核对子进程终止(SIGCHLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程就像没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCHLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就像普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCHLD的作用仅仅是唤醒一个睡眠在可被中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。
如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则内核将向该进程发一个SIGCLD信号。
1.4.非本地跳转
前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。
在介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核 为进程将原先setjmp调用保存在进程用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道该次系统调用失败。这就是它们的作用。

2.信号的种类
序号名称默认行为相应事件
1SIGHUP终止终端线挂断
2SIGINT终止来自键盘的中断
3SIGQUIT终止来自键盘的退出
4SIGILL终止非法指令
5SIGTRAP终止并转储内存跟踪陷阱
6SIGABRT终止并转储内存来自abort函数的终止信号
7SIGBUS终止总线错误
8SIGFPE终止并转储内存浮点异常
9SIGKILL终止杀死程序
10SIGUSR1终止用户定义的信号1
11SIGSEGV终止并转储内存无效的内存引用(段故障)
12SIGUSR2终止用户定义的信号2
13SIGPIPE终止向一个没有读用户的管道做写操作
14SIGALRM终止来自alarm函数的定时器信号
15SIGTERM终止软件终止信号
16SIGSTKFLT终止协处理器上的栈故障
17SIGCHLD忽略一个子进程停止或终止
18SIGCONT忽略继续进程如果该进程停止
19SIGSTOP停止直到下一个SIGCONT不是来自终端的停止信号
20SIGTSTP停止直到下一个SIGCONT来自终端的停止信号
21SIGTTIN停止直到下一个SIGCONT后台进程向终端读
22SIGTTOU停止直到下一个SIGCONT后台进程向终端写
23SIGURG忽略套接字上的紧急情况
24SIGXCPU终止CPU时间限制超出
25SIGXFSZ终止文件大小限制超出
26SIGVTALRM终止虚拟定时器满
27SIGPROF终止剖析定时器满
28SIGWINCH忽略窗口大小变化
29SIGIO终止在某个描述符上可执行I/O操作
30SIGPWR终止电源故障

2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分)

1.信号的发送方法

Unix系统提供了大量向进程发送信号的机制。所有这些机制都是基于进程组这个概念。
1.1.用/bin/kill程序发送信号
/bin/kill程序可以向另外的进程发送任意的信号。一个为负的PID会导致信号被发送到进程组PID中的每个进程。
需要注意的是,在此我们使用完整的路径/bin/kill,因为有些Unix shell有自己内置的kill命令。
1.2.从键盘发送信号
在键盘上输入Ctrl+C会导致内核发送一个 SIGINT信号到前台进程组中的每个进程。默认情况,结果是终止前台作业。类似地,输入Ctrl+Z会发送一个SIGTSTP信号到前台进程组中的每个进程。默认情况,结果是停止(挂起)前台作业。
1.3.用kill,alarm,raise,pause等函数发射信号
1.3.1.kill()和raise()
kill()函数和熟知的kill系统命令一样,可以发送信号给信号和进程组(实际上kill系统命令只是kill函数的一个用户接口),需要注意的是他不仅可以终止进程(发送SIGKILL信号),也可以向进程发送其他信号。
与kill函数不同的是raise()函数允许进程向自身发送信号。
1.3.2.alarm()和pause()
alarm()-----也称为闹钟函数,可以在进程中设置一个定时器,等到时间到达时,就会想进程发送SIGALARM信号,注意的是一个进程只能有一个闹钟时间,如果调用alarm()之前已经设置了闹钟时间,那么任何以前的闹钟时间都会被新值所代替。
pause()----此函数用于将进程挂起直到捕捉到信号为止,这个函数很常用,通常用于判断信号是否已到

2.信号的阻塞

2.1.阻塞和解除阻塞信号
隐式阻塞机制:内核默认阻塞与当前正在处理信号类型相同的待处理信号。如:一个SIGINT 信号处理程序不能被另一个 SIGINT信号中断 (此时另一个SIGINT信号被阻塞)
2.2.显式阻塞和解除阻塞机制:sigprocmask 函数及其辅助函数可以明确地阻塞/解除阻塞。其中选定的信号辅助函数如下:
sigemptyset – 初始化set为空集合
sigfillset – 把每个信号都添加到set中
sigaddset – 把指定的信号signum添加到set
sigdelset – 从set中删除指定的信号signum

3.设置信号处理程序

3.1. 可以使用 signal函数修改和信号signum相关联的默认行为: handler_t *signal(int signum, handler_t *handler);
3.2. handler的不同取值:
3.2.1. SIG_IGN: 忽略类型为signum的信号
3.2.2. SIG_DFL: 类型为 signum的信号行为恢复为默认行为
3.2.3. 否则,handler就是用户定义的函数的地址,这个函数称为信号处理程序
3.3. 只要进程接收到类型为 signum 的信号就会调用信号处理程序
将处理程序的地址传递到signal函数从而改变默认行为,这叫作设置信号处理程序。调用信号处理程序称为捕获信号,执行信号处理程序称为处理信号。当处理程序执行return时,控制会传递到控制流中被信号接收所中断的指令处。

2.4 什么是shell,功能和处理流程(5分)

1.shell的定义

shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。

2.shell的功能

实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。不仅如此,Shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序。Shell编程语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等,用这种编程语言编写的Shell程序与其他应用程序具有同样的效果。

3.shell的处理流程

shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。

第3章 TinyShell的设计与实现

总分45分

3.1 设计

3.1.1 void eval(char *cmdline)函数(10分)

函数功能:
评估用户刚输入的命令行。如果用户请求了内置命令(quit,jobs,bg或fg),则立即执行。 否则,fork一个子进程并在子进程的上下文中运行该作业。 如果作业在前台运行,等待它终止然后返回。
参 数:
形参是用户输入的命令行cmdline 。实参是argv , mask, SIG_UNBLOCK , argv[0] , environ。
处理流程:
第一步:定义各个变量。使用parseline()函数函数解析命令行,得到命令行参数。如果argv[0]=NULL,则无命令。
第二步:使用builtin_cmd()函数判断命令是否为内置命令,如果不是内置命令,则继续执行。
第三步:设置阻塞集合。先初始化mask为空集合,再将SIGCHLD , SIGINT ,SIGTSTP 信号加入阻塞集合。然后创建子进程。
第四步:阻塞SIGCHLD,防止子进程在父进程之前结束,防止addjob()函数错误地把(不存在的)子进程添加到作业列表中。
第五步:子进程中,先解除对SIG_CHLD的阻塞,再使用setpgid(0,0)创建一个虚拟的进程组,使子进程拥有自己唯一的进程组ID,目的是为了在键盘上键入Ctrl+C (Ctrl+Z)时,我们的后台子进程就不会从内核接收SIGINT (SIGTSTP)信号。该进程组ID表明其不和tsh进程在一个进程组。然后调用execve函数,执行相应的文件。
第六步:将job添加到job list,解除SIG_CHLD阻塞信号。判断进程是否为前台进程,如果是前台进程,调用waitfg()函数,等待前台进程,如果是后台进程,则打印出进程信息。
要点分析:

1.在键盘上键入Ctrl+C (Ctrl+Z)时,我们的后台子进程就不会从内核接收SIGINT (SIGTSTP)信号。
2.在执行addjob之前需要阻塞信号,防止addjob()函数错误地把(不存在的)子进程添加到作业列表中。
函数如下:

/* 
 * eval - Evaluate the command line that the user has just typed in
 * 
 * If the user has requested a built-in command (quit, jobs, bg or fg)
 * then execute it immediately. Otherwise, fork a child process and
 * run the job in the context of the child. If the job is running in
 * the foreground, wait for it to terminate and then return.  Note:
 * each child process must have a unique process group ID so that our
 * background children don't receive SIGINT (SIGTSTP) from the kernel
 * when we type ctrl-c (ctrl-z) at the keyboard.  
*/
void eval(char *cmdline) 
{
    /* $begin handout */
    char *argv[MAXARGS]; /* argv for execve() */
    int bg;              /* should the job run in bg or fg? */
    pid_t pid;           /* process id */
    sigset_t mask;       /* signal mask */

    /* Parse command line */
    bg = parseline(cmdline, argv); 
    if (argv[0] == NULL)  
		return;   /* ignore empty lines */

    if (!builtin_cmd(argv)) { 

        /* 
		 * This is a little tricky. Block SIGCHLD, SIGINT, and SIGTSTP
		 * signals until we can add the job to the job list. This
		 * eliminates some nasty races between adding a job to the job
		 * list and the arrival of SIGCHLD, SIGINT, and SIGTSTP signals.  
		 */

		if (sigemptyset(&mask) < 0)//block  mask集合置空
	    	unix_error("sigemptyset error");
		if (sigaddset(&mask, SIGCHLD)) //添加SIGCHLD进mask集合
	    	unix_error("sigaddset error");
		if (sigaddset(&mask, SIGINT)) //添加SIGINT进集合
	    	unix_error("sigaddset error");
		if (sigaddset(&mask, SIGTSTP)) //添加SIGTSTP(来自终端的停止信号)进集合
	    	unix_error("sigaddset error");
		if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)//用mask更新BLOCK
	    	unix_error("sigprocmask error");

		/* Create a child process */
		if ((pid = fork()) < 0)
	    	unix_error("fork error");
	
		/* 
	 	* Child  process 
	 	*/

		if (pid == 0) {
	    	/* Child unblocks signals */
	    	sigprocmask(SIG_UNBLOCK, &mask, NULL);//用mask删除SIG_BLOCK

	    	/* Each new job must get a new process group ID 
	       	   so that the kernel doesn't send ctrl-c and ctrl-z
	       	   signals to all of the shell's jobs */
	        if (setpgid(0, 0) < 0) //将创建新的进程组,进程组ID为调用进程ID
				unix_error("setpgid error"); 

	    	/* Now load and run the program in the new job */
	    	if (execve(argv[0], argv, environ) < 0) {
				printf("%s: Command not found\n", argv[0]);
				exit(0);
	    	}
		}

		/* 
	 	* Parent process
	 	*/

		/* Parent adds the job, and then unblocks signals so that
	   	   the signals handlers can run again */
		addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);
		sigprocmask(SIG_UNBLOCK, &mask, NULL);//还原Block

		if (!bg) 
	    	waitfg(pid);//前台进行
		else
	    	printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);//后台进行
    }
    /* $end handout */
    return;
}
3. 1.2 int builtin_cmd(char **argv)函数(5分)

函数功能:
如果用户键入了内置命令,则立即执行。
参 数:
形参 传入的参数数组argv 。实参argv[0] ,“quit” ,"&" ,“jobs” ,“bg” ,“fg”, argv,SIG_BLOCK ,mask ,prev ,NULL。
处理流程:
函数需要根据传入的参数数组,判断用户键入的是否为内置命令,采取的办法就是比较argv[0]和内置命令,如果是内置命令,则跳到相应的函数,并且返回1,如果不是,则什么也不做,并且返回0。
其中,如果命令为quit,则直接退出;如果命令是内置的jobs命令,则调用listjobs()函数,打印job列表;如果是fg或是bg两条内置命令,则调用do_bgfg()函数来处理即可。
要点分析:
1.要注意如果用户仅仅按下回车键,那么在解析后argv的第一个变量将是一个空指针。如果用这个空指针去调用strcmp函数会引发segment fault。
2.因为jobs是全局变量,为了防止其被修改,需要阻塞全部信号,过程大致为(后面函数阻塞全部信号的做法与此基本一致):

sigset_t mask,prev;  
sigfillset(&mask);sigprocmask(SIG_BLOCK, &mask, &prev);  
sigprocmask(SIG_SETMASK, &prev, NULL);  

函数如下:

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
	sigset_t mask, prev;
	sigfillset(&mask);//阻塞全部信号,为下面lishjobs调用全局变量jobs准备
	char *cmd = argv[0];//第一个字符
	if(cmd == NULL) //若为空,就直接返回,返回1表示不用进行if语句内包含的内容因为如果仅仅按下回车会使得strcmp失效
		return 1;
	if(!strcmp(cmd, "&"))//若只单单有一个&字符,同上
		return 1;
	if(!strcmp(cmd, "quit"))//若退出,则选择退出,并选用安全的退出函数
		_exit(0);
	if(!strcmp(cmd, "jobs")){//jobs列出当前的进程
		sigprocmask(SIG_BLOCK, &mask, &prev);//调用全局变量前需要阻塞全部信号
		listjobs(jobs);
		sigprocmask(SIG_SETMASK, &prev, NULL);//恢复原来信号
		return 1;
	}
	if(!strcmp(cmd, "bg") || !strcmp(cmd, "fg")){//交由do_bgfg函数处理
		do_bgfg(argv);
		return 1;
	}
    return 0;     /* not a builtin command */
}
3. 1.3 void do_bgfg(char **argv) 函数(5分)

函数功能:
执行内置bg和fg命令。
参 数:
形参是传入的参数数组argv 。实参是argv[1] , argv[1][0] , jobs , pid , argv[1] , argv[0]。
处理流程:
第一步:先判断fg或bg后是否有参数,如果没有,则忽略命令。
第二步:如果fg或bg后面只是数字,说明取的是进程号,获取该进程号后,使用getjobpid(jobs, pid)得到job;如果fg或bg后面是%加上数字的形式,说明%后面是任务号(第几个任务),此时获取jid后,可以使用getjobjid(jobs, jid)得到job。
第三步:比较区分argv[0]是“bg”还是“fg”。如果是后台进程,则发送SIGCONT信号给进程组PID的每个进程,并且设置任务的状态为BG,打印任务的jid,pid和命令行;如果是前台进程,则发送SIGCONT信号给进程组PID的每个进程,并且设置任务的状态为FG,调用waitfg(jobp->pid),等待前台进程结束。
要点分析:
1.函数主要是先判断fg后面是%+数字还是只有数字的形式,从而根据进程号pid或是工作组号jid来获取结构体job;然后在根据前台和后台进程的不同,执行相应的操作。
2. isdigit()函数判断是否为数字,不是数字返回0。
3. atoi()函数把字符串转化为整型数。
4. SIGCONT信号对应事件为:继续进程如果该进程停止。
函数如下:

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
    /* $begin handout */
    struct job_t *jobp=NULL;
    
    /* Ignore command if no argument */
    if (argv[1] == NULL) {
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
    }
    
    /* Parse the required PID or %JID arg */
    if (isdigit(argv[1][0])) {
		pid_t pid = atoi(argv[1]);
		if (!(jobp = getjobpid(jobs, pid))) {
	    	printf("(%d): No such process\n", pid);
	    	return;
		}
    }
    else if (argv[1][0] == '%') {
		int jid = atoi(&argv[1][1]);
		if (!(jobp = getjobjid(jobs, jid))) {
	    	printf("%s: No such job\n", argv[1]);
	    	return;
		}
    }	    
    else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
    }

    /* bg command */
    if (!strcmp(argv[0], "bg")) { 
		if (kill(-(jobp->pid), SIGCONT) < 0)
	    	unix_error("kill (bg) error");
		jobp->state = BG;
		printf("[%d] (%d) %s", jobp->jid, jobp->pid, jobp->cmdline);
    }

    /* fg command */
    else if (!strcmp(argv[0], "fg")) { 
		if (kill(-(jobp->pid), SIGCONT) < 0)
	    	unix_error("kill (fg) error");
		jobp->state = FG;
		waitfg(jobp->pid);
    }
    else {
		printf("do_bgfg: Internal error\n");
		exit(0);
    }
    /* $end handout */
    return;
}
3. 1.4 void waitfg(pid_t pid) 函数(5分)

函数功能:
阻止直到进程pid不再是前台进程。
参 数:
形参是前台进程pid。实参是mask,jobs。
处理流程:
函数主体是while循环语句,判断传入的pid是否为一个前台进程的pid,如果是,则一直循环,如果不是,则跳出循环。其中while循环内部使用sigsuspend()函数,暂时用mask替换当前的阻塞集合,然后挂起该进程,直到收到一个信号,选择运行一个处理程序或者终止该进程。
要点分析:
1.在while内部,如果使用的只是pause()函数,那么程序必须等待相当长的一段时间才会再次检查循环的终止条件,如果使用向nanosleep这样的高精度休眠函数也是不可接受的,因为没有很好的办法来确定休眠的间隔。但是PPT上示例代码却给出sleep(1),我:???。
2.在while循环语句之前,初始化mask结合为空,在while内部用SIG_SETMASK使block=mask,这样sigsuspend()才不会因为收不到SIGCHLD信号而永远睡眠。
函数如下:

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	sigset_t mask;
	sigemptyset(&mask);//在挂起进程的过程中清空block以便信号能够响应
	while(pid == fgpid(jobs)) {//前台工作的进程值比较
		sigsuspend(&mask);//挂起进程
	}
    return;
}
3. 1.5 void sigchld_handler(int sig) 函数(10分)

函数功能:
函数1:
每当一个子进程终止(成为僵死进程),或者因为接收到SIGSTOP或SIGTSTP信号而停止时,内核就向shell发送一个SIGCHLD。处理程序获取所有可用的僵死子进程,但不等待当前正在运行的任何其他子进程终止。
函数2:
当用户在键盘上键入Ctrl+C时,内核向shell发送一个SIGINT。捕获它并将其发送到前台作业。
函数3:
每当用户在键盘上键入Ctrl+Z时,内核都会向shell发送一个SIGTSTP。捕获它并通过发送一个SIGTSTP来挂起前台作业。
参 数:
函数1:
形参是sig,实参是status , WNOHANG | WUNTRACED , SIG_BLOCK , mask , prev, SIG_SETMASK。
函数2:
形参是sig,实参是SIG_BLOCK , mask , prev, SIG_SETMASK。
函数3:
形参是sig,实参是SIG_BLOCK , mask , prev, SIG_SETMASK。
处理流程:
函数1:
第一步:把每个信号都添加到mask阻塞集合中,设置olderrno = errno 。
第二步:在while循环中使用waitpid(-1, &status, WNOHANG | WUNTRA
CED)),其中,目的是尽可能回收子进程,其中WNOHANG | WUNTRACED表示立即返回,如果等待集合中没有进程被中止或停止返回0,否则孩子返回进程的pid。
第三步:在循环中阻塞信号,并且使用getjobpid()函数,通过pid找到job 。
第四步:通过waitpid在status中放上的返回子进程的状态信息,判断子进程的退出状态。如果引起返回的子进程当前是停止的,那么WIFSTOPPED(status)就返回真,此时只需要将pid找到的job的状态改为ST,并且按照示例程序输出的信息,将job的jid,pid以及导致子进程停止的信号的编号输出即可。如果子进程是因为一个未被捕获的信号终止的,那么WIFSIGNALED(status)就返回真,此时同样按照示例程序输出的信息,将job的jid,pid以及导致子进程终止的信息的编号输出即可,因为此时进程是中止的的进程,所以还需要deletejob()将发出SIGCHLD信号的将其直接回收。
第五步:清空缓冲区,解除阻塞,恢复errno。
函数2:
第一步:将每个信号添加至mask阻塞集合,设置olderrno = errno。
第二步:获取前台进程组的pgid。
第三步:还原原阻塞信号,若有前台进程组,将信号SIGINT发给这个进程组。
第四步:还原errno。
函数3:
第一步:将每个信号添加至mask阻塞集合,设置olderrno = errno。
第二步:获取前台进程组的pgid。
第三步:还原原阻塞信号,若有前台进程组,将信号SIGTSTP发给这个进程组。
第四步:还原errno。
要点分析:
函数1:
1.while循环来避免信号阻塞的问题,循环中使用waitpid()函数,以尽可能多的回收僵尸进程。但是使用while可能会让waitpid等待后台还在进行的进程结束,但如果使用一次if可能会导致信号累加的问题,例如多个后台程序同时结束的情况。然后PPT上建议使用一次waitpid,示例代码却又将其用在了while循环判断条件。考虑到函数的目的是要获取所有可用的僵死进程,故而采用while循环。
2.调用deletejob()函数时,因为jobs是全局变量,因此需要阻塞信号。
3.通过waitpid在status中放上的返回子进程的状态信息,判断子进程的退出状态。WIFSIGNALED判断子进程是否因为一个未被捕获的信号中止的,WIFSTOPPED判断引起返回地子进程当前是否为停止的。WIFEXITED判断是否是正常返回。
函数2:
jobs为全局共享数据结构,需要进行信号阻塞后对它进行调用,然后需还原信号阻塞。发送信号的时候需要对整个进程组进行。
函数3:
jobs为全局共享数据结构,需要进行信号阻塞后对它进行调用,然后需还原信号阻塞。发送信号的时候需要对整个进程组进行。
函数如下:

/* 
 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
 *     a child job terminates (becomes a zombie), or stops because it
 *     received a SIGSTOP or SIGTSTP signal. The handler reaps all
 *     available zombie children, but doesn't wait for any other
 *     currently running children to terminate.  
 */
void sigchld_handler(int sig) 
{
	int olderrno = errno, status;//status 用于检查回收子进程的退出状态
	sigset_t mask, prev;//用于阻塞全局共享数据结构
	struct job_t *jobfirst;//方便后续删除
	sigfillset(&mask);
	pid_t pid;//记录停止的子进程ID
	while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){//立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID
		sigprocmask(SIG_BLOCK, &mask, &prev);//删除进程时需要阻塞所有信号——引用了全局共享数据结构
		jobfirst = getjobpid(jobs, pid);//根据PID寻找进程
		if(WIFEXITED(status)) {//引起返回的子进程当前是正常停止的
			deletejob(jobs, pid);
		}
		else if(WIFSIGNALED(status)){//因一个未被捕获的信号终止
			printf("Job [%d] (%d) terminated by signal %d\n", jobfirst->jid, jobfirst->pid, WTERMSIG(status));
			deletejob(jobs, pid);
		}
		else if(WIFSTOPPED(status)){//因为子进程停止而停止
			jobfirst->state = ST;
			printf("Job [%d] (%d) stopped by signal %d\n", jobfirst->jid, jobfirst->pid, WSTOPSIG(status));
		}
		fflush(stdout);
		sigprocmask(SIG_SETMASK, &prev, NULL);//恢复Block
	}
	errno = olderrno;//还原errno
    return;
}

/* 
 * sigint_handler - The kernel sends a SIGINT to the shell whenver the
 *    user types ctrl-c at the keyboard.  Catch it and send it along
 *    to the foreground job.  
 */
void sigint_handler(int sig) 
{
	pid_t pid;
	sigset_t mask, prev;
	int olderrno = errno;
	sigfillset(&mask);
	sigprocmask(SIG_SETMASK, &mask, &prev);//日常……
	pid = fgpid(jobs);//获取前台进程组
	sigprocmask(SIG_SETMASK, &prev, NULL);
	if(pid != 0)
		kill(-pid, SIGINT);//发给前台进程组信号
	errno = olderrno;
    return;
}

/*
 * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
 *     the user types ctrl-z at the keyboard. Catch it and suspend the
 *     foreground job by sending it a SIGTSTP.  
 */
void sigtstp_handler(int sig) 
{
	pid_t pid;
	sigset_t mask, prev;
	int olderrno = errno;
	sigfillset(&mask);
	sigprocmask(SIG_SETMASK, &mask, &prev);//日常……
	pid = fgpid(jobs);//获取前台进程组
	sigprocmask(SIG_SETMASK, &prev, NULL);
	if(pid != 0)
		kill(-pid, SIGTSTP);//发给前台进程组信号
	errno = olderrno;
    return;
}

3.2 程序实现(tsh.c的全部内容)(10分)

重点检查代码风格:
(1) 用较好的代码注释说明——5分
(2) 检查每个系统调用的返回值——5分

/* 
 * tsh - A tiny shell program with job control
 * xxx 11803xxxxx
 * <Put your name and login ID here>
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* Misc manifest constants */
#define MAXLINE    1024   /* max line size */
#define MAXARGS     128   /* max args on a command line */
#define MAXJOBS      16   /* max jobs at any point in time */
#define MAXJID    1<<16   /* max job ID */

/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1    /* running in foreground */
#define BG 2    /* running in background */
#define ST 3    /* stopped */

/* 
 * Jobs states: FG (foreground), BG (background), ST (stopped)
 * Job state transitions and enabling actions:
 *     FG -> ST  : ctrl-z
 *     ST -> FG  : fg command
 *     ST -> BG  : bg command
 *     BG -> FG  : fg command
 * At most 1 job can be in the FG state.
 */

/* Global variables */
extern char **environ;      /* defined in libc */
char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */
int verbose = 0;            /* if true, print additional output */
int nextjid = 1;            /* next job ID to allocate */
char sbuf[MAXLINE];         /* for composing sprintf messages */

struct job_t {              /* The job struct */
    pid_t pid;              /* job PID */
    int jid;                /* job ID [1, 2, ...] */
    int state;              /* UNDEF, BG, FG, or ST */
    char cmdline[MAXLINE];  /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */


/* Function prototypes */

/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv); 
void sigquit_handler(int sig);

void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs); 
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid); 
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid); 
int pid2jid(pid_t pid); 
void listjobs(struct job_t *jobs);

void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);

/*
 * main - The shell's main routine 
 */
int main(int argc, char **argv) 
{
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1; /* emit prompt (default) */

    /* Redirect stderr to stdout (so that driver will get all output
     * on the pipe connected to stdout) */
    dup2(1, 2);

    /* Parse the command line */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
        	case 'h':             /* print help message */
            	usage();
	   			break;
        	case 'v':             /* emit additional diagnostic info */
            	verbose = 1;
	    		break;
        	case 'p':             /* don't print a prompt */
            	emit_prompt = 0;  /* handy for automatic testing */
	    		break;
			default:
            	usage();
		}
    }

    /* Install the signal handlers */

    /* These are the ones you will need to implement */
    Signal(SIGINT,  sigint_handler);   /* ctrl-c */
    Signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */
    Signal(SIGCHLD, sigchld_handler);  /* Terminated or stopped child */

    /* This one provides a clean way to kill the shell */
    Signal(SIGQUIT, sigquit_handler); 

    /* Initialize the job list */
    initjobs(jobs);

    /* Execute the shell's read/eval loop */
    while (1) {

		/* Read command line */
		if (emit_prompt) {
	    	printf("%s", prompt);
	    	fflush(stdout);
		}
		if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
	    	app_error("fgets error");
		if (feof(stdin)) { /* End of file (ctrl-d) */
	    	fflush(stdout);
	    	exit(0);
		}

		/* Evaluate the command line */
		eval(cmdline);
		fflush(stdout);
		fflush(stdout);
    } 

    exit(0); /* control never reaches here */
}
  
/* 
 * eval - Evaluate the command line that the user has just typed in
 * 
 * If the user has requested a built-in command (quit, jobs, bg or fg)
 * then execute it immediately. Otherwise, fork a child process and
 * run the job in the context of the child. If the job is running in
 * the foreground, wait for it to terminate and then return.  Note:
 * each child process must have a unique process group ID so that our
 * background children don't receive SIGINT (SIGTSTP) from the kernel
 * when we type ctrl-c (ctrl-z) at the keyboard.  
*/
void eval(char *cmdline) 
{
    /* $begin handout */
    char *argv[MAXARGS]; /* argv for execve() */
    int bg;              /* should the job run in bg or fg? */
    pid_t pid;           /* process id */
    sigset_t mask;       /* signal mask */

    /* Parse command line */
    bg = parseline(cmdline, argv); 
    if (argv[0] == NULL)  
		return;   /* ignore empty lines */

    if (!builtin_cmd(argv)) { 

        /* 
		 * This is a little tricky. Block SIGCHLD, SIGINT, and SIGTSTP
		 * signals until we can add the job to the job list. This
		 * eliminates some nasty races between adding a job to the job
		 * list and the arrival of SIGCHLD, SIGINT, and SIGTSTP signals.  
		 */

		if (sigemptyset(&mask) < 0)//block  mask集合置空
	    	unix_error("sigemptyset error");
		if (sigaddset(&mask, SIGCHLD)) //添加SIGCHLD进mask集合
	    	unix_error("sigaddset error");
		if (sigaddset(&mask, SIGINT)) //添加SIGINT进集合
	    	unix_error("sigaddset error");
		if (sigaddset(&mask, SIGTSTP)) //添加SIGTSTP(来自终端的停止信号)进集合
	    	unix_error("sigaddset error");
		if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)//用mask更新BLOCK
	    	unix_error("sigprocmask error");

		/* Create a child process */
		if ((pid = fork()) < 0)
	    	unix_error("fork error");
	
		/* 
	 	* Child  process 
	 	*/

		if (pid == 0) {
	    	/* Child unblocks signals */
	    	sigprocmask(SIG_UNBLOCK, &mask, NULL);//用mask删除SIG_BLOCK

	    	/* Each new job must get a new process group ID 
	       	   so that the kernel doesn't send ctrl-c and ctrl-z
	       	   signals to all of the shell's jobs */
	        if (setpgid(0, 0) < 0) //将创建新的进程组,进程组ID为调用进程ID
				unix_error("setpgid error"); 

	    	/* Now load and run the program in the new job */
	    	if (execve(argv[0], argv, environ) < 0) {
				printf("%s: Command not found\n", argv[0]);
				exit(0);
	    	}
		}

		/* 
	 	* Parent process
	 	*/

		/* Parent adds the job, and then unblocks signals so that
	   	   the signals handlers can run again */
		addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);
		sigprocmask(SIG_UNBLOCK, &mask, NULL);//还原Block

		if (!bg) 
	    	waitfg(pid);//前台进行
		else
	    	printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);//后台进行
    }
    /* $end handout */
    return;
}

/* 
 * parseline - Parse the command line and build the argv array.
 * 
 * Characters enclosed in single quotes are treated as a single
 * argument.  Return true if the user has requested a BG job, false if
 * the user has requested a FG job.  
 */
int parseline(const char *cmdline, char **argv) 
{
    static char array[MAXLINE]; /* holds local copy of command line */
    char *buf = array;          /* ptr that traverses command line */
    char *delim;                /* points to first space delimiter */
    int argc;                   /* number of args */
    int bg;                     /* background job? */

    strcpy(buf, cmdline);
    buf[strlen(buf)-1] = ' ';  /* replace trailing '\n' with space */
    while (*buf && (*buf == ' ')) /* ignore leading spaces */
	buf++;

    /* Build the argv list */
    argc = 0;
    if (*buf == '\'') {
		buf++;
		delim = strchr(buf, '\'');
    }
    else {
		delim = strchr(buf, ' ');
    }

    while (delim) {
		argv[argc++] = buf;
		*delim = '\0';
		buf = delim + 1;
		while (*buf && (*buf == ' ')) /* ignore spaces */
	       buf++;

		if (*buf == '\'') {
	    	buf++;
	    	delim = strchr(buf, '\'');
		}
		else {
	    	delim = strchr(buf, ' ');
		}
    }
    argv[argc] = NULL;
    
    if (argc == 0)  /* ignore blank line */
		return 1;

    /* should the job run in the background? */
    if ((bg = (*argv[argc-1] == '&')) != 0) {
		argv[--argc] = NULL;
    }
    return bg;
}

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
	sigset_t mask, prev;
	sigfillset(&mask);//阻塞全部信号,为下面lishjobs调用全局变量jobs准备
	char *cmd = argv[0];//第一个字符
	if(cmd == NULL) //若为空,就直接返回,返回1表示不用进行if语句内包含的内容因为如果仅仅按下回车会使得strcmp失效
		return 1;
	if(!strcmp(cmd, "&"))//若只单单有一个&字符,同上
		return 1;
	if(!strcmp(cmd, "quit"))//若退出,则选择退出,并选用安全的退出函数
		_exit(0);
	if(!strcmp(cmd, "jobs")){//jobs列出当前的进程
		sigprocmask(SIG_BLOCK, &mask, &prev);//调用全局变量前需要阻塞全部信号
		listjobs(jobs);
		sigprocmask(SIG_SETMASK, &prev, NULL);//恢复原来信号
		return 1;
	}
	if(!strcmp(cmd, "bg") || !strcmp(cmd, "fg")){//交由do_bgfg函数处理
		do_bgfg(argv);
		return 1;
	}
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
    /* $begin handout */
    struct job_t *jobp=NULL;
    
    /* Ignore command if no argument */
    if (argv[1] == NULL) {
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
    }
    
    /* Parse the required PID or %JID arg */
    if (isdigit(argv[1][0])) {
		pid_t pid = atoi(argv[1]);
		if (!(jobp = getjobpid(jobs, pid))) {
	    	printf("(%d): No such process\n", pid);
	    	return;
		}
    }
    else if (argv[1][0] == '%') {
		int jid = atoi(&argv[1][1]);
		if (!(jobp = getjobjid(jobs, jid))) {
	    	printf("%s: No such job\n", argv[1]);
	    	return;
		}
    }	    
    else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
    }

    /* bg command */
    if (!strcmp(argv[0], "bg")) { 
		if (kill(-(jobp->pid), SIGCONT) < 0)
	    	unix_error("kill (bg) error");
		jobp->state = BG;
		printf("[%d] (%d) %s", jobp->jid, jobp->pid, jobp->cmdline);
    }

    /* fg command */
    else if (!strcmp(argv[0], "fg")) { 
		if (kill(-(jobp->pid), SIGCONT) < 0)
	    	unix_error("kill (fg) error");
		jobp->state = FG;
		waitfg(jobp->pid);
    }
    else {
		printf("do_bgfg: Internal error\n");
		exit(0);
    }
    /* $end handout */
    return;
}

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	sigset_t mask;
	sigemptyset(&mask);//在挂起进程的过程中清空block以便信号能够响应
	while(pid == fgpid(jobs)) {//前台工作的进程值比较
		sigsuspend(&mask);//挂起进程
	}
    return;
}

/*****************
 * Signal handlers
 *****************/

/* 
 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
 *     a child job terminates (becomes a zombie), or stops because it
 *     received a SIGSTOP or SIGTSTP signal. The handler reaps all
 *     available zombie children, but doesn't wait for any other
 *     currently running children to terminate.  
 */
void sigchld_handler(int sig) 
{
	int olderrno = errno, status;//status 用于检查回收子进程的退出状态
	sigset_t mask, prev;//用于阻塞全局共享数据结构
	struct job_t *jobfirst;//方便后续删除
	sigfillset(&mask);
	pid_t pid;//记录停止的子进程ID
	while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){//立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID
		sigprocmask(SIG_BLOCK, &mask, &prev);//删除进程时需要阻塞所有信号——引用了全局共享数据结构
		jobfirst = getjobpid(jobs, pid);//根据PID寻找进程
		if(WIFEXITED(status)) {//引起返回的子进程当前是正常停止的
			deletejob(jobs, pid);
		}
		else if(WIFSIGNALED(status)){//因一个未被捕获的信号终止
			printf("Job [%d] (%d) terminated by signal %d\n", jobfirst->jid, jobfirst->pid, WTERMSIG(status));
			deletejob(jobs, pid);
		}
		else if(WIFSTOPPED(status)){//因为子进程停止而停止
			jobfirst->state = ST;
			printf("Job [%d] (%d) stopped by signal %d\n", jobfirst->jid, jobfirst->pid, WSTOPSIG(status));
		}
		fflush(stdout);
		sigprocmask(SIG_SETMASK, &prev, NULL);//恢复Block
	}
	errno = olderrno;//还原errno
    return;
}

/* 
 * sigint_handler - The kernel sends a SIGINT to the shell whenver the
 *    user types ctrl-c at the keyboard.  Catch it and send it along
 *    to the foreground job.  
 */
void sigint_handler(int sig) 
{
	pid_t pid;
	sigset_t mask, prev;
	int olderrno = errno;
	sigfillset(&mask);
	sigprocmask(SIG_SETMASK, &mask, &prev);//日常……
	pid = fgpid(jobs);//获取前台进程组
	sigprocmask(SIG_SETMASK, &prev, NULL);
	if(pid != 0)
		kill(-pid, SIGINT);//发给前台进程组信号
	errno = olderrno;
    return;
}

/*
 * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
 *     the user types ctrl-z at the keyboard. Catch it and suspend the
 *     foreground job by sending it a SIGTSTP.  
 */
void sigtstp_handler(int sig) 
{
	pid_t pid;
	sigset_t mask, prev;
	int olderrno = errno;
	sigfillset(&mask);
	sigprocmask(SIG_SETMASK, &mask, &prev);//日常……
	pid = fgpid(jobs);//获取前台进程组
	sigprocmask(SIG_SETMASK, &prev, NULL);
	if(pid != 0)
		kill(-pid, SIGTSTP);//发给前台进程组信号
	errno = olderrno;
    return;
}

/*********************
 * End signal handlers
 *********************/

/***********************************************
 * Helper routines that manipulate the job list
 **********************************************/

/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
    job->pid = 0;
    job->jid = 0;
    job->state = UNDEF;
    job->cmdline[0] = '\0';
}

/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
    int i;

    for (i = 0; i < MAXJOBS; i++)
		clearjob(&jobs[i]);
}

/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs) 
{
    int i, max=0;

    for (i = 0; i < MAXJOBS; i++)
		if (jobs[i].jid > max)
	    	max = jobs[i].jid;
    return max;
}

/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) 
{
    int i;
    
    if (pid < 1)
	return 0;

    for (i = 0; i < MAXJOBS; i++) {
		if (jobs[i].pid == 0) {
	    	jobs[i].pid = pid;
	    	jobs[i].state = state;
	    	jobs[i].jid = nextjid++;
	    	if (nextjid > MAXJOBS)
			nextjid = 1;
	    	strcpy(jobs[i].cmdline, cmdline);
  	    	if(verbose){
	        	printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
            }
            return 1;
		}
    }
    printf("Tried to create too many jobs\n");
    return 0;
}

/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid) 
{
    int i;

    if (pid < 1)
		return 0;

    for (i = 0; i < MAXJOBS; i++) {
		if (jobs[i].pid == pid) {
	    	clearjob(&jobs[i]);
	    	nextjid = maxjid(jobs)+1;
	    	return 1;
		}
    }
    return 0;
}

/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) {
    int i;

    for (i = 0; i < MAXJOBS; i++)
		if (jobs[i].state == FG)
	    	return jobs[i].pid;
    return 0;
}

/* getjobpid  - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
    int i;

    if (pid < 1)
		return NULL;
    for (i = 0; i < MAXJOBS; i++)
		if (jobs[i].pid == pid)
	    	return &jobs[i];
    return NULL;
}

/* getjobjid  - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid) 
{
    int i;

    if (jid < 1)
	return NULL;
    for (i = 0; i < MAXJOBS; i++)
		if (jobs[i].jid == jid)
	    	return &jobs[i];
    return NULL;
}

/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid) 
{
    int i;

    if (pid < 1)
	return 0;
    for (i = 0; i < MAXJOBS; i++)
		if (jobs[i].pid == pid) {
            return jobs[i].jid;
        }
    return 0;
}

/* listjobs - Print the job list */
void listjobs(struct job_t *jobs) 
{
    int i;
    
    for (i = 0; i < MAXJOBS; i++) {
		if (jobs[i].pid != 0) {
	    	printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
	    	switch (jobs[i].state) {
				case BG: 
		    		printf("Running ");
		    		break;
				case FG: 
		    		printf("Foreground ");
		    		break;
				case ST: 
		    		printf("Stopped ");
		    		break;
	    		default:
		    		printf("listjobs: Internal error: job[%d].state=%d ", i, jobs[i].state);
	    	}
	    	printf("%s", jobs[i].cmdline);
		}
    }
}
/******************************
 * end job list helper routines
 ******************************/


/***********************
 * Other helper routines
 ***********************/

/*
 * usage - print a help message
 */
void usage(void) 
{
    printf("Usage: shell [-hvp]\n");
    printf("   -h   print this message\n");
    printf("   -v   print additional diagnostic information\n");
    printf("   -p   do not emit a command prompt\n");
    exit(1);
}

/*
 * unix_error - unix-style error routine
 */
void unix_error(char *msg)
{
    fprintf(stdout, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

/*
 * app_error - application-style error routine
 */
void app_error(char *msg)
{
    fprintf(stdout, "%s\n", msg);
    exit(1);
}

/*
 * Signal - wrapper for the sigaction function
 */
handler_t *Signal(int signum, handler_t *handler) 
{
    struct sigaction action, old_action;

    action.sa_handler = handler;  
    sigemptyset(&action.sa_mask); /* block sigs of type being handled */
    action.sa_flags = SA_RESTART; /* restart syscalls if possible */

    if (sigaction(signum, &action, &old_action) < 0)
		unix_error("Signal error");
    return (old_action.sa_handler);
}

/*
 * sigquit_handler - The driver program can gracefully terminate the
 *    child shell by sending it a SIGQUIT signal.
 */
void sigquit_handler(int sig) 
{
    printf("Terminating after receipt of SIGQUIT signal\n");
    exit(1);
}

第4章 TinyShell测试

总分15分

4.1 测试方法

针对tsh和参考shell程序tshref,完成测试项目4.1-4.15的对比测试,并将测试结果截图或者通过重定向保存到文本文件(例如:./sdriver.pl -t trace01.txt -s ./tsh -a “-p” > tshresult01.txt)。

这个txt文件不用交,可以pass哦

4.2 测试结果评价

tsh与tshref的输出在一下两个方面可以不同:
(1)PID
(2)测试文件trace11.txt, trace12.txt和trace13.txt中的/bin/ps命令,每次运行的输出都会不同,但每个mysplit进程的运行状态应该相同。
除了上述两方面允许的差异,tsh与tshref的输出相同则判为正确,如不同则给出原因分析。

4.3 自测试结果

4.3.1测试用例trace01.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.2测试用例trace02.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.3测试用例trace03.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.4测试用例trace04.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.5测试用例trace05.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.6测试用例trace06.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.7测试用例trace07.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.8测试用例trace08.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.9测试用例trace09.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.10测试用例trace10.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.11测试用例trace11.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.12测试用例trace12.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.13测试用例trace13.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.14测试用例trace14.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.15测试用例trace15.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗
4.3.16测试用例trace16.txt的输出截图(1分)
tsh测试结果tshref测试结果
在这里插入图片描述在这里插入图片描述
测试结论相同/不同,原因分析如下: 相同,对了呗

4.4 自测试评分

根据节4.3的自测试结果,程序的测试评分为:100 。

参考资料:
[1] tracy_668. linux系统下进程的信号处理流程[EB/OL].https://www.jianshu.com/p/4fd8e35a6580,2018:11-26.

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BUPT CSAPP Lab4是指北京邮电大学计算机科学与技术课程《CSAPP-深入理解计算机系统》的实验四。该实验主要涉及操作系统相关的内容,旨在帮助学生加深对操作系统的理解和认识。 该实验的主要内容是通过编写一个简单的Unix shell来实现一个命令行解释器。这个shell可以读取来自用户输入的命令,并执行这些命令。在实现过程中,需要学生理解和掌握进程控制、文件描述符、信号处理、文件I/O等操作系统的核心概念和技术。 实验4主要由以下几个部分组成: 1. 实现命令行解释器的基本功能,包括读取用户输入的命令、解析命令的参数和选项、执行命令等。此外,还需要处理输入输出重定向、管道、后台运行等特殊操作。 2. 实现信号处理功能,包括捕获和处理常见的信号(如SIGINT、SIGCHLD等),以及重新设置信号处理程序等。 3. 实现文件I/O功能,包括文件打开、读写、关闭等操作。需要学生理解文件描述符的概念和用法,并能正确地管理文件描述符。 4. 实现进程控制功能,包括创建新的进程、加载可执行文件、执行命令、等待子进程退出等。学生需要理解进程的创建、终止、调度等基本概念和原理,以及在实践中正确地使用这些操作。 通过完成该实验,学生可以加深对操作系统内核的理解,掌握操作系统的基本功能和组成,提高对计算机系统的整体把握能力。此外,实验也有助于学生培养编程能力、问题解决能力和团队合作能力等重要的综合素养。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值