Linux信号详解~

目录

前言

一、初识信号

二、信号的概念

三、信号的发送与捕捉

3.1 信号的发送

3.1.1 kill 命令

3.1.2 kill 函数

3.1.3 raise函数

3.1.4 abort函数

3.2 信号的捕捉

3.2.1 signal函数

3.2.2 sigaction函数

3.2.3 图示

四、信号的产生

4.1 硬件异常产生信号

4.2 软件条件产生信号

五、Core dump

5.1 core dump介绍

5.2 core dump作用

 六、阻塞信号

6.1 相关概念

6.2 在内核中的表示

6.3 sigprocmask

6.4 sigpending

七、可重入函数


前言

        在日常生活中,有许多方面都涉及到了信号的知识,例如信号弹、上下课铃声、红绿灯、闹钟等等。我们可以仔细想想由信号引发的几个问题:

  1. 我们是如何认识这些信号的?----有人教,随后记住了;
  2. 即使现在没有信号产生,我们也知道信号产生之后应该做什么
  3. 信号产生了,我们可能并不会立即处理这个信号,因为我们可能在做一些更重要的事情,由此可以得出信号产生后到信号处理之间其实会有一段时间窗口,而在这个时间窗口内,我们必须记住信号的到来。

        而在计算机当中,执行的主体就是进程,也就是说进程必须能够识别并处理信号,这是属于进程内置功能的一部分。同样的,一个进程由信号产生,到信号开始被处理,就一定会有时间窗口,而进程具有临时保存哪些信号已经发生了的能力。

一、初识信号

        在linux中运行某一个进程时,我们可以随时按下 ctrl+c 来杀掉前台进程,如下图一个死循环的输出我们用ctrl+c使进程退出:

它为什么能够杀掉前台进程呢?

        在Linux中的一次登录中,一个终端一般会配上一个bash,每一个登录只允许一个进程是前台进程,可以允许多个进程是后台进程。因此如果我们运行时在进程名后面加上&,则代表让它在后台运行,这个时候我们使用ctrl+c就没有用了,必须使用kill -9 + pid 号来杀掉进程。

        所以正常我们在运行一个进程时,在对bash进行输入命令就没有用了,因为这个进程运行时就成为了前台进程。

kill -l 可以查看操作系统拥有的信号:

        ctrl + c 的本质是被进程解释成为收到了2号信号SIGINT,需要注意的是,不同的操作系统可能对信号的编号有所不同,因此在跨平台开发时应当注意信号编号的兼容性。

        普通信号可以不立即处理,实时信号必须立即处理。

信号的处理方式:

        1. 忽略此信号。

        2. 执行该信号的默认处理动作。

        3. 自定义提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉 (Catch)一个信号。

二、信号的概念

        信号是 Linux 操作系统中用于进程间通信、处理异常等情况的一种机制。它是由操作系统向一个进程或者线程发送的一种异步通知,用于通知该进程或线程某种事件已经发生,需要做出相应的处理。

        信号的产生和我们自己的代码的运行是异步的,这意味着信号的产生与代码的执行没有直接的关联,信号属于软中断。

三、信号的发送与捕捉

3.1 信号的发送

在 Linux 中,进程可以通过向其他进程或自身发送信号的方式进行通信或处理异常情况。下面介绍几种常见的发送信号的方法。

3.1.1 kill 命令

kill [-signal] PID

其中,-signal 可选参数表示要发送的信号类型,如果省略该参数,则默认发送 SIGTERM 信号。PID 表示接收信号的进程 ID。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下命令:

kill -SIGINT 123

3.1.2 kill 函数

我们也可以使用系统调用的一些函数来发送信号:

其中,pid 表示接收信号的进程 ID,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向进程 ID 123 发送 SIGINT 信号,可以执行以下代码:

#include <signal.h>
#include <unistd.h>int main() {pid_t pid = 123;int sig = SIGINT;if (kill(pid, sig) == -1) {perror("kill");return 1;}return 0;
}

3.1.3 raise函数

