码迷,mamicode.com
首页 > 系统相关 > 详细

进程间通信之——信号(一)

时间:2021-02-18 13:08:04      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:max   硬编码   href   知识点   不能   异常   读写   order   printf   

  关于linux信号的知识点,我找到一篇博客写的非常好:https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html

  本篇博客主要是为了加深自己的理解,并且在上篇博客的基础上做一些扩充,有可能会有说的不对的地方。

  具体与信号相关的资料可以用输入man 3 signal命令查看。

1、概述

  信号其实和硬件中的中断非常类似,硬件中断有很多种类型,比如串口中断、定时器中断、I2C中断等,每个不同的中断会有对应的中断处理函数,在中断处理函数里面就会处理引起中断的事件。linux中的信号就相当于这些中断,因此不同的信号代表发生了不同的事件。但是它们的区别其实还是挺大的,比如信号是发送给某一进程的,而不是整个系统。

2、信号种类

  在终端输入kill -l命令可以看到linux支持的所有信号类型,如下:

技术图片

   从这个列表中能看出下列几点:

  1. 所有的名称都是以SIG开头的;

  2. 每个信号名称前面有1个数字,用半边括号包围;

  3. 没有32和33号信号。

  4. 34号信号名为SIGRTMIN,接下来的15个信号分别为SIGRTMIN+1~SIGRTMIN+15,64号信号名为SIGRTMAX,它之前的14个信号分别为SIGRTMAX-14~SIGRTMAX-1;

  下面解释一下上述问题,SIG是signal前3个字母,后面的部分是具体的信号简称,比如第14个信号SIGALRM其实就是signal alarm(闹钟信号),这个就有点像硬件中的RTC中断。每个信号名其实就是一个宏(常量),这在signal.h中定义了(不是include目录下的signal.h,而是include目录下的某个目录下的signal.h,具体是哪个目录视平台而定。但奇怪的是signal.h中明明定义的SIGRTMIN的值为32,但是我打印出来却是34,不过和kill -l列出来的SIGRTMIN也是34)。

  信号的命名和值和历史原因有关,这个在man手册上写了,在不同的标准中加入了不同的信号,基本上每个信号都有对应的事件,但是这些信号个问题,就是不支持排队,也就是说如果同时出现好几个信号,那么只有1个信号能处理,其他的信号会丢失。如果修改这个机制的话,可能会导致天下大乱,然后大神们就想出一种办法,增加了信号的类型,新增的信号类型与旧的信号类型有所区别,那就是新增的信号类型支持排队,它们即使同时来好几个也不会丢失,那么顺理成章的,旧的信号就成了不可靠信号,新的信号就是可靠信号(在手册中的说法是普通信号实时信号,只不过按照我的理解我更倾向于叫它们不可靠信号和可靠信号)。

  但是新增的这些信号并没有对应任何一个实际的事件,它们根据实际情况来使用(手册上是这么写的),因此就不好给它们取名字了,索性就把新增的这些信号划分到一个范围里,最小的值叫做SIGRTMIN,最大的值叫SIGRTMAX,其中RT就是Real-Time,然后用SIGRTMIN+n和SIGRTMAX-n的方法来表示它们。

  需要注意的是,虽然定义了SIGRTMIN和SIGRTMAX的值,但是在使用的时候还是应该要用SIGRTMIN+n和SIGRTMAX-n的方式,原因如下:

  The Linux kernel supports a range of 32 different real-time signals, numbered 33 to 64. However, the glibc POSIX threads implementation internally uses two (for NPTL) or three (for LinuxThreads) real-time signals (see pthreads(7)), and adjusts the value of SIGRTMIN suitably (to 34 or 35). Because the range of available real-time signals varies according to the glibc threading implementation (and this variation can occur at run time according to the available kernel and glibc), and indeed the range of real-time signals varies across UNIX systems, programs should never refer to real-time signals using hard-coded numbers, but instead should always refer to real-time signals using the notation SIGRTMIN+n, and include suitable (run-time) checks that SIGRTMIN+n does not exceed SIGRTMAX.

  翻译如下:

  Linux内核支持32种不同的实时信号,范围从33到64。但是,glibc POSIX线程实现内部使用两个(对于NPTL)或三个(对于LinuxThreads)实时信号(请参阅pthreads(7))。 ,并适当调整SIGRTMIN的值(至34或35)。 因为可用的实时信号的范围根据glibc线程实现的不同而有所不同(并且这种变化可能会在运行时根据可用的内核和glibc发生),并且实际上,实时信号的范围在UNIX系统上也有所不同,所以程序应 不要使用硬编码数字引用实时信号,而应该使用SIGRTMIN + n引用实时信号,并包括适当的(运行时)检查,以确保SIGRTMIN + n不超过SIGRTMAX。

   虽然实时信号没有对应具体的事件,但是普通信号有对应具体的事件,而且系统还给每个普通信号设定了一个默认的处理方法,下表列出了信号名,信号值,系统对该信号的默认处理动作和产生该信号的原因:

