现在的位置: 首页 > 综合 > 正文

UNIX环境高级编程读书笔记(十)—信号(1)

2012年12月11日 ⁄ 综合 ⁄ 共 5121字 ⁄ 字号 评论关闭

1信号本质与来源

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarmsetitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

2信号的种类

可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通信(一):管道及有名管道》的附1中列出了系统所支持的所有信号。

2.1可靠信号与不可靠信号

2.1.1  "不可靠信号"

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:

  • 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
  • 信号可能丢失,后面将对此详细阐述。
    因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。

Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。

2.1.2 "可靠信号"

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。

可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()

但应注意的是:信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。

信号值小于SIGRTMIN的信号为不可靠信号,信号值在SIGRTMINSIGRTMAX之间的信号为可靠信号。

2.2   实时信号与非实时信号

早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

3   进程对信号的响应

进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILLSIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。

4   信号的发送

发送信号的主要函数有:kill()raise() sigqueue()alarm()setitimer()以及abort()。这里仅给出kill()raise()

4.1   kill()

#include <sys/types.h>

#include <signal.h>

int kill(pid_t pid,int signo)

参数pid的值

信号的接收进程

pid>0

进程IDpid的进程

pid=0

同一个进程组的进程

pid<0 pid!=-1

进程组ID -pid的所有进程

pid=-1

除发送进程自身外,所有进程ID大于1的进程

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

Kill()最常用于pid>0时的信号发送,调用成功返回 0; 否则,返回 -1。注:对于pid<0时的情况,对于哪些进程将接受信号,各种版本说法不一,其实很简单,参阅内核源码kernal/signal.c即可,上表中的规则是参考red hat 7.2

4.2   raise()

#include <signal.h>

int raise(int signo)

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

#include <stdlib.h>
void abort(void);

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

5 信号的安装(设置信号关联动作)

如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。

linux主要有两个函数实现信号的安装:signal()sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。

5.1 signal()
#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:

typedef void (*sighandler_t)(int)

sighandler_t signal(int signum, sighandler_t handler));

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)

如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR

5.2   sigaction()
#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILLSIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldactNULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。

       信号是软件中断。很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法.每个信号都有一个名字,这些名字都以三个字符SIG开头。在头文件<signal.h>中,这些信号都被定义为正整数(信号编号)。

      

很多条件可以产生一个信号。

1.当用户按某些终端键时,产生信号。如:用户按ctrl+c 产生中断信号,中止当前进程。

2.硬件异常产生信号。如:除数为0,无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。

3.进程用kill(1) 命令将信号发送给另一个进程或进程组。自然,有些限制:按收信号进程和发送信号进程的所有这者必须相同,或发送信号的所有者必须是超级用户。

4.用户可用kill(1) 命令将信号发送给其他进程。此程序是kill函数的界面,常用此命名终止一个失控的后台进程。

5.当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。

 

Redhatkill -l 得到:

      1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL

      5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE

      9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2

     13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD

     18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN

     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ

     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO

     30) SIGPWR      31) SIGSYS      32) SIGRTMIN    33) SIGRTMIN+1

     34) SIGRTMIN+2  35) SIGRTMIN+3  36) SIGRTMIN+4  37) SIGRTMIN+5

     38) SIGRTMIN+6  39) SIGRTMIN+7  40) SIGRTMIN+8  41) SIGRTMIN+9

     42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13

     46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

     50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10

     54) SIGRTMAX-9  55) SIGRTMAX-8  56) SIGRTMAX-7  57) SIGRTMAX-6

     58) SIGRTMAX-5  59) SIGRTMAX-4  60) SIGRTMAX-3  61) SIGRTMAX-2

     62) SIGRTMAX-1  63) SIGRTMAX

 

抱歉!评论已关闭.