raise 函数是一个简单的发送信号的函数,可以用来向当前进程发送信号。raise 函数的原型如下:

其中,sig 表示要发送的信号类型。如果函数调用成功,则返回 0,否则返回 -1 并设置 errno。

例如,要向当前进程发送 SIGTERM 信号,可以执行以下代码:

#include <signal.h>int main() {int sig = SIGTERM;if (raise(sig) == -1) {perror("raise");return 1;}return 0;
}

3.1.4 abort函数

abort函数的作用是引起一个正常函数的终止,它会给自己发送一个6号信号SIGABRT:

3.2 信号的捕捉

        在上文提到过,信号是可以被自定义捕捉的,下面介绍几种常见的捕捉信号的方法。

3.2.1 signal函数

signal 函数可以用来注册信号处理函数。signal 函数的原型如下:

其中,sig 表示要注册的信号类型handler 是一个函数指针,指向信号处理函数。signal 函数返回一个函数指针,指向之前注册的信号处理函数。如果注册信号处理函数失败,则返回 SIG_ERR。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{cout << "process get a SIGINT signal: " << signo <<endl;// exit(1);
}
int main()
{if (signal(SIGINT, sigcb) == SIG_ERR) {perror("signal");return 1;}while(true){cout<<"I am a process,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

当我们按下ctrl+c时,可以看到程序输出,最后我们使用ctrl+\退出程序,运行结果如下:

3.2.2 sigaction函数

在Linux中,sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回 -1。

其中,sig 表示要注册的信号类型act 是一个指向 struct sigaction 结构体的指针,表示新的信号处理函数和信号处理选项,oldact 是一个指向 struct sigaction 结构体的指针,用于获取之前注册的信号处理函数和信号处理选项。

struct sigaction 结构体的定义如下:

struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
  • 其中,sa_handler 字段指定信号处理函数的地址。如果设置为SIG_IGN,则表示忽略该信号。如果设置为SIG_DFL,则表示使用默认处理器,也可以自己设置需处理的函数逻辑。
  • sa_sigaction 字段指定一个信号处理器函数,这个函数包含三个参数:一个整数表示信号编号,一个指向siginfo_t结构体的指针,和一个指向void类型的指针。
  • sa_mask字段指定了在执行信号处理函数期间要阻塞哪些信号。
  • 后面两个字段本章不做详细解释。

例如,要注册 SIGINT 信号的处理函数,自定义处理函数名称为“sigcb”, 在sigcb当中完成打印触发本次事件的信号值,可以执行以下代码:

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
void sigcb(int signo)
{cout << "process get a SIGINT signal: " << signo <<endl;// exit(1);
}
int main()
{struct sigaction newact = {newact.sa_handler = sigcb};struct sigaction oldact;if (sigaction(SIGINT, &newact, &oldact) == -1) {perror("sigaction");return 1;}while(true){cout<<"I am a process,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

3.2.3 图示

当我们的进程从内核态返回到用户态的时候,进行信号的检测和处理:

四、信号的产生

4.1 硬件异常产生信号

        硬件异常产生信号指硬件发现进程的某种异常,而硬件是被操作系统管理。硬件会将异常通知给系统,系统就会向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

4.2 软件条件产生信号

        alarm函数相当于设置一个闹钟,告诉内核多少秒后,发送一个SIGALRM信号给当前进程,它的返回值是一个闹钟的剩余时间。

#include<iostream>
#include<signal.h>
#include <unistd.h>
using namespace std;
int main()
{int n=0;alarm(1);    //1秒后给进程发送SIGALRM信号while(true){cout<<n<<endl;n++;}return 0;
}

五、Core dump

5.1 core dump介绍

        当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。但默认云服务器上面的core功能是被关闭的,我们可以使用ulimit-a 查看, ulimit-c +字节数 设置core文件大小:

 

        子进程的status可以当作位图来看,因此我们可以手写一段代码来提取出Core Dump的值,原理就是通过使用(status >> 8) & 0xFF 获取子进程的退出码(高8位),通过使用(status & 0x7F)获取子进程的退出信号(低7位),最后使用((status >> 7) & 1) 表达式用于判断是否发生了核心转储(第8位即core dump标志)。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{pid_t id = fork();if(id == 0){//childint cnt = 500;while(cnt){cout << "i am a child process, pid: " << getpid() << "cnt: " << cnt << endl;sleep(1);cnt--;}exit(0);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) <<" core dump: " << ((status>>7)&1) << endl; }return 0;
}

对于8号信号,默认云服务器上面的core功能是被关闭的,可以看到它的core dump为0,使用ulimit -c 设置文件大小以后,再运行可以看到core dump标志位变为1并且生成了core.pid号的文件:

5.2 core dump作用

        假设我们写一段除0的错误代码:

#include <iostream>
using namespace std;int main()
{int a = 10;int b = 0;a /= b;cout << " a = " << a << endl;return 0;
}

 运行后可以结合gdb来进行事后调试:

 六、阻塞信号

6.1 相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block)某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

6.2 在内核中的表示

在task_struct结构中信号的构成实质是两个位图和一个数组,我们看的顺序也是横着从左往右看。

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
  • 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号被阻塞未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
  • 当一个信号被阻塞时,它仍然可以被发送到进程,并且会被添加到未决信号集合中。阻塞仅仅阻止信号的传递,即阻止信号的处理,但不阻止信号的接收。
  • 如果一个信号被设置为忽略,那么即使该信号被发送到进程,它也不会被添加到未决信号集合中,因为忽略的信号不会对进程产生任何影响。

6.3 sigprocmask

        函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集/BLOCK表),成功返回0,出错返回-1

how参数:

sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,它用来存储未决和阻塞标志。

set参数:指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号。如果 set 是一个空指针(NULL),则 how 参数没有效果指向一个信号集的指针,这个信号集指定了要阻塞或解除阻塞的信号,是一个输入型函数。如果 set 是一个空指针(NULL),则 how 参数没有效果。

oldset参数:如果不是空指针(NULL),则进程的当前信号屏蔽字会被存储在 oset 指向的位置。如果 oset 是空指针,则不返回当前的信号屏蔽字,是一个输出型参数。

6.4 sigpending

        sigpending函数读取当前进程的未决信号集,通过set参数传出,即把调用进程所对应的pending表带出来,调用成功则返回0,出错则返回-1

 例如,可以使用下段代码屏蔽2号信号并通过打印pending表来观察:

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void PrintPending(sigset_t &pending)
{for(int signo=31;signo>=1;signo--){//存在打印1,否则为0if(sigismember(&pending,signo)){cout<<"1";}else{cout<<"0";}}cout<<endl;
}
int main()
{//阻塞2号新号       --数据预备sigset_t bset,oset;//函数sigemptyset初始化set所指向的信号集使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。sigemptyset(&bset); sigemptyset(&oset); //函数sigaddset在该信号集中添加2号信号sigaddset(&bset,2); //----系统调用,将数据设置进内核----sigprocmask(SIG_SETMASK,&bset,&oset);//重复打印pending信息便于观察sigset_t pending;while(true){//获取int n = sigpending(&pending);if(n<0) continue;//打印PrintPending(pending);sleep(1);}return 0;
}

注意,9号和19号信号是无法被屏蔽的:

七、可重入函数

如果一个函数在被重复进入的情况下不会出错,则是可重入函数,否则是不可重入函数。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用雨云的虚拟主机建设兰空图床

安装前须知 因需要安装FileInfo拓展&#xff0c;因此你需要购买2048MB及以上运存的服务器。 确保MySQL版本高于或等于5.7 什么是兰空图床&#xff1f; Lsky Pro 是一个用于在线上传、管理图片的图床程序&#xff0c;中文名&#xff1a;兰空图床&#xff0c;你可以将它作为自己…

latex multirow学习

今天搞了一晚上的这个multirow&#xff0c;总算弄出来了几个比较好的例子&#xff0c;主要是这个multirow的语法我没看懂&#xff0c;这个逻辑我是没理解&#xff0c;就很尴尬&#xff0c;一改就报错&#xff0c;只能先弄几个例子&#xff0c;自己慢慢试 \documentclass{artic…

【计网·湖科大·思科】实验七 路由信息协议RIP、开放最短路径优先协议OSPF、边界网关协议BGP

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

【Spark系列6】如何做SQL查询优化和执行计划分析

Apache Spark SQL 使用 Catalyst 优化器来生成逻辑执行计划和物理执行计划。逻辑执行计划描述了逻辑上如何执行查询&#xff0c;而物理执行计划则是 Spark 实际执行的步骤。 一、查询优化 示例 1&#xff1a;过滤提前 未优化的查询 val salesData spark.read.parquet(&quo…

TCP TIME_WAIT 过多怎么处理

文章目录 1.什么是 TCP TIME_WAIT&#xff1f;2.为什么要 TIME_WAIT?3.TIME_WAIT 过多的影响4.解决办法4.1 调整短连接为长连接4.2 调整系统内核参数 5.小结参考文献 1.什么是 TCP TIME_WAIT&#xff1f; TCP 断开连接四次挥手过程中&#xff0c;主动断开连接的一方&#xff…

Windows Server 2019 Web服务器搭建

系列文章目录 目录 系列文章目录 文章目录 前言 二、配置服务器 1.实验环境搭建 1)实验服务器配置和客户端 2)实验环境 3)配置服务器IP 2.搭建服务器 在网站上右击新建网站搭建成功 文章目录 Windows Server 2003 Web服务器搭建Windows Server 2003 FTP服务器搭建Wi…

Linux下tar命令详解

tar #归档命令 格式 • Tar -参数 [args]..... 参数&#xff1a; 必选参数&#xff1a; 辅助参数&#xff1a; 额外参数&#xff1a; # 打包时排除某个文件 tar cf 文件名.tar --exclude路径/文件 路径 注&#xff1a;此处的路径前后需要保持保持一致&#xff0c;统一…

在本地电脑上打开服务器里面的localhost网址

远程连接服务器&#xff0c;启动了一个服务 显示访问地址为&#xff1a;http://127.0.0.1:7860 在本地浏览器将127.0.0.1改成服务器ip但是无法访问 解决办法&#xff1a; 1. ssh新建一个远程连接&#xff0c;将服务器的7860端口重定向到本机 ssh -L 18097:127.0.0.1:7860 us…

【Java 数据结构】排序

排序算法 1. 排序的概念及引用1.1 排序的概念1.2 常见的排序算法 2. 常见排序算法的实现2.1 插入排序2.1.1 直接插入排序2.1.2 希尔排序( 缩小增量排序 ) 2.2 选择排序2.2.1 直接选择排序2.2.2 堆排序 2.3 交换排序2.3.1冒泡排序2.3.2 快速排序2.3.3 快速排序非递归 2.4 归并排…

cmd常用命令

一、启动cmd的方式 用户启动&#xff0c;Win r 输入cmd&#xff0c;Enter管理员启动&#xff0c;Win r 输入cmd&#xff0c;Ctrl Shift Enter 二、修改窗口背景色和文字颜色 在打开的cmd窗口顶部空白区右击点击属性&#xff0c;进行设置即可 设置成这样不是美汁汁嘛 三、…

idea 中 tomcat 乱码问题修复

之前是修改 Tomcat 目录下 conf/logging.properties 的配置&#xff0c;将 UTF-8 修改为 GBK&#xff0c;现在发现不用这样修改了。只需要修改 IDEA 中 Tomcat 的配置就可以了。 修改IDEA中Tomcat的配置&#xff1a;添加-Dfile.encodingUTF-8 本文结束

【机器学习】科学库使用手册第2篇:机器学习任务和工作流程(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论人工智能相关知识。主要内容包括&#xff0c;了解机器学习定义以及应用场景&#xff0c;掌握机器学习基础环境的安装和使用&#xff0c;掌握利用常用的科学计算库对数据进行展示、分析&#xff0c;学会使用jupyter note…