Linux进程信号处理:深入理解与应用(3)

 

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:it's 6pm but I miss u already.—bbbluelee

                                                                0:01━━━━━━️💟──────── 3:18
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

前言

信号的保存

首先了解几个概念

什么是信号递达(Delivery)?

什么是信号未决(Pending)?

什么是阻塞 (Block )?

信号在内核中的表示

sigset

sigprocmask

信号的处理

信号的捕捉

使用 sigaction() 捕捉信号

信号处理时机


前言

        本文书接上回Linux进程信号处理:深入理解与应用(2),本文是Linux进程信号处理的最后一篇文章。主要介绍信号的保存以及信号的处理。其中较为重要的是sigset以及基于它的函数调用。

信号的保存

首先了解几个概念

        实际执行信号的处理动作称为信号递达(Delivery)

        信号从产生到递达之间的状态,称为信号未决(Pending)

        进程可以选择阻塞 (Block )某个信号

        被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

        注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

什么是信号递达(Delivery)?

        实际执行信号的处理动作称为信号递达(Delivery),我们在前面的文章Linux进程信号处理:深入理解与应用(1)中有提到可以通过signal替换信号,实际上就是替换的处理动作。其中提到了有三种处理方式:

    • SIG_IGN:表示忽略该信号,即信号发生时不采取任何行动。
    • SIG_DFL:表示采用系统默认的处理方式,通常是终止进程或忽略该信号。
    • 自定义处理函数:如果你希望在信号发生时执行特定的操作,可以设置一个自定义的处理函数。这个函数通常需要接受一个整数参数(信号编号)并且返回void。

        信号的递达就是上述的三种处理,信号的递达就是处理信号!我们在认识signal的时候,对于函数原型的认识如下:

typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

        可以看到我们传递的SIG_IGN、SIG_DFL和自定义处理函数都是一个函数指针,自定义处理函数就是函数,那么对于SIG_IGN和SIG_DFL该怎么理解呢?实际上的定义如下:

#define SIG_DFL ((sighandler_t) 0)
#define SIG_IGN ((sighandler_t) 1)

        他们实际上就是对于0和1的强转,系统可以根据该特征来判断是默认还是自定义还是忽略,因为自定义的地址肯定不会是0和1。

什么是信号未决(Pending)?

        信号从产生到递达之间的状态,称为信号未决(Pending)。信号产生的时候,当前的进程可能在做更重要的事情,信号无法被立即处理,他需要在合适的时候的处理,所以需要有对信号进行保存的能力,这就是前面的文章Linux进程信号处理:深入理解与应用(1)中提到的系统可以保存信号!保持信号则是通过位图来进行保存对应的信号!

什么是阻塞 (Block )?

        信号的阻塞(Block)是指将某个信号设置为不可递达状态,即暂时忽略该信号。当一个信号被阻塞时,即使它被发送给进程,也不会立即递达,而是保持未决状态,直到信号解除阻塞。说大白话:阻塞实际上就是未决之后,暂时不递达,直到解决对信号的阻塞!

信号在内核中的表示

        如下,根据我们上面的了解,我们可以对以下这张图进行了解,每一个进程都会有这三张表,用于保存信号,这也是前面提到,为什么改变了其中的handler,其他的进程不会改变,因为handler的改变只是对于对应的进程而言的。我们通过横向的看着三张表,就可以很好的理解信号的保存以及后续的处理过程:

        每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

        SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

        SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

 

sigset

    sigset_t是Linux系统中用于表示信号集的数据类型。它用于存储一组信号,以便在进程之间进行信号的发送和接收操作。

   sigset_t实际上就是一个由操作系统提供的位图结构,实际上就是结构体里套了一个数组:

typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;
typedef __sigset_t sigset_t;

        以下是一些常用的函数和操作与sigset_t相关的:

  1. sigemptyset(sigset_t *set): 初始化一个空的信号集,将所有位设置为0。
  2. sigfillset(sigset_t *set): 初始化一个包含所有信号的信号集,将所有位设置为1。
  3. sigaddset(sigset_t *set, int signum): 向信号集中添加一个信号。
  4. sigdelset(sigset_t *set, int signum): 从信号集中删除一个信号。
  5. sigismember(const sigset_t *set, int signum): 检查信号是否在信号集中。
  6. sigprocmask(int how, const sigset_t *set, sigset_t *oldset): 修改进程的信号掩码,即阻塞或解除阻塞信号集中的信号。
  7. sigpending(sigset_t *set): 获取当前进程未决的信号集。
  8. sigsuspend(const sigset_t *set): 暂停进程执行,直到收到信号集中的一个信号为止。
  9. sigwait(const sigset_t *set, int *sig): 等待信号集中的一个信号,并返回接收到的信号编号。

        下面他们通过sigprocmask和sigpending来理解这些操作:

sigprocmask

    sigprocmask()是一个用于修改进程信号掩码的函数,它允许进程阻止或解除阻止某些特定信号。

        函数原型:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

        参数说明:

  • how:指定要执行的操作,可以是以下三个值之一:
    • SIG_BLOCK:将set中的信号加入到进程的信号掩码中,即阻塞这些信号。
    • SIG_UNBLOCK:从进程的信号掩码中移除set中的信号,即解除阻塞这些信号。
    • SIG_SETMASK:直接将set设置为进程的信号掩码,替换原有的信号掩码。

  • set:指向一个sigset_t类型的信号集,包含了要添加到或从进程信号掩码中移除的信号。根据how的值不同,该参数的含义也不同。
  • oldset:指向一个sigset_t类型的信号集,用于保存修改前的信号掩码。如果不需要保存旧的信号掩码,可以将此参数设置为NULL

        返回值:

  • 成功时返回0,失败时返回-1,并设置errno为相应的错误码。

        使用示例:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{std::cout << "获得一个:NO." << signo << "信号" << std::endl;}int main()
{//替换2号信号的处理方法signal(2, handler);std::cout << "i am running,but block~,pid:" << getpid() << std::endl;// 初始化信号集sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block,2);// 阻塞2号信号sigprocmask(SIG_BLOCK,&block,&oblock);int time=5;while(time--){sleep(1);}std::cout<<std::endl;std::cout << "i am running and do not block~,pid:" << getpid() << std::endl;//恢复原来的信号掩码//sigprocmask(SIG_UNBLOCK,&block,&oblock);sigprocmask(SIG_SETMASK,&oblock,&block);return 0;
}

        注意事项:

  • 在使用sigprocmask()函数时,需要注意避免产生死锁。例如,如果在信号处理函数中调用了sigprocmask(),则可能导致死锁。
  • 在多线程环境中,每个线程都有自己的信号掩码。因此,当在一个线程中调用sigprocmask()时,只会影响该线程的信号掩码。
  • sigprocmask()函数会返回之前的信号掩码,以便在后续操作中恢复。如果不需要在后续操作中恢复信号掩码,可以将oldset参数设置为NULL


sigpending

    sigpending()函数用于获取当前进程未决(pending)的信号集。

        函数原型:

#include <signal.h>
int sigpending(sigset_t *set);

        参数说明:

  • set:指向一个sigset_t类型的信号集,用于存储获取到的未决信号。

        返回值:

  • 成功时返回0,失败时返回-1,并设置errno为相应的错误码。

        使用示例:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>void handler(int signo)
{std::cout << "获得一个:NO." << signo << "信号" << std::endl;
}int main()
{// 替换2号信号的处理方法signal(2, handler);std::cout << "i am running,but block~,pid:" << getpid() << std::endl;// 初始化信号集sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block, 2);// 阻塞2号信号sigprocmask(SIG_BLOCK, &block, &oblock);sigset_t pend;while (true){//获取当前进程未决信号集sigpending(&pend);//打印直观显示for (int i = 31; i > 0; i--){if (sigismember(&pend, i)){std::cout << "1";}else{std::cout << "0";}}std::cout << std::endl;sleep(1);}return 0;
}

        注意事项:

  • sigpending()函数只能获取未决信号集,不能获取阻塞或忽略的信号。
  • 在多线程环境中,每个线程都有自己的未决信号集。因此,当在一个线程中调用sigpending()时,只会获取该线程的未决信号集。

信号的处理

