CSAPP-ShellLab

ShellLab

教训

一开始并没有仔细看题目, 没有按照关卡一步一步完善shell, 把什么父子进程竞争, 前台进程挂起shell, 作业添加删除………杂七杂八的东西一股脑全写上去, 最终导致的结果就是拿去运行了一下, 连最基本的quit指令都运行不了, 提示是段错误(核心已转储). 忙活两天还是个小丑.

所以还是得扎扎实实一步一步来. 先把书看明白了, 然后把实验说明书看完了(虽然是英文的, 但是还得看). 再来做这个shell lab.

第一关

不用做任何调整都是对的

Untitled

第二关

测试quit指令, 直接在builtin_cmd里修改即可

Untitled

测试一下, 是正确的

Untitled

第三关

这一关是运行一个前台作业, 这就要求对shell建立起一个基本框架了.

我是在第四关的时候才想起来要写WP, 所以这里的代码没有粘贴下来, 但是大部分都是参考书中P525的代码. 这一道题的前台进程不用挂起shell也是正确的, 所以照抄应该也能过.

第四关

这一关是先运行一个前台进程(第一条指令用的是Linux的绝对路径, 一开始没看出来, 后来参考别人的实验博客才知道是运行一个打印字符串的前台进程), 然后再运行一个后台进程.

涉及到的知识点:

  • 因为运行后台进程后要打印作业信息, 所以我们要添加和删除作业信息
  • 前台进程需要挂起
  • 父子进程之间的race

贴一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
volatile pid_t hangpid;//用于挂起的while循环判断

void eval(char *cmdline)
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
int bgjid;
sigset_t mask_all_job, prev_all_job;//为作业设置而成立的信号集合
sigset_t mask_hang, prev_hang;

/*这里的信号设置针对工作添加*/
sigfillset(&mask_all_job);
signal(SIGCHLD, sigchld_handler);
strcpy(buf, cmdline);
/*这里的信号针对父子进程race*/
sigemptyset(&mask_hang);
sigaddset(&mask_hang, SIGCHLD);

bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return;

if (!builtin_cmd(argv))
{
sigprocmask(SIG_BLOCK, &mask_hang, &prev_hang);//这里阻塞是为了防止父子进程间的竞争, 防止先delete后add

if ((pid = fork()) == 0){
if (execve(argv[0], argv, environ) < 0){
printf("Command not found.\n");
exit(0);
}
}

if (!bg){
hangpid = 0;
/*这里针对job的信号阻塞是防止addjob被中断, 但是我看其他人的博客并没有这样的操作, 所以后面可能会修改*/
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
addjob(jobs, pid, 1, cmdline);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);
/*挂起父进程, 等待SIGCHLD*/
waitfg(hangpid);

}

else{ /*这里针对job的信号阻塞是防止addjob被中断, 但是我看其他人的博客并没有这样的操作, 所以后面可能会修改*/
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
addjob(jobs, pid, 2, cmdline);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);

bgjid = pid2jid(pid);
printf("[%d] (%d) %s", bgjid, pid, cmdline);
}
}
return;
}

void waitfg(pid_t pid)
{
sigset_t prev_hang;

sigemptyset(&prev_hang);
while (!hangpid)
sigsuspend(&prev_hang);
return;
}

void sigchld_handler(int sig)
{
int olderrno = errno;

sigset_t mask_all_job, prev_all_job;//为job创建的信号集合

sigfillset(&mask_all_job);
/*1.hangpid使得父进程不再挂起; 2.删除作业*/
while ((hangpid = waitpid(-1, NULL, 0)) > 0)
{
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
deletejob(jobs, hangpid);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);
}
if (errno != ECHILD)
unix_error("waitpid error");
errno = olderrno;
}

中间有个错误没注意, 导致shell一直被挂起.
就是waitfg的形式参数设置为hangpid跟全局变量hangpid冲突.

最后检验一下

Untitled

第五关

这一关是运行两个后台作业, 并用jobs内置指令打印出两个作业的信息. (但其实是运行了三个前台进程, 两个后台进程. 这个检验指令的机制应该是屏蔽掉了我们对shell的输入, 只保留了shell对我们的输出. 总而言之, 我们是可以把这个这些行为当成是运行两个后台作业, 然后打印作业信息.)

这道题的涉及的知识点:

  • waitpid的option的选择
  • 如何使用fgpid来实现前台进程挂起, 而不是我们之前用到的全局变量hangpid
  • 对于我个人而言(因为前面eval有漏洞), 我还要考虑到修改hangpid的影响
  • jobs内置指令的实现

关于前台进程挂起的实现, 在第四关是使用书上的方法, 但是在书中的情景没有题目那么复杂: 书中的挂起是单个前台作业, 而没有后台作业; 但是在我们实现第五关时却有两个后台作业.

修正

首先要知道我在哪里错了, 在我的调试中: 我的后台进程会使得shell挂起, 直到终止, 最终导致了我在使用jobs指令后什么都没有打印出来.

第一步修正

我首先发现的是sigchld_handler()中的while ((hangpid = waitpid(-1, NULL, 0)) > 0)的存在错误, 我们假设有两个后台进程在同一个进程组中, 当第一个后台进程终止时会发送SIGCHLD信号给内核, 然后进入sigchld_handler()中, 回收完第一个后台进程后, 又会继续循环, 但是我们的第三个参数为0, 也就是默认行为, waitpid会等待第二个后台进程结束发送信号. 而我们这道题一个后台作业的实际实现是: 先运行一个前台作业打印我们的输入, 然后再运行我们的后台作业, 我认为这两个作业的父进程都是shell(虽然它们的作业组不一样), 这样当第一个前台进程结束的时候, waitpid会等待后台进程结束. 所以我们修改掉waitpid的默认行为为对停止和终止不等待WNOHANG | WUNTRACED

修改完了以后, 发现还是不行, 甚至连第五关的整个流程都跑不完了.

第二步修正

然后发现的是hangpid的问题, 经过检验发现实际实现用到的前台进程再改完了以后, 用于判断是否挂起的全局变量hangpid再最终判断处while (!hangpid)一直都是0, 也就导致了前台进程一直挂起.原因是:

  • 在我们修改waitpid默认行为前, sigchld_handler()的实际处理方式是一直等待SIGCHLD信号, 直到不再有子进程; 也就是说返回值给hangpid只会是非零数(即便发生错误, 也返回的是负数而不是零), 这导致了不管怎样到了最后挂起都会被终止.
  • 而我们将waitpid的默认行为修改后, sigchld_handler()的处理方式是不等待了, 当处理完了一个僵死进程后, 如果后面没有更多的僵死进程了, 那就直接返回0; 再结合while循环, 也就是说到了循环的最后hangpid都会是零. 在题目的情境下, 挂起了第一个前台作业, 然后前台作业终止, 进入sigchld_handler()中, 第一次循环hangpid为回收的pid, 然后回到循环检查, 第二次检查hangpid被赋值为0. 离开sigchld_handler()函数, 来到挂起判断那里, 就一直挂起了

所以我们的第一部修正是没有问题的, 问题在于我们不应该使用hangpid来作为挂起判断依据.

我们将用fgpid()函数来代替hangpid判断是否需要挂起.(这里是参考了别人的博客的, 其实博客里的一些东西实验说明书有. 才发现读实验说明书是真的很重要!!!!!, 里面会教你使用实验室, 给你hint, 给你更详细的实验要求.)

fgpid()函数是直接查找你的作业列表里面没有前台作业, 如果有就挂起, 如果没有就取消挂起. 在sigchld_handler()中我们就删除了第一个前台作业了, 所以使用这个就可以不用管waitpid的返回值是什么了.非常好用.

第三步修改

前面的两步修改其实已经完成了我们的主要工作了, 这个第三步修改主要是清理掉已经没有用处的hangpid, 详细的就是在sigchld_handler()中把所有的hangpid换成局部变量pid, 然后删除掉在eval中的hangpid, 以及hangpid的定义

一下是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
void eval(char *cmdline) 
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
int bgjid;
sigset_t mask_all_job, prev_all_job;//为作业设置而成立的信号集合
sigset_t mask_hang, prev_hang;

/*这里的信号设置针对工作添加*/
sigfillset(&mask_all_job);
signal(SIGCHLD, sigchld_handler);
strcpy(buf, cmdline);
/*这里的信号针对父子进程race*/
sigemptyset(&mask_hang);
sigaddset(&mask_hang, SIGCHLD);

bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return;

if (!builtin_cmd(argv)){
sigprocmask(SIG_BLOCK, &mask_hang, &prev_hang);

if ((pid = fork()) == 0){
if (execve(argv[0], argv, environ) < 0){
printf("Command not found.\n");
exit(0);
}
}
if (!bg){
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
addjob(jobs, pid, 1, cmdline);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);
waitfg(pid);
}
else{
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
addjob(jobs, pid, 2, cmdline);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);

bgjid = pid2jid(pid);
printf("[%d] (%d) %s", bgjid, pid, cmdline);
}
}
return;
}

void waitfg(pid_t pid){
sigset_t prev_hang;
sigemptyset(&prev_hang);

while (pid == fgpid(jobs))
sigsuspend(&prev_hang);
return;
}

void sigchld_handler(int sig){
int status;
pid_t pid;
int olderrno = errno;
sigset_t mask_all_job, prev_all_job;

sigfillset(&mask_all_job);

while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){
sigprocmask(SIG_BLOCK, &mask_all_job, &prev_all_job);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev_all_job, NULL);
}
errno = olderrno;

}

最终的检验

Untitled

第六关

这一关是用Ctrl + c(对应的信号是SIGINT)来结束一个前台作业(注意, 是前台!!!)

知识点考察:

  • 如何分辨前台和后台作业: fgpid()
  • 如何发送SIGINT信号: kill()

这一关还是比较直接的, 我们只用修改sigint_handler()即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void sigint_handler(int sig) 
{
pid_t int_fg_pid;
int int_jid;
sigset_t mask_all_int, prev_all_int;
sigfillset(&mask_all_int);
/*保护赋值操作不被中断*/
sigprocmask(SIG_BLOCK, &mask_all_int, &prev_all_int);
int_fg_pid = fgpid(jobs);
int_jid = pid2jid(int_fg_pid);
sigprocmask(SIG_SETMASK, &prev_all_int, NULL);
/*如果存在前台作业的话, 就发送SIGINT信号给那个前台作业*/
if(int_fg_pid)
{
printf("Job [%d] (%d) terminated by signal 2\n", int_jid, int_fg_pid);
kill(-int_fg_pid, SIGINT);
}
return;
}

检验

Untitled

第七关

要求是只将SIGINT信号发送给前台进程, 中断前台进程

知识点考察:

  • 使用status状态来处理不同的情况(进程正常终止, 进程因SIGINT中止)

才知道上面的sigint处理函数是错的, printf要放在sigint函数中去

在进行第七关的修改的同时, 前面的eval()和sigchld处理函数中信号阻塞太乱了, 所以接着第六关的三个修改我们再次添加一个第四次修改.

此外还修改了sigchld_handler中的情况分类, 感觉在这里对sigchld_handler有了更加清楚的认识, 这个信号处理不仅仅只是针对一个信号, 而是对多种信号进行分类处理, 通过status来区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
void sigchld_handler(int sig) 
{
int status;
pid_t pid;
int olderrno = errno;
sigset_t mask_all, mask_prev;
sigfillset(&mask_all);

/*删除作业*/
while ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0)
{
sigprocmask(SIG_BLOCK, &mask_all, &mask_prev);//下面的情况都需要信号屏蔽,所以直接在开头和结尾处屏蔽和取消屏蔽信号即可

if (WIFEXITED(status))
{
deletejob(jobs, pid);
}

if (WIFSIGNALED(status))
{
printf("Job [%d] (%d) terminated by signal 2\n", pid2jid(pid), pid);
deletejob(jobs, pid);

}

sigprocmask(SIG_SETMASK, &mask_prev, NULL);
}
errno = olderrno;
}

