首页 > 系统相关 >【Linux-进程信号】详谈信号捕捉

【Linux-进程信号】详谈信号捕捉

时间:2024-11-19 11:14:12浏览次数:3  
标签:调用 函数 信号处理 堆栈 内核 信号 Linux 详谈

详谈信号捕捉

内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数(调用signal函数自定义处理函数),在信号递达时就调用这个函数,这称为信号捕捉。由于信号处理函数的代码是在用户空间的,处理过程比较复杂

5f5d339dd5e54b409de4d18f08a2971c.png

典型的操作系统中信号处理的机制

1.进入内核态:当程序因为中断、异常或系统调用陷入内核态时,系统会检查进程的信号屏蔽位图(block)和待处理信号位图(pending)。这些位图用于管理进程当前需要阻塞的信号和待处理的信号。

2.信号处理:如果待处理的信号存在且没有被阻塞,内核会准备处理这个信号。如果处理方式不是默认动作(SIG_DFL)或忽略信号(SIG_IGN),则需要进入用户态执行相应的信号处理函数。

3.清除信号位:在执行信号处理函数时,内核会将待处理信号位图中该信号对应的位清零,表示信号已被处理。同时,为了防止在信号处理函数执行时再次收到相同的信号,内核会自动将该信号加入进程的信号屏蔽字(block),直到信号处理函数返回时,恢复原来的信号屏蔽字。

4.再次检查信号:当信号处理函数执行完成并返回内核态时,系统会再次检查是否还有待处理的信号。如果有,系统会继续处理其他待处理的信号。如果没有,则恢复用户态继续执行主流程。

 

这种机制的优点在于:

减少用户态与内核态之间的上下文切换:在内核态处理完毕后,通过顺带处理信号,减少了频繁的上下文切换开销。

信号的屏蔽与自动恢复:信号处理时会暂时屏蔽同种信号,避免信号处理函数嵌套调用,从而保证信号处理的安全性。

 

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

信号处理过程涉及以下三个堆栈:

1.主执行流调用堆栈:这是进程在用户态正常执行时的调用堆栈。当进程在用户态运行时,函数的调用和返回都在这个堆栈上进行。

2.内核调用堆栈:当进程因系统调用、中断或异常陷入内核态时,内核会使用内核态专门的堆栈来处理这些事件。内核堆栈与用户态的主执行流堆栈是独立的,防止了用户态的执行流干扰内核态的处理。

3.信号处理函数调用堆栈:当信号到达并且需要调用相应的处理函数时,操作系统会切换到一个专门用于信号处理的堆栈上(可以是主执行流的堆栈,也可以是专门分配的信号处理堆栈)。信号处理函数执行时并不是主执行流堆栈上的函数调用,内核也不会直接调用信号处理函数。

 

这些堆栈之间的关系:

信号处理函数的独立性:信号处理函数的调用是异步的,它可以在主执行流的任意时刻打断当前执行的用户态代码,执行完信号处理函数后再返回主执行流。因此,信号处理函数并不作为主执行流的一个普通函数调用,而是在独立的上下文中进行处理。

内核调用堆栈的独立性:当程序因为系统调用、中断或异常进入内核态时,内核使用内核堆栈处理应的任务。内核的任务完成后,可能会处理信号的挂起状态,但处理信号的函数仍然是在用户态堆栈中执行。内核本身不会调用用户态的信号处理函数。

主执行流和信号处理的关系:当信号到来时,信号处理函数可能打断主执行流的执行,但它并不从主执行流堆栈中弹出当前正在执行的函数,而是在信号处理完后返回继续执行主流代码。这是通过保存当前的上下文,并在处理完信号后恢复主流执行流的上下文来实现的。

因此,这三个堆栈之间的独立性确保了信号处理可以在任何时刻插入,且不会破坏主程序的调用栈或与内核态的操作混淆。这种设计也是为了提高系统的安全性和健壮性,避免不同堆栈之间的相互影响。

 

若某个信号的处理函数位于用户空间,且当前只有该信号需要递达,没有其他信号的前提下。一次信号处理过程中,总共会发生2次用户态到内核态的转化,2次内核态到用户态的转化

07a9dc8b271c4126bbdd5fecc0956ff9.png

 

★ps:如果收到2号信号的时候收到3号信号,在执行完2号信号后,会自动屏蔽2号信号,此时会处理3号信号。因此,在整个信号处理的过程中,可能会出现交替处理不同信号的情况,但是不会出现连续重复处理同一信号的情况

 

