Linux进程信号(一)

本文介绍了信号的基础知识,包括信号的异步性质、处理方式以及进程如何记录和处理信号。通过示例代码展示了如何使用`signal`函数自定义信号处理函数,以及如何通过`kill`和`raise`函数发送信号。此外,讨论了硬件异常如除以零错误和非法内存访问如何产生信号,并提到了核心转储(coredump)的概念及其在调试中的作用。
摘要由CSDN通过智能技术生成

🌟🌟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的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轩情吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值