---- 整理自狄泰软件唐佐林老师课程
查看所有文章链接:(更新中)Linux系统编程训练营 - 目录
文章目录
- 1. 信号的概念及分类
- 1.1 问题
- 1.2 什么是信号
- 1.3 信号的分类
- 1.3.1 硬件异常信号
- 1.3.2 终端相关信号
- 1.3.3 软件相关信号
- 1.4 内核与信号
- 1.5 System V vs BSD
- 2. 信号发送与处理
- 2.1 信号处理
- 2.1.1 信号的默认处理
- 2.1.2 自定义信号处理
- 2.1.3 信号处理示例
- 2.2 信号发送
- 2.2.1 自定义信号发送
- 2.2.2 信号发送示例
- 2.3 编程实验:信号发送与处理
- 3. 信号处理三大特性
- 3.1 问题
- 3.2 信号的OneShot 性
- 3.3 信号的自身屏蔽特性
- 3.4 系统调用重启特性
- 3.5 默认signal函数的特性
- 3.6 编程实验:三种特性实验
- 3.6.1 oneshot 实验
- 3.6.2 信号的自身屏蔽特性(信号重入)实验
- 3.6.3 系统调用重启实验
- 3.6.4 处理A收到B
- 3.7 注意事项
- 3.8 小结
- 4. 初探现代信号处理
- 4.1 现代信号处理注册函数
- 4.1.1 现代信号处理语义分析
- 4.1.2 信号状态小知识
- 4.1.3 信号屏蔽 vs 信号阻塞
- 4.2 现代信号处理注册示例
- 4.3 编程实验:现代信号处理
- 4.3.1 oneshot
- 4.3.2 屏蔽&阻塞相关
- 4.3.3 系统调用重启
- 4.3.4 处理A收到B
- 4.3.5 signal实际调用sigaction
- 5. 现代信号发送与处理
- 5.1 sigqueue
- 5.2 现代信号处理函数的关键参数
- 5.3 现代信号发送处理示例
- 5.4 编程实验:现代信号发送
- 5.5 思考
1. 信号的概念及分类
1.1 问题
- 按下ctrl+c后,命令行的前台进程会被终止,why?
1.2 什么是信号
- 信号是一种 “软件中断”,用来处理异步事件
- 内核发送信号到某个进程,通知进程事件的发生
- 事件可能来自硬件,可能来自用户输入,可能来自程序自身错误(如除零错误等)
- 信号是一种类型的 进程间通信方式(一个进程向另一个进程发送信号)
- A进程发生事件T,向B进程发送信号,B进程执行动作响应事件
- 进程可以对接收到的 不同信号 进行 不同动作响应 (信号,处理)
1.3 信号的分类
- 硬件异常
内核检测到硬件错误,发送相应信号给相关进程 - 终端信号(用户交互信号)
在终端输入“特殊字符”等价于向前台进程组发送相应的信号 - 软件信号
在软件层面(进程代码中)触发的信号(发送给自身或其它进程)
1.3.1 硬件异常信号
1.3.2 终端相关信号
- SIGINT(ctrl+c)
- 程序终止信号,用于通知前台进程组终止进程
- SIGQUIT(ctrl+\)
- 与SIGINT类似,进程收到该信号退出时可产生coredump文件
- SIGTSTP(ctrl+z)
- 停止进程的运行,进程收到该信号后可以选择处理和忽略
- 进程收到该信号后停止运行(状态发生转换),后续可恢复运行状态
1.3.3 软件相关信号
- 子进程退出:父进程收到SIGCHLD信号
- 父进程退出:子进程可能收到信号(什么信号?)
- 定时器到期:alarm(),ualarm(),timer_create(),……
- 主动发送信号:kill(),raise(),……
- ……
1.4 内核与信号
1.5 System V vs BSD
- System V:也被称为AT&T SystemV,是Unix操作系统众多版本中的一支
- BSD:加州大学伯克利分校开创,Unix衍生系统,代表由此派生出的各种套件组合
Linux之所以被称为类Unix操作系统,部分原因就是Linux的操作风格是介于上述二者之间,且不同厂商为了照顾不同的用户,其发行版本的操作风格存在差异
2. 信号发送与处理
2.1 信号处理
2.1.1 信号的默认处理
2.1.2 自定义信号处理
#include <sys/types.h>
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sighandler_t sysv_signal(int signum, sighandler_t handler);
sighandler_t bsd_signal(int signum, sighandler_t handler);
2.1.3 信号处理示例
2.2 信号发送
2.2.1 自定义信号发送
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig); // 信号处理完毕后返回
标准信号是Unix系统中的信号,编号范围从1到31
实时信号是Linux独有的信号,编号范围从32到64
- kill和raise是用来发送信号的:
- raise把信号发送给(进程)自身
- kill把信号发送给进程或进程组
- pid > 0,将信号传给进程识别码为pid 的进程。
- pid == 0,将信号传给和目前进程相同进程组的所有进程
- pid == -1,将信号广播传送给系统内所有的进程
- pid < 0,将信号传给进程组识别码为pid绝对值的所有进程
2.2.2 信号发送示例
2.3 编程实验:信号发送与处理
【参看链接】:11-12 - 信号发送与处理 / 11 / 00信号处理 / main.c
【参看链接】:11-12 - 信号发送与处理 / 11 / 01信号发送
3. 信号处理三大特性
3.1 问题
- 三种注册信号与处理函数的方法有什么区别?
3.2 信号的OneShot 性
- System V风格的signal函数,注册的 信号处理是 一次性的
- 进程收到信号后,调用由signal注册的处理函数
- 处理函数一旦执行之后,进程通过默认的方式处理后续相同信号
- 如果想要重复触发,那么必须再次调用signal注册处理函数
- BSD风格的signal函数不存在OneShot,能够自动反复触发处理函数的调用
- 默认的signal函数和BSD风格的signal函数一致
3.3 信号的自身屏蔽特性
-
在信号处理函数执行期间(还未处理结束),很可能再次收到当前信号
即:处理 A 信号的时候,再次收到 A 信号 -
对于 System V 风格的 signal 函数,会引起信号处理函数的重入
即:调用处理函数的过程中,再次收到同个信号触发信号处理函数的调用
-
在注册信号处理函数时:
- System V风格的signal不屏蔽任何信号
- BSD风格的signal会屏蔽当前注册的信号,即:再次收到同个信号时,等待第一次收到的信号的处理函数执行完之后,再触发第二次的信号处理函数的调用
-
思考:BSD风格的signal函数,处理A信号期间,如果收到B信号会发生什么?
3.4 系统调用重启特性
- 系统调用期间,可能收到信号,此时进程必须从系统调用中返回
- 对于执行时间较长的系统调用( write / read),被信号中断的可能性很大
- 如果希望信号处理之后,被中断的系统调用能够重启,则:
可以通过条件errno == EINTR判断手动重启系统调用 - 系统调用重启示例代码(wait)
- 系统调用重启特性:
- System V 风格的 signal 函数:(手工重启)
系统调用被信号中断后,直接返回 -1,并且 errno == EINTR - BSD 风格的 signal 函数:(自动重启)
系统调用被中断,内核在信号处理函数结束后,自动重启系统调用
- System V 风格的 signal 函数:(手工重启)
3.5 默认signal函数的特性
对于大多数的Linux发行版本来说,默认signal函数的行为和BSD风格的signal函数一致
3.6 编程实验:三种特性实验
3.6.1 oneshot 实验
【参看链接】:11-12 - 信号发送与处理 / 12 / 00oneshot
3.6.2 信号的自身屏蔽特性(信号重入)实验
【参看链接】:11-12 - 信号发送与处理 / 12 / 01信号重入
3.6.3 系统调用重启实验
【参看链接】:11-12 - 信号发送与处理 / 12 / 02系统调用重启
3.6.4 处理A收到B
- BSD风格的signal函数,处理A信号期间,如果收到B信号会发生什么?
【参看链接】:11-12 - 信号发送与处理 / 12 / 03bsd_处理A时再收到B
3.7 注意事项
- 并非所有的系统调用对信号中断都表现同样的行为
- 一些系统调用支持信号中断后自动重启
read() , write() , wait() , waitpid() , ioctl() , … - 一些系统调用完全不支持信号中断后自动重启
poll() , select() , usleep() , …
- 一些系统调用支持信号中断后自动重启
3.8 小结
- 三种方法的区别:
在信号处理上,Linux系统更接近BSD风格的操作;默认的signal函数在不同的Linux发行版上语义可能不同,从代码移植性角度,避免直接使用signal(…)函数。
4. 初探现代信号处理
4.1 现代信号处理注册函数
4.1.1 现代信号处理语义分析
- 信号屏蔽与标记:
- sigset_t sa_mask
- 信号屏蔽:sa_mask = SIGHUP | SIGINT | SIGUSR1;
- 注意:并不是所有信号都可以被屏蔽,如:SIGKILL , SIGSTOP
- int sa_flags
- 信号特性:sa_flags = SA_ONESHOT | SA_RESTART;
- 特殊特性(SA_SIGINFO),信号处理时能够收到额外的附加信息
- sigset_t sa_mask
4.1.2 信号状态小知识
- 信号 产生
- 信号来源,如:SI_KERNEL , SI_USER , SI_TIMER , …
- 信号 未决
- 从信号产生到信号被进程接受的状态(处于未决状态的信号必然已存在)
- 信号 递达
- 信号送达进程,被进程接收(忽略,默认处理,自定义处理)
4.1.3 信号屏蔽 vs 信号阻塞
- 信号屏蔽 ==> 信号未决
- 信号处理函数执行期间,被屏蔽的信号不会被递送给进程(针对多个信号)
- sa_mask = SIGHUP | SIGINT | SIGUSR1;
- 信号阻塞 ==> 信号未决
- 信号处理函数执行期间,当前信号不会递送给进程(当前信号)
- act.sa_flags = SA_RESTART | SA_NODEFER;
4.2 现代信号处理注册示例
4.3 编程实验:现代信号处理
【参看链接】:11-12 - 信号发送与处理 / 12 / 04现代信号处理
4.3.1 oneshot
4.3.2 屏蔽&阻塞相关
4.3.3 系统调用重启
4.3.4 处理A收到B
4.3.5 signal实际调用sigaction
5. 现代信号发送与处理
5.1 sigqueue
- sigqueue(…)的黄金搭档是sigaction(…)
- sa_flags设置 SA_SIGINFO 标志位,可使用 三参数 信号处理函数
5.2 现代信号处理函数的关键参数
5.3 现代信号发送处理示例
5.4 编程实验:现代信号发送
【参看链接】:11-12 - 信号发送与处理 / 12 / 05现代信号发送
5.5 思考
- 利用信号搞进程间通信靠谱吗???