10.21 作业控制信号
POSIX.1使用了六个信号来实现作业控制:
- SIGCHLD 子进程已经停止或者终止
- SIGCONT 已经停止的进程继续运行
- SIGSTOP 停止进程信号(不能捕获或者忽略)
- SIGTSTP 交互式停止信号
- SIGTTIN 后台进程组成员从控制终端读取
- SIGTTOU 后台进程组成员向控制终端写出
除了信号SIGCHLD之外,许多应用程序并不处理上述信号:交互式shell通常会完成处理上述信号的必要的全部工作。当我们输入挂起字符的时候(通常是Control-Z),SIGTSTP被发送到前台进程组的所有进程。当我们告知shell恢复某个作业的运行的时候,shell将会向指定作业中的所有进程发送SIGCONT信号,类似地,如果SIGTTIN或者SIGTTOU被发送到进程,进程默认情况下会被停止,作业控制shell将会识别到这一事件并通知我们。
在任务控制信号之间还存在一些交互,当四个停止信号(SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU)中的任何一个产生的时候,任何对于相同进程的处于挂起状态的SIGCONT信号都将被抛弃,类似地,当SIGCONT信号产生的时候,任何对于相同进程处于挂起状态的停止信号都将被丢弃。
注意,SIGCONT的默认行为是继续运行一个处于停止状态的进程,否则,信号将被忽略,通常情况下,我们不必对此信号做任何处理,当SIGCONT信号被产生的时候,该进程就会继续运行,即是该信号被阻塞或者忽略。
Example
图10.31中的程序阐述了当一个进程处理作业控制的时候的正常的代码序列。该程序只是简单地复制其标准输入到其标准输出,但是在信号处理函数中给出的注释适用于管理屏幕的程序执行的典型动作。
“`#include “apue.h”
define BUFFSIZE 1024
static void sig_tstp(int signo) /signal handler for SIGSTOP/
{
sigset_t mask;
/* … move cursor to lower left corner,reset tty mode … /
/ Unblock SIGSTOP,since it’s blocked while we’re handling it */
sigempty(&mask);
sigaddset(&mask, SIGSTOP);
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);
/* ... reset tty mode, redraw screen ... */
}
int main(void)
{
int n;
char buf[BUFFSIZE];
/*
* only catch SIGTSTP if we're running with a jb-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);
}
“`
Figure 10.31 How to handle SIGTSTP
当图10.31中的程序开始运行的时候,仅仅在SIGTSTP信号的处理时SIG_DFL的时候才会去设置捕获信号SIGTSTP,其原因为:
- 当上述程序被不支持作业控制的的程序启动的时候(比如说/bin/sh),该信号的处理将被设置为SIG_IGN;事实上,shell并不会显示地忽略信号,init设置了三个作业控制信号(SIGTSTP,SIGTTIN,SIGTTOU)的处理方式是SIG_IGN,这一处理被所有登录的shell继承
- 仅仅只有拥有作业控制的shell才会重置这三个信号的处理方式为SIG_DFL.
当我们输入挂起信号的时候,进程会收到SIGTSTP信号,并且信号处理函数将被调用,在这个时候,我们将会执行任何与终端相关的处理:比如说移动光标到左下角,恢复终端模式等等,然后在复位其处理方式为默认处理(停止进程)并且解除信号阻塞以后,我们向自身发送相同的信号SIGTSTP。我们必须解除该信号的阻塞因为我们当前正在处理相同信号,系统在其被捕获的时候会自动阻塞信号。在这个时候,系统就会停止进程的运行。后续只有当进程收到SIGCONT信号的时候,才会恢复运行,我们并没有捕获SIGCONT信号,因为SIGCONT信号的默认处理方式是恢复已经停止运行进程的运行,在这个时候,程序将会继续运行就像其刚刚从kill函数返回一样,在程序恢复运行的时候,我们将会复位信号SIGTSTP的处理,并执行任何我们想要的终端处理(比如说,我们可以重绘屏幕)。