[施工完成] CSAPP shell lab

Posted by 111qqz on Saturday, June 26, 2021

TOC

背景

动手实现一个简单的Lab,主要依赖于课本第八章的内容 感觉主要是05比较难。。发现执行的顺序不太对。。原因是SIGCHLD里面waitpid参数没写对。。 后面的就相对简单了 累计大概花了10个小时的样子

实现细节

built-in comamnd

built-in command 指的是shell自身的命令,因此只有少数几个,比如pwd.在上使用which pwd的时候,会提示"pwd: shell built-in command”

测试文件的构成

以trace04.txt举例

#
# trace04.txt - Run a background job.
#
/bin/echo -e tsh> ./myspin 1 \046
./myspin 1 &

这是两条测试命令。。第一条是调用了 “/bin/echo -e"来执行,而且这条命令是一个fg job, \046 是’&‘的ascii,这是输出字符串的一部分。

子进程中的log打印不正确

有些执行路径的log没有打印出来 可以调用flush(stdout) 确保打印

在执行fg job的时候,会等待上一个未结束的bg job执行

这是因为waitpid中的option写了默认的0,没有传入正确的option导致的

SIGCHLD的实现

感觉这个函数是整个Lab的难点

注意这里除了要对正常结束的process处理以外,也要处理因为其他原因导致的进程退出 可以根据下图的内容来判断是因为哪种原因进程结束的 这段调了好久。。虽然书上已经讲了比较多的情况。。。不过还是觉得略难

void sigchld_handler(int sig) {
	int olderrno = errno;
	sigset_t mask_all, prev_all;
	pid_t pid;
	int status;
	Sigfillset(&mask_all);

	while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
		if (WIFEXITED(status)) {
			Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			deletejob(jobs, pid);
			Sigprocmask(SIG_SETMASK, &prev_all, NULL);
		} else if (WIFSIGNALED(status)) {
			printf("Job (%d) [%d] terminated by signal %d\n",
			       pid2jid(pid), pid, WTERMSIG(status));
			Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);

			deletejob(jobs, pid);
			Sigprocmask(SIG_SETMASK, &prev_all, NULL);
		} else if (WIFSTOPPED(status)) {
			printf("Job (%d) [%d] stopped by signal %d\n",
			       pid2jid(pid), pid, WSTOPSIG(status));
			struct job_t* job = getjobpid(jobs, pid);
			if (job != NULL) {
				job->state = ST;
			}
		}
	}
	errno = olderrno;
}

do_bgfg

一开始可能会有些纠结。。怎么转换bg和fg呢。。 实际上直接改变state就好了 所谓bg job还是 fg job,其实区别就在于当前process要不要等其结束。。并没有什么本质区别

void do_bgfg(char** argv) {
	char* cmd = argv[0];
	char* id = argv[1];
	if (id == NULL) {
		printf("%s command requires PID or %%jobid argument\n", cmd);
		return;
	}

	struct job_t* job;
	if (id[0] == '%') {
		int jid = atoi(&id[1]);
		if (jid == 0) {
			printf("%s: argument must be a PID or %%jobid\n", cmd);
			return;
		}
		job = getjobjid(jobs, jid);
		if (job == NULL) {
			printf("%%%d: No such job\n", jid);
			return;
		}

	} else {
		pid_t pid = atoi(id);
		if (pid == 0) {
			printf("%s: argument must be a PID or %%jobid\n", cmd);
			return;
		}
		job = getjobpid(jobs, pid);
		if (job == NULL) {
			printf("(%d): No such process\n", pid);
			return;
		}
	}

	Kill(-job->pid, SIGCONT);
	if (cmd[0] == 'b') {
		job->state = BG;
		printf("[%d] (%d) %s", pid2jid(job->pid), job->pid,
		       job->cmdline);

	} else {
		job->state = FG;
		// 变为fg后,需要一直等到结束
		waitfg(job->pid);
	}
}

eval

直接让写可能确实比较有难度。。好在课本以及lab的说明上给了足够多的示例代码和hint.. 所以难度其实还好


void eval(char* cmdline) {
	char* parsed_args[MAXARGS];
	char buf[MAXLINE];

	strcpy(buf, cmdline);
	int bg = parseline(cmdline, &parsed_args[0]);
	if (parsed_args[0] == NULL) {
		// ignore empty lines
		return;
	}
	sigset_t mask_all, mask_one, prev_one;
	// mask_all 是屏蔽全部信号
	// mask_one 是屏蔽SIGCHLD信号
	Sigfillset(&mask_all);
	Sigemptyset(&mask_one);
	Sigaddset(&mask_one, SIGCHLD);

	if (builtin_cmd(parsed_args) == 0) {
		pid_t pid;
		// printf("bg:%d cmdline :%s\n", bg, cmdline);

		// block SIGCHLD
		Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
		if ((pid = Fork()) == 0) {
			// child process
			// printf("in child process\n");
			Sigprocmask(SIG_SETMASK, &prev_one,
				    NULL);  // unblock SIGCHLD
			// printf("before setpgid\n");
			setpgid(0, 0);
			// printf("before execve\n");

			// 打log记得调用fflush,不然可能还没来得及输出到屏幕上就exit了
			fflush(stdout);
			fflush(stdout);
			// 现在的background是虚假的。。其实还是会等待。。
			// 如果后面都是bg指令,就不会等待,但是如果有fg指令,就会等待。
			Execve(parsed_args[0], parsed_args, environ);
		}
		//	printf("pid =%d\n", pid);
		// printf("before add job block all signals\n");
		Sigprocmask(SIG_BLOCK, &mask_all, NULL);
		addjob(jobs, pid, bg ? BG : FG, cmdline);
		Sigprocmask(SIG_SETMASK, &prev_one, NULL);

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

完整代码参考: 这里