跳转至

信号处理

这一部分需要处理 CTRL-Ccontrol-C)的按键。

在使用 shell 的时候按下 CTRL-C 可以丢弃当前输入到一半的命令,重新显示提示符并接受新的命令输入。当有程序运行时,按下 CTRL-C 可以终结运行中的程序,立即回到 shell 开始新的命令输入(shell 没有随程序一起结束)。

例如(^C 表示遇到 CTRL-C 的输入):

$ echo osh-2023
osh-2023
$ echo osh-2023^C  # 命令没有输入完。^C 会丢弃当前命令
$ sleep 5          # 回车确认命令后,在 sleep 未运行完时按下 ^C
^C
$                  # 重新回到等待输入命令的状态

请为你的 shell 实现对 CTRL-C 的处理。

信号

按下 CTRL-C 实际是向进程发送了 SIGINT 信号,这一信号通常会终止当前正在运行的程序。

对于 shell 进程和正在执行某个程序的 shell 创建出的子进程而言,需要确保 CTRL-C 不会停止 shell 进程。例如,如果你现在在自制的 shell 中尝试 CTRL-C,shell 进程也将收到信号并停止,而我们希望信号只影响 shell 创建的子进程。你可能需要先阅读下文的相关知识。

进程组

每个进程都有自己的独特的 pid,标识进程 id。除此之外,每个进程还有一个 pgid,即进程组 id。一个进程的默认 pgid 与它父进程相同。可以通过 getpgid(), setpgid(), getpgrp(), 或 setpgrp() 来获取和设置进程组 id。

你的 shell 可能会打开一个需要多个进程的程序,但这些进程都会从最初的某个进程继承相同的 pgid。所以在此之前,你可以尝试将 fork 出的子进程所在的进程组与父进程分离,以确保 fork 出的子进程是独立的进程组成员,从而更好地进行管理和控制。

前台进程组

每个终端都有一个关联的前台进程组 ID。当输入 CTRL-C 时,终端会向前台进程组内的每个进程发送一个 SIGINT 信号。可以使用 tcsetpgrp(int fd, pid_t pgrp) 更改哪个进程组在终端的前台。fd 参数应设置为标准输入 0。

信号概览

信号是传递给进程的异步消息。它们有自己的数字编号,对应一个以 SIG 开头的名称。一些常见的包括:

SIGINT

键入 CTRL-C 时产生。默认情况下,会停止程序。

SIGKILL

该信号强制停止程序,不能被程序覆写信号处理方式。

SIGTSTPSIGCONT

分别暂停和恢复进程。

SIGTTIN

发送到试图从键盘读取输入的后台进程。默认情况会暂停程序,因为后台进程无法从键盘读取输入。当使用 SIGCONT 恢复后台进程并将其置于前台时,可以尝试再次从键盘读取输入。

SIGTTOU

发送给试图将输出写入已有前台进程的终端控制台的后台进程。后台进程组成员调用 tcsetpgrp 时也会产生该信号。默认情况下与 SIGTTIN 行为相同。

信号处理

可以使用 sigaction 系统调用来更改当前进程处理信号的方式。注意,shell 进程本身可能需要忽略一些信号,但 shell 的子进程仍应以默认操作响应。例如,shell 可以选择忽略 SIGTTOU,但子进程不应该。

提醒

fork 出的进程将继承原始进程的信号处理程序。

更多信息可以参考 man 2 sigactionman 7 signal。你可能需要关注 SIG_DFLSIG_IGN

更多关于进程组和信号处理的知识可以参考:https://cs162.org/static/readings/ic221_s16_lec17.html

要求 满分
CTRL+C 正确终止正在运行的进程 5%
CTRL+C 在 shell 嵌套时也能正确终止正在运行的进程 10%
CTRL+C 可以丢弃命令 10%