本文共 6768 字,大约阅读时间需要 22 分钟。
目录
信号又称软终端,通知程序发生异步事件,程序执行中随时被各种信号中断,进程可以忽略该信号,也可以中断当前程序转而去处理信号。
信号本质: 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。 信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。 信号来源: 信号事件的发生有两个来源:硬件来源(比如按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm, setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。可以从两个不同的角度对信号进行分类:
(1)可靠性方面:可靠信号和不可靠信号。 (2)时间的关系上:实时信号和非实时信号。下面对这四种信号分别说明。
在说明之前,首先可以看一下Linux上面的信号列表: 其中,1-31是不可靠信号(可能丢失);35-64是可靠信号(操作系统保证不丢失)。Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Fedora19中,SIGRTMIN=34,SIGRTMAX=64)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:
--进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理。因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号; --信号可能丢失。 因此,早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。 Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上实现的)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失。随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种Unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号。这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigaction()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,以及信号发送函数kill()。非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。因此,所有在SIGRTMIN和SIGRTMAX之间的信号都是实时信号,小于SIGRTMIN的都是非实时信号。
早期Unix系统只定义了32种信号,Fedora19支持64种信号,编号1-64(SIGRTMIN=34,SIGRTMAX=64),将来可能进一步增加,这需要得到内核的支持。前31种信号(1~31)已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后31个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。Linux/Unix系统的信号机制最简单的接口就是signal函数
#includevoid (*signal (int signo, void (*func) (int) )) (int); // 成功则返回信号以前的处理配置, 失败则返回SIG_ERR
函数原型说明此函数需要两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。
举例说明:
/* * Copyright (c) 2013, 水草 * * All rights reserved. **/#include#include void HandleSignalFunc(int signo) //信号处理函数{ printf(" receive signal %d\n",signo);}int main(){ if(SIG_ERR == signal(SIGINT,HandleSignalFunc))//安装信号 { perror("signal"); exit(0); } pause(); //暂停进程,以等待信号 return 0;}
运行此段代码,按Ctrl+C, 得到下面输出:
receive signal 2sigaction函数的功能是检查或修改与指定信号相关联的处理动作(或者同时执行这两种操作)。此函数取代了早期unix/linux版本使用的signal函数。
// 返回值:成功则返回0;出错则返回-1 #includeint sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
其中,参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。若oact指针非空,则系统通过oact指针返回该信号的上一个动作。
参数结构体sigaction定义如下:struct sigaction { void (*sa_handler) (int); //一个带有int参数的函数指针, 或者SIG_IGN(忽略), 或者SIG_DFL(默认). sigset_t sa_mask; //信号屏蔽字(集). 当该信号处理函数返回时, 屏蔽字恢复. int sa_flags; //SA_INTERRUPT(由此信号中断的系统调用不会自动重启), // SA_RESTART(由此信号中断的系统调用会自动重启), //SA_SIGINFO (提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针) //替代的信号处理程序, 当使用了SA_SIGINFO标志时, 使用该信号处理程序. void (*sa_sigaction)(int, siginfo_t *, void *); }
举例说明:
/* * Copyright (c) 2013, 水草 * * All rights reserved. * */ #include#include #include #include void HandleSignalFunc(int signo,siginfo_t *info,void *none){ int i; printf(" got signal %d\n", signo); printf(" append info %d\n", info->si_value.sival_int); for(i = 0; i < 5; i++) { printf("i = %d\n", i); sleep(1); }}int main(void){ int i = 0; //信号处理函数结构 struct sigaction act, oldact; //清空结构体以及掩码 memset(&act, 0x00, sizeof(struct sigaction)); //定义信号处理函数 act.sa_sigaction = HandleSignalFunc; sigaddset(&act.sa_mask, SIGQUIT); //若在SIGINT(Ctrl+C)的信号处理函数执行过程中,本进程收到信号SIGQUIT(Ctrl+\),将阻塞该信号,知道HandleSignalFunc执行结束才会处理信号SIGQUIT. act.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO; //act.sa_flags = 0; if(-1 == sigaction(SIGINT, &act, &oldact)) { perror("sigaction"); exit(0); } while(1) { sleep(1); printf("sleeping %d\n", i); i++; }}
运行此段代码,按Ctrl+C, 得到下面输出:
[shltsh@localhost csdn_blog]$ ./a.out sleeping 0 sleeping 1 sleeping 2 ^C got signal 2 append info -1080677492 i = 0 i = 1 i = 2 i = 3 i = 4 sleeping 3 sleeping 4 ^C 注意:不要有这样的误解,使用sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。 对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。进程可以通过以下3种方式来响应一个信号:
1).忽略信号 - 大多可以忽略,只有SIGKILL和SIGSTOP除外; 2).捕捉信号 - 定义信号处理函数,当信号发生时,执行相应的处理函数 3).默认操作 - Linux对每种信号都规定了默认操作。注意,进程对实时信号的默认反应是进程终止。阻塞是指系统内核暂停向进程发送指定信号,由内核对进程接收到的信号缓存,直到解除阻塞为止。
信号3种进入阻塞的情况: 1).信号处理函数执行过程中,该信号将阻塞; 2).通过sigaction()安装信号,如果设置了sa_mask阻塞信号集; 3).通过系统调用sigprocmask() 其中,sigprocmask函数说明:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); how:用于指定信号修改的方式,可能选择有三种: SIG_BLOCK //加入信号到进程屏蔽。 SIG_UNBLOCK //从进程屏蔽里将信号删除。 SIG_SETMASK //将set的值设定为新的进程屏蔽。 set:为指向信号集的指针,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。 oldset:也是指向信号集的指针,在此存放原来的信号集。
举例说明:
/* * Copyright (c) 2013, 水草 * * All rights reserved. * */#include#include void signal_handle(int signo){ printf(" receive signal %d\n",signo);}int main(){ sigset_t set,oldset; //安装信号函数 signal(SIGINT,signal_handle); //清空信号处理掩码 sigemptyset(&set); //向掩码中增加信号 sigaddset(&set,SIGINT); //设置掩码,设置完成后SIGINT信号将被阻塞 sigprocmask(SIG_BLOCK,&set,&oldset); sleep(10); //恢复原有的信号处理掩码 sigprocmask(SIG_SETMASK,&oldset,NULL); return 0;}
运行此段代码,如果不输入Ctrl+C则10秒后程序结束,没有任何输出;如果期间有Ctrl+C, 则10秒结束后,有下面输出:
[shltsh@localhost csdn_blog]$ ./a.out receive signal 2数据类型 - sigset_t
清空信号集 - sigemptyset 信号集填充全部信号 - sigfillset 信号集增加信号 - sigaddset 信号集中删除信号 - sigdelset 判断信号集是否包含某信号的 - sigismember未决信号即信号产生后到信号被接收进程处理前的过渡状态,未决状态时间很短。
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。 下面是与信号阻塞相关的几个函数: #include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)); //阻塞某种信号,则向进程发送这种信号处于未决状态 int sigpending(sigset_t *set)); //获取当前进程中处于未决状态的信号 int sigsuspend(const sigset_t *mask)); 其中,sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种: 参数 | 进程当前信号集 |
SIG_UNBLOCK | 在进程当前阻塞信号集中添加set指向信号集中的信号 |
SIG_UNBLOCK | 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞 |
SIG_SETMASK | 更新进程阻塞信号集为set指向的信号集 |