一、什么是信号
用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止。
 
信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。一个信号的产生叫生成,接收到一个信号叫捕获。
 
二、信号的种类
信号的名称是在头文件signal.h中定义的,信号都以SIG开头,常用的信号并不多,常用的信号如下:
更多的信号类型可查看附录表。
 
三、信号的处理——signal函数
程序可用使用signal函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal函数的原型如下:
 
  1. #include <signal.h>
  2. void (*signal(int sig, void (*func)(int)))(int);
这是一个相当复杂的声明,耐心点看可以知道signal是一个带有sig和func两个参数的函数,func是一个类型为void (*)(int)的函数指针。该函数返回一个与func相同类型的指针,指向先前指定信号处理函数的函数指针。准备捕获的信号的参数由sig给出,接收到的指定信号后要调用的函数由参数func给出。其实这个函数的使用是相当简单的,通过下面的例子就可以知道。注意信号处理函数的原型必须为void func(int),或者是下面的特殊值:
    SIG_IGN:忽略信号
    SIG_DFL:恢复信号的默认行为
 
说了这么多,还是给出一个例子来说明一下吧,源文件名为signal1.c,代码如下:
 
  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. void ouch(int sig)
  5. {
  6. printf("\nOUCH! - I got signal %d\n", sig);
  7. //恢复终端中断信号SIGINT的默认行为
  8. (void) signal(SIGINT, SIG_DFL);
  9. }
  10. int main()
  11. {
  12. //改变终端中断信号SIGINT的默认行为,使之执行ouch函数
  13. //而不是终止程序的执行
  14. (void) signal(SIGINT, ouch);
  15. while(1)
  16. {
  17. printf("Hello World!\n");
  18. sleep(1);
  19. }
  20. return 0;
  21. }
运行结果如下:
 
可以看到,第一次按下终止命令(ctrl+c)时,进程并没有被终止,面是输出OUCH! - I got signal 2,因为SIGINT的默认行为被signal函数改变了,当进程接受到信号SIGINT时,它就去调用函数ouch去处理,注意ouch函数把信号SIGINT的处理方式改变成默认的方式,所以当你再按一次ctrl+c时,进程就像之前那样被终止了。
 
四、信号处理——sigaction函数
前面我们看到了signal函数对信号的处理,但是一般情况下我们可以使用一个更加健壮的信号接口——sigaction函数。它的原型为:
 
  1. #include <signal.h>
  2. int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
该函数与signal函数一样,用于设置与信号sig关联的动作,而oact如果不是空指针的话,就用它来保存原先对该信号的动作的位置,act则用于设置指定信号的动作。
 
sigaction结构体定义在signal.h中,但是它至少包括以下成员:
void (*) (int) sa_handler;处理函数指针,相当于signal函数的func参数。
sigset_t sa_mask; 指定一个。信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中。信号屏蔽字是指当前被阻塞的一组信号,它们不能被当前进程接收到
int sa_flags;信号处理修改器;
 
sa_mask的值通常是通过使用信号集函数来设置的,关于信号集函数,我将会在我的下一篇文章——Linux进程间通信——信号集函数,详细讲述。
sa_flags,通常可以取以下的值:
 
此外,现在有一个这样的问题,我们使用signal或sigaction函数来指定处理信号的函数,但是如果这个信号处理函数建立之前就接收到要处理的信号的话,进程会有怎样的反应呢?它就不会像我们想像的那样用我们设定的处理函数来处理了。sa_mask就可以解决这样的问题,sa_mask指定了一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中,设置信号屏蔽字可以防止信号在它的处理函数还未运行结束时就被接收到的情况,即使用sa_mask字段可以消除这一竞态条件。
 
承接上面的例子,下面给出用sigaction函数重写的例子代码,源文件为signal2.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <signal.h>
  4. void ouch(int sig)
  5. {
  6. printf("\nOUCH! - I got signal %d\n", sig);
  7. }
  8. int main()
  9. {
  10. struct sigaction act;
  11. act.sa_handler = ouch;
  12. //创建空的信号屏蔽字,即不屏蔽任何信息
  13. sigemptyset(&act.sa_mask);
  14. //使sigaction函数重置为默认行为
  15. act.sa_flags = SA_RESETHAND;
  16. sigaction(SIGINT, &act, 0);
  17. while(1)
  18. {
  19. printf("Hello World!\n");
  20. sleep(1);
  21. }
  22. return 0;
  23. }
运行结果与前一个例子中的相同。注意sigaction函数在默认情况下是不被重置的,如果要想它重置,则sa_flags就要为SA_RESETHAND。
 
