# 家庭作业(续)

家庭作业 8.22(★★★)

编写 Unix system 函数的你自己的版本

int mysystem(char *command);

mysystem 函数通过调用 "/bin/sh -c command" 来执行 command,然后在 command 完成后返回。如果 command(通过调用 exit 函数或者执行一条 return 语句)正常退出,那么 mysystem 返回 command 退出状态。例如,如果 command 通过调用 exit(8) 终止,那么 mysystem 返回值 8。否则,如果 command 是异常终止的,那么 mysystem 就返回 shell 返回的状态。

家庭作业 8.23(★★)

你的一个同事想要使用信号来让一个父进程对发生在子进程中的事件计数。其想法是每次发生一个事件时,通过向父进程发送一个信号来通知它,并且让父进程的信号处理程序对一个全局变量 counter 加一,在子进程终止之后,父进程就可以检查这个变量。然而,当他在系统上运行图 8-45 中的测试程序时,发现当父进程调用 printf 时,counter 的值总是 2,即使子进程向父进程发送了 5 个信号也是如此。他很困惑,向你寻求帮助。你能解释这个程序有什么错误吗?

#include "csapp.h"

int counter = 0;

void handler(int sig)
{
    counter++;
    sleep(1); /* Do some work in the handler */
    return;
}

int main()
{
    int i;

    Signal(SIGUSR2, handler);

    if (Fork() == 0) { /* Child */
        for (i = 0; i < 5; i++) {
            Kill(getppid(), SIGUSR2);
            printf("sent SIGUSR2 to parent\n");
        }
        exit(0);
    }

    Wait(NULL);
    printf("counter=%d\n", counter);
    exit(0);
}

图 8-45 家庭作业 8.23 中引用的计数器程序

家庭作业 8.24(★★★)

修改图 8-18 中的程序,以满足下面两个条件:

  1. 每个子进程在试图写一个只读文本段中的位置时会异常终止。
  2. 父进程打印和下面所示相同(除了 PID)的输出:
child 12255 terminated by signal 11: Segmentation fault
child 12254 terminated by signal 11: Segmentation fault

提示:请参考 psignal(3) 的 man 页。

家庭作业 8.25(★★★)

编写 fgets 函数的一个版本,叫做 tfgets,它 5 秒钟后会超时。tfgets 函数接收和 fgets 相同的输入。如果用户在 5 秒内不键入一个输入行,tfgets 返回 NULL。否则,它返回一个指向输入行的指针。

家庭作业 8.26(★★★)

以图 8-23 中的示例作为开始点,编写一个支持作业控制的 shell 程序。shell 必须具有以下特性:

  • 用户输入的命令行由一个 name、零个或者多个参数组成,它们都由一个或者多个空格分隔开。如果 name 是一个内置命令,那么 shell 就立即处理它,并等待下一个命令行。否则,shell 就假设 name 是一个可执行文件,在一个初始的子进程(作业)的上下文中加载并运行它。作业的进程组 ID 与子进程的 PID 相同。
  • 每个作业是由一个进程 ID(PID)或者一个作业 ID(JID)来标识的,它是由一个 shell 分配的任意的小正整数。JID 在命令行上用前缀 "%" 来表示。比如,"%5" 表示 JID 5,而 "5" 表示 PID 5。
  • 如果命令行以 & 来结束,那么 shell 就在后台运行这个作业。否则,shell 就在前台运行这个作业。
  • 输入 Ctrl+C(Ctrl+Z),使得内核发送一个 SIGINTSIGTSTP)信号给 shell,shell 再转发给前台进程组中的每个进程。
  • 内置命令 jobs 列出所有的后台作业。
  • 内置命令 bg job 通过发送一个 SIGCONT 信号重启 job,然后在后台运行它。job 参数可以是一个 PID,也可以是一个 JID。
  • 内置命令 fg job 通过发送一个 SIGCONT 信号重启 job,然后在前台运行它。
  • shell 回收它所有的僵死子进程。如果任何作业因为收到一个未捕获的信号而终止,那么 shell 就输出一条消息到终端,消息中包含该作业的 PID 和对该信号的描述。

注意这是对真实的 shell 工作方式的简化。真实的 shell 里,内核响应 Ctrl+C(Ctrl+Z),把 SIGINTSIGTSTP)直接发送给终端前台进程组中的每个进程。shell 用 tcsetpgrp 函数管理这个进程组的成员,用 tcsetattr 函数管理终端的属性,这两个函数都超出了本书讲述的范围。可以参考 [62] 获得详细信息。

图 8-46 展示了一个 shell 会话示例。

Shell 会话 说明
linux> ./shell Run your shell program
>bogusbogus: Command not found. Execve can't find executable
>foo 10Job 5035 terminated by signal: Interrupt User types Ctrl+C
>foo 100 &[1] 5036 foo 100 &
>foo 200 &[2] 5037 foo 200 &
>jobs[1] 5036 Running foo 100 &[2] 5037 Running foo 200 &
>fg %1Job [1] 5036 stopped by signal: Stopped User types Ctrl+Z
>jobs[1] 5036 Stopped foo 100 &[2] 5037 Running foo 200 &
>bg 50355035: No such process
>bg 5036[1] 5036 foo 100 &
>/bin/kill 5036Job 5036 terminated by signal: Terminated
>fg %2 Wait for fg job to finish
>quitlinux> Back to the Unix shell

图 8-46 家庭作业 8.26 的 shell 会话示例