跳过正文
  1. Posts/

[施工完成] CSAPP shell lab

·1205 字·3 分钟
67 - 这篇文章属于一个选集。

背景
#

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

实现细节
#

built-in command
#

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

测试文件的构成
#

以 trace04.txt 举例:

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

这是两条测试命令。。第一条通过 “/bin/echo -e” 来执行,而且这条命令是一个 fg job;\046 是 ‘&’ 的 ASCII 码,是输出字符串的一部分。

子进程中的 log 打印不正确
#

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

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

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

SIGCHLD 的实现
#

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

注意这里除了要对正常结束的 process 处理以外,也要处理因为其他原因导致的进程退出,可以根据下图判断进程是哪种结束原因。

进程结束原因的判断流程

这段调了好久。。虽然书上已经讲了比较多的情况,不过还是觉得略难。

 1void sigchld_handler(int sig) {
 2	int olderrno = errno;
 3	sigset_t mask_all, prev_all;
 4	pid_t pid;
 5	int status;
 6	Sigfillset(&mask_all);
 7
 8	while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
 9		if (WIFEXITED(status)) {
10			Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
11			deletejob(jobs, pid);
12			Sigprocmask(SIG_SETMASK, &prev_all, NULL);
13		} else if (WIFSIGNALED(status)) {
14			printf("Job (%d) [%d] terminated by signal %d\n",
15			       pid2jid(pid), pid, WTERMSIG(status));
16			Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
17
18			deletejob(jobs, pid);
19			Sigprocmask(SIG_SETMASK, &prev_all, NULL);
20		} else if (WIFSTOPPED(status)) {
21			printf("Job (%d) [%d] stopped by signal %d\n",
22			       pid2jid(pid), pid, WSTOPSIG(status));
23			struct job_t* job = getjobpid(jobs, pid);
24			if (job != NULL) {
25				job->state = ST;
26			}
27		}
28	}
29	errno = olderrno;
30}

do_bgfg
#

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

job 在 FG/BG/ST 之间的状态转换如下:

job 状态机:FG、BG、ST 之间的转换
 1void do_bgfg(char** argv) {
 2	char* cmd = argv[0];
 3	char* id = argv[1];
 4	if (id == NULL) {
 5		printf("%s command requires PID or %%jobid argument\n", cmd);
 6		return;
 7	}
 8
 9	struct job_t* job;
10	if (id[0] == '%') {
11		int jid = atoi(&id[1]);
12		if (jid == 0) {
13			printf("%s: argument must be a PID or %%jobid\n", cmd);
14			return;
15		}
16		job = getjobjid(jobs, jid);
17		if (job == NULL) {
18			printf("%%%d: No such job\n", jid);
19			return;
20		}
21
22	} else {
23		pid_t pid = atoi(id);
24		if (pid == 0) {
25			printf("%s: argument must be a PID or %%jobid\n", cmd);
26			return;
27		}
28		job = getjobpid(jobs, pid);
29		if (job == NULL) {
30			printf("(%d): No such process\n", pid);
31			return;
32		}
33	}
34
35	Kill(-job->pid, SIGCONT);
36	if (cmd[0] == 'b') {
37		job->state = BG;
38		printf("[%d] (%d) %s", pid2jid(job->pid), job->pid,
39		       job->cmdline);
40
41	} else {
42		job->state = FG;
43		// 变为fg后,需要一直等到结束
44		waitfg(job->pid);
45	}
46}

eval
#

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

详细代码
 1
 2void eval(char* cmdline) {
 3	char* parsed_args[MAXARGS];
 4	char buf[MAXLINE];
 5
 6	strcpy(buf, cmdline);
 7	int bg = parseline(cmdline, &parsed_args[0]);
 8	if (parsed_args[0] == NULL) {
 9		// ignore empty lines
10		return;
11	}
12	sigset_t mask_all, mask_one, prev_one;
13	// mask_all 是屏蔽全部信号
14	// mask_one 是屏蔽SIGCHLD信号
15	Sigfillset(&mask_all);
16	Sigemptyset(&mask_one);
17	Sigaddset(&mask_one, SIGCHLD);
18
19	if (builtin_cmd(parsed_args) == 0) {
20		pid_t pid;
21		// printf("bg:%d cmdline :%s\n", bg, cmdline);
22
23		// block SIGCHLD
24		Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
25		if ((pid = Fork()) == 0) {
26			// child process
27			// printf("in child process\n");
28			Sigprocmask(SIG_SETMASK, &prev_one,
29				    NULL);  // unblock SIGCHLD
30			// printf("before setpgid\n");
31			setpgid(0, 0);
32			// printf("before execve\n");
33
34			// 打log记得调用fflush,不然可能还没来得及输出到屏幕上就exit了
35			fflush(stdout);
36			fflush(stdout);
37			// 现在的background是虚假的。。其实还是会等待。。
38			// 如果后面都是bg指令,就不会等待,但是如果有fg指令,就会等待。
39			Execve(parsed_args[0], parsed_args, environ);
40		}
41		//	printf("pid =%d\n", pid);
42		// printf("before add job block all signals\n");
43		Sigprocmask(SIG_BLOCK, &mask_all, NULL);
44		addjob(jobs, pid, bg ? BG : FG, cmdline);
45		Sigprocmask(SIG_SETMASK, &prev_one, NULL);
46
47		// parent wait child
48		if (!bg) {
49			waitfg(pid);
50		} else {
51			printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
52		}
53	}
54	return;
55}

完整代码参考:这里

67 - 这篇文章属于一个选集。

相关文章

[施工完成] CSAPP Cachelab

·1870 字·4 分钟
背景 # CSAPP:3e 的配套实验 地址 分成了两个部分,第一部分是模拟一下 cache 的 miss、hit、evict 的规则,第二部分是优化一个矩阵的转置,使得 miss 尽可能少。

【施工完成】CSAPP bomb lab

·5867 字·12 分钟
背景 # 疫情肆虐,在家百无聊赖,于是开始拆炸弹。 炸弹分为 6 个阶段,每个阶段必须输入一个特定的字符串,否则炸弹就会爆炸。 提供给我们的是一个 .c 文件和一个 linux 可执行文件 bomb。