一问搞懂Linux信号【上】

Linux信号在Linux系统中的地位仅此于进程间通信,其重要程度不言而喻。本文我们将从信号产生,信号保存,信号处理三个方面来讲解信号。

🚩结合现实认识信号

在讲解信号产生之前,我们先做些预备的工作。

现实生活中信号无处不在,大家见过哪些信号呀?红绿灯,手机铃声,闹钟等等。

我们拿红绿灯来举例说明

毫无疑问,之所以会出现信号,一定有它自己的用处。我们在十字路口看到了绿灯,就知道可以安全过马路了。所以首先,我们需要认识这个红绿灯,假如一个老奶奶一辈子生活在农村,没有见过红绿灯,所以即使老奶奶看到了绿灯,她也不认识绿灯的含义。其次,要对信号有相应的行为产生。

接下来,我们谈谈这背后的几个问题

:你为什么可以认识红绿灯呢?

:有人教育过你(手段),让你在大脑中记住了绿灯对应的属性和行为(结果)。


假设星期天张三一个人在家打游戏。然后张三点了一份外卖,张三知道:一会有人敲门(敲门声对张三来说就是信号),就是外卖来了,他就该取外卖了(对信号的反应)。 不一会,果然有人敲门,但是,张三打游戏正尽心着呢,到了关键时刻。所以就对外卖员说:你把外卖放在门口吧。我一会开门去拿。

这就是当信号来时,我们可能坐着更重要的事情,信号的来临是异步的所以我们要暂时存储这个信号。也就是张三要记住待会拿外卖这个时,如果张三是一个记忆力为0的人,这个敲门声对他来说就是无意义的。

总结:信号被捕捉,可能不会马上被处理。会存在一个时间窗口,所以我们要保存信号


一个信号产生,我们就要对这个信号作出反应。包括:默认行为,自定义行为,忽略行为。

绿灯亮了人们纷纷过马路(这种行为就是默认行为),李四妈妈从小就教育李四:在绿灯亮了之后,不要马上就过马路,要先跳个舞(这个行为就是自定义行为)。

早上妈妈叫我们起床,我们继续睡觉,当她没说,这种行为就是忽略行为。

🚩初识Linux中的信号

信号是进程之间事件异步通知的一种方式,属于 软中断

查看Linux信号指令:kill -l

 

并且每个信号的编号都有自己的名字,这些 名字 其实就  C 语言的 ,如果调用信号,既可以通过信号的名称调用,也可通过信号的编号调用。当然,这么多的信号并不需要你全部记下来,我们在运用的过程中就会知道哪些信号常用,哪些不常用。

仔细观察,我们看到:

  •  这个信号集中一共有62个信号,没有32号和33号。
  • 1--31称为普通信号,34-64称为实时信号,我们只学习普通信号。

我们可以查询7号手册来查询信号的默认行为。


 我们应如何把现实生活中信号的属性和特征迁移到操作系统的信号中呢?

我们要明白:操作系统中的信号是给进程发的。

问:进程是如何识别信号的呢?

答:认识+动作。进程本身就是程序员编写的属性和逻辑的集合,所以认识的过程由程序员编码完成。

当进程收到信号时,进程可能做着更加重要的代码,所以信号不一定会立即处理,这也就以为着要有地方存储信号。即进程本身要有对信号的存储能力。

进程在处理信号时,又称捕捉到了信号,一般对信号的处理分为三种:默认行为,自定义行为,忽略行为。


如果一个信号是发给某一个进程的,而进程要对信号进行保存,而保存的位置就是该进程所对应的task_struct结构体中。如下图:

在task_struct结构体中有一个unsigned int类型的变量。这个变量有32个比特位,4个字节。

比特位的位置,代表着信号的编号。比特位的内容,代表着是否收到对应的信号,0表示没有,1表示有。

信号发送的本质:就是修好对应的进程PCB的信号位图。PCB作为内核数据结构,只有操作系统才有权利修改PCB中的数据,所以,无论将来我们学习多少种发送信号的方式,本质都是通过OS向对应的进程发送信号。OS必须提供发送信号处理信号的相关的系统调用接口。

所以接下来我们就要重点学习发送信号和处理信号两部分内容。

到现在,我们还未介绍新的知识,一切都是我们通过现有的知识推导出来的。所以kill命令的底层一定调用了相关的系统调用接口。 

 🚩Linux中信号的产生
🌸通过键盘组合键产生信号

我们来看一段代码

#include<iostream>
#include<unistd.h>
int main()
{
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;

}

此时,我们就有必要简单介绍一下Ctrl+C组合键了。

 Ctrl+C本质是是一个热键,我们按下这个组合键,会被操作系统捕获。操作系统就会把Ctrl+C解释位2号信号。

2号信号的默认行为是终止前台进程,为了证明操作系统会把Ctrl+C解释为2号信号,我们自定义2号信号的行为。

接下来,我们迎来了我们信号部分第一个函数,也是最常用的一个函数。

🚀signal

参数介绍

①signum:传入需要捕捉的信号(名字或编号),当进程收到与其相匹配的信号时则会调用第二个参数,否则不会有任何动作

②handler:handlder方法,此方法为自定义方法,当收到signum信号则不会执行该信号的默认动作,变为执行该方法。

返回值

返回上一个信号处理方法。


接下来,我们就2号信号设置一个自定义行为,值得注意的是,我们不需要将这个接口放在循环体中,在一份代码中对一个信号自定义一次即可。

#include<iostream>
#include<unistd.h>
#include<signal.h>
void my_hander(int signo)
{
    std::cout<<"get a sig:"<<signo<<std::endl;

}
int main()
{
    signal(2,my_hander);
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;

}

 我们发现:使用ctrl+c竟然杀不死这个进程了,因为我们的自定义函数没有设置让进程退出,如果想让进程退出,可以使用exit

值得注意的是:我们的自定义行为只有当我们向进程发送该信号时,我们的自定义行为才凸显出来。 


除了使用Ctrl+C来终止一个进程外,我们还可以使用Ctrl+\来终止一个进程。

 操作系统会将Ctrl+\解释为3号信号。我们还是使用刚刚的代码

 总结一下:通过键盘发送信号给指定进程的过程为:

键盘特定输入 ——> OS解释为信号 ——> 向目标进程发送信号 ——> 进程收到信号 ——> 进程做出响应

🌸通过系统调用产生信号

我们先认识一个系统地要用接口:kill 可以将任意进程发送任意信号

这个函数使用起来非常简单。

参数

①pid:要发送信号给进程pid。

②sig;要发送的信号的编号。

返回值:

成功的话,返回0;失败,错误码被设置。


接下来,我们写一段有意思的代码

mykill.cc

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cstring>
#include<cstdio>
void Usage(const std::string &path)
{
    std::cout<<"\nUsage: "<<path<<" pid sig\n"<<std::endl;

}
int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }

    pid_t pid=atoi(argv[1]);
    int num=atoi(argv[2]);


    int n=kill(pid,num);
    std::cout<<"send sig successful"<<std::endl;
    return 0;

}
#include<iostream>
#include<unistd.h>
#include<signal.h>
void my_hander(int signo)
{
    std::cout<<"get a sig:"<<signo<<std::endl;

}
int main()
{
    signal(2,my_hander);
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
    }
    return 0;

}

我们这就完成了通过一个进程发送信号来终止一个进程的工作。 


接着,我们再来一个接口:raise:给调用这个接口的进程发送信号。

SYNOPSIS
       #include <signal.h>

       int raise(int sig);

参数只有一个,发送的信号的编号

返回值:成功返回0,失败错误码被设置。

接着,我们用一用这个接口:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>
void my_hander(int signo)
{
    std::cout<<"get a sig:"<<signo<<std::endl;

}
int main()
{
    signal(2,my_hander);
    int cnt=0;
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
        cnt++;
        if(cnt==10)
        {
            int n=raise(2);
            assert(n==0);
        }
    }
    return 0;

}

其实,如果将kill的第一个参数传入调用kill的进程本身的pid,其功能和raise相同。 


接着下一个函数,这个函数是C语言的函数,向使用该函数的进程发送特定的信号(6号信号)。

       #include <stdlib.h>

       void abort(void);

无参数无返回值,要是所有的函数都这么简单该多好呀! 

 按照惯例,我们使用一下:

#include <iostream>
#include <unistd.h>
#include <cstdlib>
int main()
{
    int cnt=0;
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
        cnt++;
        if(cnt==10)
        {
           abort();
        }
    }
}

空口白牙,何以证明?我们把代码给改一下。

#include <iostream>
#include <unistd.h>
#include <cstdlib>
int main()
{
  
    while(1)
    {
        std::cout<<"我是一个进程:"<<getpid()<<std::endl;
        sleep(1);
        
    }
}

证据已经展现,我们使用kill 发送6号信号和abort()的现象一样。

总结: 

 我们一共认为了3个函数:kill ,raise,abort,raise和abort的都可以用kill通过传入不同的参数来实现。

 🌸信号的意义

:我们已经发现,很多信号的作用都是终止进程,那既然都是终止进程,为社么要有那么多种类的信号呢?

 信号的意义并不由信号的处理动作决定,不同的信号,代表着不同的事件。对信号的处理可以一样。就像代码出错,返回不同的错误码,代表着不同的意义,但结构就是终止运行。

🌸通过硬件异常产生信号

信号的产生,不一定非得用户显示的发送。硬件异常也可以通知操作系统,由操作系统向进程发送信号,来终止该进程。

来看一份代码

#include <iostream>
#include <unistd.h>
#include <cstdlib>
int main()
{
    int a=0;
    a=a/0;
    return a;
   
}

这份代码很明显的出现了除零错误,是编译不过的。 

不出所料,操作系统通过指定信号终止了进程,这种情况下,操作系统终止进程发送的信号为8号信号。如何证明?自定义捕捉。

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>
void my_hander(int signo)
{
    std::cout<<"get a sig:"<<signo<<std::endl;

}
int main()
{
    signal(8,my_hander);
    int a=0;
    a=a/0;
    return a;
    
}

我的天呀!运行起来,就疯狂的刷屏,我明明只出现了一次除零错误,OS犯得着一直给我发送信号吗?操作系统怎么知道该进程发生除零错误了? 这里就要理解一下除零错误了。


 发生除零错误,程序默认终止。

cpu中存在诸多的寄存器, 我们可以大致区分为普通寄存器和状态寄存器。CPU不仅要负责运算,而且要负责正确的运算,而运算是否正确就要看状态寄存器。0可以看作一个接近零的数,一个数除以一个很小的数,结果一定很大,所以寄存器不能装下这个数据,就会发生溢出,溢出标志位就由零变为1,表示发生运算错误。

操作系统作为软硬件资源的管理者,知道发生错误后,就向发生错误的代码所属的进程发送信号,终止进程。


现在我们就可以理解为什么我只发生一次除零错误,但是操作系统会一直给我发送信号?

通过自定义行为,进程在收到信号时,不一定会退出。没有退出就有可能被再次被调度。cpu的内部寄存器只有一份,但是寄存器里的数据属于当前进程的上下文。这些数据除了操作系统,用户没有能力被修改。当进程被切换时,就有无数次寄存器被保存和恢复的过程。所以每日一次恢复的过程。就让OS识别到了CPU内部的状态寄存器为1。所以就引发操作系统向该进程发送信号终止进程。因为一直杀不死该进程,所以操作系统就会一直给该进程发送信号,恶性循化。

🌸软件条件产生信号

记得我们再学匿名管道时,当读端关闭时,写端写入管道中的数据就没有用处了,操作系统不允许浪费资源的情况出现,这时会向写进程发送13号信号(SIGPIPE)。由于这种信号是因为在软件层次上读端关闭引起的,所以叫做软件条件产生的信号