信号 默认动作 含义
SIGHUP 1 终止

终端挂起或者控制进程终止。该信号在用户终端连接(正常或非正常)退出时发出,通常是在终端的控制进程结束时通知同一会话内的各个作业与控制终端不再关联。

SIGINT 2 终止 来自键盘的中断信号,如Ctrl+C,或者break键被按下,但是笔记本上可能没有break键。
SIGQUIT 3

终止,并进行Core dump

(后面解释什么是Core dump)

来自键盘的退出信号,与SIGINT类似,但是由 Ctrl+\ 产生
SIGILL 4 终止,并进行Core dump 非法指令(可执行文件本身发生错误,或者试图执行数据段,或堆栈溢出时发出)
SIGTRAP 5 终止,并进行Core dump 由断点指令或其它陷阱(trap)指令产生. 由debugger使用
SIGABRT 6 终止,并进行Core dump 由abort(3)发出的终止指令(这个括号带个3指的是可以用man 3 abort查看,下同)
SIGBUS 7 终止,并进行Core dump 总线错误(错误的内存访问,包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数)
SIGFPE 8 终止,并进行Core dump 浮点异常。在发生致命的算术运算错误时发出。不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误
SIGKILL 9 终止 kill信号。该信号不能被忽略、阻塞以及自定义处理方法,如果发现某个进程结束不了可以用这个信号将其杀死
SIGUSR1 10 终止 给用户使用的信号1
SIGSEGV 11 终止,并进行Core dump 无效的内存参考,当程序访问没有访问权限的内存区域,或者访问非可读的内存区域时,产生该信号,如数组越界。它与SIGBUS的区别是,SIGSEGV是对合法地址的非法访问,而SIGBUS访问的本身就是非法的地址。
SIGUSR2 12 终止 给用户使用的信号2
 SIGPIPE 13 终止  管道破裂:往管道写数据的时候读端已经关闭,或者在socket通信的时候,写数据的时候读端已经关闭
 SIGALRM 14 终止  来自alarm(2)的定时器信号
 SIGTERM 15 终止  终止信号。kill命令的默认方式,与SIGKILL不同的是,该信号可以被阻塞或者自定义处理方式
 SIGSTKFLT 16 终止  协处理器上的堆栈故障(未使用)
 SIGCHLD 17  忽略  子进程停止(stopped)或者终止
 SIGCONT 18 继续运行  让一个停止(stopped)的进程继续执行。本信号不能被阻塞
 SIGSTOP 19 暂停运行  暂停进程。该信号不能被忽略、阻塞以及自定义处理方法
 SIGTSTP 20 暂停运行  由tty发出的停止(stopped)信号,如Ctrl+Z
 SIGTTIN 21 暂停运行 tty输入用于后台进程。其实就是指后台进程试图从终端读取数据,比如从stdin读数据。
 SIGTTOU 22 暂停运行 tty输出用于后台进程。其实就是指后台进程试图向终端读写数据,比如向stdout写数据。
 SIGURG 23 忽略 有”紧急”数据或带外数据out-of-band到达socket时产生。带外数据是socket编程的知识点。
 SIGXCPU 24  终止,并进行Core dump 超过CPU时间资源限制。这个限制可以由getrlimit/setrlimit来读取/改变。
 SIGXFSZ 25  终止,并进行Core dump 文件大小超出限制。
 SIGVTALRM 26 终止  虚拟闹钟。
 SIGPROF 27 终止   
 SIGWINCH 28 忽略 窗口大小改变
 SIGIO 29 终止  I/O准备就绪
 SIGPWR 30 终止 Power failure(电源失败?)
 SIGSYS 31 终止,并进行Core dump Bad argument to routine(非法的系统调用)

  注:上表中很多暂停和停止其实是一个意思,因为这些东西都是从英语翻译过来的,原文就是stopped,直译应该为“停止”,但按照中文的习惯,停止代表不能恢复,能恢复就应该叫“暂停”。

  Core dump:当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。参见:https://www.cnblogs.com/s-lisheng/p/11278193.html

  从上表可以看出系统对信号默认的处理方法一共有5种,分别是:1. 终止;2. 终止,并进行Core dump;3. 暂停运行;4. 继续运行;5. 忽略。

