Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1119570
  • 博文数量: 284
  • 博客积分: 8223
  • 博客等级: 中将
  • 技术积分: 3188
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-01 13:26
文章分类

全部博文(284)

文章存档

2012年(18)

2011年(33)

2010年(83)

2009年(147)

2008年(3)

分类: LINUX

2009-09-14 16:45:52

   本章将详细介绍信号机制的基本概念、UNIX对信号机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。
   信号机制也是进程之间相互传递消息的一种方法,但作为进程间通信来介绍是不合适的。信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实 质和使用很象中断。所以,信号可以说是进程控制的一部分。这里将它单独作为一章,是因为它有独特和重要的作用。下面我们就来介绍信号。

1    信号的基本概念
   本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件。基本概念对于理解和使用信号,对于理解信号机制都特别重要。下面就来看看什么是信号。

1.1    基本概念
   软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。核心也可以因为内部事件而 给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

   收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来 处理。第二种方法是, 忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得 进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
   在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。

1.2    信号的类型
    发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:
    (1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
    (2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
    (3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
    (4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
    (5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
    (6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
    (7) 跟踪进程执行的信号。

表4-1 常见的信号名称及含义

信号名                           默认处理动作                  发出信号的原因                             符合标准

              

SIGHUP                1            终止程序                            终端挂起                                          POSIX

SIGINT                2            终止程序                            中断键(如break)被按下                 ANSI

SIGQUIT              3            核心映像转储coredump       键盘的退出键被按下                        POSIX

SIGILL                 4            core dump                           遇到非法指令                                   ANSI

SIGTRAP             5             core dump                          跟踪捕捉                                          POSIX

SIGIOT                6            core dump                           I/O捕捉指令,硬件故障                   4.2 BSD

SIGABRT             6             core dump                          核心标准子例程abort发出                 ANSI

SIGEMT         7,-,7             core dump                           仿真捕捉指令                                   4.2 BSD

SIGFPE                8            core dump                           浮点异常                                          ANSI

SIGKILL              9             终止程序                            终止程序                                          POSIX

SIGBUS        10,7,10           core dump                           总线错误                                          4.2 BSD

SIGSEGV            11            core dump                           段违例                                              ANSI

表4-1 常见的信号名称及含义(续)

信号名                                    默认处理动作                     发出信号的原因                                符合标准

SIGSYS         12,-,12           core dump          调用不存在的系统调用                     SVID

SIGPIPE               13           终止程序                  写一个没有读端口的管道                  POSIX

SIGALRM             14           终止程序                      实时定时器计时到                            POSIX

SIGTERM             15           终止程序                       软件结束信号                                   ANSI

SIGURG        16,23,21         忽略该信号                       Socket出现紧急条件                         4.2 BSD

SIGSTOP       17,19,23         停止进程                            停止进程(不能被捕捉或忽略)            POSIX

SIGTSTP       18,20,24         停止进程                            从键盘产生的进程停止信号              POSIX

SIGCONT      19,18,25         忽略该信号                         进程继续(曾被停止的进程)           POSIX

SIGCHLD      20,17,18         忽略该信号                         子进程状态改变                                OSIX

SIGCLD         -,-,18         忽略该信号                         同上(只是定义名字不同)              SYSV

SIGTTIN        21,21,26       停止进程                            后台进程企图从控制终端读              POSIX

SIGTTOU      22,22,27         停止进程                            后台进程企图向控制终端写              POSIX

SIGIO        23,29,22         忽略该信号                         I/O操作现在可以进行了               4.2 BSD

SIGXCPU      24,24,30         终止程序                            超出设定的CPU时间限制                  4.2 BSD

SIGXFSZ      25,25,31         终止程序                            超出设定的文件大小限制                4.2 BSD

SIGVTALRM    26,26,28         终止程序                            实际时间报警时钟信号                     4.2 BSD

SIGPROF      27,27,29         终止程序                            Profile定时器到时                            4.2 BSD

SIGWINCH     28,28,20         忽略该信号                         窗口大小改变                                 4.3 BSD

SIGINFO     29,-,-            忽略该信号                         键盘请求状态信息                           4.2 BSD

SIGUSR1      30,10,16         终止程序                            用户自定义信号1                             POSIX

SIGUSR2      31,12,17         终止程序                            用户自定义信号2                             POSIX

SIGSTKFLT  -,16,-             终止程序                            协处理器堆栈错误                           SYSV

         在UNIX系统V的版本2中有19个信号,目前还有所增加。上面介绍的35个信号是常见系统所支持的。我们以表格的形式介绍了各种信号的名称、作用及其在 默认情况下的处理动作。表4-1中各种默认处理动作的含义是:终止程序是指进程退出;忽略该信号是将该信号丢弃,不做处理;停止程序是指程序挂起,进入停 止状况以后还能重新进行下去,一般是在调试的过程中(ptrace系统调用);核心映像转储是指将进程数据在内存的映像和进程在核心结构中存储的部分内容 以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并 且可以调试他们的程序。

        注意  信 号SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信号SIGIOT与SIGABRT是一个信号。对于表中的信号值一列是标出信号的取值,表 中给出了三个取值(只给出一个信号值时,表示三个取值相同),第一个取值一般对应于alpha和sparc平台,中间的对应于i386和PowerPC, 最后一个对应于mips。当然在不同系统中的实现可能不同,这取决于读者使用的系统的实现。所以建议最好使用为信号定义的名字,而不要直接使用信号的值。

2      信 号 机 制

          上一节中介绍了信号的基本概念,在这一节中,我们将介绍核心如何实现信号机制。即核心如何向一个进程发送信号、进程如何接收一个信号、进程怎样控制自己对 信号的反应、核心在什么时机处理和怎样处理进程收到的信号。还要介绍一下setjmp和longjmp在信号中起到的作用。

2.1     核心对信号的基本处理方法

         核心给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位,这个概念在第一节中已经提到。这里要补充的是,如果信号发送 给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进 程。这一点比较重要,因为进程检查是否收到信号的时机是:一个进程在即将从核心态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡 眠状态时。
         核心处理一个进程收到的信号的时机是在一个进程从核心态返回用户态时。所以,当一个进程在核心态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
         核心处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。前面介绍概念的时候讲过,处理信号有三种类型:进程接收到信号后退 出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。这些在表4-1中有详细的描述。当进程接收到一个它忽略的信号时,进程丢 弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从核心态返回用户态时执行用户定义的函数。而且执行用户定义的函数的 方法很巧妙,核心是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从核心返回弹出栈顶时就返回到用户定义的函 数处,从函数返回再弹出栈顶时,才返回原先进入核心的地方。这样做的原因是用户定义的处理函数不能且不允许在核心态下执行(如果用户定义的函数在核心态下 运行的话,用户就可以获得任何权限)。
         在信号的处理方法中有几点特别要引起注意。第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,核心清除u区中设定的对该信号的处理例程的地 址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程在调用signal之前又得 到该信号而导致退出。在BSD中,核心不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢出。为了避免出现上述情况。在 BSD系统中,核心模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。
         第二个要引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp, 跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中 断。这要注意的是,BSD系统中核心可以自动地重新开始系统调用。
         第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。
         第四个要注意的地方:核心对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进 程就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作 (找出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了 这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在 可被中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。
         如果一个进程调用signal系统调用,并设置了SIGCLD的处理方法,并且该进程有子进程处于僵死状态,则核心将向该进程发一个SIGCLD信号。

2.2    setjmp和longjmp的作用

        前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。
        在介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就 是使用setjmp和longjmp的结果。setjmp将保存的上下文存入u区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资 源或其他原因要去睡眠时,核心为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,核心为进程调用longjmp,该操作是核心 为进程将原先setjmp调用保存在进程u区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且核心为setjmp返回1,使得 进程知道该次系统调用失败。这就是它们的作用。

3     有关信号的系统调用

   前面两节已经介绍了有关信号的大部分知识。这一节我们来了解一下这些系统调用。其中,系统调用signal是进程用来设定某个信号的处理方法,系统调用 kill是用来发送信号给指定进程的。这两个调用可以形成信号的基本操作。后两个调用pause和alarm是通过信号实现的进程暂停和定时器,调用 alarm是通过信号通知进程定时器到时。所以在这里,我们还要介绍这两个调用。

3.1    signal 系统调用

   系统调用signal用来设定某个信号的处理方法。该调用声明的格式如下:
   void (*signal(int signum, void (*handler)(int)))(int);
   在使用该调用的进程中加入以下头文件:
   #include 上述声明格式比较复杂,如果不清楚如何使用,也可以通过下面这种类型定义的格式来使用(POSIX的定义):
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    但这种格式在不同的系统中有不同的类型定义,比如在BSD 4.4系统中是通过下面这种类型定义使用的:
     typedef void (*sig_t) (int);
     sig_t signal(int signum, sig_t handler);
    所以要使用这种格式,最好还是参考一下联机手册。在调用中,参数signum指出要设置处理方法的信号。第二个参数handler是一个处理函数,或者是
       + SIG_IGN:忽略参数signum所指的信号。
       + SIG_DFL:恢复参数signum所指信号的处理方法为默认值。传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。 系统调用signal返回值是指定信号signum前一次的处理例程或者错误时返回错误代码SIG_ERR。注意,根据 POSIX(B.3.3.1.3),进程不能将SIGCHLD设置为SIG_IGN,这一点与BSD系统不同。下面来看一个简单的例子:     #include
    #include
    #include
  
    void sigroutine(int dunno)
    {
    /* 信号处理例程,其中dunno将会得到信号的值 */  
    switch (dunno)
    {    
    case 1:      
    printf("Get a signal -- SIGHUP \n");      
    break;    
    case 2:      
    printf("Get a signal -- SIGINT \n");      
    break;    
    case 3:      
    printf("Get a signal -- SIGQUIT \n");      
    break;  
    }  
    return;
    }
    int main()
    {  
    printf("process id is %d\n",getpid());
    signal(SIGHUP, sigroutine);
    //* 下面设置三个信号的处理方法  
    signal(SIGINT, sigroutine);
    signal(SIGQUIT, sigroutine);  
    for (;;);
    }
    其中信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-\发出。该程序执行的结果如下:
    localhost:~$ ./sig_test
    process id is 463
    Get a signal -- SIGINT       //按下Ctrl-C得到的结果
    Get a signal -- SIGQUIT     //按下Ctrl-\得到的结果                      
                                            //按下Ctrl-z将进程置于后台
    [1]+    Stopped                   ./sig_test
    localhost:~$ bg
    [1]+ ./sig_test &
    localhost:~$ kill -1 463      //向进程发送SIGHUP信号
    localhost:~$ Get a signal -- SIGHUP
    kill -9 463                //向进程发送SIGKILL信号,终止进程
    localhost:~$

3.2   kill 系统调用

系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
   int kill(pid_t pid, int sig);
   在使用该调用的进程中加入以下头文件:
   #include
   #include
   该系统调用可以用来向任何进程或进程组发送任何信号。如果参数pid是正数,那么该调用将信号sig发送到进程号为pid的进程。如果pid等于0,那么 信号sig将发送给当前进程所属进程组里的所有进程。如果参数pid等于-1,且发送进程有超级用户的权力时,信号sig将发送给除了进程1和自身以外的 所有进程,若发送进程没有超级用户的权力,信号sig将发送给所有uid等于发送信号进程的euid的进程。如果参数pid小于-1,信号sig将发送给 属于进程组-pid的所有进程。如果参数sig为0,将不发送信号。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应的错误代码errno。 下面是一些可能返回的错误代码:
   + EINVAL:指定的信号sig无效。
   + ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。
   + EPERM:进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的 进程的UID或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个 组,则该错误表示组中有成员进程不能接收该信号。

3.3   pause系统调用

系统调用pause的作用是等待一个信号。该调用的声明格式如下:
   int pause(void);
   在使用该调用的进程中加入以下头文件:
   #include
   该调用使得发出调用的进程进入睡眠,直到接收到一个信号为止。该调用总是返回-1,并设置错误代码为EINTR(接收到一个信号)。下面是一个简单的范例:
   #include
   #include
   #include
  
   void sigroutine(int unused)
   {  
      printf("Catch a signal SIGINT\n");
   }
   int main()
   {  
     signal(SIGINT, sigroutine);  
     pause();  
     printf("receive a signal\n");
   }
   在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当我们按下Ctrl-C时,信号被捕捉,并且使得pause退出等待状态。

3.4   alarm和 setitimer系统调用

系统调用alarm的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。该调用的声明格式如下:
   unsigned int alarm(unsigned int seconds);
   在使用该调用的进程中加入以下头文件:
   #include
   系统调用alarm安排核心为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。注意,在使 用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。
   对于alarm,这里不再举例。现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,这两个调用的声明格式如下:
   int getitimer(int which, struct itimerval *value);
   int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
   在使用这两个调用的进程中加入以下头文件:       
   #include
   该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:
   +   ITIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
   +   ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。
   + ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和核心态花费的时间。计时到达将发送SIGPROF信号给进程。

定时器中的参数value用来指明定时器的时间,其结构如下:
   struct itimerval
   {     
     struct timeval it_interval; /* 下一次的取值 */     
     struct timeval it_value;     /* 本次的设定值 */
   };
   该结构中timeval结构定义如下:
   struct timeval
   {     
     long tv_sec;                 /* 秒 */     
     long tv_usec;                /* 微秒,1秒 = 1000000 微秒*/
   };
   在setitimer调用中,参数ovalue如果不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将 it_value的值设定为it_interval的值,然后重新开始计时,如此往复。当it_value设定为0时,计时器停止,或者当它计时到期,而 it_interval为0时停止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno:
   +   EFAULT:参数value或ovalue是无效的指针。
   +   EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。
   下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号:

   #include
   #include
   #include
   #include
   int sec;
  
   void sigroutine(int signo)
   {  
     switch (signo)
     {    
       case SIGALRM:      
       printf("Catch a signal -- SIGALRM\n");      
       break;    
       case SIGVTALRM:      
       printf("Catch a signal -- SIGVTALRM\n");      
       break;  
     }  
     return;
   }
  
   int main()
   {
     struct itimerval value,ovalue,value2;
     sec = 5;  
     printf("process id is %d\n",getpid());  
     signal(SIGALRM, sigroutine);  
     signal(SIGVTALRM, sigroutine);  
     value.it_value.tv_sec = 1;  
     value.it_value.tv_usec = 0;
     value.it_interval.tv_sec = 1;  
     value.it_interval.tv_usec = 0;  
     setitimer(ITIMER_REAL, &value, &ovalue);
     value2.it_value.tv_sec = 0;  
     value2.it_value.tv_usec = 500000;  
     value2.it_interval.tv_sec = 0;  
     value2.it_interval.tv_usec = 500000;  
     setitimer(ITIMER_VIRTUAL, &value2, &ovalue);  
     for (;;) ;
   }
   该例子的屏幕拷贝如下:
   localhost:~$ ./timer_testprocess id is 579
   Catch a signal -- SIGVTALRM
   Catch a signal -- SIGALRM
   Catch a signal -- SIGVTALRM
   Catch a signal -- SIGVTALRM
   Catch a signal -- SIGALRM
   Catch a signal -- SIGVTALRM
   本章不再介绍其他的调用,如果读者希望了解其他调用,请参考联机手册或其他文档。

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

chinaunix网友2010-01-25 09:42:20

多谢,很有帮助!!(: