信号和信号量是俩个东西,俩者无关系。
信号
信号本质是一种通知机制,用户or操作系统通过发送一定的信号,通知进程,某些事件已经发送,让进程进行后续处理。
结合进程,信号结论:
进程要处理信号,进程必须具备识别信号的能力。
凭什么进程能够识别信号呢?程序员让进程具备了识别信号的能力
信号是随机产生的,进程可能正在忙自己的事情,所以,信号的后续处理可能不是立即处理的
信号会被临时记录下来,方便后续处理
什么时候处理呢?合适的时候。
一般而言信号的产生,相对于进程而言是异步的(信号和进程各干各的,互不影响)。
信号如何产生
我们写一个死循环程序,按ctrl+c可直接退出
这是因为ctrl+c:本质是向进程发送二号信号,我们输入kill -2 PID值,效果跟ctrl+c一样
下图都是信号,左侧是信号编号,右侧是宏值
处理信号的方式
默认(进程自带的,程序员写好的逻辑)
忽略(也是信号处理的一种方式)
自定义动作(捕捉信号)
常见信号(kill -l可查看所有信号)
【1,31】是普通信号,后面的是实时信号,实时信号对信号处理要求比较高
也可输入man 7 signal,可查看信号的详细描述
如何理解组合键变成信号呢?
键盘的工作方式是通过中断方式进行的,操作系统可以识别组合键
如何理解信号被进程保存呢?
什么信号?
是否产生?
进程必须要有保存信号的相关数据结构(位图,unsigned int,用比特位的位置表示信号),PCB内部保存了信号位图字段
信号发送本质:信号位图是在task_struct内核数据结构中,只有操作系统去发送信号,即信号发送的本质:OS向目标进程写信号,OS直接修改PCB中指定的位图结构,完成“发送”信号的过程。
OS解释组合键->查找进程列表->前台运行的进程->OS写入对应的信号到进程内部的位图结构中
产生信号的方式
1. 通过终端按键产生信号
signal函数,作用:对特定的信号进行捕捉,这种方式属于自定义捕捉
第一个参数是要捕捉的信号,可传入[1,31]对应的数字,也可传入对应的字母如SIGINT
typedef void (*sighandler_t)(int) 是一个函数指针,因此第二个参数时一个函数指针
signal通过回调的方式,修改对应的信号捕捉方法。
返回值也是个函数指针,一般不需要关心返回值,当我们捕捉了一个新的方法时,该函数会返回老的方法
catchSig的参数含义是:将信号对应的编号以参数形式传到这里
运行程序后,我们ctrl+c,此时会捕捉到信号,但是程序还在运行,ctrl+c未能让进程终止
这是因为以前2号信号处理动作默认是终止进程,而现在我们把二号信号的处理动作给改了(改成了打印一句话),因此对应的进程就不再退出。
signal函数仅仅是修改进程对特定信号的后续处理动作,不是直接调用对应的处理动作。即:如果后续没有任何SIGINT信号产生,catchSig永远也不会被调用
就好比新颁布了一项法律,若没人触犯该法,该条法律则永远不会被使用。
信号也是如此,我们只是提前注册了一个捕捉到信号的方法,若后续未捕捉到信号,则不会使用该方法。
特定信号的处理动作一般只有一个。
我们此时发送别的信号,可使程序退出如ctrl+\,这其实是3号信号
我们对2号和3号信号使用同一个捕捉方法
再次运行
用kill发送信号
我们发送8号信号,可终止程序,8号信号是浮点数异常
man 7 signal,我们可以看到3号信号后面是Core
Core核心转储
在进程等待部分,当进程退出时,我们要获取它的退出结果,次低8位代表子进程退出时的退出码,最低的7个比特位保存退出时的退出信号,而core dump代表进程退出时是否具备core dump,core dump是核心转储标志,代表是否发生了核心转储。
一般而言,云服务器(生产环境)的核心转储功能是被关闭的
ulimit -a查看当前服务器相关资源配置
打开了当前的core file选项由0变为10240
此时程序跑起来,kill -8 ,我们发现此时多了一个core dump和一个临时文件
我们看到这个文件在所有文件中大小算是比较大的了
核心转储:当进程出现某种异常的时候,是否由OS将当前进程在内存种的相关核心数据,转存到磁盘中。保存到了如图所示的core文件。核心转储主要是为了调试。
此时我们不捕捉这俩个信号
运行程序,分别用2号和3号信号进程退出,3号信号退出时出现了core dump
此时又有了核心转储
我们可以看到里面还有Ign(忽略),cont(继续)
此时运行程序,程序直接终止
产生了core文件
GDB调试
我们先输入gdb mysignal,之后输入core -file core.26559,此时直接定位到出错的地方而且有出错的原因,我们就不需要像以前一样一行一行调试
验证进程等待中的core dump标记位
这个标记为代表是否发生核心转储
我们可以看到发生了核心转储
多了一个core文件
我们sleep(100),之后运行程序 使用kill -2
此时core dump标记位为0
我们关闭核心转储,core dump标记位为0
为什么生产环境一般都是要关闭core dump?
core文件产生过多可能会把磁盘写满,造成无法预估的后果。
调用系统接口发送信号
kill
man 2 kill
向指定的进程发送指定的信号。
使用kill写一个程序
我们此时sleep(10000),然后运行我们的程序,进程被杀掉
raise
如果kill是向指定的进程发送指定的信号,kill就是向自己发送指定的信号
自己给自己发送8号信号
abort
abort让进程直接终止, 给自己发送abort信号(自己终止自己)。
输入ulimit -c 10240打开core,此时再运行程序
abort通常用来终止进程。
用户调用系统接口->执行OS对应的系统调用代码->OS提取参数或者设置特定的数值->OS向目标进程写信号->修改对应进程的信号标记位->进程后续会处理信号->执行对应的动作
由软件条件产生信号
管道,读端不光不读,而且关闭了,写端一直在写,会发生什么?
此时意味着写没有意义,OS会自动终止对应的写端进程,通过发送信号的方式,发送SIGPIPE(13号信号)。
SIGALARM是14号信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动
作是终止当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后
响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就
是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数
上面这段程序验证1s之内,能运算多少次count++;
为什么只计算到6w多呢?因为我们进行了cout,而且因为我们的Xshell在本地,云服务器在远端,cout还是通过网络发送的,因此会比较慢。
若单纯想计算算力,可这样做
我们每发送14号信号count值就会被发出来
我们设定了一个闹钟,这个闹钟一旦触发,就自动移除了(设置一次就只会触发一次)
我们在每次捕捉到信号时,再设置一个闹钟
每隔一秒打印一条消息
我们上面实现了类似的定时器功能
每隔一秒打印一下
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
using namespace std;
typedef function<void ()> func;
vector<func> callbacks;
uint64_t count = 0;
void showCount()
{
// cout << "进程捕捉到了一个信号,正在处理中: " << signum << " Pid: " << getpid() << endl;
cout << "final count : " << count << endl;
}
void showLog()
{
cout << "这个是日志功能" << endl;
}
void logUser()
{
if(fork() == 0)
{
execl("/usr/bin/who", "who", nullptr);
exit(1);
}
wait(nullptr);
}
void flushdata()
{
}
// 定时器功能
// sig:
void catchSig(int signum)
{
for(auto &f : callbacks)
{
f();
}
alarm(1);
}
static void Usage(string proc)
{
cout << "Usage:\r\n\t" << proc << " signumber processid" << endl;
}
void handler(int signum)
{
sleep(1);
cout << "获得了一个信号: " << signum << endl;
exit(1);
}
int main(int argc, char* argv[])
{
signal(SIGFPE, handler);
alarm(1);
callbacks.push_back(showCount);
callbacks.push_back(showLog);
callbacks.push_back(logUser);
while (true) count++;
return 0;
}
我们可以发现IO的效率非常低,尤其是带上网络。
如何理解软件条件产生信号?
OS先识别到某种软件条件触发或者不满足,然后OS构建信号,发送给指定的进程
硬件异常产生信号
捕捉了一个信号,但为什么这里开始循环了?
如何理解除0?
进行计算的是CPU,这个硬件
CPU内部是有寄存器的,其中有一个是状态寄存器(位图),里面保存每次计算的计算状态,状态寄存器有对应的标志位,有溢出标记为,OS会自动进行计算完毕之后的检测。如果溢出标记位是1,OS识别到有溢出问题,立即找到当前谁在运行提取PID,OS完成信号发送的过程,进程会在合适的时候,进行处理。
除0错误本质是硬件异常。
一旦出现硬件异常,进程不一定退出,一般默认是退出,但是即便不退出,我们也做不了什么
为什么会死循环?寄存器中的异常一直没有被解决,OS不断地重复发送信号
我们写一个段错误
段错误本质也是收到信号
此时又陷入了死循环。
如何理解野指针和越界问题?
都必须通过地址找到目标位置
语言上面的地址全部都是虚拟地址,
将虚拟地址转为物理地址
通过页表+MMU(Memory Manager Unit是硬件)找到物理内存
野指针,越界-》非法地址,MMU转换的时候一定会报错。错误会被OS转成信号发送给进程
所有的信号,都有它的来源。但最终全部是被OS识别,解释并发送的。