信号处理¶
这一部分需要处理 CTRL-C
(control-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
该信号强制停止程序,不能被程序覆写信号处理方式。
SIGTSTP
和 SIGCONT
分别暂停和恢复进程。
SIGTTIN
发送到试图从键盘读取输入的后台进程。默认情况会暂停程序,因为后台进程无法从键盘读取输入。当使用
SIGCONT
恢复后台进程并将其置于前台时,可以尝试再次从键盘读取输入。
SIGTTOU
发送给试图将输出写入已有前台进程的终端控制台的后台进程。后台进程组成员调用
tcsetpgrp
时也会产生该信号。默认情况下与 SIGTTIN 行为相同。
信号处理¶
可以使用 sigaction
系统调用来更改当前进程处理信号的方式。注意,shell 进程本身可能需要忽略一些信号,但 shell 的子进程仍应以默认操作响应。例如,shell 可以选择忽略 SIGTTOU
,但子进程不应该。
提醒
fork 出的进程将继承原始进程的信号处理程序。
更多信息可以参考 man 2 sigaction
和 man 7 signal
。你可能需要关注 SIG_DFL
和 SIG_IGN
。
更多关于进程组和信号处理的知识可以参考:https://cs162.org/static/readings/ic221_s16_lec17.html
要求 | 满分 |
---|---|
CTRL+C 正确终止正在运行的进程 |
5% |
CTRL+C 在 shell 嵌套时也能正确终止正在运行的进程 |
10% |
CTRL+C 可以丢弃命令 |
10% |