目录
什么是信号?
信号的产生
信号产生方式1:键盘
前台进程
后台进程
查看信号
signal系统调用
案例
理解进程记录信号
软件层面
硬件层面
信号产生方式2:指令
信号产生方式3:系统调用
kill系统调用
案例
其他产生信号的函数调用
1.raise
2.abort
信号产生方式4:软件条件
什么是软件条件?
alarm系统调用
使用
1.
2.
3.
信号的产生5:异常
示例1:
示例2:
那么os如何知道进程内部错误?
理解一下野指针的异常
Term和Core的区别
什么是信号?
信号!=信号量
信号:是一种用户,OS,其他进程,向目标进程发送异步事件的一种方式。
同步和异步:
1.怎么识别信号?
识别信号是内置的。即进程识别信号时程序的内置属性。可以类比一下语言的内置数据类型(int,double)
2.信号产生后,进程知道怎么处理吗?知道 。如果没产生,进程知道怎么处理吗?也知道,因为信号的处理方法在产生之前就已经准备好了。
3.信号会被进程立即处理吗?
不一定,如果不,那么什么时候处理呢?进程会在一个合适的时候处理。
信号到来 --> <进程不立即处理,记录信号> --> 合适的时候处理信号
4.处理信号的方法: 1.默认行为 2.忽略信号3.自定义动作
信号的产生
信号产生方式1:键盘
这有一段死循环的程序
#include <iostream>
#include <unistd.h>
int main()
{while (true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}
从键盘中输入 ctrl+c 可以前台进程杀掉前台进程。
前台进程
命令 : ./程序名
运行起来的是前台进程也可以用ctrl+c杀掉进程
后台进程
命令 : ./进程名&
后台进程无法用 ctrl+c 杀死
杀掉后台进程的方法:1. fg命令将后台进程切换到前台,在ctrl+c杀掉进程。
2. kill -9 pid(对应进程的pid)
方法一:
方法二:
查看信号
kill -l
man 7 signal//中可以找到相应信号的描述
signal系统调用
信号的自定义捕捉
signal的作用是捕捉signum号信号,捕捉之后,执行handler指向的函数
signum: 要捕捉的信号
#define SIGQUIT 3
handler:处理函数
案例
ctrl+\ 默认终止进程
#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{std::cout << "signo: " << signo << std::endl;
}
int main()
{//捕捉 3)SIGQUIT信号signal(SIGQUIT, handler);while (true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}
ctrl+\,被捕捉不再执行默认行为(默认终止),而是将这个信号传给signo并调用自定义函数
根据上面的案例是不是我们捕捉所有信号,那么这个死循环程序就不会被杀死了呢?
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
using func_t = std::function<void(int)>;
void handler(int signo)
{std::cout << "signo: " << signo << std::endl;
}
int main()
{//signal(SIGQUIT, handler);//捕捉信号for(int i = 1;i<=31;i++){func_t f = signal(i,handler);std::cout<<"捕捉:"<<i<<std::endl;}while (true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}
从结果上可以知道ctrl+c ctrl+\因为被捕捉了,所以杀不掉进程,但kill -9 可以 杀掉进程,也就说明了,9)SIGKILL 不能被捕捉。
理解进程记录信号
软件层面
键盘(比如ctrl+c) -> os -> 进程 --> 记录信号
因为os管理外设,所以键盘将信号传给os,os也管理进程,os在将信号传给进程,之后进程记录信号。
进程如何记录信号?
总结:
发送流程:键盘中的信号发送给os,os在发送给进程,修改进程对应的信号位图和其维护处理方法的函数指针数组。
无论什么信号都是由os发送的。
os是task_struct的唯一管理者,所以只有 os有权利修改信号位图。
硬件层面
os是如何知道键盘上有数据?
轮徇(不可以,os的任务太多了)
硬件中断
冯诺依曼体系结构
在数据层面上,我们知道cpu不与外设直接打交道。而是通过存储器
但在cpu和键盘是连接的,键盘可以直接将数据交给cpu
硬件中断 --> cpu告诉os,键盘上有数据 --> 这样硬件和os就可以并行执行了
信号vs信号中断
信号是纯软甲的
信号中断是纯硬件的
信号产生方式2:指令
指令:
kill -信号 相应进程的pid
信号产生方式3:系统调用
kill系统调用
kill的作用:就是OS向(pid)进程 发送sig信号
使用代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
int main()
{int cnt= 5;while(true){std::cout<<cnt<<std::endl;if(cnt<=0)kill(getpid(),SIGQUIT);sleep(1);cnt--;}return 0;
}
执行结果:
kill()系统调用与kill命令的关系
kill命令行就是封装的kill()系统调用。
案例
简单实现下自己的kill
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>void Usage(char* argv)
{std::cout<<"Usage error: "<<argv<<" signo"<<" pid"<<std::endl;exit(1);
}
//./kill 9 pidint main(int argc,char* argv[])
{if(argc!=3){//正确使用提醒Usage(argv[0]);}int signo = std::stoi(argv[1]);pid_t pid = std::stoi(argv[2]);int ret = kill(pid,signo);if(ret<0){perror("kill");exit(2);}return 0;}
执行结果:
其他产生信号的函数调用
1.raise
作用:让OS向调用该函数的进程返送sig信号
2.abort
作用:让OS向调用该函数的进程返送6号信号(该进程被干掉)
这两个函数比较简单
这两个函数都是由kill封装的,libc库函数
kill(getpid(),sig);//raise
kill(getpid(),6);//abort
信号产生方式4:软件条件
什么是软件条件?
由软件条件产生信号,通常指的是在软件应用程序中,根据某些特定的条件或逻辑判断来触发或生成信号
比如:管道中,读端关闭,但写端没有关闭 这时,os回想进程发送 13)SIGPIPE信号
alarm系统调用
作用:设置一个seconds秒的闹钟,时间一到就会给调用闹钟的进程发送 14)SIGALRM信号
alarm(条件);
里面设置的时间就相当于条件,满足条件就发送信号。
OS中维护的定时器的大概内核数据结构
使用
1.
#include <iostream>
#include <unistd.h>int main()
{alarm(1);int cnt = 0;while(true){//一秒打印多少次std::cout<<cnt<<std::endl;cnt++;}return 0;
}
结果:
打印了11万多次,快只能说很慢。
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>int cnt = 0;
void handler(int signo)
{std::cout<<cnt<<std::endl;exit(0);
}
int main()
{alarm(1);signal(SIGALRM,handler);while(true){//std::cout<<cnt<<std::endl;cnt++;}return 0;
}
结果:计算了将近16亿次
从上面两个程序可以看出,IO影响计算速度。
2.
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>int main()
{alarm(5);sleep(1);int n = alarm(0);//alarm(0)取消闹钟 n上一个闹钟剩余的时间std::cout<<n<<std::endl;return 0;
}
3.
pause
作用:等待信号
闹钟是一次性的发送完信号后就被取消。
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <vector>
using func_t = std::function<void()>;int cnt = 0;
std::vector<func_t> v;//任务void handler(int signo)
{for (auto &e : v){e();}alarm(2);cnt++;
}
int main()
{v.push_back([](){ std::cout << "我是一个日志任务" << std::endl; });v.push_back([](){ std::cout << "我是一个下载任务" << std::endl; });v.push_back([](){ std::cout << "我是一个mysql任务" << std::endl; });alarm(2);//一次性闹钟,超时后就会被取消signal(SIGALRM, handler);while (true){pause();//等待信号std::cout << "我醒来了..." << std::endl;std::cout << "cnt: " << cnt << std::endl;}
}
运行结果:
将上述逻辑中的信号变为硬件中断,就是OS的运行逻辑。
OS本质上就是一个中断处理程序
信号的产生5:异常
示例1:
野指针
int main()
{int *p = nullptr;*p = 0;//野指针return 0;
}
Segmentation fault 段错误
11)SIGEGV
示例2:
int main()
{int a = 10;a/=0;return 0;
}
Floating point exception 浮点异常
8)SIGFPE
现在我们可以理解一下为什么程序会崩溃?
出现错误时,OS会向进程发送相应的信号,从而进程被干掉。
那么os如何知道进程内部错误?
比如:div 0
cpu中有状态寄存器 Eflag,Eflag中有个core dump(溢出标记为)标记为,1.cpu计算错误
0.无错误
验证:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>int main()
{pid_t pid = fork();if(pid==0){//子进程int a = 10;a/=0;}else{int status;waitpid(pid,&status,0);std::cout<<"core dump: "<<((status>>7)&1)<<std::endl;std::cout<<"signo: "<<(status&0x7F)<<std::endl;}
}
结果:
正如所料:core dump被设为1,并向子进程发送8)SIGFPE信号
--------------------------------------------------------------------------------------------------------------------------------
void handler(int signo)
{std::cout<<"signo: "<<signo<<std::endl;
}
int main()
{pid_t pid = fork();if(pid==0){//子进程int a = 10;a/=0;}else{int status;waitpid(pid,&status,0);std::cout<<"core dump: "<<((status>>7)&1)<<std::endl;std::cout<<"signo: "<<(status&0x7F)<<std::endl;}
}
差不多的程序如果捕获了8号信号,
结果:
os一直发送8号信号。
总结:
cpu错误 --> 谁弄坏了cpu,os发送信号杀死进程 (--> 如果捕捉了8号信号--->进程没退出--->还要调度-->还要切换执行等 --> os不会修复Eflag中core dump(1)-->一直触发信号)
理解一下野指针的异常
野指针的错误是由cpu中MMU错误引起的
Term和Core的区别
Trem:正常终止,不需要debug
Core:核心转储--->在当前目录下形成core文件-->进程崩溃,将进程在内存中的部分信息保存起来,方便以后调试-->一般被云服务器关闭(如果崩溃一次形成一个新的core,那么就可能把磁盘空间占满)
但当前目录下没有啊
如何打开?
ulimit -a//查看
可以看到core file size为0;
ulimit -c 大小//改变core file size
readelf -S ./sig //查看程序elf格式
运行程序就可已生成core.pid的文件
如果无法生成core文件的话
在root下,输入下面的命令就可以了
echo core > /proc/sys/kernel/core_pattern
core-file core.pid//方便调试
如果是子进程异常呢?core的生成条件?
core dump--> 退出信号的终止动作是core && 云服务器开启core