信号是一种软中断。很多比较重要的应用程序都需要处理信号。信号提供了一种异步处理事件的方法,例如:终端用户输入中断键,会通过信号机制终止一个程序等。早期的信号存在丢失的风险,且执行在临界代码区时无法关闭所选择的信号,后来一些系统便增加了可靠信号机制。下面的章节提供详细的说明。
首先,每一个信号都有一个名字。这些名字都是以"SIG"开头的。Linux支持31种基本信号,不同的操作系统可能支持的信号数量略有不同。信号是在头文件
toney@ubantu:~$ 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
不存在编号为0的信号。在后面的章节中会说明编号为0的信号的特殊用途。
产生信号的条件有很多:
信号是异步事件的典型示例。产生信号的事件对进程而言是随机出现的。进程不能通过测试一个简单的变量(如errno)来判断是否有信号发生,而是应该告诉内核:“当此信号发生时,应该执行如下操作”。这里一共有三种方式可供选择:
(1)忽略此信号。
(2)捕捉此信号。
(3)执行系统默认操作。
首先来说忽略信号的用法。大多数的信号都可以使用这种方式来处理信号,但是有两种信号是绝不能被忽略的,它们分别是SIGKILL和SIGSTOP信号。这有两种信号不能被忽略的原因是:它们向内核和用户提供了使进程终止或者停止的可靠方法。此外,如果忽略某些由硬件产生的信号(例如SIGSEG信号),会导致软件出现无法预料的问题。
为了实现捕捉信号的目的,我们必须通知内核在某种信号发生时,调用一个用户函数。在用户函数中,我们可以执行我们希望对该信号的处理方式。例如我们可以捕捉SIGALRM信号,当定时时间到时打印某些提示信息等。注意:不能捕捉SIGKILL和SIGSTOP信号。
对于大多数信号的系统默认操作都是终止该进程。
下表中列出了31中信号编号、信号名称,Linux系统的默认操作,并对其中常见或者常用到的信号做了一个简单的说明。如果以后用到再做详细补充说明。
序号
信号名称
说明
默认操作
1
SIGHUP
暂不介绍
terminate
2
SIGINT
当用户按中断键(一般是Ctrl+C或Delete键)时,驱动程序会产生此信号来终止进程。
terminate
3
SIGQUIT
当用户按退出键(一般是Ctrl+)时,中断驱动程序会产生该信号,发送给所有前台进程。
coredump
4
SIGILL
该信号表示已经执行一条非法硬件指令。
coredump
5
SIGTRAP
指示一个实现的硬件故障。
coredump
6
SIGABRT
调用abort()函数来终止进程时会产生该信号。
coredump
7
SIGBUS
指示一个已定义的硬件故障
coredump
8
SIGFPE
表示算数运算异常。例如除0操作,浮点溢出等。
coredump
9
SIGKILL
无法被忽略和捕捉的信号。它向系统提供一种可以杀死任意进程的可靠方法。
terminate
0
SIGUSR1
用户定义的信号,可用于应用程序
terminate
11
SIGSEGV
无效的内存访问。例如经典的“段错误”。
coredump
12
SIGUSR2
用户定义的另一个信号,可用于应用程序
terminate
13
SIGPIPE
管道的读进程已经终止时写管道会产生该信号。
terminate
14
SIGALRM
当使用alarm()函数,或者setitimer()设置的定时时间到时会产生此信号
terminate
15
SIGTERM
是由kill(1)命令发送的系统默认终止信号。该信号可被应用程序捕获,从而进行清理工作,完成优雅的终止(相对于SIGKILL而言,SIGKILL信号不能被捕获或者忽略)。
terminate
16
SIGSTKFLT
暂不介绍
17
SIGCHLD
一个进程终止或者停止时,SIGCHLD信号会发送给其父进程。按系统默认,将忽略此信号。如果父进程需要被告知该子进程退出状态,则需要捕捉此信号。一般在信号处理函数中调用wait()函数回收子进程的资源。
ignore
18
SIGCONT
作业控制信号。它用来发送给需要继续运行,但当前处于停止状态的进程。收到此信号后,挂起的进程继续运行。如果本来已经在运行则忽略该信号。
ignore
19
SIGSTOP
作业控制信号,它停止一个进程。不能被捕捉或忽略。
stop
20
SIGTSTP
交互停止信号。当用户按挂起键(一般是Ctrl+z)时,中断驱动程序产生此信号。
stop
21
SIGTTIN
暂不介绍
stop
22
SIGTTOU
暂不介绍
stop
23
SIGURG
暂不介绍
ignore
24
SIGXCPU
暂不介绍
coredump
25
SIGXFSZ
暂不介绍
coredump
26
SIGVTALRM
暂不介绍
terminate
27
SIGPROF
暂不介绍
terminate
28
SIGWINCH
暂不介绍
ignore
29
SIGIO
暂不介绍
terminate
30
SIGPWR
暂不介绍
terminate
31
SIGUNUSED / SIGSYS
一个无效的系统调用
coredump
Unix系统信号机制最简单的接口是signal函数。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
返回值:若成功,返回之前的信号处理配置;若失败,返回SIG_ERR.
代码中:
signo是指信号名称(详情参见2.4常见的信号)
func是常量SIG_IGN、SIG_DFL或者接收到信号时自定义的信号处理函数地址。
说句心里话,signal的函数原型看起来有点看不懂:( 。下面我们也按前辈先人的说法再熟悉下(原话):
本节开头所示的signal函数原型太复杂了,如果使用下面的typedef,则可以使其简单些。
typedef void Sigfunc(int); (3-1)
然后,可将signal函数原型写成:
Sigfunc *signal(int, Sigfunc *); (3-2)
这样,signal的函数看起来就简单了很多:signal函数要求两个参数,并返回一个函数指针(如3-2所示),而该函数指针指向的函数有一个整型参数且无返回值(如3-1所示)。
用通俗一点的话描述:定义一个信号处理函数,它有一个整型参数signo, 无返回值;当调用signal函数设置信号处理程序时,signal函数的第二个参数是指向该信号处理函数的指针,signal函数的返回值是指向未修改之前的信号处理函数指针。
在上述的描述中,我们提到了三个宏定义: SIG_IGN、SIG_DFL、SIG_ERR。这三个宏Linux上的原型如下:
typedef void __sighandler_t(int);
#define SIG_DFL ((__sighandler_t)0) /* default signal handling */
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */
#define SIG_ERR ((__sighandler_t)-1) /* error return from signal */
这三个常量可用于表示“指向函数的指针”。
该实例中定义了两个信号处理函数,捕获了三个信号(SIGUSR1, SIGUSR2共用一个信号处理函数)。
/*************************************************************************
> File Name: signal_demo.c
> Author: Toney Sun
> Mail: vip_13031075266@163.com
> Created Time: 2020年04月27日 星期一 11时50分47秒
************************************************************************/
#include <stdio.h>
#include <signal.h>
static void sig_handler(int); /*自定义的信号处理函数*/
static void sig_usr(int); /*自定义的信号处理函数*/
int signal_install()
{
if(signal(SIGINT, sig_handler)==SIG_ERR){
printf("SIGINT handle function register error\n");
}
if(signal(SIGUSR1, sig_usr)==SIG_ERR){
printf("SIGUSR1 handle function register error\n");
}
if(signal(SIGUSR2, sig_usr)==SIG_ERR){
printf("SIGUSR2 handle function register error\n");
}
}
void sig_handler(int signo)
{
if(signo == SIGINT){
printf("Recieved SIGINT signal\n");
}else{
printf("sig_handler receieve Error signal\n");
}
}
void sig_usr(int signo)
{
if(signo == SIGUSR1){
printf("Recieved SIGUSR1 signal\n");
}else if(signo == SIGUSR2){
printf("Recieved SIGUSR2 signal\n");
}else{
printf("sig_usr receieve Error signal\n");
}
}
void main(int argc, char *argv[])
{
//signal_demo();
//exec_funcs();
signal_install();
while(1){
pause();
}
}
结果如下:
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out
^CRecieved SIGINT signal
^CRecieved SIGINT signal
^CRecieved SIGINT signal
^Z
[3]+ Stopped ./demo.out
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out &
[6] 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR2 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR2 signal
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR1 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR1 signal
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$
如果想使用signal函数来获取当前进程对某一信号的处理方式,会修改当前的处理方式,否则无法确定当前的处理方式。常见的用法如下:
if(signal(SIGINT, SIG_IGN)!=SIG_IGN)
signal(SIGINT, sig_handler);
if(signal(SIGUSR1, SIG_IGN)!=SIG_IGN)
signal(SIGINT, sig_usr);
后面我们将使用另一种信号处理方式:sigaction()函数,此函数无需修改便可以查询当前的处理方式。
进程创建
当一个进程调用fork时,其子进程继承了父进程的信号处理方式。因为子进程在创建时复制了父进程的内存映像,所以信号捕捉函数的地址在子进程中是有效的。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章