信号产生
- 1.信号基础知识
- 2.初步认识信号
- 3.signal函数
- 4.技术应用角度的信号
- 5.调用系统函数向进程发信号
- 6.由软件条件产生的信号
- 7.硬件异常产生信号
- 8.core
🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【Linux的学习】
📝📝本篇内容:信号基础知识;初步认识信号;signal函数;技术应用角度的信号;调用系统函数向进程发信号;由软件条件产生的信号;硬件异常产生信号;core
⬆⬆⬆⬆上一篇: 进程间通信
💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-
1.信号基础知识
1.信号没有产生,进程也知道该怎么处理它
2.进程在没有收到信号的时候,它就已经能够认识并处理一个信号。程序员设计进程的时候,早就设计了对信号的识别能力
3.信号可能随时产生,所以在信号产生前,进程可能在做优先级更高的事情,可能不能立马处理这个信号,只能在后续合适的时候处理,因此进程收到信号时,需要进程具有记录信号的能力,保存起来
4.信号的产生对于进程来说是异步的(信号产生归产生,进程执行自己的代码)
5.1-31:普通信号 34-64:实时信号
6.进程的task_struct内部必定存在一个位图结构,用int表示;uint32_t signals;比特位的位置是信号的编号;比特位的内容是指是否收到该信号(0,1)
7.所谓的发送信号,本质其实是写入信号,直接修改特定进程的信号位图中的特定比特位,0->1
8.信号产生之后,不是立即处理,而是在合适的时候
9.处理信号的方式:①默认动作 ②忽略信号 ③ 用户自定义动作
2.初步认识信号
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
while (1)
{
cout << "我是一个进程,我在执行。。。" << endl;
sleep(1);
}
return 0;
}
一般执行的进程都是前台进程,不执行时bash为前台进程,因此执行时输入指令无法识别。当执行进程时后面加一个&,为后台进程
但此时要想要退出这个进程只能使用kill -9来杀掉,用别的比如ctrl^c都不行
其实不管用kill -9还是ctrl^c都是向进程发送了信号,kill -9发送的是9号信号,而ctrl+c是2号信号
3.signal函数
signal函数可以用来对指定的信号设定自定义处理动作,它的第二个参数就是把对应的处理动作传过去,它的第一个参数是对应要处理的信号,来看下面的例子,我们自定义一下2号信号的处理方式
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout<<"我是对应的"<<signo<<"信号"<<endl;
}
int main()
{
cout<<"pid:"<<getpid()<<endl;
signal(2,handle);
while (1)
{
cout << "我是一个进程,我在执行。。。" << endl;
sleep(1);
}
return 0;
}
可以发现我们使用kill -2时,它对应的处理方法是我们所自定义的,其实2号信号就是ctrl+c,我们的2
号信号的默认处理动作是终止进程
①signal(2,handle)调用这个函数的时候,handle方法并没有被调用,只是更改了2号信号的处理动作,handle方法只有在2号信号产生的时候才被调用
②默认我们对2号信号的处理动作:终止进程,我们用signal函数,我们在执行用户自定义动作的捕捉
③handle的int参数是特定信号被发送给当前进程的时候,执行handle方法的时候,自动填充对应的信号编号
我们甚至可以给所有的信号设置同一个处理函数,但9号信号不受影响
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout<<"我是对应的"<<signo<<"信号"<<endl;
}
int main()
{
cout<<"pid:"<<getpid()<<endl;
for(int i=1;i<=31;i++)
{
signal(i,handle);
}
while (1)
{
cout << "我是一个进程,我在执行。。。" << endl;
sleep(1);
}
return 0;
}
4.技术应用角度的信号
用户输入命令,在shell下启动一个前台进程。用户按下ctrl+c,这个键盘输入产生一个硬件中断,被OS获取。解释成信号,发送给目标前台进程。前台进程因为收到信号进而引起进程退出。
硬件中断的理解:当键盘按下时,通过类似8259硬件给CPU的脚针发送脉冲,其中脚针有对应的中断号,在系统中存在中断向量表,向量表中存的是对应的函数指针,中断号是向量表的下标,通过函数得知键盘哪些位置被摁下,此时OS得知了ctrl+c的按下,解释成了一个信号2,找到前台进程向其写入2号信号
5.调用系统函数向进程发信号
函数一:
kill命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号
//模拟实现kill命令
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
using namespace std;
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"格式是:kill -sign pid"<<endl;
exit(1);
}
int pid=atoi(argv[2]);//atoi函数用来把字符串转换成整数
int signo=atoi(argv[1]);
int ret=kill(pid,signo);
if(ret!=0)
{
cout<<"kill fails"<<endl;
exit(2);
}
return 0;
}
函数二:
这个函数可以给当前进程发送指定的信号(自己给自己发信号)
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int main()
{
int cnt=5;
while(cnt--)
{
cout<<cnt<<endl;
sleep(1);
}
raise(2);//五秒后进程自己给自己发2号信号
cout<<"发送信号失败"<<endl;//如果正常发送就不能看见这句话
return 0;
}
函数三:
absort()不会因为信号捕捉而导致无法终止,在执行完自定义处理方法后立马终止
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout<<"我是对应的"<<signo<<"信号"<<endl;
}
int main()
{
handle(SIGABRT);//对应的信号是6
abort();
return 0;
}
就像exit函数一样,abort函数总会成功
6.由软件条件产生的信号
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程
这个函数的返回值是0或者是以前设定的闹钟时间还剩下的秒数。如果seconds为0,表示取消以前设定的闹钟,返回值依然是以前设定的闹钟时间还剩下的秒数
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout<<"我是对应的"<<signo<<"信号"<<endl;
}
int main()
{
signal(SIGALRM,handle);
int cnt=10;
alarm(5);//五秒后会调用自定义处理方法handle
while (cnt--)
{
cout<<cnt<<endl;
sleep(1);
}
return 0;
}
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout<<"我是对应的"<<signo<<"信号"<<endl;
}
int main()
{
int cnt=5;
alarm(10);
while (cnt--)
{
cout<<"倒计时:"<<cnt<<endl;
sleep(1);
}
cout<<"alarm返回值"<<alarm(5)<<endl;
return 0;
}
OS中有数据结构(小堆)来存闹钟,在堆顶的是距离现在最近的时间
每个进程只有一个闹钟
7.硬件异常产生信号
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号
①当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程
可以发现编译的时候会报警告,但是运行时直接终止了
②当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGEGV信号发送给进程
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout << "我是对应的" << signo << "信号" << endl;
}
int main()
{
int *p=nullptr;
*p=10;
return 0;
}
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
void handle(int signo)
{
cout << "我是对应的" << signo << "信号" << endl;
}
int main()
{
int *p;
*p=10;
return 0;
}
其实本质上第一步并不是直接写入100,而是先通过MMU进行虚拟地址到物理地址的转换,如果没有映射,MMU硬件报错,有映射,但没有权限,MMU直接报错,MMU报错后OS能检测到
上面两种情况都使用自定义捕捉,会出现死循环,因为当硬件出现了异常,被OS捕捉到后,会给对应的进程发信号的,但是进程没有做出什么反应,因为当CPU又执行这个进程时,异常还在,OS继续给进程发信号,所以导致了死循环
8.core
这当中大多数信号都是终止,但是为什么有term和core的区别?
core:终止,会先进行核心转储,然后再终止进程
term:终止就是终止,没有多余动作
之前进程退出部分中的core dump标志是为了表示是否发生核心转储
OS可以将该进程在异常的时候,核心代码部分进行核心转储,将内存中的进程相关数据全部dump到磁盘中,一般核心转储文件在云服务器看不到,因此默认关闭这个功能的,因为我们的是生产环境,怕多次运行,导致core文件过多
查看核心转储命令:ulimit -a
修改核心转储:ulimit -c 大小
此时在运行我们的代码
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int main()
{
int a=10;
a/=0;
return 0;
}
可以发现我们当前目录下多了一个core文件
那这个core文件有什么用呢?
可以通过核心转储在异常后·1,方便进行调试,在gdb通过core-file 文件名进行自动定位
🌸🌸信号产生的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