五、发送信号
上面说到的函数都是一些进程接收到一个信号之后怎么对这个信号作出反应,即信号的处理的问题,有没有什么函数可以向一个进程主动地发出一个信号呢?我们可以通过两个函数kill和alarm来发送一个信号。
 
1、kill函数
先来看看kill函数,进程可以通过kill函数向包括它本身在内的其他进程发送一个信号,如果程序没有发送这个信号的权限,对kill函数的调用就将失败,而失败的常见原因是目标进程由另一个用户所拥有。想一想也是容易明白的,你总不能控制别人的程序吧,当然超级用户root,这种上帝般的存在就除外了。
 
kill函数的原型为:
 
  1. #include <sys/types.h>
  2. #include <signal.h>
  3. int kill(pid_t pid, int sig);
它的作用把信号sig发送给进程号为pid的进程,成功时返回0。
 
kill调用失败返回-1,调用失败通常有三大原因:
1、给定的信号无效(errno = EINVAL)
2、发送权限不够( errno = EPERM )
3、目标进程不存在( errno = ESRCH )
 
2、alarm函数
这个函数跟它的名字一样,给我们提供了一个闹钟的功能,进程可以调用alarm函数在经过预定时间后向发送一个SIGALRM信号。
 
alarm函数的型如下:
 
  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
alarm函数用来在seconds秒之后安排发送一个SIGALRM信号,如果seconds为0,将取消所有已设置的闹钟请求。alarm函数的返回值是以前设置的闹钟时间的余留秒数,如果返回失败返回-1。
 
马不停蹄,下面就给合fork、sleep和signal函数,用一个例子来说明kill函数的用法吧,源文件为signal3.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <signal.h>
  6. static int alarm_fired = 0;
  7. void ouch(int sig)
  8. {
  9. alarm_fired = 1;
  10. }
  11. int main()
  12. {
  13. pid_t pid;
  14. pid = fork();
  15. switch(pid)
  16. {
  17. case -1:
  18. perror("fork failed\n");
  19. exit(1);
  20. case 0:
  21. //子进程
  22. sleep(5);
  23. //向父进程发送信号
  24. kill(getppid(), SIGALRM);
  25. exit(0);
  26. default:;
  27. }
  28. //设置处理函数
  29. signal(SIGALRM, ouch);
  30. while(!alarm_fired)
  31. {
  32. printf("Hello World!\n");
  33. sleep(1);
  34. }
  35. if(alarm_fired)
  36. printf("\nI got a signal %d\n", SIGALRM);
  37. exit(0);
  38. }
运行结果如下:
在代码中我们使用fork调用复制了一个新进程,在子进程中,5秒后向父进程中发送一个SIGALRM信号,父进程中捕获这个信号,并用ouch函数来处理,变改alarm_fired的值,然后退出循环。从结果中我们也可以看到输出了5个Hello World!之后,程序就收到一个SIGARLM信号,然后结束了进程。
 
注:如果父进程在子进程的信号到来之前没有事情可做,我们可以用函数pause()来挂起父进程,直到父进程接收到信号。当进程接收到一个信号时,预设好的信号处理函数将开始运行,程序也将恢复正常的执行。这样可以节省CPU的资源,因为可以避免使用一个循环来等待。以本例子为例,则可以把while循环改为一句pause();
 
下面再以一个小小的例子来说明alarm函数和pause函数的用法吧,源文件名为,signal4.c,代码如下:
 
  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <signal.h>
  6. static int alarm_fired = 0;
  7. void ouch(int sig)
  8. {
  9. alarm_fired = 1;
  10. }
  11. int main()
  12. {
  13. //关联信号处理函数
  14. signal(SIGALRM, ouch);
  15. //调用alarm函数,5秒后发送信号SIGALRM
  16. alarm(5);
  17. //挂起进程
  18. pause();
  19. //接收到信号后,恢复正常执行
  20. if(alarm_fired == 1)
  21. printf("Receive a signal %d\n", SIGALRM);
  22. exit(0);
  23. }
运行结果如下:
进程在5秒后接收到一个SIGALRM,进程恢复运行,打印信息并退出。
 
六、信号处理函数的安全问题
试想一个问题,当进程接收到一个信号时,转到你关联的函数中执行,但是在执行的时候,进程又接收到同一个信号或另一个信号,又要执行相关联的函数时,程序会怎么执行?
 
也就是说,信号处理函数可以在其执行期间被中断并被再次调用。当返回到第一次调用时,它能否继续正确操作是很关键的。这不仅仅是递归的问题,而是可重入的(即可以完全地进入和再次执行)的问题。而反观Linux,其内核在同一时期负责处理多个设备的中断服务例程就需要可重入的,因为优先级更高的中断可能会在同一段代码的执行期间“插入”进来。
 