void eval(char *cmdline)
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
sigset_t mask_all, mask, mask_prev;

sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigfillset(&mask_all);

strcpy(buf, cmdline);

bg = parseline(buf, argv);
if (argv[0] == NULL)
return;

if (!builtin_cmd(argv))
{
sigprocmask(SIG_BLOCK, &mask, &mask_prev);//这里阻塞是为了防止父子进程间的竞争, 防止先delete后add

if ((pid = fork()) == 0)//对于子进程
{
sigprocmask(SIG_SETMASK, &mask_prev, NULL);
setpgid(0, 0);
if (execve(argv[0], argv, environ) < 0)
{
printf("Command not found.\n");
exit(0);
}
}

if (!bg)
{
sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, 1, cmdline);
sigprocmask(SIG_SETMASK, &mask, NULL);
waitfg(pid);
}
else
{
sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, 2, cmdline);
sigprocmask(SIG_SETMASK, &mask, NULL);
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
sigprocmask(SIG_SETMASK, &mask_prev, NULL);
}
return;
}

void sigint_handler(int sig)
{
pid_t int_fg_pid = fgpid(jobs);

if(int_fg_pid)
{
// printf("即将发送SIGINT信号\n");
kill(-int_fg_pid, SIGINT);
}
return;
}

第八关

要求: 使用SIGSTP信号只(!!!!)让前台作业停止

考察知识点:

  • 前台后台作业的区别
  • sigtstp_handler的编写
  • WIFSTOPPED(status)来区别信号情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void sigchld_handler(int sig) 
{
int status;
pid_t pid;
int olderrno = errno;
sigset_t mask_all, mask_prev;
sigfillset(&mask_all);
struct job_t *job;

/*删除作业*/
while ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0)
{
sigprocmask(SIG_BLOCK, &mask_all, &mask_prev);

if (WIFEXITED(status))
{
deletejob(jobs, pid);
}

if (WIFSIGNALED(status))
{
printf("Job [%d] (%d) terminated by signal 2\n", pid2jid(pid), pid);
deletejob(jobs, pid);
}

if (WIFSTOPPED(status))
{
job = getjobpid(jobs, pid);
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
}

sigprocmask(SIG_SETMASK, &mask_prev, NULL);
}
errno = olderrno;
}

void sigtstp_handler(int sig)
{
pid_t pid = fgpid(jobs);
if (pid)
kill(-pid, SIGTSTP);
return;
}

检验

实现的结果

Untitled

实验室提供的结果

Untitled

第九关

实现内置指令bg(讲挂起的进程转入后台运行)和fg(讲挂起的进程转入前台进行)

知识点: 就是重新编写biltin_cmd函数和do_bgfg函数

贴上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int builtin_cmd(char **argv) 
{
sigset_t mask_all, mask_prev;
sigfillset(&mask_all);

if (!strcmp(argv[0], "quit"))
{
exit(0);
}
if(!strcmp(argv[0],"jobs")){
sigprocmask(SIG_BLOCK, &mask_all, &mask_prev);
listjobs(jobs);
sigprocmask(SIG_SETMASK, &mask_prev, NULL);
return 1;
}
if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
{
do_bgfg(argv);
return 1;
}
return 0;
}

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

if (!strcmp(argv[0], "bg"))
{
jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
job->state = BG;//将其状态改为后台进程, 这样shell就不会挂起等待该进程了
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
printf("[%d] (%d) %s", jid, job->pid, job->cmdline);
}

if (!strcmp(argv[0], "fg"))
{
jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
job->state = FG;//将其状态改为前台进程, shell挂起等待该进程
waitfg(job->pid);
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
printf("[%d] (%d) %s", jid, job->pid, job->cmdline);
}
return;
}

