简述Linux的信号处理

简述Linux的信号处理

  • 背景
  • 什么是信号?
    • 信号状态
    • 可靠信号与不可靠信号
  • 如何产生信号?
  • 信号处理?
    • 捕捉信号
      • signal函数
      • sigaction函数
    • 阻塞信号
    • 中断的系统调用
    • Async-signal-safe
    • 多线程与信号处理
  • 实战
    • 不可靠信号,多次产生信号信号处理函数会被重复调用吗?
    • 信号屏蔽字对不可靠信号是否产生作用?
    • 解读一下glog的FailureWriter
    • Signal Handler的Tips

背景

工作上有一个需求:希望在程序crash的情况下能够回收内存中的一些数据,将其落到硬盘上,所以研究了一下Signal Handle。

什么是信号?

信号是软件中断,提供了一种处理异步事件的方法,它会中断程序正常执行,然后去执行注册的信号处理函数。例如:终端用户键入中断键,会通过信号机制停止一个程序。在Linux系统下有31种信号(新版可能会有扩展),包括我们熟悉的:SIGINT(Ctrl + C)、SIGSEGV(段错误)、SIGTERM(终止信号)等。

信号状态

  • 信号产生(generation):硬件异常(除0)、软件条件(如alarm定时器超时)、终端产生的信号或调用kill函数
  • 信号递送(delivery):进程可以处理这个信号了
  • 信号未决的(pending):在信号generation和delivery之间的时间间隔内,信号的状态是pending

可靠信号与不可靠信号

可靠信号:

  • 定义:可靠信号又称为实时信号,信号代码从SIGRTMIN到SIGRTMAX之间的信号都是可靠信号。

  • 特性:可靠信号支持排队,即如果发送了多个相同的可靠信号到同一进程,这些信号都会被接收并排队等待处理。内核会为每个接收到的可靠信号分配一个sigqueue结构,并注册在进程的未决信号链中,因此不存在信号丢失的问题。

  • 应用:可靠信号通常用于需要确保信号被准确接收和处理的场景,如实时系统、多线程程序等。

不可靠信号:

  • 定义:不可靠信号又称为非实时信号,信号代码从1到32(如SIGHUP到SIGSYS)都是不可靠信号。
  • 特性:不可靠信号不支持排队,即如果发送了多个相同的不可靠信号到同一进程,这些信号可能会被合并或丢弃,只保留一个信号等待处理。此外,不可靠信号在每次处理完之后,通常会恢复成默认处理,这可能是调用者不希望看到的。
  • 应用:不可靠信号通常用于传统的UNIX系统信号处理,如进程终止(SIGINT)、非法内存访问(SIGSEGV)等。

如何产生信号?

很多条件都可以产生信号:

  1. 当用户按某些终端键时,引发终端产生的信号,比如Ctrl + C产生的SIGINT信号
  2. 硬件异常产生信号:除数为0、无效的内存引用等,这些由硬件检测到,并通知内核。内核为该条件发生时正在运行的进程产生适当的信号,例如:SIGSEGV
  3. 进程调用kill函数可将任意信号发送给另一个进程或进程组,不过一些限制:要么发送和接收是同一个所有者,要么发送进程具备超级用户权限
  4. 用户可用kill命令将信号发送给其他进程,只是对kill函数的封装
  5. 进程调用pthread_kill函数可以向任意一个线程发送信号
  6. 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号
  7. raise函数

信号处理?

因为产生信号的事件对进程而言是随机出现的,所以进程不能判断怎么时候信号发生了,只能通过系统调用告诉内核“此信号发生时,请执行下列操作”。在某个信号出现时,可以告诉内核按下列3中方式之一进行处理,称之为Signal Handler:

  1. 忽略此信号,不做任何处理,SIGKILL和SIGSTOP是不可忽略的
  2. 捕捉信号,注册一个signal handler函数来处理信号
  3. 执行系统默认动作,大部分系统默认动作时终止进程,有些信号还会产生core文件

捕捉信号

signal函数

signal 是一个用于设置信号处理方式的函数,它允许程序在接收到特定信号时执行自定义的处理函数,或者采用默认的处理方式,也可以选择忽略该信号。

注意事项:

  • 当信号发生后,第二次发生,信号会恢复到系统默认的处理动作上。(测试了Linux系统发现并不是这样的,所以不同的操作系统实现不一样)
  • 信号处理函数应该尽量简单快速,避免执行复杂的操作或长时间的阻塞操作,因为信号可能在任何时候中断程序的执行。
  • 信号处理可能会被其他信号中断,所以在信号处理函数中要考虑到这种情况。
  • 不同的操作系统对信号的处理可能会有所不同,所以在跨平台开发时需要注意兼容性问题。
  • 一旦设置了信号处理函数,它将在程序的整个生命周期内有效,除非再次调用 signal 函数来改变信号的处理方式。

