• 【Linux信号专题】四、信号的捕捉


    在这里插入图片描述

    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。



    专栏传送门 :《Linux从小白到大神》 | 系统学习Linux开发、VIM/GCC/GDB/Make工具、Linux文件IO、进程管理、进程通信、多线程等,请关注专栏免费学习。


    信号捕捉主要是为了防止进程意外结束,并得到异常信息,捕捉信号后可以执行我们想要的动作。

    1. 信号捕捉函数

    1.1 signal函数

    • 包含头文件及函数原型
    #include 
    
    typedef void (*sighandler_t)(int);
    
    sighandler_t signal(int signum, sighandler_t handler);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 函数功能

      The behavior of signal() varies across Unix versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead. See Portability below. 注册一个信号捕捉函数,该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

    • 函数参数

      • signum:要捕捉的信号编号。
      • handler:捕捉函数,它是一个回调函数,当产生信号signum的时候,执行信号处理函数handler。
    • 函数返回值

      signal() returns the previous value of the signal handler, or SIG_ERR on error.

    1.2 sigaction函数

    • 包含头文件及函数原型
    #include 
    
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    
    • 1
    • 2
    • 3
    • 函数功能

      The sigaction() system call is used to change the action taken by a process on receipt of a specific signal. (See signal(7) for an overview of signals.) 注册捕捉函数,所谓的捕捉信号就是指,信号发生时执行什么动作。

    • 函数参数

      • signum:要捕捉的信号编号。

      • act:传入参数(const修饰,不可修改),新的处理方式。

      • oldact:传出参数,旧的处理方式。(用于恢复原始动作)

        • 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);
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 结构体解析
    结构体成员作用
    sa_handler是一个函数指针,指定信号捕捉后的处理函数名(即注册函数),也可赋值为SIG_IGN表忽略或SIG_DFL表执行默认动作。实际上是一个回调函数。
    sa_mask调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。实际上就是执行捕捉函数期间临时屏蔽的信号集。
    sa_flags通常设置为0,表示使用默认属性。(sa_flags设置为0时,使用sa_handler动作)
    sa_restorer该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
    sa_sigaction当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)
    • 函数返回值

      sigaction() returns 0 on success and -1 on error.

    使用示例: 使用sigaction捕获信号

    /************************************************************
      >File Name  : sigaction_test.c
      >Author     : Mindtechnist
      >Company    : Mindtechnist
      >Create Time: 2022年05月23日 星期一 14时20分42秒
    ************************************************************/
    #include 
    #include 
    #include 
    #include 
    
    void m_catch(int signalno)
    {
        printf("catch signal: %d\n", signalno);
    }
    
    int main(int argc, char* argv[])
    {
        /*注册信号捕捉函数*/
        struct sigaction mact;
        mact.sa_flags = 0;
        mact.sa_handler = m_catch;
        sigemptyset(&mact.sa_mask);
        sigaction(SIGALRM, &mact, NULL);
        
        /*设置周期性定时器*/
        struct itimerval mit = {{2, 0}, {3, 0}};
        setitimer(ITIMER_REAL, &mit, NULL);
        while(1)
        {
            printf("pid: %d\n", getpid());
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    2. 信号捕捉的特性和处理

    2.1 信号捕捉过程中有什么特性

    在信号捕捉的时候,有如下几个特性

    • 进程正常运行时,默认PCB中有一个信号屏蔽字假设为M,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,在捕捉到该信号以后,就要调用该信号捕捉函数,而该函数有可能执行很长时间,在这期间所要屏蔽的信号不由M来指定,而是用sa_mask(临时屏蔽信号集)来指定,等到调用完信号处理函数,再把信号屏蔽字恢复为M。

    • 某个信号sig捕捉函数执行期间,sig信号自动被屏蔽。

    • 阻塞的常规信号不支持排队,如果产生多次,只记录一次。实际上是这样的,未决信号集中使用某一位的0和1来记录信号是否被处理的,所以不管这个信号被发送了几次,未决信号集对应位也只能有一个1,后续也只能处理一次,它不会记录信号屏蔽期间总共发送了几次该信号,解除屏蔽后只会处理一次。后32个实时信号支持排队。

    示例分析:

    #include 
    #include 
    #include 
    
    void m_catch(int signo)
    {
        printf("m_catch begin... sig: %d\n", signo);
        sleep(7);
        printf("m_catch end...\n");
    }
    
    int main(int argc, char* argv[])
    {
        struct sigaction mact;
        mact.sa_flags = 0;
        sigemptyset(&mact.sa_mask);
        sigaddset(&mact.sa_mask, SIGQUIT); /*设置一个临时屏蔽信号
        在调用m_catch的时候,该信号会被屏蔽,m_catch执行完就会恢复原来的屏蔽信号,该信号解除屏蔽*/
        mact.sa_handler = m_catch;
        sigaction(SIGINT, &mact, NULL);
        while(1)
        {
            printf("pid: %d\n", getpid());
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    编译运行可以看到,虽然按了多次ctrl+c,发送了多个信号,但是信号处理函数执行了一次,也就说明阻塞的常规信号不支持排队,如果产生多次,只记录一次,且只处理一次。

    在这里插入图片描述

    如果我们在进入m_catch函数后按ctrl+\,程序不会退出,因为在m_catch函数内临时屏蔽了信号SIGQUIT,当执行m_catch函数后,才会处理SIGQUIT信号。可以看到下面的测试结果,在m_catch begin…打印后,也就是函数m_catch执行的时候按ctrl+\并不会退出程序,而m_catch end…打印后,也就是m_catch函数结束后,临时信号屏蔽字失效,恢复原来的信号屏蔽字,内核处理SIGQUIT,程序退出。

    在这里插入图片描述

    2.2 内核是如何捕捉信号的

    我们拿上面的程序为例,程序正常执行的时候,应该是一直在循环体内打印一句话,直到有信号产生

    while(1)
    {
    	printf("pid: %d\n", getpid());
    	sleep(1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当产生信号的时候,会进入内核态,此时内核会执行信号处理函数,如果有用户自定义信号处理函数会再次返回用户态去执行该函数。执行完信号处理函数后通过系统调用sigreturn再次陷入内核,然后返回用户态从被中断的地方继续执行主控制逻辑。如果上面的程序不是printf打印,而是read读,因为read会阻塞,处理完信号后,只有从下一次while循环的时候才能正常读数据。

    整体流程如下图所示

    在这里插入图片描述


    在这里插入图片描述
    在这里插入图片描述


  • 相关阅读:
    从原理到实战,详解XXE攻击
    共享开源技术,共建开放生态丨平凯星辰余梦杰出席 2022 世界互联网大会开源论坛圆桌对话
    openpose脚部标注问题梳理
    前端性能优化
    成都扬帆际海教育咨询有限公司—Tiktok电商入驻条件有哪些?
    小主机折腾记17
    在线音乐播放项目——BY音乐
    23种设计模式之工厂模式(不包括抽象工厂)
    【iMessage苹果相册推送苹果相册推】ProtocolBuffer拥有多项比XML更高档的序列化结构数据的特性
    Yandex的直接流量来源
  • 原文地址:https://blog.csdn.net/qq_43471489/article/details/126734325