检验

Untitled

第十关

fg内置指令的更加全面的实现:

  • 将挂起的进程转到前台运行(上一个关卡已经实现了)
  • 讲一个后台运行的进程转到前台

而挂起的进程跟一个后台运行的进程最直观(对于shell来说)的不同就是其结构体成员的state不同, 所以我们的根据state再次分情况讨论即可.关于fg的第一个情况的处理有错误的地方: 1.waitfg要放在kill后面, 否则进程会先挂起shell, 然后再发送kill的时候已经发送不了了, 因为已经挂起了, shell就会卡死.2. fg不用printf打印

贴一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void do_bgfg(char **argv) 
{
int jid;
struct job_t *job;

if (!strcmp(argv[0], "bg"))
{
jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
job->state = BG;//将其状态改为后台进程, 这样shell就不会挂起等待该进程了
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
printf("[%d] (%d) %s", jid, job->pid, job->cmdline);
}

if (!strcmp(argv[0], "fg"))
{
jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
if (job->state == ST)
{
job->state = FG;//将其状态改为前台进程, shell挂起等待该进程
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
waitfg(job->pid);
}
if (job->state == BG)
{
job->state = FG;
waitfg(job->pid);
}
}
return;
}

检验

Untitled

第十一关

这一关是发送SIGINT给前台进程组的每一个进程

检验

即便不做修改, 结果跟参考是一样

Untitled

Untitled

第十二关

跟上面差不多, 不做修改也可以过

Untitled

Untitled

第十三关

跟上面一样, 不做修改也可以通过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
这个是tsh的:
./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
#
# trace13.txt - Restart every stopped process in process group
#
tsh> ./mysplit 4
Job [1] (9578) stopped by signal 20
tsh> jobs
[1] (9578) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
1214 tty2 Ssl+ 0:00 /usr/libexec/gdm-wayland-session env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
1229 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --session=ubuntu
4709 pts/0 Ss 0:00 bash
9084 pts/0 S 0:00 ./tsh -p
9086 pts/0 T 0:00 ./myspin 4
9156 pts/0 S 0:00 ./tsh -p
9158 pts/0 T 0:00 ./myspin 4
9573 pts/0 S+ 0:00 make test13
9574 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
9575 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
9576 pts/0 S+ 0:00 ./tsh -p
9578 pts/0 T 0:00 ./mysplit 4
9579 pts/0 T 0:00 ./mysplit 4
9582 pts/0 R 0:00 /bin/ps a
tsh> fg %1
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
1214 tty2 Ssl+ 0:00 /usr/libexec/gdm-wayland-session env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
1229 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --session=ubuntu
4709 pts/0 Ss 0:00 bash
9084 pts/0 S 0:00 ./tsh -p
9086 pts/0 T 0:00 ./myspin 4
9156 pts/0 S 0:00 ./tsh -p
9158 pts/0 T 0:00 ./myspin 4
9573 pts/0 S+ 0:00 make test13
9574 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
9575 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
9576 pts/0 S+ 0:00 ./tsh -p
9585 pts/0 R 0:00 /bin/ps a

