linux信号总结

来源:互联网 发布:淘宝网企业店铺申请 编辑:程序博客网 时间:2024/06/05 09:53

参考连接
http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html
http://www.2cto.com/os/201209/158568.html
http://blog.csdn.net/dlutbrucezhang/article/details/11577985

1. 信号

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号,内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

2. 信号处理流程

一个完整的信号生命周期可以分为三个阶段:

  • 信号诞生
    信号事件的发生有两个来源:

    • 硬件来源(比如我们按下了键盘或者其它硬件故障);
    • 软件来源,最常用发送信号的系统函数是kill(), raise(), alarm()setitimer()以及sigqueue(),软件来源还包括一些非法运算等操作。
  • 信号在进程中注册
    在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。
    struct sigqueue {
     struct sigqueue *next;
     siginfo_t info;  //信号所携带的信息
    }
    struct sigpending {
     struct sigqueue *head, *tail;  //指向信号链表的首尾
     sigset_t signal;   //所有未决信号集,每个信号占用一位
    };
    信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理或者该信号被进程阻塞。
    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做”可靠信号”。
    当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做”不可靠信号”。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。
    总之信号注册与否,与发送信号的函数如kill()sigqueue()以及信号安装函数signal()sigaction()无关,只与信号值有关:
    信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册。

  • 信号的执行和注销
    内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当其由于被信号唤醒或者正常调度重新获得CPU时,在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。
    对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。
    当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。
    内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

    处理信号有三种类型:

    • 退出
    • 忽略
      当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行
    • 调用用户指定函数
      如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)

在信号的处理方法中有几点特别要引起注意:

第一,在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清除用户区中设定的对该信号的处理例程的地 址,即下一次进程对该信号的处理方法又改为默认值,除非在下一次信号到来之前再次使用signal系统调用。这可能会使得进程在调用signal之前又得 到该信号而导致退出。在BSD中,内核不再清除该地址。但不清除该地址可能使得进程因为过多过快的得到某个信号而导致堆栈溢出。为了避免出现上述情况。在 BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。

第二个要引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上(若系统调用未睡眠而是在运行,根据上面的分 析,等该系统调用运行完毕后再处理信号),这时该信号引起进程作一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回 时,进程就象从系统调用返回一样,但返回了一个错误如-1,并将errno设置为EINTR,指出该次系统调用曾经被中断。这要注意的是,BSD系统中内 核可以自动地重新开始系统调用,或者手如上面所述手动设置重启。

第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不 到进程曾经被唤醒,而是象没有发生过该信号一样。所以能够使pause、sleep等函数从挂起态返回的信号必须要有信号处理函数,如果没有什么动作,可 以将处理函数设为空。

第四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程正常或异常终止时,内核都向其父进程发一个SIGCLD 信号,缺省情况下,父进程忽略该信号,就象没有收到该信号似的,如果父进程希望获得子进程终止的状态,则应该事先用signal函数为SIGCLD信号设 置信号处理程序,在信号处理程序中调用wait。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,则 什么也不做。其实wait不一定放在信号处理函数中,但这样的话因为不知道子进程何时终止,在子进程终止前,wait将使父进程挂起休眠。

3. 编程接口

  • 信号的安装

    • signal(),参数是信号值,这样可以使得一个信号处理例程处理多个信号
    • sigaction(),支持信号传递信息,主要用来与 sigqueue()系统调用配合使用
  • 信号的发送

    • kill()
      Sinno为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

    • sigqueue()
      支持信号带有参数,与函数sigaction()配合使用。sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
      在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

    • alarm()
      在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。

    • setitimer()
      现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态
      该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:
      TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
      ITIMER_VIRTUAL:仅当进程执行时才进行计时,计时到达将发送SIGVTALRM信号给进程。
      ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIRTUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。

    • abort()
      向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

    • raise()
      向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。

  • 信号集及信号集操作函数
    信号集用来描述信号的集合,每个信号占用一位。Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
    int sigemptyset(sigset_t *set)
    int sigfillset(sigset_t *set)
    int sigaddset(sigset_t *set, int signum)
    int sigdelset(sigset_t *set, int signum)
    int sigismember(const sigset_t *set, int signum)
    sigemptyset(sigset_t *set)
    sigfillset(sigset_t *set)
    sigaddset(sigset_t *set, int signum)
    sigdelset(sigset_t *set, int signum)
    sigismember(const sigset_t *set, int signum)

  • 信号阻塞
    每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));

  • 信号未决
    已递送到进程,却被阻塞的所有信号
    int sigpending(sigset_t *set));

0 0
原创粉丝点击