sigaction函数

sigaction函数的功能是检测或修改(或检查并修改)与指定信号相关联的处理动作。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signum:要捕捉的信号的编号。例如,SIGINT 表示中断信号(通常由 Ctrl+C 产生),SIGTERM 表示终止信号等。
  • act:指向一个 struct sigaction 结构体的指针,该结构体包含了要设置的信号处理程序的详细信息。如果此参数为 NULL,则不会更改信号的处理程序,但可以用来获取当前信号的处理程序(通过 oldact 参数)。
  • oldact:指向一个 struct sigaction 结构体的指针,用于存储先前的信号处理程序信息。如果此参数为 NULL,则不保存旧的信号处理程序信息。
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. sa_handler:这是一个指向信号处理函数的指针。当某个信号发生时,操作系统会调用这个函数。该函数接受一个 int 类型的参数,表示信号编号(如 SIGINT, SIGTERM 等)。自定义信号处理函数用于处理信号,也可以是特殊值 SIG_DFL(执行该信号的默认处理动作)或 SIG_IGN(忽略该信号)。

  2. sa_sigaction:这是 sa_handler 的一个增强版本,适用于需要获取更详细信号信息的情况。当 sa_flags 中设置了 SA_SIGINFO 标志时,sa_sigaction 会被调用,而不是 sa_handler。它接受三个参数:信号编号、指向 siginfo_t 结构体的指针(提供关于信号的更多详细信息,如信号来源、进程 ID 等)和指向与信号相关的上下文信息的指针(如 CPU 寄存器的状态)。

  3. sa_mask:这是一个 sigset_t 类型的信号集,用于指定在处理当前信号时,应该被阻塞的其他信号。在信号处理程序运行时,sa_mask 中的信号会被暂时阻塞,以防止它们中断当前的信号处理。可以通过 sigemptyset() 清空信号集,或通过 sigaddset() 添加需要阻塞的信号。

  4. sa_flags:这是一组标志位,用于指定信号处理行为。常见的标志位包括:

    1. SA_RESTART:让被信号中断的系统调用自动重启。
    2. SA_SIGINFO:启用 sa_sigaction 处理信号,而非 sa_handler。
    3. SA_NOCLDSTOP:如果信号为 SIGCHLD,当子进程暂停时,不发送此信号。
    4. SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值 SIG_DFL。
    5. SA_NODEFER:在调用信号处理程序时不将本信号添加到进程的信号屏蔽字中。
  5. sa_restorer:这是一个过时的字段,通常不需要设置和使用。它曾经用于指定信号处理函数返回时的清理函数,但现在已经被废弃。

示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void signal_handler(int signum) {printf("Caught signal %d\n", signum);
}int main() {struct sigaction act;// 指定处理函数act.sa_handler = signal_handler;// 清空信号掩码,表示不阻塞任何信号sigemptyset(&act.sa_mask);// 使用默认标志act.sa_flags = 0;// 注册 SIGINT 信号的处理程序sigaction(SIGINT, &act, NULL);// 无限循环,等待信号while (1) {printf("Waiting for signal...\n");sleep(1);}return 0;
}

阻塞信号

进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者将对此信号的动作更改为忽略。进程可以调用sigpending函数来判断哪些信号是设置为阻塞并处于未决状态。

每个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集。可以使用sigprocmask函数来检测和更改当前的信号屏蔽字。

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>// 信号处理函数(实际上在这个例子中不会被调用,因为SIGINT被阻塞了)
void handle_sigint(int signum) {printf("Caught SIGINT (signal %d), but this should not happen immediately.\n", signum);
}// 全局变量用于设置jmpbuf,以便在需要时跳出循环
jmp_buf env;// 另一个信号处理函数,用于设置全局变量并跳出循环(虽然在这个例子中不被直接用于SIGINT)
void handle_sigterm(int signum) {longjmp(env, 1);
}int main() {sigset_t block_set, pending_set;struct sigaction act;// 设置SIGTERM的处理函数为handle_sigterm,以便我们可以优雅地跳出循环act.sa_handler = handle_sigterm;sigemptyset(&act.sa_mask);act.sa_flags = 0;if (sigaction(SIGTERM, &act, NULL) == -1) {perror("sigaction SIGTERM");exit(EXIT_FAILURE);}// 初始化jmpbuf,以便在需要时可以跳出循环if (setjmp(env) != 0) {printf("Received SIGTERM, exiting gracefully.\n");exit(EXIT_SUCCESS);}// 将SIGINT加入到阻塞信号集中sigemptyset(&block_set);sigaddset(&block_set, SIGINT);if (sigprocmask(SIG_BLOCK, &block_set, NULL) == -1) {perror("sigprocmask SIGINT");exit(EXIT_FAILURE);}printf("SIGINT is now blocked. Waiting for 5 seconds...\n");sleep(5);// 检查是否有SIGINT信号在等待(在这个例子中,应该不会有,因为我们还没有解除阻塞)sigemptyset(&pending_set);if (sigpending(&pending_set) == -1) {perror("sigpending");exit(EXIT_FAILURE);}if (sigismember(&pending_set, SIGINT)) {printf("SIGINT is pending, but this should not happen because it's blocked.\n");} else {printf("No SIGINT is pending, as expected.\n");}// 从阻塞信号集中移除SIGINTif (sigprocmask(SIG_UNBLOCK, &block_set, NULL) == -1) {perror("sigprocmask SIGINT unblock");exit(EXIT_FAILURE);}printf("SIGINT is now unblocked. You can now interrupt the program with Ctrl+C.\n");// 无限循环,等待信号(现在SIGINT可以被捕捉到了)while (1) {printf("Waiting for signals...\n");sleep(1);}// 注意:由于上面的无限循环,下面的代码实际上永远不会被执行到。// 为了测试SIGINT的处理,你可以发送SIGTERM信号来跳出循环(例如,使用kill命令)。return 0;
}

中断的系统调用

某些系统调用可以被信号中断,系统返回EINTR的errno码,此时需要根据系统调用返回值再次调用系统调用;有一些系统调用支持自动重启动,但是最好不要依赖它,因为各个系统(UNIX、Linux)实现都不一样,并且也很难确定哪些系统调用实现了自动重启动。

Async-signal-safe

Signal Handler中不是所有函数都可以被调用:假设程序正在执行malloc,此时由于捕捉到信号而插入执行该信号处理函数,其中有调用了malloc,这时可能破坏堆内存的维护链表。

Single UNIX Specifications说明了哪些函数可以被信号处理函数调用,这些函数是可重入的并被成为异步信号安全的(async-signal safe)。

多线程与信号处理

参考:https://cloud.tencent.com/developer/news/1260924

关键点:

  1. 每个线程都可以处理信号,操作系统会优先将信号递送给引发信号的线程,所以类似glog的FailureWriter才可以输出crash的backtrace
  2. 每个线程都有自己的阻塞信号集,控制自己响应哪些信号或阻塞哪些信号,API是phtread_sigmask
  3. 每个线程都有自己的未决信号队列,也有共享的未决信号队列(主线程)

实战

不可靠信号,多次产生信号信号处理函数会被重复调用吗?

    #include <iostream>#include <csignal>#include <cstdlib>#include <unistd.h>void HandleSIGINT(int signum) {std::cout << "\n捕获到SIGINT信号,程序即将退出..." << std::endl;}int main() {signal(SIGINT, HandleSIGINT);while (1) {sleep(1);}}

运行结果:从运行结果来看,即使signal函数也是支持反复处理信号的,和UNIX的设计还是不一样的。

yunjingguang@walle:~/work/signal$ ./signal_test
^C
捕获到SIGINT信号,程序即将退出...
^C
捕获到SIGINT信号,程序即将退出...
^C
捕获到SIGINT信号,程序即将退出...
^C

信号屏蔽字对不可靠信号是否产生作用?

    #include <iostream>#include <csignal>#include <cstdlib>#include <unistd.h>void HandleSIGINT(int signum) {std::cout << "\n捕获到SIGINT信号,程序即将退出..." << std::endl;}int main() {signal(SIGINT, HandleSIGINT);sigset_t block_set;sigemptyset(&block_set);sigaddset(&block_set, SIGINT);if (sigprocmask(SIG_BLOCK, &block_set, NULL) == -1) {perror("sigprocmask SIGINT");exit(EXIT_FAILURE);}while (1) {sleep(1);}}

运行结果:看起来已经将SIGINT屏蔽掉了

yunjingguang@walle:~/work/signal$ ./signal_test
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C

解读一下glog的FailureWriter

注册信号处理函数,并且sa_flags是SA_SIGINFO,会进一步输出细节信息:

void InstallFailureSignalHandler() {
#ifdef HAVE_SIGACTION// Build the sigaction struct.struct sigaction sig_action;memset(&sig_action, 0, sizeof(sig_action));sigemptyset(&sig_action.sa_mask);sig_action.sa_flags |= SA_SIGINFO;sig_action.sa_sigaction = &FailureSignalHandler;for (auto kFailureSignal : kFailureSignals) {CHECK_ERR(sigaction(kFailureSignal.number, &sig_action, nullptr));}kFailureSignalHandlerInstalled = true;
#elif defined(GLOG_OS_WINDOWS)for (size_t i = 0; i < ARRAYSIZE(kFailureSignals); ++i) {CHECK_NE(signal(kFailureSignals[i].number, &FailureSignalHandler), SIG_ERR);}kFailureSignalHandlerInstalled = true;
#endif  // HAVE_SIGACTION
}// Dumps signal and stack frame information, and invokes the default
// signal handler once our job is done.
#if defined(GLOG_OS_WINDOWS)
void FailureSignalHandler(int signal_number)
#else
void FailureSignalHandler(int signal_number, siginfo_t* signal_info,void* ucontext)
#endif
{std::call_once(signaled, &HandleSignal, signal_number
#if !defined(GLOG_OS_WINDOWS),signal_info, ucontext
#endif);
}

Signal Handler的Tips

  1. callback使用C语言的函数指针,保证生命周期的安全性
  2. std::once_flag,解决重入的问题
  3. sem_post是async-signal-safe的,可以在Signal Handler中调用,用于通知其他线程开始收尾
  4. 在信号处理函数中获取pthread id,获得是发生问题的线程的ID,它会中断

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

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

相关文章

如何通过API实现淘宝商品评论数据抓取?item_review获取淘宝商品评论

前几天一个好朋友要我帮忙抓一下淘宝商品的评论数据&#xff0c;获取淘宝评论数据可以帮忙商家们做好市场调研&#xff0c;对自己的产品进行升级&#xff0c;从而更好地获取市场。我将详细爬取方法封装成API&#xff0c;以供方便调用。 item_review-获得淘宝商品评论 响应示例…

springboot550乐乐农产品销售系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统乐乐农产品销售系统信息管理难度大&#xff0c;容错率低&…

百度贴吧的ip属地什么意思?怎么看ip属地

在数字化时代&#xff0c;IP地址不仅是网络设备的唯一标识符&#xff0c;更承载着用户的网络身份与位置信息。百度贴吧作为广受欢迎的社交平台&#xff0c;也遵循相关规定&#xff0c;在用户个人主页等位置展示账号IP属地信息。那么&#xff0c;百度贴吧的IP属地究竟意味着什么…

[读书日志]从零开始学习Chisel 第一篇:书籍介绍,Scala与Chisel概述,Scala安装运行(敏捷硬件开发语言Chisel与数字系统设计)

简介&#xff1a;从20世纪90年代开始&#xff0c;利用硬件描述语言和综合技术设计实现复杂数字系统的方法已经在集成电路设计领域得到普及。随着集成电路集成度的不断提高&#xff0c;传统硬件描述语言和设计方法的开发效率低下的问题越来越明显。近年来逐渐崭露头角的敏捷化设…

element-plus大版本一样,但是小版本不一样导致页面出bug

npm 的版本 node的版本 npm的源这些都一样&#xff0c;但是效果不一样 发现是element的包版本不一样导致的 2.9.1与2.8.1的源是不一样的&#xff0c;导致页面出bug;

【网络协议】开放式最短路径优先协议OSPF详解(一)

OSPF 是为取代 RIP 而开发的一种无类别的链路状态路由协议&#xff0c;它通过使用区域划分以实现更好的可扩展性。 文章目录 链路状态路由协议OSPF 的工作原理OSPF 数据包类型Dijkstra算法、管理距离与度量值OSPF的管理距离OSPF的度量值 链路状态路由协议的优势拓扑结构路由器O…

《数据结构》期末考试测试题【中】

《数据结构》期末考试测试题【中】 21.循环队列队空的判断条件为&#xff1f;22. 单链表的存储密度比1&#xff1f;23.单链表的那些操作的效率受链表长度的影响&#xff1f;24.顺序表中某元素的地址为&#xff1f;25.m叉树第K层的结点数为&#xff1f;26. 在双向循环链表某节点…

华为数通考试模拟真题(附带答案解析)题库领取

【多选题】 管理员想要更新华为路由器的VRP版本&#xff0c;则正确的方法有? A管理员把路由器配置为FTP服务器&#xff0c;通过FTP来传输VRP软件 B:管理员把路由器置为FTP客户端&#xff0c;通过FTP来传输VRP软件 C:管理员把路由器配置为TFTP客户端&#xff0c;通过TFTP来传…

Linux:操作系统不朽的传说

操作系统是计算机的灵魂&#xff0c;它掌控着计算机的硬件和软件资源&#xff0c;为用户和应用程序提供了一个稳定、高效、安全的运行环境。 在众多操作系统中&#xff0c;Linux 的地位举足轻重。它被广泛应用于服务器、云计算、物联网、嵌入式设备等领域。Linux 的成功离不开…

前端(API)学习笔记(CLASS 4):进阶

1、日期对象 日期对象&#xff1a;用来表示事件的对象 作用&#xff1a;可以得到当前系统时间 1、实例化 在代码中发现了new关键字&#xff0c;一般将这个操作称为实例化 创建一个时间对象并获取时间 获得当前时间 const datenew Date() 使用日志查看&#xff0c;得到的…

【USRP】教程:在Macos M1(Apple芯片)上安装UHD驱动(最正确的安装方法)

Apple芯片 前言安装Homebrew安装uhd安装gnuradio使用b200mini安装好的路径下载固件后续启动频谱仪功能启动 gnu radio关于博主 前言 请参考本文进行安装&#xff0c;好多人买了Apple芯片的电脑&#xff0c;这种情况下&#xff0c;可以使用UHD吗&#xff1f;答案是肯定的&#…

SAP 01-初识AMDP(ABAP-Managed Database Procedure)

1. 什么是AMDP(ABAP-Managed Database Procedure) 1.&#xff09;AMDP - ABAP管理数据库程序&#xff0c;是一种程序&#xff0c;我们可以使用SQLSCRIPT在AMDP内部编写代码&#xff0c;SQLSCRIPT是一种与SQL脚本相同的数据库语言&#xff0c;这种语言易于理解和编码。 将AM…

智能客户服务:科技如何重塑客户服务体验

在数字化时代&#xff0c;客户对于服务的需求和期望在不断演变。传统的客户服务模式已经难以满足现代消费者对于即时性、个性化和高效性的追求。随着人工智能、大数据、云计算等先进技术的蓬勃发展&#xff0c;智能客户服务应运而生&#xff0c;不仅重塑了客户服务的体验&#…

[论文笔记]Representation Learning with Contrastive Predictive Coding

引言 今天带来论文 Representation Learning with Contrastive Predictive Coding的笔记。 提出了一种通用的无监督学习方法从高维数据中提取有用表示&#xff0c;称为对比预测编码(Contrastive Predictive Coding,CPC)。使用了一种概率对比损失&#xff0c; 通过使用负采样使…

Kafka 消费者专题

目录 消费者消费者组消费方式消费规则独立消费主题代码示例&#xff08;极简&#xff09;代码示例&#xff08;独立消费分区&#xff09; offset自动提交代码示例&#xff08;自动提交&#xff09;手动提交代码示例&#xff08;同步&#xff09;代码示例&#xff08;异步&#…

【踩坑指南2.0 2025最新】Scala中如何在命令行传入参数以运行主函数

这个地方基本没有任何文档记录&#xff0c;在学习的过程中屡屡碰壁&#xff0c;因此记录一下这部分的内容&#xff0c;懒得看可以直接跳到总结看结论。 踩坑步骤 首先来看看书上让我们怎么写&#xff1a; //main.scala object Start {def main(args:Array[String]) {try {v…

数据分析思维(七):分析方法——群组分析方法

数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python&#xff0c;更重要的是数据分析思维。没有数据分析思维和业务知识&#xff0c;就算拿到一堆数据&#xff0c;也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》&#xff0c;本文内容就是提取…

CSS 之 position 定位属性详解

CSS系列文章目录 CSS 之 display 布局属性详解 CSS 之 position 定位属性详解一文搞懂flex布局 【弹性盒布局】 文章目录 CSS系列文章目录一、前言二、静态定位&#xff1a;position:static&#xff1b;二、相对定位&#xff1a;position:relative三、绝对定位&#xff1a;pos…

麒麟信安云在长沙某银行的应用入选“云建设与应用领航计划(2024)”,打造湖湘金融云化升级优质范本

12月26日&#xff0c;2024云计算产业和标准应用大会在北京成功召开。大会汇集政产学研用各方专家学者&#xff0c;共同探讨云计算产业发展方向和未来机遇&#xff0c;展示云计算标准化工作重要成果。 会上&#xff0c;云建设与应用领航计划&#xff08;2024&#xff09;建云用…

微信小程序Uniapp

使用命令行创建项目&#xff08;vuets&#xff09; npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project然后用HBX打开项目 再安装依赖 npm i 再运行开发版本&#xff0c;生成dist目录 pnpm dev:mp-weixin 注意要设置APPid 再用微信小程序打开