这个是tshref的:
make rtest13
./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
#
# trace13.txt - Restart every stopped process in process group
#
tsh> ./mysplit 4
Job [1] (9591) stopped by signal 20
tsh> jobs
[1] (9591) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
1214 tty2 Ssl+ 0:00 /usr/libexec/gdm-wayland-session env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
1229 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --session=ubuntu
4709 pts/0 Ss 0:00 bash
9084 pts/0 S 0:00 ./tsh -p
9086 pts/0 T 0:00 ./myspin 4
9156 pts/0 S 0:00 ./tsh -p
9158 pts/0 T 0:00 ./myspin 4
9586 pts/0 S+ 0:00 make rtest13
9587 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
9588 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
9589 pts/0 S+ 0:00 ./tshref -p
9591 pts/0 T 0:00 ./mysplit 4
9592 pts/0 T 0:00 ./mysplit 4
9595 pts/0 R 0:00 /bin/ps a
tsh> fg %1
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
1214 tty2 Ssl+ 0:00 /usr/libexec/gdm-wayland-session env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --session=ubuntu
1229 tty2 Sl+ 0:00 /usr/libexec/gnome-session-binary --systemd --session=ubuntu
4709 pts/0 Ss 0:00 bash
9084 pts/0 S 0:00 ./tsh -p
9086 pts/0 T 0:00 ./myspin 4
9156 pts/0 S 0:00 ./tsh -p
9158 pts/0 T 0:00 ./myspin 4
9586 pts/0 S+ 0:00 make rtest13
9587 pts/0 S+ 0:00 /bin/sh -c ./sdriver.pl -t trace13.txt -s ./tshref -a "-p"
9588 pts/0 S+ 0:00 /usr/bin/perl ./sdriver.pl -t trace13.txt -s ./tshref -a -p
9589 pts/0 S+ 0:00 ./tshref -p
9598 pts/0 R 0:00 /bin/ps a

第十四关

这道题是针对不同的输入错误情况, shell能正确识别并指出错误

主要是情况的分类, 参考了博客: https://blog.csdn.net/qq_45475106/article/details/111766734

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void do_bgfg(char **argv) 
{
int jid;
struct job_t *job;

if (!strcmp(argv[0], "bg"))
{
if (argv[1] == NULL)
{
printf("bg command requires PID or %%jobid argument\n");
return;
}
jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
job->state = BG;//将其状态改为后台进程, 这样shell就不会挂起等待该进程了
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
printf("[%d] (%d) %s", jid, job->pid, job->cmdline);
}

if (!strcmp(argv[0], "fg"))
{

if (argv[1] = NULL)
{
printf("fg command requires PID or %%jobid argument\n");
return;
}

else if (argv[1][0] == '%'){
jid = atoi(&argv[1][1]);
job = getjobjid(jobs, jid);
if (job == NULL){
printf("%%%d: No such job\n", jid);
return;
}
}

else if (isdigit(argv[1][0]))
{
jid = atoi(argv[1]);
job = getjobjid(jobs, jid);
if (job == NULL)
{
printf("(%d): Nosuch process\n", jid);
return;
}
}

else
{
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return ;
}

jid = atoi(&argv[1][1]);//atoi函数: 获取参数字符串, 并返回其字符串所代表的数字
job = getjobjid(jobs, jid);
if (job->state == ST)
{
job->state = FG;//将其状态改为前台进程, shell挂起等待该进程
kill(-(job->pid), SIGCONT);//发送继续运行信号给该进程
waitfg(job->pid);
}
if (job->state == BG)
{
job->state = FG;
waitfg(job->pid);
}
}
return;
}

检验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
tsh:
crow@crow-virtual-machine:~/CSAPP/CSAPP/Shell Lab/shlab-handout (2)$ make test14
./sdriver.pl -t trace14.txt -s ./tsh -a "-p"
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 4 &
[1] (2124) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
fg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): Nosuch process
tsh> bg 9999999
(9999999): Nosuch process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (2124) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (2124) ./myspin 4 &
tsh> jobs
[1] (2124) Running ./myspin 4 &

rtsh:
crow@crow-virtual-machine:~/CSAPP/CSAPP/Shell Lab/shlab-handout (2)$ make rtest14
./sdriver.pl -t trace14.txt -s ./tshref -a "-p"
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (2146) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (2146) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (2146) ./myspin 4 &
tsh> jobs
[1] (2146) Running ./myspin 4 &

第十五关, 第十六关

不用修改直接过

警惕

后面的waitpid跟各种信号bgfg的混合使用还是有点混乱, 然后是后面的错误情况提示等等……

文章作者: LamのCrow
文章链接: http://example.com/2022/03/19/ShellLab d6fe4/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LamのCrow