在图10-1所示的信号中,POSIX.1认为有以下6个与作业控制有关。
SIGCHLD
子进程已停止或终止。SIGCONT
如果进程已停止,则使其继续运行。SIGSTOP
停止信号(不能被捕捉或忽略)。SIGTSTP
交互式停止信号。SIGTTIN
后台进程组成员读控制终端。SIGTTOU
后台进程组成员写控制终端。
除SIGCHLD
以外,大多数应用程序并不处理这些信号,交互式shell则通常会处理这些信号的所有工作。当键入挂起字符(通常是Ctrl+Z)时,SIGTSTP
被送至前台进程组的所有进程。当我们通知shell在前台或后台恢复运行一个作业时,shell向该作业中的所有进程发送SIGCONT
信号。与此类似,如果向一个进程递送了SIGTTIN
或SIGTTOU
信号,则根据系统默认的方式,停止此进程,作业控制shell了解到这一点后就通知我们。
一个例外是管理终端的进程,例如,vi(1)
编辑器。当用户要挂起它时,它需要能了解到这一点,这样就能将终端状态恢复到vi 启动时的情况。另外,当在前台恢复它时,它需要将终端状态设置回它所希望的状态,并需要重新绘制终端屏幕。可以在下面的例子中观察到与 vi 类似的程序是如何处理这种情况的。
在作业控制信号间有某些交互。当对一个进程产生 4 种停止信号(SIGTSTP
、SIGSTOP
、SIGTTIN
或SIGTTOU
)中的任意一种时,对该进程的任一未决SIGCONT
信号就被丢弃。与此类似,当对一个进程产生SIGCONT
信号时,对同一进程的任一未决停止信号被丢弃。
注意,如果进程是停止的,则SIGCONT
的默认动作是继续该进程;否则忽略此信号。通常,对该信号无需做任何事情。当对一个停止的进程产生一个 SIGCONT
信号时,该进程就继续,即使该信号是被阻塞或忽略的也是如此。
实例
图10-31中的程序演示了当一个程序处理作业控制时通常所使用的规范代码序列。该程序只是将其标准输入复制到其标准输出,而在信号处理程序中以注释形式给出了管理屏幕的程序所执行的典型操作。
#include "apue.h"
#define BUFFSIZE 1024
static void
sig_tstp(int signo) /* signal handler for SIGTSTP */
{
sigset_t mask;
/* ... move cursor to lower left corner, reset tty mode ... */
/*
* Unblock SIGTSTP, since it's blocked while we're handling it.
*/
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL); /* reset disposition to default */
kill(getpid(), SIGTSTP); /* and send the signal to ourself */
/* we won't return from the kill until we're continued */
signal(SIGTSTP, sig_tstp); /* reestablish signal handler */
/* ... reset tty mode, redraw screen ... */
}
int
main(void)
{
int n;
char buf[BUFFSIZE];
/*
* Only catch SIGTSTP if we're running with a job-control shell.
*/
if (signal(SIGTSTP, SIG_IGN) == SIG_DFL)
signal(SIGTSTP, sig_tstp);
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
图10-31 如何处理SIGTSTP
当图10-31中的程序启动时,仅当SIGTSTP
信号的配置是SIG_DFL
,它才安排捕捉该信号。
其理由是:当此程序由不支持作业控制的shell(如/bin/sh)启动时,此信号的配置应当设置为SIG_IGN
。实际上,shell并不显式地忽略此信号,而是由init
将这3个作业控制信号SIGTSTP
、SIGTTIN
和SIGTTOU
设置为SIG_IGN
。然后,这种配置由所有登录shell继承。只有作业控制shell才应将这3个信号重新设置为SIG_DFL
。
当键入挂起字符时,进程接到 SIGTSTP
信号,然后调用该信号处理程序。此时,应当进行与终端有关的处理:将光标移到左下角、恢复终端工作方式等。在将SIGTSTP
重置为默认值(停止该进程),并且解除了对此信号的阻塞之后,进程向自己发送同一信号SIGTSTP
。因为正在处理 SIGTSTP
信号,而在捕捉该信号期间系统自动地阻塞它,所以应当解除对此信号的阻塞。到达这一点时,系统停止该进程。仅当某个进程(通常是正响应一个交互式fg命令的作业控制shell)向该进程发送一个 SIGCONT
信号时,该进程才继续。我们不捕捉 SIGCONT
信号。该信号的默认配置是继续运行停止的进程,当此发生时,此程序如同从kill
函数返回一样继续运行。当此程序继续运行时,将SIGTSTP
信号重置为捕捉,并且做我们所希望做的终端处理(如重新绘制屏幕)。