CMU 15-213 Lab5 Shell Lab
时隔一年多重新开始写csapp的lap,这次回顾Shell Lab。
课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html
课程资料:https://github.com/EugeneLiu/translationCSAPP
课程视频:https://www.bilibili.com/video/av31289365/
参考资料:https://zhuanlan.zhihu.com/p/8922435
说明
为了完成该lab,可以先阅读官方的如下代码:
- http://csapp.cs.cmu.edu/3e/ics3/code/ecf/procmask1.c
- http://csapp.cs.cmu.edu/3e/ics3/code/ecf/procmask2.c
- http://csapp.cs.cmu.edu/3e/ics3/code/ecf/shellex.c
- http://csapp.cs.cmu.edu/3e/ics3/code/ecf/sigsuspend.c
剩余参考资料:
编译命令:
make tsh
测试命令:
make test0$i > r1
make rtest0$i > r2
diff r1 r2
辅助函数
// 更新状态
int updatejob(struct job_t *jobs, pid_t pid, int state) {
for (int i = 0; i < MAXJOBS; i++){
if (jobs[i].pid == pid) {
jobs[i].state = state;
return 1;
}
}
printf("Job %d does not find!\n", pid);
return 0;
}
/* jid2pid - Map job ID to process ID */
pid_t jid2pid(int jid) {
for (int i = 0; i < MAXJOBS; i++){
if (jobs[i].jid == jid){
return jobs[i].pid;
}
}
return 0;
}
eval
执行命令的主程序,整体结构参考了procmask2:
/*
* 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]; /* Argument list execve() */
char buf[MAXLINE]; /* Holds modified command line */
int bg; /* Should the job run in bg or fg? */
pid_t pid; /* Process id */
// 参考procmask2: mask_all表示屏蔽所有信号, mask_one屏蔽某指定信号, prev_one保存之前的值
sigset_t mask_all, mask_one, prev_one;
// 复制cmdline
strcpy(buf, cmdline);
bg = parseline(buf, argv);
// 如果不存在命令则返回
if (argv[0] == NULL) {
return;
}
// 判断是否为内置cmd, 如果是则直接运行, 否则fork & execve
if (!builtin_cmd(argv)) {
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);
// 阻塞SIGCHLD
// 把set中的信号添加到blocked中(blocked = blocked | set), 父进程屏蔽SIGCHLD信号
sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
if((pid = fork()) == 0) {
// child
// 恢复信号
// block = set
sigprocmask(SIG_SETMASK, &prev_one, NULL);
// 更改进程组id
if (setpgid(0, 0) < 0) {
unix_error("setpgid error!");
}
// 执行
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
// parent
// 阻塞所有信号, 防止出现先删除后添加的情形
sigprocmask(SIG_BLOCK, &mask_all, NULL);
// addjob
if (bg) {
addjob(jobs, pid, BG, buf);
}else {
addjob(jobs, pid, FG, buf);
}
// 恢复
sigprocmask(SIG_SETMASK, &prev_one, NULL);
// 如果是FG, 则等待任务结束
if (!bg) {
waitfg(pid);
}else{
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
}
return;
}
builtin_cmd
比较容易实现的一个函数,主要是通过判断来调用其他函数:
/*
* 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], "fg")) {
do_bgfg(argv);
return 1;
}else if (!strcmp(argv[0], "bg")) {
do_bgfg(argv);
return 1;
}else if (!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
}
return 0; /* not a builtin command */
}
do_bgfg
整体思路为先判断是pid还是jid,然后发送SIGCONT给进程组中每个进程,如果是FG则需要等待对应的进程结束:
/*
* do_bgfg - Execute the builtin bg and fg commands
*/
// 判断是否为数字
int is_number(char *str) {
int n = strlen(str);
for (int i = 0; i < n; i++) {
if (!isdigit(str[i])) {
return 0;
}
}
return 1;
}
void do_bgfg(char **argv)
{
// 判断是否有参数
if (argv[1] == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}
// 判断是否为jid
int isjid = (argv[1][0] == '%');
int id = -1;
// 解析id
if (isjid){
sscanf(argv[1], "%%%d", &id);
}else{
sscanf(argv[1], "%d", &id);
}
// 如果无法解析, 则报错
if (id == -1) {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
// jid转换为pid
pid_t pid;
if (isjid){
pid = jid2pid(id);
}else{
pid = id;
}
if (isjid){
// 查找jid对应的job
struct job_t *job = getjobjid(jobs, id);
if (job == NULL){
printf("%%%d: No such job\n", id);
return;
}
}else{
// 查找pid对应的job
struct job_t *job = getjobpid(jobs, id);
if (job == NULL){
printf("(%d): No such process\n", id);
return;
}
}
// 发送信号sig给进程组|pid|中的每个进程
if (!strcmp(argv[0], "fg")) {
// 发送信号
kill(-pid, SIGCONT);
// 更改状态
updatejob(jobs, pid, FG);
// 等待pid结束
waitfg(pid);
}else {
// 发送信号
kill(-pid, SIGCONT);
// 更改状态
updatejob(jobs, pid, BG);
struct job_t *job = getjobpid(jobs, pid);
printf("[%d] (%d) %s", pid2jid(pid), pid, job->cmdline);
}
return;
}
waitfg
参考8.5.7,以及sigsuspend.c文件:
/*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)
{
sigset_t mask;
// 初始化为空集合
sigemptyset(&mask);
// 如果存在fg进程
while (fgpid(jobs)) {
// 则等待直至进程结束
sigsuspend(&mask);
}
return;
}
sigchld_handler
利用waitpid获得状态,WNOHANG | WUNTRACED表示:立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID:
/*
* 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;
int status;
sigset_t mask_all, prev_all;
pid_t pid;
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)){
// 因为一个未捕获的信号终止
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
// 返回信号编号
int sig = WTERMSIG(status);
// 先输出后删除
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, sig);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
}else if (WIFSTOPPED(status)) {
// 如果引起返回的子进程当前是停止的
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
int sig = WSTOPSIG(status);
// 先输出后更新
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, sig);
updatejob(jobs, pid, ST);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
} else {
// 剩余情形直接删除
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
}
errno = olderrno;
return;
}
sigint_handler
终止前台进程所在进程组:
/*
* 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 = fgpid(jobs);
if (pid != 0){
kill(-pid, sig);
}
return;
}
sigtstp_handler
和sigint_handler类似:
/*
* 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 = fgpid(jobs);
if (pid != 0){
kill(-pid, sig);
}
return;
}
测试
编写脚本test.sh:
#! /bin/bash
for i in {1..9}
do
make test0$i > r1
make rtest0$i > r2
echo test$i >> res
diff r1 r2 >> res
echo -e "\n" >> res
done
for i in {10..16}
do
make test$i > r1
make rtest$i > r2
echo test$i >> res
diff r1 r2 >> res
echo -e "\n" >> res
done
查看res的结果:
test1
1c1
< ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace01.txt -s ./tshref -a "-p"
test2
1c1
< ./sdriver.pl -t trace02.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace02.txt -s ./tshref -a "-p"
test3
1c1
< ./sdriver.pl -t trace03.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace03.txt -s ./tshref -a "-p"
test4
1c1
< ./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace04.txt -s ./tshref -a "-p"
6c6
< [1] (5661) ./myspin 1 &
---
> [1] (5681) ./myspin 1 &
test5
1c1
< ./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace05.txt -s ./tshref -a "-p"
6c6
< [1] (5697) ./myspin 2 &
---
> [1] (5733) ./myspin 2 &
8c8
< [2] (5699) ./myspin 3 &
---
> [2] (5735) ./myspin 3 &
10,11c10,11
< [1] (5697) Running ./myspin 2 &
< [2] (5699) Running ./myspin 3 &
---
> [1] (5733) Running ./myspin 2 &
> [2] (5735) Running ./myspin 3 &
test6
1c1
< ./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace06.txt -s ./tshref -a "-p"
6c6
< Job [1] (5744) terminated by signal 2
---
> Job [1] (5750) terminated by signal 2
test7
1c1
< ./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace07.txt -s ./tshref -a "-p"
6c6
< [1] (5758) ./myspin 4 &
---
> [1] (5767) ./myspin 4 &
8c8
< Job [2] (5760) terminated by signal 2
---
> Job [2] (5769) terminated by signal 2
10c10
< [1] (5758) Running ./myspin 4 &
---
> [1] (5767) Running ./myspin 4 &
test8
1c1
< ./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace08.txt -s ./tshref -a "-p"
6c6
< [1] (5777) ./myspin 4 &
---
> [1] (5786) ./myspin 4 &
8c8
< Job [2] (5779) stopped by signal 20
---
> Job [2] (5788) stopped by signal 20
10,11c10,11
< [1] (5777) Running ./myspin 4 &
< [2] (5779) Stopped ./myspin 5
---
> [1] (5786) Running ./myspin 4 &
> [2] (5788) Stopped ./myspin 5
test9
1c1
< ./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace09.txt -s ./tshref -a "-p"
6c6
< [1] (5796) ./myspin 4 &
---
> [1] (5807) ./myspin 4 &
8c8
< Job [2] (5798) stopped by signal 20
---
> Job [2] (5809) stopped by signal 20
10,11c10,11
< [1] (5796) Running ./myspin 4 &
< [2] (5798) Stopped ./myspin 5
---
> [1] (5807) Running ./myspin 4 &
> [2] (5809) Stopped ./myspin 5
13c13
< [2] (5798) ./myspin 5
---
> [2] (5809) ./myspin 5
15,16c15,16
< [1] (5796) Running ./myspin 4 &
< [2] (5798) Running ./myspin 5
---
> [1] (5807) Running ./myspin 4 &
> [2] (5809) Running ./myspin 5
test10
1c1
< ./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace10.txt -s ./tshref -a "-p"
6c6
< [1] (5820) ./myspin 4 &
---
> [1] (5830) ./myspin 4 &
8c8
< Job [1] (5820) stopped by signal 20
---
> Job [1] (5830) stopped by signal 20
10c10
< [1] (5820) Stopped ./myspin 4 &
---
> [1] (5830) Stopped ./myspin 4 &
test11
1c1
< ./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace11.txt -s ./tshref -a "-p"
6c6
< Job [1] (5841) terminated by signal 2
---
> Job [1] (5850) terminated by signal 2
30,34c30,34
< 5836 pts/1 S+ 0:00 make test11
< 5837 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
< 5838 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p
< 5839 pts/1 S+ 0:00 ./tsh -p
< 5844 pts/1 R 0:00 /bin/ps a
---
> 5845 pts/1 S+ 0:00 make rtest11
> 5846 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tshref -a "-p"
> 5847 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tshref -a -p
> 5848 pts/1 S+ 0:00 ./tshref -p
> 5853 pts/1 R 0:00 /bin/ps a
test12
1c1
< ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace12.txt -s ./tshref -a "-p"
6c6
< Job [1] (5860) stopped by signal 20
---
> Job [1] (5871) stopped by signal 20
8c8
< [1] (5860) Stopped ./mysplit 4
---
> [1] (5871) Stopped ./mysplit 4
32,38c32,38
< 5855 pts/1 S+ 0:00 make test12
< 5856 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
< 5857 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p
< 5858 pts/1 S+ 0:00 ./tsh -p
< 5860 pts/1 T 0:00 ./mysplit 4
< 5861 pts/1 T 0:00 ./mysplit 4
< 5865 pts/1 R 0:00 /bin/ps a
---
> 5866 pts/1 S+ 0:00 make rtest12
> 5867 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace12.txt -s ./tshref -a "-p"
> 5868 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace12.txt -s ./tshref -a -p
> 5869 pts/1 S+ 0:00 ./tshref -p
> 5871 pts/1 T 0:00 ./mysplit 4
> 5872 pts/1 T 0:00 ./mysplit 4
> 5876 pts/1 R 0:00 /bin/ps a
test13
1c1
< ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
6c6
< Job [1] (5883) stopped by signal 20
---
> Job [1] (5896) stopped by signal 20
8c8
< [1] (5883) Stopped ./mysplit 4
---
> [1] (5896) Stopped ./mysplit 4
32,38c32,38
< 5878 pts/1 S+ 0:00 make test13
< 5879 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
< 5880 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
< 5881 pts/1 S+ 0:00 ./tsh -p
< 5883 pts/1 T 0:00 ./mysplit 4
< 5884 pts/1 T 0:00 ./mysplit 4
< 5887 pts/1 R 0:00 /bin/ps a
---
> 5891 pts/1 S+ 0:00 make rtest13
> 5892 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
> 5893 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
> 5894 pts/1 S+ 0:00 ./tshref -p
> 5896 pts/1 T 0:00 ./mysplit 4
> 5897 pts/1 T 0:00 ./mysplit 4
> 5900 pts/1 R 0:00 /bin/ps a
47c47
< 67 pts/0 Sl+ 0:22 /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/node /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/out/bootstrap-fork --type=watcherService
---
> 67 pts/0 Sl+ 0:23 /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/node /home/qinzhen/.vscode-server/bin/3866c3553be8b268c8a7f8c0482c0c0177aa8bfa/out/bootstrap-fork --type=watcherService
63,67c63,67
< 5878 pts/1 S+ 0:00 make test13
< 5879 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
< 5880 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
< 5881 pts/1 S+ 0:00 ./tsh -p
< 5890 pts/1 R 0:00 /bin/ps a
---
> 5891 pts/1 S+ 0:00 make rtest13
> 5892 pts/1 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
> 5893 pts/1 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
> 5894 pts/1 S+ 0:00 ./tshref -p
> 5903 pts/1 R 0:00 /bin/ps a
test14
1c1
< ./sdriver.pl -t trace14.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace14.txt -s ./tshref -a "-p"
8c8
< [1] (5912) ./myspin 4 &
---
> [1] (5931) ./myspin 4 &
24c24
< Job [1] (5912) stopped by signal 20
---
> Job [1] (5931) stopped by signal 20
28c28
< [1] (5912) ./myspin 4 &
---
> [1] (5931) ./myspin 4 &
30c30
< [1] (5912) Running ./myspin 4 &
---
> [1] (5931) Running ./myspin 4 &
test15
1c1
< ./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
---
> ./sdriver.pl -t trace15.txt -s ./tshref -a "-p"
8c8
< Job [1] (5951) terminated by signal 2
---
> Job [1] (5971) terminated by signal 2
10c10
< [1] (5953) ./myspin 3 &
---
> [1] (5973) ./myspin 3 &
12c12
< [2] (5955) ./myspin 4 &
---
> [2] (5975) ./myspin 4 &
14,15c14,15
< [1] (5953) Running ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Running ./myspin 3 &
> [2] (5975) Running ./myspin 4 &
17c17
< Job [1] (5953) stopped by signal 20
---
> Job [1] (5973) stopped by signal 20
19,20c19,20
< [1] (5953) Stopped ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Stopped ./myspin 3 &
> [2] (5975) Running ./myspin 4 &
24c24
< [1] (5953) ./myspin 3 &
---
> [1] (5973) ./myspin 3 &
26,27c26,27
< [1] (5953) Running ./myspin 3 &
< [2] (5955) Running ./myspin 4 &
---
> [1] (5973) Running ./myspin 3 &
> [2] (5975) Running ./myspin 4 &
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere