【探索Linux】—— 强大的命令行工具 P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )

在这里插入图片描述

阅读导航

  • 引言
  • 一、信号捕捉
    • 1. 内核实现信号捕捉过程
    • 2. sigaction() 函数
      • (1)函数原型
      • (2)参数说明
      • (3)返回值
      • (4)函数使用
  • 二、可重入函数与不可重入函数
    • 1. 可重入函数条件
    • 2. 不可重入函数特征
  • 三、volatile关键字
  • 温馨提示

引言

在Linux系统中,信号是进程之间通信的重要方式之一。前面的两篇文章已经介绍了信号的产生和保存,本篇文章将进一步探讨信号的捕捉、处理以及使用sigaction()函数的方法。信号捕捉是指进程在接收到信号时采取的行动,而信号处理则是指对接收到的信号进行适当的处理逻辑。通过使用sigaction()函数,我们可以在程序中设置对特定信号的处理方式,从而实现更加灵活和精确的信号处理机制。本文将详细介绍信号捕捉的原理和使用方法,以及sigaction()函数的具体用法,帮助读者更好地理解和应用信号处理的相关知识。无论是开发基于Linux的应用程序,还是进行系统级编程,信号处理都是一个至关重要的主题,相信通过学习本文,您将对信号处理有更深入的了解。

一、信号捕捉

1. 内核实现信号捕捉过程

当信号的处理动作是用户自定义函数,并且在信号到达时调用该函数,这被称为捕捉信号。由于信号处理函数的代码运行在用户空间,处理过程可能会比较复杂,下面举一个例子来说明:

  1. 用户程序注册了处理函数sighandler来捕捉SIGINT信号。
  2. 当前正在执行main函数时,若发生中断或异常导致切换到内核态。
  3. 在中断处理完成后,在返回用户态执行main函数之前,检测到有SIGINT信号递达。
  4. 内核决定在返回用户态后,不恢复main函数的上下文继续执行,而是调用sighandler函数。sighandler函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
  5. sighandler函数执行完毕后,会自动执行特殊的系统调用sigreturn,再次进入内核态。
  6. 如果没有新的信号递达,此次返回用户态将会恢复main函数的上下文,并继续执行。
    在这里插入图片描述

2. sigaction() 函数

sigaction()函数是一个用于设置信号处理函数的系统调用。它允许用户程序指定对特定信号的处理方式,包括捕捉信号、忽略信号或使用默认处理方式。
在这里插入图片描述

(1)函数原型

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

(2)参数说明

  • signum:指定要设置处理方式的信号编号。
  • 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:用于指定信号处理函数的扩展形式,可以获取更多关于信号的信息,如发送信号的进程ID等。
  • sa_mask:指定一个信号屏蔽集,当进入信号处理函数时,会将这个屏蔽集与当前进程的信号屏蔽字进行按位或操作,从而阻塞其他指定的信号。
  • sa_flags:用于设置一些标志位,如SA_RESTART表示在信号处理函数返回后自动重启被中断的系统调用。
  • sa_restorer:已废弃的字段,现在不再使用。

(3)返回值

sigaction()函数返回值为0表示操作成功,-1表示出现了错误。如果发生错误,可以通过errno变量获取错误码。常见的错误码包括:

  • EINVAL:指定的信号编号无效或者提供的struct sigaction结构体无效。
  • ENOENT:指定的信号编号不存在。

(4)函数使用

使用sigaction()函数进行信号处理的一般步骤如下:

  1. 创建一个struct sigaction结构体对象,并根据需要设置其中的成员,特别是sa_handlersa_sigaction成员来指定信号处理函数。
  2. 调用sigaction()函数,传入要设置处理方式的信号编号、指向上述结构体对象的指针以及可选的保存之前处理方式的结构体指针。
  3. 根据sigaction()函数的返回值判断操作是否成功。

下面是一个简单的C语言示例,演示如何使用sigaction()函数来捕获和处理SIGINT信号(即Ctrl + C):

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int signo) {printf("Caught SIGINT, exiting...\n");exit(1);
}int main() {struct sigaction sa;// 设置信号处理函数为sigint_handlersa.sa_handler = sigint_handler;// 清空sa_mask,即不阻塞任何其他信号sigemptyset(&sa.sa_mask);// 设置一些标志位,这里使用默认值0sa.sa_flags = 0;// 注册对SIGINT信号的处理方式if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");return 1;}printf("Press Ctrl+C to send a SIGINT...\n");// 进入一个无限循环,等待信号while (1) {sleep(1);}return 0;
}

在这个示例中,首先定义了一个名为sigint_handler的函数,用于处理SIGINT信号。然后在main函数中,创建了一个struct sigaction对象sa,并设置了其中的成员,包括sa_handler指向sigint_handler函数地址,sa_mask为空,sa_flags为0。接着调用sigaction()函数注册对SIGINT信号的处理方式。最后进入一个无限循环,等待信号的到来。

当用户按下Ctrl+C时,会发送SIGINT信号,程序会捕获该信号并调用sigint_handler函数进行处理,打印一条消息并退出程序。这样就实现了对SIGINT信号的自定义处理。

二、可重入函数与不可重入函数

在这里插入图片描述
main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数。sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是 main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入insert函数访问一个全局链表有可能因为重入而造成错乱。像这样的函数称为不可重入函数,反之如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?

1. 可重入函数条件

可重入函数必须满足以下条件

  1. 不使用全局变量或静态变量,或者只读取这些变量的值。

  2. 不修改非本地的内存区域,或者仅修改线程本地的内存区域。

  3. 不调用可能导致线程挂起或阻塞的函数,如sleep()wait()等。

一些示例可重入函数包括:memcpy()strlen()sprintf()strtok_r()等。

🚨注意为了确保函数的可重入性,可以使用线程安全的函数或使用锁或其他同步机制来保护共享资源。同时,应该避免在函数中使用全局变量和静态变量,并尽可能将数据和状态存储在本地变量中

2. 不可重入函数特征

不可重入函数通常具有以下特征

  1. 使用全局变量或静态变量,或者修改非本地的内存区域。

  2. 调用可能导致线程挂起或阻塞的函数。

  3. 依赖于某些外部状态或资源。

一些示例不可重入函数包括:printf()scanf()malloc()signal()等。

🚨注意在信号处理程序中只能使用可重入函数。由于信号处理程序执行时可能会中断主程序的正常执行流程,因此不能使用不可重入函数,否则可能会导致意外行为或安全问题

三、volatile关键字

在C和C++中,volatile用于告诉编译器不要对该变量进行优化,以确保每次访问该变量都从内存中读取或写入。

volatile关键字通常用于以下两种情况:

  1. 并发访问:当多个线程或多个任务并发地访问同一个变量时,为了避免出现数据竞争和意外的优化行为,可以使用volatile关键字修饰变量。这样可以确保每次访问都从内存中读取或写入,而不是依赖于编译器的优化策略。

  2. 中断处理:在嵌入式系统或操作系统开发中,中断处理程序通常需要访问硬件寄存器或共享变量。由于中断可能在任何时间发生,编译器可能会对变量进行优化,导致不正确的结果。通过使用volatile关键字修饰这些变量,可以确保每次访问都是实时的,不受编译器的优化干扰。

  3. 在信号处理程序中,volatile关键字可以用于告诉编译器不要对某些变量进行优化。由于信号处理程序执行时可能会中断主程序的正常执行流程,因此编译器可能会错误地优化某些变量或表达式,导致程序行为异常。

正如下面这个示例

#include <stdio.h>
#include <signal.h>sig_atomic_t flag = 0;void handle_signal(int signum) {flag = 1;
}int main() {signal(SIGINT, handle_signal);while (1) {if (flag) {printf("Received SIGINT signal, exiting...\n");break;}}return 0;
}

优化情况下,键入 CTRL + C ,2号信号被捕捉,执行自定义动作,修改 flag=1 ,但是 while 条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题while 检测的flag其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要 volatile!!

#include <stdio.h>
#include <signal.h>volatile sig_atomic_t flag = 0; //使用了volatile关键字,编译器不会对它进行优化void handle_signal(int signum) {flag = 1;
}int main() {signal(SIGINT, handle_signal);while (1) {if (flag) {printf("Received SIGINT signal, exiting...\n");break;}}return 0;
}

在上面的示例中,定义了一个名为flagvolatile sig_atomic_t类型变量,用于表示是否收到了SIGINT信号。在主程序中,进入一个无限循环,检查flag变量是否被设置为1。如果收到SIGINT信号,信号处理程序会将flag变量设置为1,从而跳出循环并退出程序。由于flag变量被声明为volatile关键字,编译器不会对它进行优化,确保每次访问都从内存中读取或写入。这样可以避免由于编译器优化导致的意外行为。

🚨注意:在信号处理程序中,只有少量的函数和表达式可以安全地使用。具体来说,只有那些不分配内存或锁定全局资源的函数和表达式才能被安全地使用。为了确保信号处理程序的可重入性和线程安全性,应该尽可能避免在信号处理程序中使用非安全函数和表达式。

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

网站优化进阶指南:如何用Python爬虫进行网站结构优化

前段时间一个做网络优化的朋友找我&#xff0c;问我能不能通过爬虫的手段对他们自己的网络进行优化。这个看着着实比较新颖&#xff0c;对于从事爬虫行业的程序员来说&#xff0c;很有挑战性&#xff0c;值得尝试尝试。 说白了使用爬虫进行网站优化需要对网站的结构、内容、链…

深光标准谷歌GMS认证实际EDLA项目分享

一、谷歌EDLA协议简单介绍&#xff0c;设备过认证是做MADA的还是EDLA的&#xff1f;EDLA跟MADA有什么区别&#xff1f; 在了解EDLA跟MADA的差异之前&#xff0c;我们需要先了解这两个协议的中文意思&#xff0c;EDLA&#xff0c;英文为Enterprise Device Licensing Agreeement—…

python类的多重继承继承和查找顺序

1 python类的多重继承继承和查找顺序 python中&#xff0c;类的多重继承允许子类继承多个基类&#xff0c;子类可以访问多个基类的属性和方法。 1.1 多重继承基础 用法 class MulClass(BaseC1,BaseC2,...BaseCn):pass描述 Mulclass&#xff1a;子类&#xff08;或者称混合…

LeetCode(36)旋转图像【矩阵】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 48. 旋转图像 1.题目 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在** 原地** 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 …

【01】侯小啾python入门计划_导语

侯小啾python入门计划_导语 欢迎大家订阅《侯小啾python入门计划》专栏&#xff0c;本专栏经作者侯小啾精心打造&#xff0c;极致把握初学者心理状态与困境&#xff0c;让小白少走弯路&#xff0c;让大佬快速成长。Python是一种简单易学且功能强大的编程语言。对于大多数非科班…

国密加密工业路由器 数据安全升级

国密加密工业路由器&#xff0c;简称国密加密路由器&#xff0c;是指遵循“商用密码管理规范”中规定的国家商用密码算法&#xff0c;采用国密加密芯片和密码算法的专业路由器。相比-般路由器&#xff0c;国密加密路由器具有更高级别的加密保护&#xff0c;可以有效提高数据传输…

什么是requestIdleCallback?和requestAnimationFrame有什么区别?

什么是requestIdleCallback? 我们都知道React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断&#xff0c;其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。 在 JavaScript 中&#xff0c;requestIdleCallback 是一个用于执行回调函…

ffmpeg 把mp4文件中某段视频转成gif文件

一 缘起背景&#xff1a; 有视频文件转gif动图的需求&#xff1b;网上下载的转换工具需要注册会员、否则带水印&#xff0c;还限制时长。 二 工具环境&#xff1a; win10 下 dos 操作 ffmpeg 三 操作命令&#xff1a; ffmpeg -i test.mp4 -ss 00:01:01 -t 00:00:19 -vf &q…

Mysql安全之基础合规

一、背景 某次某平台进行安全性符合型评估时&#xff0c;列出了数据库相关安全选项&#xff0c;本文特对此记录&#xff0c;以供备忘参考。 二、安全配置 2.1、数据库系统登录时的用户进行身份标识和鉴别&#xff1b; 1&#xff09;对登录Mysql系统用户的密码复杂度是否有要…

C++学习之路(十二)C++ 用Qt5实现一个工具箱(增加一个XML文本格式化功能)- 示例代码拆分讲解

上篇文章&#xff0c;我们用 Qt5 实现了在小工具箱中添加了《进制转换器功能》功能。为了继续丰富我们的工具箱&#xff0c;今天我们就再增加一个平时经常用到的功能吧&#xff0c;就是「 XML文本格式化 」功能。下面我们就来看看如何来规划开发一个这样的小功能并且添加到我们…

ESP32-Web-Server 实战编程- 使用 AJAX 自动更新网页内容

ESP32-Web-Server 实战编程- 使用 AJAX 自动更新网页内容 概述 什么是 AJAX &#xff1f; AJAX Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 AJAX 是一种用于创建快速动态网页的技术。 传统的网页&#xff08;不使用 AJAX&#…

【动态规划】LeetCode2552:优化了6版的1324模式

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划 本题其它解法 C前缀和算法的应用&#xff1a;统计上升四元组 类似题解法 包括题目及代码C二分查找算法&#xff1a;132 模式解法一枚举3C二分查找算法&am…