- A+
实验网站
课程网站:CSAPP
源码下载
我的实验环境:Ubuntu 20.04
lab7文档解读
查看 tsh.c (tiny shell) 文件,您会看到它包含一个简单的 Unix shell 的功能骨架。为了帮助您入门,我们已经实现了不太有趣的功能。你的任务是完成下面列出的剩余的空函数。作为对您的健全性检查,我们在参考解决方案中列出了每个函数的大致代码行数(其中包含大量注释)。
- eval: 解析和解释命令行的主要例程。[70行]
- builtin cmd:识别和解释内置命令:quit、fg、bg和jobs。[25行]
- do bgfg:实现bg和fg内置命令。[50行]
- waitfg: 等待前台作业完成。[20行]
- sigchld handler: 捕获SIGCHILD信号。[80行]
- sigint handler: 捕获SIGINT (ctrl-c) 信号。[15行]
- sigtstp handler: 捕获SIGTSTP (ctrl-z) 信号。[15行]
Unix Shell 概述
shell 是一个交互式命令行解释器,它代表用户运行程序。 shell 反复打印提示,等待 stdin 上的命令行,然后按照命令行内容的指示执行一些操作。
命令行是由空格分隔的 ASCII 文本单词序列。命令行中的第一个单词要么是内置命令的名称,要么是可执行文件的路径名。剩下的词是命令行参数。如果第一个单词是内置命令,shell 会立即执行当前进程中的命令。否则,该词被假定为可执行程序的路径名。在这种情况下,shell 会派生一个子进程,然后在子进程的上下文中加载和运行程序。由于解释单个命令行而创建的子进程统称为作业。一般来说,一个作业可以由多个通过 Unix 管道连接的子进程组成。
如果命令行以 & 符号结尾,则作业在后台运行,这意味着 shell 在打印提示符并等待下一个命令行之前不会等待作业终止。否则,作业在前台运行,这意味着 shell 在等待下一个命令行之前等待作业终止。因此,在任何时间点,最多可以有一个作业在前台运行。但是,可以在后台运行任意数量的作业。例如,键入命令行
tsh> jobs
使shell执行内置的jobs命令。键入命令行T
tsh> /bin/ls -l -d
在前台运行ls程序。按照惯例,shell确保程序开始执行其主例程时
int main(int argc, char *argv[])
argc和argv参数具有以下值:
argc == 3,
argv[0] == ‘‘/bin/ls’’,
argv[1]== ‘‘-l’’,
argv[2]== ‘‘-d’’.
或者,键入命令行
tsh> /bin/ls -l -d &
在后台运行ls程序。
Unix shell 支持作业控制的概念,它允许用户在后台和前台之间来回移动作业,并更改作业中进程的进程状态(运行、停止或终止)。键入 ctrl-c 会导致将 SIGINT 信号传递给前台作业中的每个进程。 SIGINT 的默认操作是终止进程。同样,键入 ctrl-z 会导致将 SIGTSTP 信号传递给前台作业中的每个进程。 SIGTSTP 的默认操作是将进程置于停止状态,直到它被接收到 SIGCONT 信号唤醒为止。 Unix shell 还提供了各种支持作业控制的内置命令。例如:
• 作业:列出正在运行和已停止的后台作业。
• bg
:将已停止的后台作业更改为正在运行的后台作业。 • fg
:将已停止或正在运行的后台作业更改为在前台运行。 • kill
:终止作业。
tsh规范
您的tsh-shell应具有以下功能:
- 提示应该是字符串“tsh>”。
- 用户键入的命令行应由名称和零个或多个参数组成,所有参数均由一个或多个空格分隔。如果name是内置命令,那么tsh应该立即处理它,并等待下一个命令行。否则,tsh应该假设name是可执行文件的路径,它在初始子进程的上下文中加载和运行 (在这种情况下,术语job指的是这个初始子进程)。
- tsh 不需要支持管道 (|) 或 I/O 重定向(< 和 >)。
- 键入 ctrl-c (ctrl-z) 应该会导致将 SIGINT (SIGTSTP) 信号发送到当前前台作业,以及该作业的任何后代(例如,它派生的任何子进程)。如果没有前台工作,那么信号应该没有效果。
- 如果命令行以 & 符号结尾,那么 tsh 应该在后台运行该作业。否则,它应该在前台运行作业。
- 每个作业都可以通过进程 ID (PID) 或作业 ID (JID) 来标识,这是一个由 tsh 分配的正整数。 JID 应该在命令行上用前缀 '%' 表示。例如,“%5”表示 JID 5,“5”表示 PID 5。(我们为您提供了操作作业列表所需的所有例程。)
- tsh应该支持以下内置命令:
- quit命令终止shell。
- job命令列出所有后台运行的工作。
- bg
命令重新启动<工作>通过发送SIGCONT,然后在后台运行。 参数可以是一个PID或JID。 - fg
命令重新启动<工作>通过发送SIGCONT,然后在前台运行它。 参数可以是一个PID或JID。
- tsh 应该收获它所有的僵尸孩子。如果任何作业因为接收到它没有捕获的信号而终止,则 tsh 应该识别此事件并打印一条带有作业 PID 和违规信号描述的消息。
Checking your work
我们提供了一些工具来帮助您检查您的工作。 在运行任何可执行程序之前,请确保它具有执行权限。 如果没有,使用“chmod +x”给它执行权限。
**Reference solution. ** Linux 可执行文件 tshref 是 shell 的参考解决方案。 运行这个程序来解决你对 shell 应该如何工作的任何问题。 您的 shell 应该发出与参考解决方案相同的输出(当然,PID 除外,它在运行之间会发生变化)。
Shell driver sdriver.pl 程序将 shell 作为子进程执行,按照跟踪文件的指示向其发送命令和信号,并捕获并显示 shell 的输出。
unix> ./sdriver.pl -h Usage: sdriver.pl [-hv] -t <trace> -s <shellprog> -a <args>
选项:
-h 打印此消息
-v 更详细
-t
跟踪文件 -s
用于测试的 Shell 程序 -a
Shell 参数 -g 为自动评分器生成输出
我们还提供了16个跟踪文件(trace{01-16}.txt),您将与shell驱动程序一起使用这些文件来测试shell的正确性。编号较低的跟踪文件执行非常简单的测试,编号较高的测试执行更复杂的测试。
您可以使用跟踪文件 trace01.txt(例如)在您的 shell 上运行 shell 驱动程序,方法是键入:
unix> ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
(-a “-p” 参数告诉您的shell不要发出提示),或
unix> make test01
同样,要将您的结果与参考 shell 进行比较,您可以通过键入以下内容在参考 shell 上运行跟踪驱动程序:
unix> ./sdriver.pl -t trace01.txt -s ./tshref -a "-p" or unix> make rtest01
供您参考,tshref.out 提供了所有比赛的参考解决方案的输出。这可能比在所有跟踪文件上手动运行 shell 驱动程序更方便。
关于跟踪文件的整洁的事情是,如果您以交互方式运行shell,它们会生成相同的输出 (除了标识跟踪的初始注释)。例如:
bass> make test15 ./sdriver.pl -t trace15.txt -s ./tsh -a "-p" # # trace15.txt - Putting it all together # tsh> ./bogus ./bogus: Command not found. tsh> ./myspin 10 Job (9721) terminated by signal 2 tsh> ./myspin 3 & [1] (9723) ./myspin 3 & tsh> ./myspin 4 & [2] (9725) ./myspin 4 & tsh> jobs [1] (9723) Running ./myspin 3 & [2] (9725) Running ./myspin 4 & tsh> fg %1 Job [1] (9723) stopped by signal 20 tsh> jobs [1] (9723) Stopped ./myspin 3 & [2] (9725) Running ./myspin 4 & tsh> bg %3 5 %3: No such job tsh> bg %1 [1] (9723) ./myspin 3 & tsh> jobs [1] (9723) Running ./myspin 3 & [2] (9725) Running ./myspin 4 & tsh> fg %1 tsh> quit bass>
Hints
-
阅读教科书中第 8 章(异常控制流)的每一个字。
-
使用跟踪文件来指导您的 shell 的开发。 从 trace01.txt 开始,确保您的 shell 产生与参考 shell 相同的输出。 然后继续跟踪文件 trace02.txt,依此类推。
-
waitpid、kill、fork、execve、setpgid 和 sigprocmask 函数会派上用场。 waitpid 的 WUNTRACED 和 WNOHANG 选项也很有用。
-
当您实现信号处理程序时,请确保将 SIGINT 和 SIGTSTP 信号发送到整个前台进程组,在 kill 函数的参数中使用“-pid”而不是“pid”。 sdriver.pl 程序测试此错误。
-
实验室的一个棘手部分是决定waitfg 和sigchld 处理函数之间的工作分配。 我们推荐以下方法:
- 在 waitfg 中,围绕 sleep 函数使用busy loop。
- 在 sigchld 处理程序中,只调用一次 waitpid。
-
虽然其他解决方案是可能的,例如在 waitfg 和 sigchld 处理程序中调用 waitpid,但这些可能会非常令人困惑。 在处理程序中进行所有收获更简单。
-
在 eval 中,父级必须在分叉子级之前使用 sigprocmask 阻止 SIGCHLD 信号,然后解除阻塞这些信号,在通过调用 addjob 将子级添加到作业列表后再次使用 sigprocmask。由于子进程继承了父进程的阻塞向量,因此子进程必须确保在执行新程序之前解除阻塞 SIGCHLD 信号。
父级需要以这种方式阻止 SIGCHLD 信号,以避免在父级调用 addjob 之前子级被 sigchld 处理程序收割(并因此从作业列表中删除)的竞争条件。
-
诸如 more、less、vi 和 emacs 之类的程序在终端设置上会做一些奇怪的事情。 不要从你的 shell 运行这些程序。 坚持使用简单的基于文本的程序,例如 /bin/ls、/bin/ps 和 /bin/echo。
-
当您从标准Unix shell运行您的shell时,您的shell正在前台进程组中运行。如果您的shell随后创建了一个子进程,则默认情况下,该子进程也将是前台进程组的成员。由于键入ctrl-c会向前台组中的每个进程发送一个SIGINT,因此键入ctrl-c会向您的shell以及您的shell创建的每个进程发送一个SIGINT,这显然是不正确的。
解决方法如下:在fork之后,但在execve之前,子进程应该调用setpgid(0,0),这会将子进程放入一个新的进程组中,其组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即您的shell。当您键入ctrl-c时,shell应该捕获生成的SIGINT,然后将其转发到相应的前台作业(或者更准确地说,是包含前台作业的进程组)。
课本实验相关内容复习
读书笔记CSAPP:19[VB]ECF:信号和非本地跳转
进程
我们可通过调用以下函数来等待子进程的终止或停止,父进程会得到被回收的子进程PID,且内核会删除僵死进程
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *statusp, int options);
-
等待集合
pid
-
- 如果
pid>0
,则等待集合就是一个单独的子进程- 如果
pid=-1
,则等待集合就是该进程的所有子进程 - 注意:当父进程创造了许多子进程,这里通过
pid=-1
进行回收时,子程序的回收顺序是不确定的,并不会按照父进程生成子进程的顺序进行回收。可通过按顺序保存子进程的PID,然后按顺序指定pid
参数来消除这种不确定性。
- 如果
- 如果
-
等待行为
options
-
0
:默认选项,则会挂起当前进程,直到等待集合中的一个子进程终止,则函数返回该子进程的PID。此时,已终止的子进程已被回收。WNOHANG
:如果等待子进程终止的同时还向做其他工作,该选项会立即返回,如果子进程终止,则返回该子进程的PID,否则返回0。WUNTRACED
:当子进程被终止或暂停时,都会返回。WCONTINUED
:挂起当前进程,知道等待集合中一个正在运行的子进程被终止,或停止的子进程收到SIGCONT
信号重新开始运行。- 注意:这些选项可通过
|
合并。
-
如果
statusp
非空,则waitpid
函数会将子进程的状态信息放在statusp
中,可通过wait.h
中定义的宏进行解析 -
WIFEXITED(statusp)
:如果子进程通过调用exit
或return
正常终止,则返回真,。此时可通过WEXITSTATUS(statusp)
获得退出状态。WIFSIGNALED(status)
:如果子进程是因为一个未捕获的信号终止的,则返回真。此时可通过WTERMSIG(statusp)
获得该信号的编号。WIFSTOPPED(statusp)
:如果引起函数返回的子进程是停止的,则返回真。此时可通过WSTOPSIG(statusp)
获得引起子进程停止的信号编号。WIFCONTINUED(statusp)
:如果子进程收到SIGCONT
信号重新运行,则返回真。
-
如果当前进程没有子进程,则
waitpid
返回-1,并设置errno
为ECHILD
,如果waitpid
函数被信号中断,则返回-1,并设置errno
为EINTR
。否则返回被回收的子进程PID。
注意:waitpid
通过设置options
来决定是否回收停止的子进程。并且能通过statusp
来判断进程终止或停止的原因。
有个简化的waitpid
函数
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *statusp);
调用wait(&status)
等价于调用waitpid(-1, &status, 0)
。
注意:当调用waitpid
函数之前,就有子进程被终止或停止,一调用waitpid
函数就会马上将该子进程回收。
8.5.2发送信号
#include <unistd.h> pid_t getpgrp(void); //返回所在的进程组 int setpgip(pid_t pid, pid_t pgid); //设置进程组 /* * 如果pid大于零,就使用进程pid;如果pid等于0,就使用当前进程的PID。 * 如果pgid大于0,就将对应的进程组ID设置为pgid;如果pgid等于0,就用pid指向的进程的PID作为进程组ID */
- 用
/bin/kill
向进程发送任意信号
/bin/kill [-信号编号] id
当id>0
时,表示将信号传递给PID为id
的进程;当id<0
时,表示将信号传递给进程组ID为|id|
的所有进程。
- 从键盘发送信号
通过键盘上输入Ctrl+C
会使得内核发送一个SIGINT
信号到前台进程组中的所有进程,终止前台作业;通过输入Ctrl+Z
会发送一个SIGTSTP
信号到前台进程组的所有进程,停止前台作业,直到该进程收到SIGCONT
信号。
- 用
kill
函数发送信号
可以在函数中调用kill
函数来对目的进程发送信号
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
当pid>0
时,会将信号sig
发送给进程pid
;当pid=0
时,会将信号sig
发送给当前进程所在进程组的所有进程;当pid<0
时,会将信号sig
发送给进程组ID为|pid|
的所有进程
- 用
alarm
函数发送SIGALARM
信号
#include <unistd.h> unsigned int alarm(unsigned int secs);
当alarm
函数时,会取消待处理的闹钟,返回待处理闹钟剩下的时间,并在secs
秒后发送一个SIGALARM
信号给当前进程。
8.5.3接受信号
每种信号类型具有以下一种预定的默认行为:
- 进程终止
- 进程终止并dumps core
- 进程挂起直到被
SIGCONT
信号重启 - 进程忽略信号
可以通过signal
函数来修改信号的默认行为,但是无法修改SIGSTOP
和SIGKILL
信号的默认行为
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
signum
为信号编号,可以直接输入信号名称handler
为我们想要对信号signum
采取的行为- 当
handler
为SIG_IGN
,表示要进程忽略该信号- 当
handler
为SIG_DFL
,表示要恢复该信号的默认行为- 当
handler
为用户自定义的信号处理程序地址,则会调用该函数来处理该信号,该函数原型为void signal_handler(int sig);
。调用信号处理程序称为捕获信号,置信信号处理程序称为处理信号。当信号处理程序返回时,会将控制传递回逻辑流中的下一条指令。注意:信号处理程序可以被别的信号处理程序中断。- 当
signal
函数执行成功,则返回之前signal handler
的值,否则返回SIG_ERR
8.5.4 阻塞信号和解除阻塞信号P532
Linux提供阻塞信号的隐式和显示的机制:
- 隐式阻塞机制:内核默认阻塞当前正在处理信号类型的待处理信号。
- 显示阻塞机制:应用程序通过
sigprocmask
函数来显示阻塞和解阻塞选定的信号。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
通过
how
来决定如何改变阻塞的信号集合blocked
-
- 当
how=SIG_BLOCK
时,blocked = blocked | set
- 当
how=SIG_UNBLOCK
时,blocked = blocked & ~set
- 当
how=SETMASK
时,block = set
- 当
- 当
-
如果
oldset
非空,则会将原始的blocked
值保存在oldset
中,用于恢复原始的阻塞信号集合
这里还提供一些额外的函数来对set
信号集合进行操作
#include <signal.h> int sigemptyset(sigset_t *set); //初始化set为空集合 int sigfillset(sigset_t *set); //把每个信号都添加到set中 int sigaddset(sigset_t *set, int signum); //将signum信号添加到set中 int sigdelset(sigset_t *set, int signum); //将signum从set中删除 int sigismember(const sigset_t *set, int signum); //如果signum是set中的成员,则返回1,否则返回0
以下是一个使用例子
以上执行内部函数时,就不会接收到SIGINT
信号,即不会被Ctrl+C
终止。
通过阻塞信号来消除函数冲突,或者保证程序运行逻辑正确。
显示等待信号
当我们想要主进程显示等待某个信号时,可以用以下代码
这里主进程会显示等待子进程被回收,这里使用了sigsuspend(&mask)
函数,它等价于
sigprocmask(SIG_SETMASK, &mask, &prev); pause(); sigprocmask(SIG_SETMASK, &prev, NULL);
但是它是这三条代码的原子版本,即第一行和第二行是一起调用的,则SIGCHLD
信号不会出现在第一行和第二行之间,造成程序不会停止。
注意:第26行要先对SIGCHLD
信号进行阻塞,防止过早发送给主进程,则pause
函数就无法中断,就会使得程序不会停止。
Reference output: # # trace14.txt - Simple error handling # tsh> ./bogus ./bogus: Command not found tsh> ./myspin 4 & [1] (42087) ./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] (42087) stopped by signal 20 tsh> bg %2 %2: No such job tsh> bg %1 [1] (42087) ./myspin 4 & tsh> jobs [1] (42087) Running ./myspin 4 & Student's output: # # trace14.txt - Simple error handling # tsh> ./bogus ./bogus: Command not found. tsh> ./myspin 4 & [1] (42141) ./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] (42141) stopped by signal 20 tsh> bg %2 %2: No such job tsh> bg %1 [1] (42141) ./myspin 4 & tsh> jobs [1] (42141) Running ./myspin 4 & Checking trace15.txt... Reference output: # # trace15.txt - Putting it all together # tsh> ./bogus ./bogus: Command not found tsh> ./myspin 10 Job [1] (42182) terminated by signal 2 tsh> ./myspin 3 & [1] (42210) ./myspin 3 & tsh> ./myspin 4 & [2] (42213) ./myspin 4 & tsh> jobs [1] (42210) Running ./myspin 3 & [2] (42213) Running ./myspin 4 & tsh> fg %1 Job [1] (42210) stopped by signal 20 tsh> jobs [1] (42210) Stopped ./myspin 3 & [2] (42213) Running ./myspin 4 & tsh> bg %3 %3: No such job tsh> bg %1 [1] (42210) ./myspin 3 & tsh> jobs [1] (42210) Running ./myspin 3 & [2] (42213) Running ./myspin 4 & tsh> fg %1 tsh> quit Student's output: # # trace15.txt - Putting it all together # tsh> ./bogus ./bogus: Command not found. tsh> ./myspin 10 Job [1] (42255) terminated by signal 2 tsh> ./myspin 3 & [1] (42269) ./myspin 3 & tsh> ./myspin 4 & [2] (42271) ./myspin 4 & tsh> jobs [1] (42269) Running ./myspin 3 & [2] (42271) Running ./myspin 4 & tsh> fg %1 Job [1] (42269) stopped by signal 20 tsh> jobs [1] (42269) Stopped ./myspin 3 & [2] (42271) Running ./myspin 4 & tsh> bg %3 %3: No such job tsh> bg %1 [1] (42269) ./myspin 3 & tsh> jobs [1] (42269) Running ./myspin 3 & [2] (42271) Running ./myspin 4 & tsh> fg %1 tsh> quit
主要任务
需要实现的命令
void eval(char *cmdline); int builtin_cmd(char **argv); void do_bgfg(char **argv); void waitfg(pid_t pid); void sigchld_handler(int sig); void sigtstp_handler(int sig); void sigint_handler(int sig);
按测试顺序实现
make test01//通过 make test02//需要实现quit
-
实现
int builtin_cmd(char **argv)
int builtin_cmd(char **argv) { if(!strcmp(argv[0],"quit")){ exit(0); } else if(!strcmp(argv[0],"jobs")){ /*需要防止冲突*/ sigset_t mask, prev_mask; sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &prev_mask); listjobs(jobs); sigprocmask(SIG_SETMASK, &prev_mask, NULL); return 1; /*返回非0*/ } else if(!strcmp(argv[0], "bg")){ /*需要防止冲突*/ do_bgfg(argv); return 1; /*返回非0*/ } else if(!strcmp(argv[0], "fg")){ /*需要防止冲突*/ do_bgfg(argv); return 1; /*返回非0*/ } else return 0; /* not a builtin command */ }
-
make test03 make test04 make test05 //jobs指令执行失败 //需完善void eval(char *cmdline)
-
完善
void eval(char *cmdline)
//一开始直接参考书上P525简单shell,jobs指令失败 // 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 */ strcpy(buf,cmdline); bg = parseline(buf, argv); /*后台执为true*/ if (argv[0] == NULL) return; /* Ignore empty lines */ if (!builtin_cmd(argv)) { /* 执行内置指令 */ if ((pid = fork()) == 0) { /* fork产生子进程执行指令 */ if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found.n", argv[0]); exit(0); } } /* Parent waits for foreground job to terminate */ if (!bg) { int status; if (waitpid(pid, &status, 0) < 0) unix_error("waitfg: waitpid error"); } else printf("%d %s", pid, cmdline); } return; }
检查知上面的
eval
缺少添加jobs,进一步添加信号阻塞和addjobs
/* * 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 */ sigset_t mask_all, mask_one, prev_one; strcpy(buf,cmdline); bg = parseline(buf, argv); /*结尾&,后台执,为true*/ if (argv[0] == NULL) return; /* Ignore empty lines */ /*sigemptyset(sigset set) 初始化集合为空 sigfillset(sigset set) 把每个信号都添加到set中 sigaddset(sigset set,int signum) 函数把signum添加到集合set中 */ Sigfillset(&mask_all); Sigemptyset(&mask_one); Sigaddset(&mask_one, SIGCHLD); //mask_one:SIGCHLD Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //阻塞SIGCHLD if (!builtin_cmd(argv)) { /* 执行内置指令,不执行则往下执行 */ if ((pid = Fork()) == 0) { /* fork产生子进程执行指令 */ Sigprocmask(SIG_SETMASK, &prev_one, NULL); //解除阻塞 setpgid(0, 0); //确保前台进程组中只有一个进程,即shell if (execve(argv[0], argv, environ) < 0) { // ref:./bogus:Commandnotfound (无.) printf("%s: Command not foundn", argv[0]); exit(0); } } /* Parent waits for foreground job to terminate */ if (!bg) { //fg执行 Sigprocmask(SIG_BLOCK, &mask_all, NULL); addjob(jobs,pid,FG,cmdline); waitfg(pid); //挂起父进程等待前台执行 // if (waitpid(pid, &status, 0) < 0) // unix_error("waitfg: waitpid error"); Sigprocmask(SIG_SETMASK, &prev_one, NULL); } else{ //bg执行 Sigprocmask(SIG_BLOCK, &mask_all, NULL); addjob(jobs,pid,BG,cmdline); Sigprocmask(SIG_SETMASK, &prev_one, NULL); struct job_t* bg_job = getjobpid(jobs, pid); printf("[%d] (%d) %s",bg_job->jid,bg_job->pid, cmdline); } } return; }
-
make test05 make test06//需要增加异常信号处理
void sigchld_handler(int sig) //P543 { int olderrno=errno; sigset_t mask_all,prev_all; pid_t pid; struct job_t *job; int status; //存储回收子进程的退出状态 sigfillset(&mask_all); /*子进程都没有停止或终止,返回0;停止或终止返回该子进程pid*/ while ((pid=waitpid(-1,&status,WNOHANG|WUNTRACED))>0){ //回收僵尸子进程 job=getjobpid(jobs,pid); int pid=job->pid; int jid=job->jid; if(!WIFSTOPPED(status)) deletejob(jobs,pid); sigprocmask(SIG_BLOCK,&mask_all,&prev_all); //Job [1] (26263) terminated by signal 2 if(WIFSIGNALED(status)){ //子进程停止因为未捕获的信号而终止,则WTERMSIG(status)返回引起子进程终止的编号 printf("Job [%d] (%d) terminated by signal %dn", jid, pid,WTERMSIG(status)); }else if(WIFSTOPPED(status)){ //子进程停止,WSTOPSIG(status)返回引起子进程停止的进程编号 job -> state = ST; printf("Job [%d] (%d) stopped by signal %dn",jid ,pid, WSTOPSIG(status)); } } if (errno != ECHILD) { unix_error("waitpid error"); } return; } /* * 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) { int olderrno = errno; sig_t mask_all,prev_all; sigfillset(&mask_all); sigprocmask(SIG_BLOCK,&mask_all,&prev_all); int fg_pid=fgpid(jobs); //返回前台进程 sigprocmask(SIG_SETMASK,&prev_all,NULL); if(fg_pid){ kill(-fg_pid,sig); //向进程组发送发送SIGINT } errno=olderrno; return; } /* * 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) { int olderrno = errno; sig_t mask_all,prev_all; Sigfillset(&mask_all); Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); int fg_pid = fgpid(jobs); Sigprocmask(SIG_SETMASK, &prev_all, NULL); if (fg_pid) { Kill(-fg_pid, sig); } errno=olderrno; return; }
-
实现do_fgbg
```c /* * do_bgfg - Execute the builtin bg and fg commands */ void do_bgfg(char **argv) { int bg=strcmp(argv[0],"bg"); sigset_t mask_all,mask_one,prev_one; sigfillset(&mask_all); Sigemptyset(&mask_one); Sigaddset(&mask_one,SIGCHLD); struct job_t *Job; if (!argv[1]) { //缺少参数PID或JID //fg command requires PID or %jobid argument printf("%s command requires PID or %%jobid argumentn", (!bg) ? "bg":"fg"); return; } else if((argv[1][0]<'0'||argv[1][0]>'9')&&argv[1][0]!='%') //指令:fg %2 fg 2 { // fg: argument must be a PID or %jobid printf("%s: argument must be a PID or %%jobidn", (!bg) ? "bg":"fg"); return; } else if (argv[1][0] == '%') { int jid = 0; for (int i = 1; argv[1][i]; i++) { jid = jid * 10 + (argv[1][i] - '0'); } Job = getjobjid(jobs, jid); if (!Job) { printf("%%%d: No such jobn",jid); return; } } else { pid_t pid = 0; for (int i = 0; argv[1][i]; i++) { pid = pid * 10 + (argv[1][i] - '0'); } Job = getjobpid(jobs, pid); if (!Job) { printf("(%d): No such processn",pid); return; } } Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); Kill( -Job -> pid, SIGCONT); int pid=Job->pid; int jid=Job->jid; if (bg) { Sigprocmask(SIG_BLOCK, &mask_all, NULL); Job -> state = FG; waitfg(pid); Sigprocmask(SIG_SETMASK, &prev_one, NULL); } else { Sigprocmask(SIG_BLOCK, &mask_all, NULL); Job -> state = BG; Sigprocmask(SIG_SETMASK, &prev_one, NULL); printf("[%d] (%d) %s",jid ,pid,Job->cmdline); } return; } ```
Evaluation
分数将根据以下分布计算出最多90分:
80 Correctness: 16 trace files at 5 points each.
10 Style points. We expect you to have good comments (5 pts) and to check the return value of EVERY system call (5 pts).
您的解决方案 shell 将在 Linux 机器上测试正确性,使用包含在您的实验室目录中的相同 shell 驱动程序和跟踪文件。 您的 shell 应该在这些跟踪上产生与参考 shell 相同的输出,只有两个例外:
- PID 可以(并且将会)不同。
- trace11.txt、trace12.txt 和trace13.txt 中的/bin/ps 命令的输出将因运行而异。但是,/bin/ps 命令输出中任何 mysplit 进程的运行状态应该相同。
我们为您提供了一个名为grade shlab的测试。pl.以下是正确案例的示例:
unix> ./grade-shlab.pl -f tsh.c CS:APP Shell Lab: Grading Sheet for tsh.c Part 0: Compiling your shell gcc -Wall -O2 tsh.c -o tsh gcc -Wall -O2 myspin.c -o myspin gcc -Wall -O2 mysplit.c -o mysplit gcc -Wall -O2 mystop.c -o mystop gcc -Wall -O2 myint.c -o myint 7 Part 1: Correctness Tests Checking trace01.txt... Checking trace02.txt... Checking trace03.txt... Checking trace04.txt... Checking trace05.txt... Checking trace06.txt... Checking trace07.txt... Checking trace08.txt... Checking trace09.txt... Checking trace10.txt... Checking trace11.txt... Checking trace12.txt... Checking trace13.txt... Checking trace14.txt... Checking trace15.txt... Checking trace16.txt... Preliminary correctness score: 80