信号的捕捉

        信号捕捉是操作系统提供的一种机制,允许进程指定特定函数(称为信号处理程序或信号处理器)来响应特定信号的接收。当一个进程收到一个它可以捕捉的信号时,操作系统会暂停进程当前的执行流程,转而调用与该信号关联的处理程序。一旦信号处理程序执行完毕,进程会继续执行被信号打断的操作。

        在Linux系统编程中,信号捕捉通常通过以下两个系统调用实现:

  1. signal() - 这是一个较老的系统调用,用于设置信号处理程序。
  2. sigaction() - 这是一个更现代、功能更丰富的系统调用,用于设置信号处理程序。

使用 sigaction() 捕捉信号

    sigaction() 提供了比 signal() 更多的控制,包括可以设置信号屏蔽、指定信号处理选项等。其原型如下:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    signum 参数是你想要捕捉的信号的编号。act 参数是一个指向 sigaction 结构体的指针,其中包含了新的信号处理程序、信号集和其他标志。oldact 参数是一个指向 sigaction 结构体的指针,用于接收旧的信号处理程序信息,如果不需要可以设置为 NULL

    sigaction 结构体定义如下:

struct sigaction {void (*sa_handler)(int);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
  • sa_handler: 信号处理程序。
  • sa_mask: 信号屏蔽字,指定在处理信号时阻塞哪些其他信号。
  • sa_flags: 影响信号处理的一组标志。
  • sa_restorer: 不常用,通常设为 NULL

        信号处理程序的编写

        编写信号处理程序时,需要注意以下几点:

  1. 信号处理程序应尽可能短小,避免长时间阻塞。
  2. 信号处理程序不应调用非异步信号安全的函数。
  3. 不要在信号处理程序中进行复杂的逻辑操作,特别是涉及数据结构和锁定的操作。
  4. 尽量避免在信号处理程序中使用全局变量,因为信号处理程序可能会在任何时间运行,从而可能导致竞态条件。
  5. 如果需要修改全局状态,可以使用原子操作或者锁来保护共享数据。

        示例代码