接下来,我们讲一讲alarm函数和SIGALRM信号。

这个函数的作用和sleep相似。作用为从执行该调用时起,经过设定的时间后,操作系统会向其发送14号信号(SIGALRM)来终止进程。

       #include <unistd.h>

       unsigned int alarm(unsigned int seconds);
  • 功能:用于进程闹钟,指定时间(以秒为单位)后,向调用它的进程发送 SIGALRM 信号。
  • seconds参数:表示在多少秒后发送14号新号,如果为0,则任何未响应的 闹钟被取消。
  • 返回值:无符号整形,表示上次设置的闹钟还剩余的秒数。之前未设置闹钟,则返回0。

接下来,我们来感受一下alarm函数的使用方法

#include <iostream>
#include <unistd.h>
#include <cstdlib>
int main()
{
    int cnt = 0;
    alarm(1);
    while (1)
    {
        std::cout << cnt << std::endl;
        cnt++;
    }
    return 0;
}

这里有一点要注意: 代码执行到alarm语句时,进程不会马上终止,而是到设定时间到了之后再终止进程。就像昨天晚上我定了一个闹钟⏰,今天早上闹钟才响,一个道理。

但是,闹钟也可能会提前响,也许在闹钟响之前,系统突然给进程发了另外一种信号,导致进程终止。

在这里系统中,闹钟分为一次性闹钟和循环性闹钟,一次性闹钟只响一次,循环闹钟可以等时间段响起。

但是,我这电脑也太low了,跑这么慢。别着急,我该一下代码。

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include<signal.h>
int cnt = 0;
void hander(int signo)
{
    std::cout<<cnt<<std::endl;
    alarm(1);
}
int main()
{
    
    alarm(1);
    signal(14,hander);
    
    while (1)
    {
        
        cnt++;
    }
    return 0;
}

卧槽,提高了一万倍左右。其实这也体现出了内存访问外设的速度有多慢,在第一种方案中,由于频频访问外设,所以导致计数过慢。 


🍃 如何理解"闹钟"是软件条件呢?

操作系统中的所有进程都可以设定闹钟,所以操作系统中会有很多闹钟,所以这就需要操作系统对这些闹钟进行有效的管理。如何管理?先描述,再组织。

操作系统会为每一个闹钟设置对应的数据结构,然后用链表的形式讲这些结构体链接起来。如图:

操作系统会将未来这个闹钟醒来的时间戳导入结构体中。然后操作系统会周期性的检查这些闹钟是否到了时间(对比时间戳),如果时间到了,操作系统会向对应的进程发送SIGALRM信号,终止进程。由于这是由操作系统这个软件来检查是否到了预定时间,这一切都是建立在软件的基础上,而我们的条件体现在超时这个条件,所以这种产生信号的方式叫做软件条件。

 🚩由信号引起的进程退出时核心转储问题

如上图,通过查询man手册,我们发现不同信号默认的关闭进程的方式不同,有的是Core,有的是Term,这两个有什么区别呢? 

Term是退出时,对进程的上下文数据不做任何的保存。

Core是退出时,保存进程的上下文数据,方便进行调试。

看下面的代码,很明显出现了数组越界问题

#include<stdio.h>
int main()
{
    while(1)
    {
        int arr[10];
        arr[100000]=0;
    }
}

果然给我们报了数组越界的错误。

我们查看一下在系统中支持的各种信息

其中,我们发现:默认的core file是关闭的,但是我们可以通过指令进行打开。

然后我们再运行一下代码:

对比一下;发现有了些许变化。在路径下多了一个文件,该文件中保存的是进程的上下文数据。

 什么是核心转储呢?

当进程出现异常的时刻,我们将进程对应的时刻,在内存中的有效数据转储到磁盘上,这就是核心转储。核心转储的存在是为了方便调试。如何支持?

如此,就大大利于我们追踪错误。

 到这里,本篇博客暂时结束了。感谢观看。