简言之,就是说,我们的信号处理函数要是可重入的,即离开后可再次安全地进入和再次执行,要使信号处理函数是可重入的,则在信息处理函数中不能调用不可重入的函数。下面给出可重入的函数在列表,不在此表中的函数都是不可重入的,可重入函数表如下:
 
七、附录——信号表
如果进程接收到上面这些信号中的一个,而事先又没有安排捕获它,进程就会终止。
 
还有其他的一些信号,如下:

Linux进程间通信——信号集函数的更多相关文章

  1. 详解linux进程间通信-信号

    前言:之前说看<C++ Primer >暂时搁浅一下,迷上公司大神写的代码,想要明白,主要是socket.进程间通信! 知道进程间通信:信号.信号量.管道.消息队列.共享内存(共享存储), ...

  2. Linux进程间通信—信号

    三.信号(Signal) 信号是Unix系统中使用的最古老的进程间通信的方法之一.操作系统通过信号来通知某一进程发生了某一种预定好的事件:接收到信号的进程可以选择不同的方式处理该信号,一是可以采用默认 ...

  3. Linux进程间通信——信号

    一.认识信号 信号(Signals)是Unix.类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式.它是一种异步的通知机制,用来提醒进程一个事件已经发生.当一个信号发送给一个进程 ...

  4. Linux进程间通信-信号

    1.什么是信号信号是Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会执行相应的操作. 2.信号的产生1)由硬件产生,如从键盘输入Ctrl+C可以终止当前进程2)由其他进程发送,如可在s ...

  5. Linux 进程间通信 信号(signal)

    1. 概念: 1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式 2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件. 3)如果 ...

  6. Linux进程间通信(一): 信号 signal()、sigaction()

    一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中 ...

  7. [转]Linux进程间通信——使用信号

    转载于:http://blog.csdn.net/ljianhui/article/details/10128731         经典!!! Linux进程间通信——使用信号 一.什么是信号 用过 ...

  8. Linux进程间通信——使用信号

    一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中 ...

  9. Linux进程间通信(五):信号量 semget()、semop()、semctl()

    这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信 -- 信号.下面 ...

随机推荐

  1. 你真的会玩SQL吗?Top和Apply

    原文:你真的会玩SQL吗?Top和Apply 本章预先想写一些Top和Apply基本的用法,但好像没什么意义,所以删掉了一些无用的东西,只留下几个示例,以保证系列的完整性. Top和Apply解决的常 ...

  2. centos单用户模式:修改ROOT密码和grub加密

    centos单用户模式:修改ROOT密码和grub加密 CentOSLinux网络应用配置管理应用服务器  Linux 系统处于正常状态时,服务器主机开机(或重新启动)后,能够由系统引导器程序自动引导 ...

  3. Combinations 解答

    Question Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. F ...

  4. 【POJ 3279 Fliptile】开关问题,模拟

    题目链接:http://poj.org/problem?id=3279 题意:给定一个n*m的坐标方格,每个位置为黑色或白色.现有如下翻转规则:每翻转一个位置的颜色,与其四连通的位置都会被翻转,但注意 ...

  5. PHP代码,拒绝频繁访问

    一个网站性能有限,如果有人恶意去频繁对页面进行刷新,其实对服务器影响是很大的,导致资源使用非常高,直接影响到其他用户的体验. 那么对于这样的一些频繁访问,我们该如何去拒绝它呢? 我总结了两种方法:第一 ...

  6. error LNK2001

    error LNK2001: 无法解析的外部符号 "public: virtual void __cdecl Observer::update(float,float,float)" ...

  7. SQL Server 数据库定时自动备份【转】

    在SQL Server中出于数据安全的考虑,所以需要定期的备份数据库.而备份数据库一般又是在凌晨时间基本没有数据库操作的时候进行,所以我们不可能要求管理员每天守到晚上1点去备份数据库.要实现数据库的定 ...

  8. 【Samza系列】实时计算Samza中文教程(二)——概念

    希望上一篇背景篇让大家对流式计算有了宏观的认识,本篇依据官网是介绍概念,先让我们看看有哪些东西呢?     概念一:Streams     Samza是处理流的.流则是由一系列不可变的一种相似类型的消 ...

  9. nginx : TCP代理和负载均衡的stream模块

    一直以来,Nginx 并不支持tcp协议,所以后台的一些基于TCP的业务就只能通过其他高可用负载软件来完成了,比如Haproxy. 这算是一个nginx比较明显的缺憾.不过,在1.90发布后这个认知将 ...

  10. nth-child和nth-of-type的区别

    以前一般都用:nth-child,后来知道了:nth-of-type,然后就一般用nth-of-type 它们两有什么区别呢? 首先来看个现象: 假如有这样一个HTML结构 <div class ...