实验网站

课程网站: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)如果子进程通过调用exitreturn正常终止,则返回真,。此时可通过WEXITSTATUS(statusp)获得退出状态。

      • WIFSIGNALED(status)如果子进程是因为一个未捕获的信号终止的,则返回真。此时可通过WTERMSIG(statusp)获得该信号的编号。
      • WIFSTOPPED(statusp)如果引起函数返回的子进程是停止的,则返回真。此时可通过WSTOPSIG(statusp)获得引起子进程停止的信号编号。
      • WIFCONTINUED(statusp)如果子进程收到SIGCONT信号重新运行,则返回真。
  • 如果当前进程没有子进程,则waitpid返回-1,并设置errnoECHILD,如果waitpid函数被信号中断,则返回-1,并设置errnoEINTR。否则返回被回收的子进程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函数来修改信号的默认行为,但是无法修改SIGSTOPSIGKILL信号的默认行为

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • signum为信号编号,可以直接输入信号名称
  • handler为我们想要对信号signum采取的行为
  • handlerSIG_IGN,表示要进程忽略该信号
  • handlerSIG_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
  1. 实现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 */
    }
  2. make test03
    make test04
    make test05 //jobs指令执行失败
    //需完善void eval(char *cmdline)
  3. 完善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 found\n", 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;
    }
  4. 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 %d\n", jid, pid,WTERMSIG(status));
    }else if(WIFSTOPPED(status)){ //子进程停止,WSTOPSIG(status)返回引起子进程停止的进程编号
    job -> state = ST;
    printf("Job [%d] (%d) stopped by signal %d\n",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;
    }
  5. 实现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 argument\n", (!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 %%jobid\n", (!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 job\n",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 process\n",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

CSAPP:lab7 shell的更多相关文章

  1. 【CSAPP】Shell Lab 实验笔记

    shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容.难度上如果熟读了<CSAPP>的"异常控制流"一章,应该是可以不 ...

  2. CSAPP shell Lab 详细解答

    Shell Lab的任务为实现一个带有作业控制的简单Shell,需要对异常控制流特别是信号有比较好的理解才能完成.需要详细阅读CS:APP第八章异常控制流并理解所有例程. Slides下载:https ...

  3. [CSAPP笔记][第九章虚拟存储器][吐血1500行]

    9.虚拟存储器 为了更加有效地管理存储器且少出错,现代系统提供了对主存的抽象概念,叫做虚拟存储器(VM). 虚拟存储器是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互. 为每个进程提供一个 ...

  4. Tinyshell: 一个简易的shell命令解释器

    这是自己最近学习Linux系统编程之后写的一个练手的小程序,能很好地复习系统编程中的进程管理.信号.管道.文件等内容. 通过回顾写的过程中遇到的问题的形式记录程序的关键点,最后给出完整程序代码. 0. ...

  5. 六星经典CSAPP笔记(1)计算机系统巡游

    CSAPP即<Computer System: A Programmer Perspective>的简称,中文名为<深入理解计算机系统>.相信很多程序员都拜读过,之前买的旧版没 ...

  6. CSAPP之阅读笔记-计算机系统漫游(1)

    最近在看CSAPP(深入理解计算机系统第二版),其实最新版是第三版.但是,我看了一下价格100多大洋,于是去老夫子旧书网上买了本第二版的,花了30多块钱.哈哈. 网上看了一些关于此书的书评,都说是本好 ...

  7. CSAPP HITICS 大作业 hello's P2P by zsz

    摘 要 摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息.摘要应包括本论文的目的.主要内容.方法.成果及其理论与实际意义.摘要中不宜使用公式.结构式.图表和非公知 ...

  8. CSAPP:第一章计算机系统漫游

    CSAPP:计算机系统漫游 关键点:上下文.程序运行.计算机系统抽象. 信息就是位+上下文一个程序的运行过程系统的硬件组成编译系统是如何工作的?一个程序的运行过程(c语言举例)计算机系统中的抽象 信息 ...

  9. 深入理解计算机系统项目之 Shell Lab

    博客中的文章均为meelo原创,请务必以链接形式注明本文地址 Shell Lab是CMU计算机系统入门课程的一个实验.在这个实验里你需要实现一个shell,shell是用户与计算机的交互界面.普通意义 ...

  10. 哈工大CSAPP大作业

    第1章 概述 1.1 Hello简介 hello的源码hello.c文件,要生成可执行文件,首先要进行预处理,其次要进行编译生成汇编代码,接着进行汇编处理生成目标文件,目标文件通过链接器形成一个可执行 ...

随机推荐

  1. vim 从嫌弃到依赖(18)——查找模式进阶

    上一篇文章中,我们初步结识了如何使用查找模式,也能够通过n和 N进行查找.这篇将会介绍搜索中更高级的用法.另外在写上一篇文章的时候我发现介绍查找相关内容的时候不能用动图来演示,主要是因为输入的内容太多 ...

  2. python3 ACM模式的输入输出例子教学

    Python的输入是字符串,所以要自己转类型 strip去掉左右两端的空白符,返回str slipt把字符串按空白符拆开,返回[str] map把list里面的值映射到指定类型,返回[type] EO ...

  3. 8.2 C++ 引用与取别名

    C/C++语言是一种通用的编程语言,具有高效.灵活和可移植等特点.C语言主要用于系统编程,如操作系统.编译器.数据库等:C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统.图形用户界面 ...

  4. C++ Boost库 实现命令行解析

    Boost库中默认自带了一个功能强大的命令行参数解析器,以往我都是自己实现参数解析的,今天偶尔发现这个好东西,就来总结一下参数解析的基本用法,该库需要引入program_options.hpp头文件, ...

  5. WebAssembly核心编程[2]:类型系统

    对于绝大多数编程语言来说,类型都是编程的基础,WebAssembly自然也不例外.总的来说,WebAssembly涉及的类型不多,很好掌握,接下来我们就来介绍一下WebAssembly编程涉及到的几种 ...

  6. 基于SLAM的规划算法仿真复现|SLAM|智能规划

    图片来自百度百科 前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总https: ...

  7. 【Linux】root密码忘记了怎么办【技能篇】一分钟教会你重置root密码

    [Linux]root密码重置 文章目录 前言介绍 操作步骤 尾声 前言介绍 那么这里博主先安利一下一些干货满满的专栏啦! 数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是 ...

  8. P2216 [HAOI2007] 理想的正方形 题解

    题目链接:理想的正方形 比较明显的,我们可以用二维 ST 表解决,具体的二维 ST 表的实现,只需要知道一点: 对于 \(st[i][j][t]=max(i \sim i+2^t,j \sim j+2 ...

  9. CH9140,CH9141,CH9143异同点

    9140:是一款蓝牙转串口芯片,芯片支持蓝牙主从一体模式或从机模式,支持蓝牙 BLE4.2.串口波特率最高 1Mbps,支持 MODEM 联络信号,蓝牙主从模式可以自动连接或绑定. 智能配对功能 当 ...

  10. 双端队列(deque)--python

    Python中的双端队列(deque)是一种特殊的数据结构,它允许在队列的两端进行插入和删除操作12.双端队列可以看成栈和队列的结合3.在Python中,我们可以使用collections模块中的de ...