【验证】处理当前信号时,当前信号屏蔽字为1,即被阻塞

#include <iostream>
#include <signal.h>

void SigHandler(int signo)
{
	std::cout << "catch a signo : " << signo << std::endl;
	sigset_t set;
	sigemptyset(&set);
	sigprocmask(SIG_BLOCK, NULL, &set);
	if(sigismember(&set, 2)) std::cout << "the block bit is 1" << std::endl;
	else std::cout << "the block bit is 0" << std::endl;
}

int main()
{
	signal(2, SigHandler);
	while(1)
	{}
	return 0;
}

f3d69ae59f164af08aa7695de0cd53fa.png

下面代码验证,在执行处理函数前,该信号的pending位图标记为已经被置0

#include <iostream>
#include <signal.h>

void SigHandler(int signo)
{
	std::cout << "catch a signo : " << signo << std::endl;
	sigset_t set;
	sigemptyset(&set);
	sigpending(&set);
	if(sigismember(&set, signo)) std::cout << "sig's pending bit is 1" << std::endl;
	else std::cout << "sig's pending bit is 0" << std::endl;
}

int main()
{
	signal(2, SigHandler);
	while(1)
	{}
	return 0;
}

 6b674b3aabdd4630a94376739dc35ca4.png

sigaction

sigaction也是一种信号捕捉函数,但相比于signal函数,它具有更大的灵活性。sigaction() 函数是 POSIX 标准中定义的一个系统调用,用于设置或查询与指定信号关联的处理动作。这个函数允许您更改信号的处理方式,例如设置自定义的信号处理函数或使用信号默认处理函数。

394507b782714322b4643267237eff30.png

  • signum:指定要操作的信号编号。

  • act:指向 struct sigaction 结构的指针,用于指定新的信号处理动作。如果这个参数为 NULL,则表示不更改信号的处理动作。

  • oldact:指向 struct sigaction 结构的指针,用于接收旧的信号处理动作。如果这个参数为 NULL,则表示不获取旧的信号处理动作。

 

struct sigaction 结构定义如下:

f0459f02386e49c486955286c7d90445.png

 

  • sa_handler:指向信号处理函数的指针。如果设置了这个字段,当信号发生时,会调用这个函数来处理信号。

  • sa_sigaction:指向高级信号处理函数的指针。这个函数可以接收更多的信息,如信号的详细信息(siginfo_t 结构)和额外的参数(void *)。

  • sa_mask:指定信号屏蔽字,用于指定哪些信号在信号处理函数执行期间应该被阻塞。

  • sa_flags:指定与信号处理相关的标志位

sa_flags描述
0使用标准的、未经修改的信号处理行为
SA_NODEFER表示在信号处理函数执行期间不阻塞该信号
SA_RESETHAND表示处理函数执行完毕后,信号的处理方式恢复为默认处理方式
SA_SIGINFO表示使用 sa_sigaction 而不是 sa_handler

返回值:如果成功,sigaction() 函数返回 0;如果失败,它返回 -1,并设置 errno 以指示错误。

【示例】用sigaction演示当正在使用一个信号的时候,OS会自动屏蔽这个信号

当前如果正在对n号信号进行处理,默认n号信号会被自动屏蔽

