一、什么是信号
信号是进程间通信的一种方式,它是异步通信的。而异步的意思就是不同步,事件的发生和处理没有协同。
二、信号的特性
Linux/Unix系统下,信号总共分成两大类,一类是最常用的标准信号,另一类是后面的引入的实时信号。一共有62个信号,前31个是标准信号,后面31个是实时信号。注意没有32,33号。
1、标准信号
(1)不排队,信号的响应会相互嵌套。
(2)如果目标进程没有及时响应,那么随后到达的相同信号将会被丢弃。
(3)每个信号都对应一个系统事件(除了SIGUSR1和SIGUSR2),当这个事件发生时,将产生这个信号。
(4)在进程的挂起信号中,进程会优先响应实时信号。
2、实时信号
(1)实时信号的响应次序按接收顺序排队。如果收到相同的信号的则不会嵌套,但是如果是不同的信号则会导致嵌套。
(2)即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次按个响应。
(3)实时信号没有特殊的系统事件与之对应。
(4)实时信号在挂起队列中信号值越大,优先级越高。
三、信号的生命周期
所谓信号的生命周期,指的是信号从产生到被响应完毕的整个过程,这个过程可被描述为:
1、信号的产生
信号既可以由特定的事件产生(比如发生了内存访问异常导致产生信号SIGSEGV),也可以由用户主动发起(比如调用了kill()函数),不管是哪种方式产生的信号,其本质都是触发了内核的信号发生器,并向特定进程(即目标进程)传递的过程。
// kill()函数接口介绍 #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); 参数: pid: 要接收信号的进程号 sig: 信号的编号 返回值:成功返回0, 失败返回-1.
2、信号的挂起
每个进程都保留有一个挂起信号集,所有被发送到这个进程的信号首先被放入到这个信号集(进程处于非执行状态),挂起信号集存储了进程的待处理信号,这些信号必须要等到进程被系统调度(占用CPU执行的时候),真正执行的时候才能被进一步响应。
3、信号的响应(处理)
信号的响应总共有如下四种方式:
(1)屏蔽(阻塞):延缓对信号的响应,直到解除对该信号的屏蔽为止。
(2)捕捉:执行一个预先设置的与信号相关联的响应函数。
(3)默认:按信号默认的情况处理。
(4)忽略:直接丢弃该信号。
注意:系统中9号,19号信号是不允许被挂起或捕获的,只允许对他们进行默认处理。
四、信号的响应(处理)
1、信号的捕捉
给函数指定关联函数的接口是:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); // 翻译过来就是 void (*signal(int sig, void (*func)(int)))(int); // 函数名为:signal // 参数为: int sig, 信号的编号 void (*func)(int), 关联的函数,函数的参数是int, 返回值是void // 返回值:void (*)(int);
2、信号的默认处理
如果程序没有对信号做任何预先准备,那么当信号到达时,则会按照信号的默认规则进行响应,具体默认规则可使用如下命令查阅:
man 7 signal
列表中的Action 一列就是系统对信号的默认处理规则,默认规则如下:
(1)Term:中断目标进程
(2)Core:中断目标进程,且产生核心转储文件core
(3)Stop:暂停目标进程,直到收到信号SIGCONT
(4)Cont:恢复目标进程运行
(5)Ign:忽略信号
3、信号的忽略
信号的忽略就是直接将收到的信号丢弃
signal(SIGINT, SIG_IGN);
4、信号的屏蔽
屏蔽信号实际上就是暂缓对信号的响应,采用如下函数进行对信号的屏蔽
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 参数解析: 1、how:操作命令字,比如阻塞、解除阻塞等 SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞) SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号自动解除) SIG_UNBLOCK:解除set中的信号 2、set:当前要操作的信号集 3、oldset:若为非空,则将原有阻塞信号集保留到该oldset中 注意:该函数的操作参数不是单个信号,而是信号集, 这意味着我们可以同时对多个信号设置阻塞或者解除阻塞 // 信号集操作函数组 int sigemptypset(sigset_t *set); // 清空信号集set int sigfillset(sigset_t *set); // 将所有信号加入信号集set中 int sigaddset(sigset_t *set, int signum); // 将信号signum添加到信号集set中 int sigdelset(sigset_t *set, int signum); // 将信号signum从信号集set中剔除 int sigsimember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中
五、案例
1、信号的捕捉
// 信号的捕捉操作示例 #include <stdio.h> #include <unistd.h> #include <signal.h> void func(int sig) { printf("call func\n"); printf("catch signal:%d\n", sig); } int main(int argc, char *argv[]) { // 设置信号响应函数,捕捉34号信号,当捕获到34号信号时,执行func函数 // 注意设置后,不会执行func函数,signal()应该尽量写在前面 signal(34, func); printf("my pid is %d\n", getpid()); // 打印本进程ID号,方便从命令行发送信号 printf("set signal 34...\n"); while(1) { sleep(1); } return 0; }
// 信号的捕捉操作示例 #include <stdio.h> #include <unistd.h> #include <signal.h> void func(int sig) { printf("call func\n"); printf("catch signal:%d\n", sig); } int main(int argc, char *argv[]) { printf("my pid is %d\n", getpid()); // 打印本进程ID号,方便从命令行发送信号 printf("set signal 34...\n"); while(1) { sleep(1); } // 如果放在这里注册信号响应函数,也能捕捉到信号,但是会有意想不到的结果 signal(34, func); return 0; }
2、信号的屏蔽
// 信号的阻塞(屏蔽)操作示例 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> void func(int sig) { printf("call func\n"); printf("catch signal %d\n", sig); } int main(int argc, char *argv[]) { // 注册信号响应函数 signal(34, func); printf("my pid is %d\n", getpid()); // 设置信号阻塞,阻塞34号信号 sigset_t set; sigemptyset(&set); // 清空信号集 sigaddset(&set, 34); // 把34信号添加到信号集中 if(sigprocmask(SIG_SETMASK, &set, NULL) == -1) // 把信号集中的所有信号设置为阻塞 { perror("设置阻塞失败\n"); } sleep(10); // 10秒内不会对34号进行处理 if(sigprocmask(SIG_UNBLOCK, &set, NULL)) // 解除信号集中的阻塞信号 { perror("设置阻塞失败\n"); } while(1) { sleep(1); } return 0; }
六、总结
信号是进程间异步通信的方式,Linux系统下有62个信号,1~31号是标准信号,34~64是实时信号,对信号的响应方式有四种,分别是屏蔽(阻塞),捕捉,默认处理和忽略。注册信号响应函数的语句应该放在主函数体内前面。