当我写此篇博客的时候,突然发现一片写得很到位的文章,所以。。
https://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html#authorN1001A
1.什么是POSIX信号
- 信号是一种软件中断(ps:在connect函数的第二种出错情况是一种硬件中断,见基本tcp套接字编程)。
- 很多重要的应用程序都需要提供处理信号的代码(在一些高级语言编程中可能很少涉及这方面,要注意)
- 信号提供了一种处理异步事件的方法(信号机制也是一种较为简单的进程间通信的一种机制)
每个信号都有一个名字,以SIG开头。不同操作系统可能支持的信号略有区别。
- 在头文件<bits/signum.h>中,信号名都被定义为正整数常量,不存在编号为0的信号(以下是ubuntu14.04支持的信号)
- 信号都是由系统内核产生
2.POSIX具体信号详解
首先确定几个概念;
什么是core文件?
具体参见我的另一篇博文:调试core文件详解
我的操作系统ubuntu14.04
vim /usr/include/x86_64-linux-gnu/bits/signum.h
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
查看系统中定义的信号列表 kill -l
sunxiaowu@sunxiaowu:~$ kill -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
16) SIGSTKFLT 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 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
常用信号说明:
截图来自unix高级环境编程(划红圈部分我们要重点掌握)
3.UNIX系统提供的signal信号函数
首先我们要明确:
在某个信号出现时,内核按照三种方式处理;
1.忽略此信号
2.捕捉信号
3.执行系统默认动作
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//注意返回值的含义。若成功,则返回以前信号的处理配置,否则SIG_ERR
- func的值:SIG_IGN,SIG_DEL,函数调用地址
- 指定的函数地址所指函数为信号处理函数,调用此函数称为捕捉该信号
我们使用UNIX环境高级编程中的源码示例
在apue.3e中的signal模块下
~/Downloads/apue/apue.3e/signals$ vim sigusr.c #include "apue.h" static void sig_usr(int); /* one handler for both signals */ int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2");
for ( ; ; )if (signal(SIGTERM, sig_usr) == SIG_ERR) err_sys("can't catch SIGTERM");
pause();
}
static void sig_usr(int signo) /* argument is signal number */
{
if (signo == SIGUSR1)
printf("received SIGUSR1\n");
else if (signo == SIGUSR2)
printf("received SIGUSR2\n");
else if(signo == SIGTERM)
printf("received SIGTERM\n");
else
err_dump("received signal %d\n", signo);
}
sunxiaowu@sunxiaowu:~/Downloads/apue/apue.3e/signals$ ./sigusr & //后台启动进程
[3] 2977 //作业控制shell打印作业编号和进程id
sunxiaowu@sunxiaowu:~/Downloads/apue/apue.3e/signals$ kill -USR1 2977 //向该进程发送SIG_USR1
received SIGUSR1
sunxiaowu@sunxiaowu:~/Downloads/apue/apue.3e/signals$ kill -USR2 2977 //向该进程发送SIG_USR2
received SIGUSR2
sunxiaowu@sunxiaowu:~/Downloads/apue/apue.3e/signals$ kill 2977 //向该进程发送SIGTERM
received SIGTERM
注意:当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程内存映像,所以信号捕捉函数地址在子进程中也有意义。
4.中断的系统调用
- 当捕捉到某个信号时,被中断的是内核中执行的系统调用。系统调用分为两类:低系统调用(类似read这种可能阻塞的)和其他系统调用。低系统调用可能的常见情况
- 某些文件(网络通道,终端设备,磁盘文件,管道等)的数据不存在,则读操作可能会阻塞
- 如果数据不能被相同类型文件立即接收,则写操作可能会阻塞
- pause函数和wait waitpid(注意wait和waitpid在捕捉到信号时总是被中断)
- 某些ioctl操作
- 某些进程间通信函数
- 早期UNIX系统的特性(注意此处是早期的特性):如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不在继续执行。该系统调用返回出错,errno被设置为EINTR。
- 注意与磁盘I/O有关的系统调用(磁盘I/O又是一个可值得探讨的问题)。I/O操作总会很快返回,并使得调用者不在处于阻塞状态
- 中断系统调用方法的一个实例:一个进程启动了读终端操作,而使用该终端设备的用户却长时间离开该终端,进程就可能阻塞很久,这时我们可以发送信号,中断该read系统调用。
- 重点:对中断系统调用的错误返回处理。posix.1要求只有中断信号的SA_RESTART标志有效,才会开启重启系统调用的机制。(可以减少应用程序的负担)。或者由应用程序显示重启被中断的系统调用。
4.不可靠的信号和可靠的信号
不可靠的信号:指可能丢失的信号,一个信号产生了,但进程却可能一直不知道这点(早期的unix版本),后来对其修改,提供了可靠信号的机制。查看其他博主博客后,发现一篇详尽讲解,以下摘自博客(http://blog.csdn.net/u013074465/article/details/45978755)
“不可靠信号”
linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。
这就是"不可靠信号"的来源。它的主要问题是:
- 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
- 信号可能丢失,后面将对此详细阐述。 如果在进程对某个信号进行处理时,这个信号发生多次,对后到来的这类信号不排队,那么仅传送该信号一次,即发生了信号丢失。
因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。
"可靠信号"
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制做了标准化。但是,POSIX只对可靠信号机制应具有的功能以及信号机制的对外接口做了标准化,对信号机制的实现没有作具体的规定。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
可靠信号术语:
- 当造成信号的事件发生时,我们称 为进程产生一个信号
- 当一个信号产生时,内核通常在进程表中以某种形式设置一个标志
- 当对进程表设置了某种标志时,我们称为向进程递送了一个信号
在两者之间的的时间间隔之内,称信号是为未决的(pending)
- 进程可以采取一种“阻塞信号递送”的机制,每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。可以调用sigprocmask来检测和更改当前信号屏蔽字
- 如果为进程产生了一个阻塞的信号,且对该信号的动作不是忽略,则为该进程将此信号保持为未决状态,直至该进程解除了对此信号的阻塞,或者将对此信号的动作更改为忽略,而后递送信号
- 内核在递送一个原来被阻塞的信号给进程时,才决定对它的处理方式
- 进程调用sigpending函数来判定哪些信号处于阻塞并处于未决状态。
- 如果在进程解除对谋个信号的阻塞之前,这种信号发生了多次,posix.1 允许该系统递送该信号一次或者多次。
- POSIX.1定义了一个新数据类型sigset_t来表示信号集。
5.可重入函数
参见我的另一篇博客:可重入函数讲解
6.信号机制常用实例说明