接下来一个重要的概念就是信号
它是一种重要的进程间通讯机制
信号signal
是发送给进程的特殊的异步消息
当进程接收到这个信号的时候
它就会立即处理
特别注意这一点
接收到信号立即处理
这个时候它并不一定
能够完成当前函数的调用
甚至当前的代码行
不一定能够做完 就会被中断
特别注意
中断这些当前函数运行
立即处理这个信号
这是信号处理中特别重要的一点
在Linux操作系统中有很多种信号
这些信号都各有各的意义
系统以数字来标识这些信号
在程序中使用的时候
一般我们使用它的名字
对应的宏名来标记它 来使用它
系统信号
都有自己的缺省的处理例程
也就是说如果你没设
那么这个对应的信号
它就有个特定的处理的方式
缺省的处理方式
一般就是终止进程
生成内核转储文件或者忽略
这么几大类
这种典型的处理方式
如果你对它的处理方式不满意
或者达不到你的要求
你可以写你的信号处理例程
把它挂接上去就OK
我们后边会有例子给大家展示
如何处理这个问题
使用“kill –l”这个命令
可以查看操作系统所支持的信号的
全部列表
特别注意这一点 不同的操作系统
它所支持的信号可能是不一样的
要看你自己所使用的实际Linux系统是什么
才能看到它用到的信号有哪些
这是Linux系统中常用的信号的列表
包括这些信号
在我们程序中所使用到的名字
包括它们对应的值
和它们的缺省动作
和每个信号所应该具有的含义
都在这个系统信号表里面
给大家列出来了
具体的信号我就不解释了
需要的时候
我们会一个一个地去解释它
总共是31个最主要的信号
两个进程之间
可能会发送一些特定的信号
A进程给B进程发出一个信号
那么B进程就会响应这个信号
进行特定的处理
两个最重要的信号是SIGTERM、SIGKILL
一个叫终止信号 一个是杀掉信号
都是终止进程的信号
前者是请求 是terminate
我请求你停下来吧
SIGTERM 请求停止
SIGKILL 不管三七二十一
杀掉它 强制杀掉那个进程
就这个意思
这是两个最重要的终止进程信号
还有两个信号SIGUSR1、SIGUSR2
这是用户自定义的信号
我们可以用这两个信号
来做自己的处理
你想让两个进程做些特定的处理
那么你就可以向它发一个SIGUSR1信号
或者SIGUSR2信号
然后那边针对这个特定的信号
就编写一个信号处理例程
就可以处理这个特定的信号
这都是用户可以定义的
整个信号处理的过程中
当一个进程接收到信号以后
它就会根据信号特定的配置
来决定如何响应这个信号
缺省配置前面不说了嘛
在程序没有进行处理的时候
那么这个信号就会按照缺省的配置
进行去处理
如果你定义了自己的信号处理例程
那么它就不会按照缺省的
那个处理过程去对它进行处理
程序处理信号的时候
它会按照信号处理例程里面
所提供的那个函数指针
来调用它所指定的那个函数
那个函数指针型式是固定的
那么你实现这个函数
就必须是那个函数指针型式
必须按照信号处理例程的函数指针型式
定义一个信号处理函数
然后调用它
最重要的一个信号处理函数就是sigaction
它设置信号的配置
这个函数需要带signum（整型参数）
act（指向const struct sigaction的一个指针）
oldact（指向struct sigaction的一个指针）
总共三个参数
signum就是对应的信号编号
act和oldact
都是指向sigaction这个结构体的指针
前者是新的指针
后者是保存老的sigaction的指针
为什么呢
因为每一个信号对应的处理例程
它原先就有一个 有的是缺省的
有的可能是你原先设定的
现在你想为这个信号
设定一个新的处理例程
那么你就把新的处理例程
给它设置进去
但是原先的那个 你得保存起来
你可能把这个信号处理例程处理完以后
要恢复成原来那个缺省的处理模式
或者原先你设置的那个处理模式
对吧 你必须要恢复过来
所以老的那个要保存起来
这就是sigaction这三个参数的意义
这个地方有一个小讨厌
就是什么呢
这个函数sigaction的名字
和这个结构体的名字一模一样
这个结构体的名字也叫sigaction
在一开始编程的时候容易犯糊涂
这到底是函数还是结构体
其实又是函数又是结构体
所以用的时候要特别去注意这一点
为了区分它 所以在结构体前面
我总是写struct
我不会省略这个struct
哪怕我写的是C++代码
我也不省略这个struct
表示它是结构体 它不是函数
要能区分它 就这个意思
这个信号结构体特别重要
我们处理信号的时候
一定要操作的就是这个信号结构体
要非常地了解
它这里面最重要的一个成员就是sa_handler
它就用来表达这个对应的信号
它的处理的例程——那个函数指针是什么
你定义了一个信号处理例程
那么你就把这个信号处理例程
的入口地址 传给sa_handler就OK了
它的取值如果你给它SIG_DFL
就表示使用缺省那个配置
来处理这个信号
如果你给它SIF_IGN 就是忽略它
这个信号来了 就忽略它
就不理它 OK
否则就是你自己定义的
那个信号处理例程的函数入口地址
那就是一个函数指针了
但同学们要特别记住
因为早期Linux在实现的时候
没有C++ 它用的是C代码
所以这个信号处理例程
这个函数是C函数
你写C++代码的时候要特别小心
传的是一个C函数的一个函数指针
不是C++的函数
更不是类的成员函数
这一点是需要注意的
在处理信号的时候要特别注意
一 信号是一个异步操作
所以当你处理这个信号的时候
主程序本身是非常非常脆弱的
信号处理例程本身应该尽可能得短
不能太长了 太长的话
它非常容易导致整个程序错误
包括导致我们的信号处理的过程中
就会被中断
那你这个信号就没有被正确处理
这肯定不行
所以信号处理例程应该尽可能得短小
尽量不要在信号处理例程中进行I/O操作
因为I/O操作在很多情况下
它是长而复杂的
在信号处理例程里边
处理复杂的I/O操作
很多时候都有可能导致问题
所以特别注意这一条
尽量不要在信号处理例程中
实施I/O操作
在信号处理例程中
进行复杂的赋值操作
理论上也是不应该的
信号处理例程本身
它可能不是一个原子操作
什么意思
就是说它本身是可能会被中断的
所以如果进行一个复杂的赋值
它就有可能导致我们赋值了一部分
然后它被中断
然后剩下一部分数据就没有被赋值
没有意义
这个数据的一致性
我就没有办法保证
正是在这一点上
在信号处理例程里面
为了对量进行赋值
它有一个特殊的规定
就是一般情况下应该使用这个型式
来定义全局量
然后赋值那个全局量
它等价于一个int
也就是说在信号处理例程里边
你赋值一个整型是OK的
赋值一个指针
不管是32位指针 还是64位指针
实际上也是OK的
但是更大一点的数据尺寸
就需要特别地小心
你必须保证那个处理的过程中
不会被中断 否则就是有问题的
这一点在处理信号的时候
是需要特别注意的
我们来看一段代码
我们定义sigusr1_count
把它初始化成0
定义一个外部C函数void OnSigUsr1()
这个信号处理例程
这是一个简单的信号处理例程
然后在我们的主函数内
我们定义struct sigaction结构体的
一个变量sa
我们调用memset() 就是memory set
就设置它的内存
把它的内存全部清0 就这个意思
然后设它的sa_handler
把它设成我们的OnSigUsr1的入口地址
这个就设定了
sigaction()的基本函数处理例程
它的信号处理例程就被你设定好了
然后你调用sigaction()这个函数
把sa 把它挂到SigUsr1这个信号上
只要完成sigaction()这个函数调用
我们的信号处理例程OnSigUsr1()这个函数
就会被挂接在SIGUSR1这个信号上
这是完成这个信号处理的挂接
当你在程序运行的过程中
触发了一个SIGUSR1信号之后
这个信号处理例程就会被调用
比如讲我们这个程序一编译
然后你在终端里输入“kill –s SIGUSR1 pid”
这个pid就是这个程序
在运行的时候
它那个对应的进程ID，PID
向这个进程发一个SIGUSR1的信号
那么它就会执行这个信号处理例程
把sigusr1_count递增1
你就能够看到它递增后的结果
注意这一点
实现的方式就是这个模样