对n号信号处理完成的时候,会自动解除对n号信号的屏蔽

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void Print(sigset_t &pending)
{
    for(int sig = 31; sig > 0; sig--)
    {
        if(sigismember(&pending, sig))
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << std::endl;
}

void handler(int signum)
{
    std::cout << "get a sig: " << signum << std::endl;
    while(true)
    {
        sigset_t pending;
        sigpending(&pending);

        Print(pending);

        sleep(1);
        // sleep(30);
        //break;
    }
    // exit(1);
}

int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask); // 如果你还想处理2号(OS对2号自动屏蔽),同时对其他信号也进行屏蔽
    sigaddset(&act.sa_mask, 3);
    act.sa_flags = 0;

    for(int i = 0; i <= 31; i++)
        sigaction(i, &act, &oact);

    while(true)
    {
        std::cout << "I am a process, pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

第一次捕捉到2号信号会进入捕捉函数,第二次捕捉到时候会进入未决状态

4d5c8b76a6f34887bb17c7ca2787044b.png

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生.,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章不详细解释这两个字段,有兴趣的同学可以在了解一下。 

 

标签:调用,函数,信号处理,堆栈,内核,信号,Linux,详谈
From: https://blog.csdn.net/2202_75331338/article/details/143831061

相关文章

  • Linux
    JupyterLab一个交互式的编程和教学环境,同时内置终端,可以很方便地查看文件,执行代码等AutoDL就是用的这个终端(Terminal,最轻量级)主要进行命令行操作,或者运行脚本和简单程序远程连接VSCode什么是SSH?SSH全称SecureShell,中文翻译为安全外壳。它是一种网络安全协议。通过加密和......
  • 【迅为】瑞芯微itop-RK3568开发板Linux+HAL启动测试
    迅为iTOP-RK3568开发板AMP AMPSDK支持Rockchip平台异构多系统AMP(非对称多核架构)的开发软件包,支持Linux(Kernel)、Standalone(Hal)、RTOS(RT-Thread)组合AMP构建形式。可以满足一些特定行业应用,如电力物联网、电网继电保护、电力系统安全控制、工业自动化的需求。     ......
  • Python实现文件夹上传到Linux服务器(带日志功能)
    功能概述实现一个FileUploader类,用于将本地文件夹及其子文件上传到Linux服务器的指定目录,并支持:冲突处理策略:覆盖:直接覆盖远程文件。跳过:跳过已存在的远程文件。重命名:避免冲突,为文件生成唯一名称。日志功能:记录上传成功的文件(upload_success.log)。记录上传失败......
  • 基于matlab的语音信号去噪的App Designer 设计
    文章目录前言1.外观设计1.1组件布局1.2修改组件标签2.代码部分设计2.1成员变量2.1.1组件成员变量2.1.2自定义成员变量2.2回调函数2.2.1导入音频2.2.2加噪处理2.2.3播放音频2.3滤波器设计2.4信噪比3.完整版功能展示结语前言在实际应用中,语音信号往往会受到......
  • Linux 链式与层级中断控制器讲解:原理与驱动开发
    往期内容本专栏往期内容,interrtupr子系统:深入解析Linux内核中断管理:从IRQ描述符到irqdomain的设计与实现Linux内核中IRQDomain的结构、操作及映射机制详解中断描述符irq_desc成员详解Linux内核中断描述符(irq_desc)的初始化与动态分配机制详解中断的硬件框架GIC介绍......
  • AlmaLinux 9.5 正式版发布 - RHEL 二进制兼容免费发行版
    AlmaLinux9.5正式版发布-RHEL二进制兼容免费发行版由社区提供的免费Linux操作系统,RHEL二进制兼容发行版请访问原文链接:https://sysin.org/blog/almalinux-9/查看最新版。原创作品,转载请保留出处。作者主页:sysin.org由社区提供的免费Linux操作系统一个开源、社区......
  • 安装宝塔解析后网站打不开linux
    当你在Linux上安装了宝塔面板并配置了解析,但网站仍然无法访问时,可以按照以下步骤进行排查和解决:检查域名解析是否生效确认域名已经正确解析到服务器的IP地址。可以通过 ping 命令或在线的DNS查询工具来验证。检查防火墙设置确保服务器的防火墙允许HTTP(80端口)和H......
  • 如何理解OpenWRT上的SquashFS 和 OverlayFS 机制,它们何区别?以OpenFi 5Pro为例讲解这两
    如何理解OpenWRT上的SquashFS和OverlayFS机制,它们何区别?以OpenFi5Pro为例讲解这两者之间的关系,以及Linux的squashfs固件类型1.SquashFS和OverlayFS机制2.文件修改和保存原理文件修改时的行为:3.分区布局示例4.检查你的设备`overlay`使用情况5.`overlay`分......
  • linux之find
    find按照文件名称查找-name""查找文件、目录-typef/-typed按照文件、目录大小-size按照文件、目录时间-atime-ctime文件类型-typefdlc(dev/nulldev/zero)b(磁盘、光盘)1.查找大文件[root@m01var]#find/-typef-size+1G2>/dev/null/proc/k......
  • linux 启动数据库和Teamcenter
    1.启动数据库:[infodba@tc24vm~]$exportORACLE_HOME=usr/apps/LINUX.X64_193000_db_home[infodba@tc24vm~]$exportORACLE_HOME=/usr/apps/LINUX.X64_193000_db_home[infodba@tc24vm~]$exportORACLE_SID=tc[infodba@tc24vm~]$cd$ORALCE_HOME[infodba@tc24vm~]$cd$OR......