声明:本博主的文章会同步到腾讯云社区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/730306.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024.6最最新版MySQL数据库安装(保姆级教程,不懂你捶我)

1.MySQL数据库下载 1.打开MySQL官网 如下页面 2.下翻网页到最底部,找到Download,点击第一个MySQL Community Server 3.选择自己需要的版本以及系统的MySQL: 4.跳转页面会有一个登录/注册页面,这里我们不鸟他,直接开始下载 2.MySQL数据库安装 1.双击我们刚刚下载的安装包 2.勾…

音乐管理系统

摘 要 现如今&#xff0c;在信息快速发展的时代&#xff0c;互联网已经成了人们在日常生活中进行信息交流的重要平台。看起来&#xff0c;听歌只是一种消遣和消遣&#xff0c;其实&#xff0c;只要你选对了曲子&#xff0c;就会产生许多不同的作用。音乐能舒缓身心&#xff0c…

上海交大阿里巴巴推出虚拟试衣新里程碑式工作——AnyFit:任意场景、任意组合!

文章链接&#xff1a;https://arxiv.org/pdf/2405.18172 工程链接&#xff1a;https://colorful-liyu.github.io/anyfit-page/ 今天和大家一起学习的是一种名为AnyFit的新型虚拟试穿系统&#xff0c;旨在解决现有技术在处理不同场景和服饰组合时出现的衣物风格不匹配和质量下…

量化系统--开源强大的qmt交易系统,提供源代码

经过的3天终于写完了qmt_trader的文档了开源直接使用我开源了全部源代码 文档地址 https://gitee.com/li-xingguo11111/qmt_trader 源代码from qmt_trader.qmt_trader import qmt_trader from qmt_trader.xtquant.xttype import StockAccountfrom qmt_trader.xtquant import …

opencascade AIS_InteractiveContext源码学习2

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

C语言练习03-字符串

一、遍历字符 #include<stdio.h>int main() {char str[100];//录入字符串printf("请输入一串字符&#xff1a;\n");scanf("%s",str);//遍历字符串char* p str;while(1){char c *p;if(c \0){//如果遍历到结束标记&#xff0c;则循环结束break;}//…

雷池社区版自动SSL

正常安装雷池&#xff0c;并配置站点&#xff0c;暂时不配置ssl 不使用雷池自带的证书申请。 安装&#xff08;acme.sh&#xff09;&#xff0c;使用域名验证方式生成证书 先安装git yum install git 或者 apt-get install git 安装完成后使用 git clone https://gitee.com/n…

应用案例 | 冷藏集装箱基于云的WiFi无线温度监测系统COMET Cloud

一、集装箱的作用和分类 集装箱运输是国际贸易货物多式联运过程中的重要运输方式。由于集装箱运输具有标准化高、密封性好&#xff0c;破损率低、集约化、规模化、班轮化、成本低、质量好等优点&#xff0c;大大提高了货物运输的安全和效率。 集装箱种类很多&#xff0c;按所…

C++类基本常识

文章目录 一、类的默认方法二、类的成员变量初始化1 类的成员变量有三种初始化方法&#xff1a;2 成员变量初始化顺序3 const和static的初始化 三、C内存区域四、const和static 一、类的默认方法 C的类都会有8个默认方法 默认构造函数默认拷贝构造函数默认析构函数默认重载赋…

mongodb嵌套聚合

