信号
关于信号举一些生活中的例子 --- 比如交通指示灯...
- 信号在生活中,随时可以产生 --- 信号的产生和我们是异步的!(异步的意思就是信号的产生和我没有直接关系)
- 你能认识这个信号 --- 我们知道这是信号,我们才知道如何处理!
- 我们知道信号产生了,信号该怎么处理! --- 我们应该知道如何处理!
- 我们可能在做着更重要的事情,把到来的信号暂不处理 --- 1.我得记得这个事。2.什么时候处理?合适的时候!!!
那么转回我们的OS中,上面的“我”不就是进程吗!!!
在OS中有31个普通信号,后面35-64为实时信号,我们主要研究普通信号
使用kill -l 命令列出信号列表
信号概念的基本储备
信号:Linux系统提供的一种,向指定进程发送特定事件的方式,做识别和处理。
信号产生是异步的!
信号处理:1.默认动作 2.忽略动作 3.自定义处理 --- 信号的捕捉
进程处理信号,都是默认的 --- 默认动作通常:终止自己、暂停、忽略...
man 7 signal 查看各种信号的信息
如何理解信号的保存和处理呢?
每个进程都有task_struct的结构体,其中有一个成员变量拥有32个比特位,那么保存就可以使用这些比特位,这就是使用位图来进行信号的保存,保存收到的信号!
发送信号:修改指定进程的PCB中的信号的指定位图,0变为1,写信号!
信号的产生
1.通过kill命令,向指定的进程发送指定的信号。
kill -9 pid --- -9代表要发送的信号,在信号表中,pid代表要发送给哪个进程号
2.键盘可以产生信号。
比如ctrl + c代表kill -9,ctrl + \代表3号信号SIGQUIT
3.系统调用。
kill --- 给指定进程发送指定信号
raise函数 --- 给当前进程发送指定信号(内部其实本质是封装了系统调用)
abort函数 --- 执行的是6号信号SIGABRT(内部其实本质是封装了系统调用)
signal --- 自定义捕捉指定信号,去执行handler函数,改变了默认的终止进程的动作
测试一下signal
#include <iostream>
#include <unistd.h>
#include<signal.h>
void handler(int sig)
{
std::cout << "get a sig:" << sig << std::endl;
}
int main()
{
signal(SIGABRT, handler);
while (true)
{
std::cout << "my pid is:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
以上总总只为证明:真正发送信号的其实是谁,是OS!!!
思考:如果我们把所有信号都捕捉了,如何终止进程?
OS也想到了这个问题! --- 9号信号不允许被自定义捕捉
如何理解上面的信号发送?
不就是通过给指定进程发送指定信号,让指定进程做出相应反应吗?
4.软件条件
管道,当管道读端关闭,写端一直写,OS会发送13号SIGPIPE信号终止这个进程!
alarm --- 设置多少秒后再执行相应信号SIGALRM --- 设置一次就默认触发一次
测试代码:
#include <iostream>
#include <unistd.h>
#include<signal.h>
int cnt;
void handler(int sig)
{
std::cout << "get a sig:" << sig << std::endl;
exit(1);
}
int main()
{
signal(SIGALRM, handler);
alarm(3);
while (true)
{
std::cout << "my pid is:" << getpid() << std::endl;
cnt++;
std:: cout << "cnt:" << cnt << std::endl;
sleep(1);
}
return 0;
}
alarm可以设置闹钟,多少秒后执行结束
alarm(0):取消闹钟
alarm还可以每隔一秒发送一次信号!
#include <iostream>
#include <unistd.h>
#include<signal.h>
int cnt;
void handler(int sig)
{
alarm(1);
std::cout << "cnt:" << cnt << " get a sig:" << sig << std::endl;
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
//std::cout << "my pid is:" << getpid() << std::endl;
cnt++;
std:: cout << "cnt:" << cnt << std::endl;
sleep(1);
}
return 0;
}
验证IO --- IO很慢
while循环进行打印++现象
#include <iostream>
#include <unistd.h>
#include<signal.h>
int cnt;
void handler(int sig)
{
std::cout << "cnt:" << cnt << " get a sig:" << sig << std::endl;
exit(1);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
//std::cout << "my pid is:" << getpid() << std::endl;
cnt++;
std:: cout << "cnt:" << cnt << std::endl;
//sleep(1);
}
return 0;
}
while循环不打印进行++现象
#include <iostream>
#include <unistd.h>
#include<signal.h>
int cnt;
void handler(int sig)
{
std::cout << "cnt:" << cnt << " get a sig:" << sig << std::endl;
exit(1);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
//std::cout << "my pid is:" << getpid() << std::endl;
cnt++;
//std:: cout << "cnt:" << cnt << std::endl;
//sleep(1);
}
return 0;
}
明显后者更快!!!
为什么这样?这是因为当我们使用cout打印到显示器上的时候,是需要拷贝的,再者我们用的是云服务器,从远端服务器上推送到本地是有很大消耗的!!!所以IO很慢!!!
理解闹钟
我们可以设置很多闹钟,那我们OS要不要对闹钟进行管理呢?
操作系统要对闹钟进行管理,先描述,再组织!
其实闹钟就是一个结构体
struct alarm
{
time_t expired;//未来的超时时间=second + Now();
pid_t pid;
func_t f;
...
};
描述有了,如何组织呢?
我们是不是可以让闹钟按超时时间进行排序,那我们可以使用小根堆,堆顶元素超时我们就pop!!!
闹钟的返回值
alarm的返回值是上一次设置的闹钟到这次设置的闹钟剩余的时间!
所以产生信号其实是系统调用alarm,发送信号是OS发的!!!
5.异常
程序为什么会崩溃???非法访问,操作 ---> 程序出现了异常,OS给进程发送信号了!!
程序崩溃了为什么会退出?--- 信号默认处理是终止进程
可以不退出吗?可以,使用signal捕捉该异常,但我们通常都推荐终止进程!
为什么推荐终止进程? --- 释放进程的上下文数据,包括溢出标志数据或者其他异常数据!
算术运算,逻辑运算原理
当我们进行10/0运算时,CPU如何得知运算是正常的还是异常的?
其实在CPU内部有很多的寄存器,有个可以状态寄存器内部有溢出标记位,标记为0,会把计算结果写回到内存,如果标记位为1,就说明运算出现了异常,CPU内部就会出现报错!
那OS是软硬件资源的管理者,OS要随时处理这种硬件问题! --- 向目标进程发送指定信号!!!
那么回到为什么出现异常推荐终止进程呢?终止进程本质上是释放进程的上下文数据,包括溢出标志数据或者其他异常数据!
野指针为什么捕捉后一直报错?11号信号SIGSEGV
Core、Term怎么理解?
term代表异常终止,没什么好说的!
core --- 异常终止但是他会帮我们形成一个Debug文件 --- 进程退出的时候的镜像数据!!
--- 核心存储
云服务器默认是把core文件关掉的!!
我们来打开
会同时为我们生成一个core文件,在Ubuntu下如果运行多次进程出现多次错误会只有1个core文件,而Centos中会有后缀pid跟着,故多次运行会产生多个core文件!!!
云服务器为什么要关闭核心存储?
所以云服务器为什么要关闭,因为当我们执行错误代码,无意下多次执行的话,可能会造成大量的core文件,这可能会影响云服务器的正常运行,故默认是关闭的!
之前的退出码与信号码之间的core dump标志位就有了解释,1代表开启即Core,0代表关闭即Term!!