3、 普通信号捕捉

  虽然表中所有的信号都有默认的处理方法,但我们可以来捕捉这些信号,并修改处理方法,需要用到的函数为signal函数,该函数介绍如下:

函数原型 void (*signal(int sig, void (*func)(int)))(int);
头文件 signal.h
功能 为信号设置处理方式
参数

[in]:sig:要设置的信号

[in]:func:该信号的处理函数,如果传入SIG_IGN则表示忽略该信号,如果传入SIG_DFL表示用系统默认方式处理该信号。

实际上SIG_IGN其实就是将数字1强转为函数指针,而SIG_DFL就是将数字0强转为函数指针。

返回 如果可以满足该请求,则signal()返回指定信号sig上一次的处理函数指针。否则,应返回SIG_ERR(其实就是强转为指针的-1)并置errno为正整数。

  该函数的原型比较难理解,可以将之拆分为两部分:void (*signal(int sig, void (*func)(int)))(int); 其中黑色部分是返回值,红色部分是其余部分,将红色部分拿出来:signal(int sig, void (*func)(int)) 发现这是一个含有2个参数的函数,第一个参数是整形的,第2个参数是void (*)(int)类型的函数指针,第2个参数的函数指针类型与返回值的函数指针类型是相同的。如果用自定义的函数来处理信号,当信号到来时就会调用传入的函数指针,而给该函数传入的参数正是信号类型。最后捋一捋,signal这个函数包含2个参数,第一个是int型的,表示信号类型,第2个是void (*)(int)型的函数指针,用来定义对第1个参数指定的信号的处理函数,它的返回值也是void (*)(int)型的函数指针,当signal调用成功时返回的函数指针是上一次处理该信号的函数指针(也包含SIG_IGN和SIG_DFL)。

  写一个简单的程序来测试一下signal的用法,选用的信号为SIGINT,通过组合键Ctrl+C发送,代码如下:

 1 /**
 2  * filename: signal_2.c
 3  * author: Suzkfly
 4  * date: 2021-02-15
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     修改MODE的值为1,2,3,执行程序时按下Ctrl+C,观察结果。
 8  */
 9 #include <stdio.h>
10 #include <fcntl.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <signal.h>
14 
15 #define MODE 3
16 
17 /* 自定义信号处理函数 */
18 void func(int sig)
19 {
20     if (sig == SIGINT) {
21         printf("SIGINT signal captured\n");
22     }
23 }
24 
25 int main(int argc, const char *argv[])
26 {
27     int fd = 0;
28     void (*p)(int); /* 定义一个函数指针 */
29 
30     printf("MODE = %d\n", MODE);
31 #if (MODE == 1)     /* 忽略 */
32     printf("SIG_IGN = %p\n", SIG_IGN);
33     p = signal(SIGINT, SIG_IGN);
34     printf("p = %p\n", p);
35     p = signal(SIGINT, SIG_IGN);
36     printf("p = %p\n", p);
37 #elif (MODE == 2)   /* 默认 */
38     printf("SIG_DFL = %p\n", SIG_DFL);
39     p = signal(SIGINT, SIG_DFL);
40     printf("p = %p\n", p);
41     p = signal(SIGINT, SIG_DFL);
42     printf("p = %p\n", p);
43 #elif (MODE == 3)   /* 自定义 */
44     printf("func = %p\n", func);
45     p = signal(SIGINT, func);
46     printf("p = %p\n", p);
47     p = signal(SIGINT, func);
48     printf("p = %p\n", p);
49 #endif
50     
51     while (1) {
52         sleep(5);
53         printf("running...\n");
54     }
55     
56     return 0;
57 }

测试结果:

修改代码中MODE的值以达到不同的测试结果,3种不同的结果如下图:

技术图片

说明:

1. 屏幕上出现的“^C”字样表示按下了Ctrl+C,屏幕上的“^\Quit”字样表示按下了Ctrl+\

2. MODE等于3时的Ctrl+C是持续按下的(间隔远小于5秒)

 结果分析:

这份代码用于验证signal函数和SIGINT信号,通过这份代码验证了下列特性:

1. signal函数能够修改信号的处理方式;

2. SIG_IGN的值其实就是1,SIG_DFL的值其实就是0;

3. signal函数如果成功,那么返回的是上一次的处理函数,也包含SIG_IGN和SIG_DFL;

4. SIGINT信号的默认处理方式是终止进程;

5. SIGINT信号能够唤醒进程。

进程间通信之——信号(一)

标签:max   硬编码   href   知识点   不能   异常   读写   order   printf   

原文地址:https://www.cnblogs.com/Suzkfly/p/14401421.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有
迷上了代码!