db.order.aggregate([{$match: {// 下单时间"createTime": {$gte: ISODate("2024-05-01T00:00:00Z"),$lte: ISODate("2024-05-31T23:59:59Z")}// 商品名称,"goods.productName": /美国皓齿/,//订单状态 2:待发货 3:已发货 4:交易成功…

最火AI角色扮演流量已达谷歌搜索20%!每秒处理2万推理请求,Transformer作者公开优化秘诀

卡奥斯智能交互引擎是卡奥斯基于海尔近40年工业生产经验积累和卡奥斯7年工业互联网平台建设的最佳实践&#xff0c;基于大语言模型和RAG技术&#xff0c;集合海量工业领域生态资源方优质产品和知识服务&#xff0c;旨在通过智能搜索、连续交互&#xff0c;实时生成个性化的内容…

springboot3 连接 oceanbase + logproxy数据同步到redis

我这用的是 社区版的 单机&#xff0c; rocky liunx 安装oceanbase 注意事项&#xff1a; logproxy 是 CDC 模式 &#xff0c; springboot 可以直接订阅 canal 是 binlog模式&#xff0c; canal 订阅 logproxy&#xff0c; springboot 订阅 canal logproxy 也可以转 bi…

何在 Vue3 中使用 Cytoscape 创建交互式网络图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 Vue.js 中加载 Cytoscape.js 的技术实现 应用场景 Cytoscape.js 是一个用于创建交互式网络的可视化库。在生物信息学、社会网络分析和药物发现等领域中得到了广泛应用。 基本功能 本代码片段演示了如何在 V…

python测试工程师 之 unittest框架总结

unittest 学习目标unittest 框架的基本使⽤⽅法(组成)断⾔的使⽤ (让程序⾃动的判断预期结果和实际结果是否相符)参数化(多个测试数据, 测试代码写⼀份 传参)⽣成测试报告 复习pythonunittest 框架的介绍核⼼要素(组成)1. TestCase 测试⽤例, 这个测试⽤例是 unittest 的组成部…

LeetCode题练习与总结:克隆图--133

一、题目描述 给你无向 连通 图中一个节点的引用&#xff0c;请你返回该图的 深拷贝&#xff08;克隆&#xff09;。 图中的每个节点都包含它的值 val&#xff08;int&#xff09; 和其邻居的列表&#xff08;list[Node]&#xff09;。 class Node {public int val;public L…

【案例分析:基于 Python 的几种神经网络构建 一维的和二维的全介质和金属SPR 材料的光谱预测与逆向设计】

案例分析&#xff1a;传播相位与几何相位超构单元仿真与器件库提取与二维超构透镜设计与传播光场仿真 案例分析&#xff1a; 片上的超构单元仿真与光学参数提取 案例分析&#xff1a;基于粒子群方法的耦合器设计 案例分析&#xff1a;基于 Python 的几种神经网络构建 一维的和二…

【大分享06】收、治、用、安“四管齐下”, 做好多业务系统电子文件归档与管理

关注我们 - 数字罗塞塔计划 - 本篇是参加由电子文件管理推进联盟联合数字罗塞塔计划发起的“大分享”活动投稿文章&#xff0c;来自上海泰宇信息技术股份有限公司&#xff0c;作者&#xff1a;金靓。 随着数字政府建设的深入推进以及“互联网政务服务”的快速发展&#xff0c…

canal 服务安装

简介&#xff1a;Canal 是阿里巴巴开源的一个基于 MySQL 数据库增量日志解析的中间件&#xff0c;用于提供准实时的数据同步功能。 准备工作 1.修改配置文件 ,需要先开启 Binlog 写入功能&#xff0c;配置 binlog-format 为 ROW 模式&#xff0c;my.cnf 中配置如下&#xf…

sqlmap使用以及GUI安装

下载 GUI版地址: GitHub - honmashironeko/sqlmap-gui: 基于官版本 SQLMAP 进行人工汉化&#xff0c;并提供GUI界面及多个自动化脚本 GUI使用 可以点击.bat启动 如果点击.bat启动不了就在这里打开cmd,输入对应的.bat来启动 linux安装 地址:sqlmap: automatic SQL injection…

最新评测:2024年13款国内外缺陷跟踪管理工具(含免费/开源)

文章中介横向对比了11款主流缺陷管理工具&#xff1a;1. PingCode&#xff1b;2. Worktile&#xff1b;3. Jira&#xff1b;4. ZenTao&#xff08;禅道&#xff09;&#xff1b;5. Bugzilla&#xff1b;6. Redmine&#xff1b;7. Tapd&#xff1b;8. MantisBT&#xff1b;9. Tr…