这个实验(文件下载)就是写一个简单的Unix Shell,也就是读取命令,创建进程,运行程序,reap僵尸进程等功能,实验文件当中已经设计好了基本框架,只要把几个函数填上就好了完整代码

eval

这是一个非常重要的函数,它要负责命令的执行。需要注意以下几点: ​

  1. 避免子进程在添加到列表之前被reap掉,在运行程序和添加任务的过程中务必要屏蔽SIGCHLD。由于子进程会集成父进程的特性,所以记得要在子进程中调用execve前解除屏蔽;

  2. 子进程和父进程在同一个进程组之中,为了避免Signal对子进程的运行产生干扰,应该把子进程放入一个新的进程组,所以要在调用execve前调用setpgid(0, 0);

  3. 由于子进程的reap依赖于SIGCHLD,所以waitfg的调用要放在解除屏蔽之后。

/* 
 * 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) 
{
	char *argv[MAXARGS];	/* Arguement list execve */
	int bg;					/* Should the job run in bg or fg */
	pid_t pid;				/* Process id */
	sigset_t mask;			

	bg = parseline(cmdline, argv);

	/* Ignore empty line */
	if (argv[0] == NULL)
		return;

	if (!builtin_cmd(argv)) { 

		/* Block SIGCHLD */
		sigemptyset(&mask);
		sigaddset(&mask, SIGCHLD);
		sigprocmask(SIG_BLOCK, &mask, NULL);

		/* Child runs user job */
		if ((pid = fork()) == 0) {
			setpgid(0, 0);
			sigprocmask(SIG_UNBLOCK, &mask, NULL);		/* Unlock SIGCHLD */
			if (execve(argv[0], argv, environ) < 0) {
				printf("%s: Command not found.\n", argv[0]);
				exit(0);
			}
		}

		addjob(jobs, pid, bg ? BG : FG, cmdline);
		sigprocmask(SIG_UNBLOCK, &mask, NULL);		/* Unlock SIGCHLD */

		if (!bg) 
			waitfg(pid);
		else 
			printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
	}
    return;
}

builtin_cmd

这个函数就是用来识别buildin命令并运行的,非常容易。

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
	if (!strcmp(argv[0], "quit"))
		exit(0);
	else if (!strcmp(argv[0], "jobs")) {
		listjobs(jobs);
		return 1;
	} else if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) {
		do_bgfg(argv);
		return 1;
	}
    return 0;     /* not a builtin command */
}

do_bgfg

这个函数进行前后台切换,需要做的就是修改一下任务的状态,发送SIGCONT给进程。如果进程切换成后台运行,就输出进程对应的jid、pid、cmdline。如果进程切换成前台运行,就调用waitfg。

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
	int jid, pid;
	struct job_t *job;

	/* Parse pid or jid and get job */
	if (argv[1] == NULL) {			/* no argument */
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
	} else if (*argv[1] == '%') {	/* by jid */
		if (!isdigit(*(argv[1]+1))) {
			printf("fg command requires PID or %%jobid argument\n");
			return;
		}
		jid = atoi(argv[1]+1);
		if ((job = getjobjid(jobs, jid)) == NULL) {
			printf("%%%d: No such job\n", jid);
			return;
		}
	} else {						/* by pid */
		if (!isdigit(*argv[1])) {
			printf("bg command requires PID or %%jobid argument\n");
			return;
		}
		pid = atoi(argv[1]);
		if ((job = getjobpid(jobs, pid)) == NULL) {
			printf("(%d): No such process\n", pid);
			return;
		}
	}

	if (!strcmp(argv[0], "bg")) {
		printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
		job->state = BG;
		kill(-(job->pid), SIGCONT);
	} else if (!strcmp(argv[0], "fg")) {
		job->state = FG;
		kill(-(job->pid), SIGCONT);
		waitfg(job->pid);
	}
    return;
}

waitfg

只要进程pid在前台运行,就一直等待,直到进程终止或者停止。

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	struct job_t *job = getjobpid(jobs, pid);
	while (job->state == FG)
		sleep(1);
}

sigchld_handler

这是处理SIGCHLD的函数,子进程一旦terminated或者stopped或者自然结束,它就会被触发。用waitpid判断到底是哪个进程触发的,然后再根据不同的情况采取不同的措施。如果进程是自然结束的,那么直接把它从任务列表里删掉就好;如果进程是被强制terminated的,那么在把它从任务列表里删除的同时,还要输出导致进程终止的Signal类型;如果进程stopped,那么 把任务的状态设为ST(stopped),同时输出导致进程中止的Signal类型。

/* 
 * 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 status;
	pid_t pid;
	if ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0) {
		if (WIFSIGNALED(status)) {
			printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
			deletejob(jobs, pid);
		} else if (WIFSTOPPED(status)) {
			printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
			getjobpid(jobs, pid)->state = ST;
		} else
			deletejob(jobs, pid);
	}
}

sigint_handler

这个函数用来处理SIGINT ,我们只需要把这个Signal发送给前台进程就好了。为了让进程组中的所有进程可以接收到Signal,一定要在pid前加上负号

/* 
 * 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;	
	if ((pid = fgpid(jobs))) {
		kill(-pid, SIGINT);
	}
}

sigtstp_handler

这个函数用来处理SIGSTP,同样地,把这个Signal发送给前台进程。

/*
 * 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;	
	if ((pid = fgpid(jobs))) {
		kill(-pid, SIGTSTP);
	}
}