Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5635004
  • 博文数量: 922
  • 博客积分: 19333
  • 博客等级: 上将
  • 技术积分: 11226
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-27 14:33
文章分类

全部博文(922)

文章存档

2023年(1)

2020年(2)

2019年(1)

2017年(1)

2016年(3)

2015年(10)

2014年(17)

2013年(49)

2012年(291)

2011年(266)

2010年(95)

2009年(54)

2008年(132)

分类: LINUX

2012-01-30 21:19:18

++++++APUE读书笔记-10信号-20作业控制信号++++++

 

20、作业控制信号
================================================ 
 POSIX.1考虑用下面6种信号来进行作业控制:
 SIGCHLD  子进程被停止或者终止。
 SIGCONT  如果之前停止的话,继续进程。
 SIGSTOP  停止信号(无法被捕获或者忽略)。
 SIGTSTP  用于交互的停止信号。
 SIGTTIN  后台进程组的一个进程从控制终端读取。
 SIGTTOU  后台进程组的一个进程向控制终端写。
 除了SIGCHLD之外,大多数应用程序不会处理这些信号:交互的shell一般已经做了处理这些信号的所有工作。当我们键入挂起字符的时候(一般为C-z),SIGTSTP信号会发送给所有在前台进程组的进程。当我们告诉shell重新启动一个前台或者后台的作业的时候,shell会给所有作业中的进程发送一个SIGCONT信号。类似,如果SIGTTIN或者SIGTTOU被发送给了一个进程,那么进程默认来说会被停止(stop),并且作业控制shell会识别出这个现象,并且通知我们。
 一个例外的进程就是控制terminal的进程,例如vi编辑器。它需要知道什么时候用户想要进行挂起,这样它能够恢复终端的状态到vi启动时候的状态。并且,当它在前台重新开始的时候,vi编辑其需要设置终端状态为之前它想要的状态,并且它需要重新绘制终端屏幕。后面的例子中我们将会看到一个类似vi的程序是如何处理这个状况的。
 有一些作业控制的交互信号。当四个停止信号(SIGTSTP,SIGSTOP,SIGTTIN,或者SIGTTOU)被发送给一个进程的时候,那个进程中任何提交状态的SIGCONT信号都会被丢弃。类似的,当SIGCONT信号被发送给一个进程的时候,那个进程任何提交状态的stop信号都会被丢弃。
 注意:默认SIGCONT的行为是重新开始一个被停止的进程;如果进程没有被停止,那么它被忽略。一般来说,我们不必对这个信号进行什么特殊的处理。当SIGCONT被发送给一个被停止的进程的时候,进程会被继续,即使这个信号被阻塞或者忽略。
 举例:

 如何处理SIGTSTP
 #define BUFFSIZE   1024
 static void sig_tstp(int signo) /* SIGTSTP的信号处理函数 */
 {
     sigset_t    mask;

     /* ... 将光标移动到左上角,重新设置tty的模式... */

     /*
      * 解开SIGTSTP的阻塞,因为在我们处理的时候它被阻塞。
      */
     sigemptyset(&mask);
     sigaddset(&mask, SIGTSTP);
     sigprocmask(SIG_UNBLOCK, &mask, NULL);

     signal(SIGTSTP, SIG_DFL);   /* 重新设置SIGTSTP的特性为默认 */

     kill(getpid(), SIGTSTP);    /* 给我们自己发送SIGTSTP信号. */

     /* 我们不会从kill中返回,直到我们重新继续了 */

     signal(SIGTSTP, sig_tstp);  /* 重新建立信号处理函数 */

     /* ... 重新设置tty模式,重新绘制屏幕... */
 }
 int main(void)
 {
     int     n;
     char    buf[BUFFSIZE];

     /*
      * 只有我们使用作业控制的shell运行的时候才捕获SIGTSTP。
      */
     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);
 }

 上面例子中的程序,给出了当一个程序处理作业控制的时候,使用的一般的代码次序。这个程序只是简单地把它的标准输入拷贝到它的标准输出,但是,在信号处理函数中,已经用注释给出了用来管理屏幕的程序的典型的动作。当程序启动的时候,只有当SIGTSTP的处理动作被设置为SIG_DFL的时候,它才会捕获SIGTSTP信号。这样的原因是,当程序通过一个不支持作业控制的shell(例如/bin/sh)启动的时候,信号的处理动作应该被设置为SIG_IGN。实际上,shell并不会显示地忽略这个信号,init会设置三个作业控制信号的处理动作(SIGTSTP,SIGTTIN,SIGTTOU)为SIG_IGN。这个动作然后会被所有的登陆shell继承下来。只有作业控制的shell才会设置这三个信号的处理动作为SIG_DFL。
 当我们键入suspend字符的时候,进程接收到SIGTSTP信号,调用信号处理函数。这里,我们做了所有与终端处理相关的动作:将光标移动到左上角,恢复终端模式,等等。我们然后在重新设置SIGTSTP的处理动作为默认的动作(即停止进程)并将它解阻塞之后,给自己发送一个同样的SIGTSTP(我们还得解阻塞这个信号的原因是我们目前正在处理同样的信号)。这样,进程停止了,只有当它接收到SIGCONT的时候(一般这个信号来自作业控制shell,而且是源于一个fg命令),它才继续执行。我们不会捕获SIGCONT信号,它的默认处理动作就是重新开始一个停止了的进程;当发生这个的时候,程序会继续执行,就好象它刚刚从kill中返回一样。当程序继续的时候,我们会重新设置SIGTSTP信号的处理动作,并且做我们想要作的一些终端处理动作(例如我们可以重新绘制屏幕)。

参考:

 

 

阅读(1240) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~