        下面是一个简单的使用 sigaction 捕捉 SIGINT 信号并设置信号处理程序的示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void int_handler(int signum) {printf("Interrupt signal (%d) received.
", signum);
}int main() {struct sigaction sa;sa.sa_handler = int_handler;sa.sa_flags = 0;sigemptyset(&sa.sa_mask);if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");exit(EXIT_FAILURE);}while (1) {pause(); // 等待信号到来}return 0;
}

        在上面的代码中,我们创建了一个信号处理程序 int_handler 来处理 SIGINT 信号。我们使用 sigaction 系统调用来注册这个处理程序,并设置 sa_flags 为 0,表示使用默认的信号处理选项。我们还清空了 sa_mask,表示在处理信号时不阻塞任何其他信号。最后,我们使用无限循环和 pause 函数使进程等待信号的到来。

 

信号处理时机

        前面我们提到,信号会在合适的时候被处理,那么是什么时候呢?答案是在进程从内核态回到用户太的到时候进行信号的检测和信号的处理。这里牵扯到了用户态和内核态的互相切换,就不细展开了。我们只需要知道用户态是一种受控的状态,能够访问的资源是有限的。内核态是一种操作系统的工作状态,能够访问大部分的资源。系统调用的背后就包含了身份的变化。下图中3和5的交点即为信号处理的时候:

        对上图的解释:如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

 


                    感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

 

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

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

相关文章

CSDN2024年我的创作纪念日1024天|不忘初心|努力上进|积极向前

CSDN2024年我的创作纪念日1024天| 学习成长机遇&#xff1a;学习成长收获&#xff1a;2023年度总结数据&#xff1a;2024新领域的探索&#xff1a;日常和自己的感慨&#xff1a;2024憧憬和规划&#xff1a;创作纪念日总结&#xff1a; 学习成长机遇&#xff1a; 大家好&#x…

QT安装与helloworld

文章目录 QT安装与helloworld1.概念&#xff1a;2.安装QT3.配置环境变量4.创建项目5.运行效果 QT安装与helloworld 1.概念&#xff1a; Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可带来两大关键益处&#xff1a;提供首个专为支持跨平台开发而设计的…

Figma怎么设置中文,Figma有中文版吗?

不是很多人不想用 Figma&#xff0c;真是因为纯英文界面而头疼。这就是为什么有人会到处搜索 Figma 如何设置中文这样的问题。 然后我们直接快刀斩乱麻&#xff0c;Figma 没有中文版&#xff0c;但是我们还有其他的方法&#xff1a;例如&#xff0c; Figma 添加一个插件来解决…

2024年考PMP还有什么用?

PMP 是项目管理专业人士资格认证的意思&#xff0c;也是项目管理领域通用的证书&#xff0c; 做项目的基本都会去考。 要说 PMP 有啥作用&#xff1f; 个人感觉 PMP 证书更多的是跳槽、转行的敲门砖的作用&#xff0c;因为现在很多公司都要 PMP 证书&#xff0c;有了可以加分…

axios下载文件打开失败解决

在axios的then中创建了a标签下载文件完成之后&#xff0c;发现下载的文件打不开。 解决方法 设置responseType: blob 注意&#xff01;&#xff01;&#xff01;这个是和headers同级别的&#xff0c;不是在headers里面的

2024最新msvcp140.dll丢失的解决方法,总结5种有效的方法

msvcp140.dll文件的丢失可能会引发一系列潜在问题并对计算机系统产生多方面的影响。首先&#xff0c;这个文件是Microsoft Visual C Redistributable Package的一部分&#xff0c;对于许多基于Windows的应用程序运行至关重要。一旦丢失&#xff0c;可能会导致部分软件无法正常启…

NLP_词的向量表示Word2Vec 和 Embedding

文章目录 词向量Word2Vec&#xff1a;CBOW模型和Skip-Gram模型通过nn.Embedding来实现词嵌入Word2Vec小结 词向量 下面这张图就形象地呈现了词向量的内涵:把词转化为向量&#xff0c;从而捕捉词与词之间的语义和句法关系&#xff0c;使得具有相似含义或相关性的词语在向量空间…

简单的TcpServer(英译中)

目录 一、TCP socket API 详解1.1 socket()1.2 bind()1.3 listen()1.4 accept()1.5 connect 二、TcpServer&#xff08;英译中&#xff09;2.1 TcpServer.hpp2.2 TcpClient.cc2.3 Task.hpp2.4 Thread.hpp2.5 ThreadPool.hpp2.6 makefile2.7 Main.cc2.8 log.hpp2.9 Init.hpp2.10…

ETL是什么,有哪些ETL工具?就业前景如何?

ETL是什么 ETL&#xff08;Extract-Transform-Load&#xff09;&#xff0c;用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目标端的过程。ETL一词较常用在数据仓库&#xff0c;但其对象并不限于数据仓库。它可以自动化数据处理过程&#xff0c;减少…

AI交互数字人究竟适合什么领域使用?

AI交互数字人可以像真人一样拥有流畅的对话能力、连贯的肢体动作&#xff0c;并且在大模型的加持下&#xff0c;通过整合语音交互、自然语言理解、图像识别等AI交互数字人技术&#xff0c;数字人可以轻松为用户提供“面对面”的语音对话交互服务。 AI交互数字人&#xff0c;赋能…

java---查找算法(二分查找,插值查找,斐波那契[黄金分割查找] )-----详解 (ᕑᗢᓫ∗)˒

目录 一. 二分查找&#xff08;递归&#xff09;&#xff1a; 代码详解&#xff1a; 运行结果&#xff1a; 二分查找优化&#xff1a; 优化代码&#xff1a; 运行结果&#xff08;返回对应查找数字的下标集合&#xff09;&#xff1a; ​编辑 二分查找&#xff08;非递归…

Mac利用brew安装mysql并设置初始密码

前言 之前一直是在windows上开发后段程序&#xff0c;所以只在windows上装mysql。(我记得linux只需要适应yum之类的命令即可) 另外, linux的移步 linux安装mysql (详细步骤,初次初始化,sql小例子,可视化操作客户端推荐) 好家伙&#xff0c;我佛了&#xff0c;写完当天网上发…