TOC
Open TOC
Shell Lab
http://csapp.cs.cmu.edu/3e/shlab.pdf
Basic
- foreground and background
- job control
Specification
tsh → tiny shell
- prompt →
tsh>
- name + arguments
- built-in command → foreground
- assume that name is the path of an executable file, which it loads and runs in the context of an initial child process
- ctrl-c and ctrl-z
- Typing ctrl-c causes a SIGINT signal to be delivered to each process in the foreground job. The default action for SIGINT is to terminate the process.
- Typing ctrl-z causes a SIGTSTP signal to be delivered to each process in the foreground job. The default action for SIGTSTP is to place a process in the stopped state, where it remains until it is awakened by the receipt of a SIGCONT signal.
- end with &
- run the job in the background
- Each job can be identified by either a process ID (PID) or a job ID (JID)
- %5 denotes JID 5
- 5 denotes PID 5
- built-in commands
- quit
- 感觉在 Linux 中的等价物是 exit
- jobs
- The
bg <job>
command restarts<job>
by sending it a SIGCONT signal, and then runs it in the background. The<job>
argument can be either a PID or a JID - The
fg <job>
command restarts<job>
by sending it a SIGCONT signal, and then runs it in the foreground. The<job>
argument can be either a PID or a JID
- quit
- reap all of its zombie children
项目结构
我们需要修改 tsh.c
有一个参考程序 tshref 和一个 shell 驱动程序 sdriver.pl
驱动程序的用法如下
键入如下命令进行 trace01 的测试
Makefile 中提供了
还可以驱动参考程序
tshref.out 则给出了所有的参考程序的输出
trace01
直接就能过
trace02
处理一下内置命令 quit 即可
调用 parseline
trace03
需要在前台执行 /bin/echo
fork 出一个子进程并 execve
主进程还需要 waitpid
关键在于 waitpid 放在 sigchld_handler 中还是 waitfg 中
- In waitfg, use a busy loop around the sleep function.
- In sigchld handler, use exactly one call to waitpid.
注意 fork / addjob / deletejob 的周围需要进行信号的阻塞
可以开启 verbose 进一步观察行为是否一致
trace04
需要在后台执行 ./myspin
这个程序的行为是 sleep
trace05
需要实现 jobs 内置命令
直接调用 listjobs 即可
trace06
需要实现 sigint_handler
<C-c>
会发送一个 SIGINT 信号到前台进程组中的每个进程
这里需要理解 job 的分发
这两个 job 都是前台作业,/bin/echo 为 job [1],然后进入 sigchld_handler 被删除,所以 ./myspin 也为 job [1]
响应了 SIGINT 后,./myspin 被终止,随即进入 sigchld_handler 删除该 job,所以 waitfg 不能只用一个 pause 来实现
trace07
同时创建前台和后台作业,测试是否只对前台作业 SIGINT
正确实现 trace06 后,本阶段可以直接过
由于输出较长,考虑使用 diff 工具判断一致性
diff-so-fancy
尝试优化 Makefile,将 trace 索引作为参数传入
https://stackoverflow.com/questions/2826029/passing-additional-variables-from-command-line-to-make
trace08
同时创建前台和后台作业,测试是否只对前台作业 SIGTSTP
发现前面写的有点问题:
- 进程组的概念
- execve 前需要
setpgid(0, 0);
- sigint_handler 和 sigtstp_handler 中的 kill 为
kill(-pid, sig);
- execve 前需要
- waitfg 中需要进行信号的阻塞,而且位置很关键,同时考虑 eval 中的信号阻塞位置
- 何时需要进行信号的阻塞 → 访问全局数据结构
- waitpid 需要设置 options 为
WNOHANG | WUNTRACED
- 使用错误处理包装函数
- 疑问:使用 printf 输出并非异步信号安全
trace09 & 10
实现内置命令 bg 和 fg
根据第二个参数来获取对应的 job,并发送 SIGCONT 信号
- 若为 bg,打印一条信息
- 若为 fg,waitfg
trace11 & 12
前台作业会创建子进程,它们共同构成了前台进程组
测试向前台进程组的每个进程发送 SIGINT 信号或 SIGTSTP 信号
可以直接过
trace13
尝试重启前台进程组的每个进程
可以直接过
trace14
对 do_bgfg 进行简单的错误处理
trace15
大杂烩
可以直接过
trace16
使用 mystop 和 myint 在其他进程中对 tsh 发出信号
通过 getpid 得到 tsh 的 pid,即调用者
最大的区别是 tsh 的 sigint_handler 和 sigtstp_handler 不会响应该信号(不是由 tsh 发出)
tsh 发现僵死进程后,直接进入 sigchld_handler
可以直接过
总结
最关键的地方在前半段对进程组的理解
tsh 利用进程组的概念,通过信号机制,实现了对进程的管理与控制,使进程可以呈现并发运行的假象(底层机制为上下文切换)
tsh 本质上是一个并发程序
为了做这个 lab,重新配置了 Vim,期间还尝试了 diff-so-fancy,优化了 Makefile 来更好的验证正确性
开启 verbose,行为与 tshref 完全一致
发现在 Vim 或 Makefile 中执行命令行,是没有 .bashrc 中的配置的
最后要仔细看书,程序中的所有细节均能从书中找到
代码
实际上不到 300 行