【Linux】进程信号的发送和保存

一、信号发送

  1. 信号动作

通过指令man -7 signal查看信号的手册,可以找到普通信号发出后对应的操作、信号编号和详细描述信息。

【Linux】进程信号的发送和保存

  1. 信号发送的本质

普通信号的发送本质上是将信号写入进程的PCB结构体中的位图(pending位图)。这个位图对应着1到31号的普通信号,收到信号后将对应比特位置为1,表示信号已收到,然后PCB执行相应的工作。值得注意的是,如果连续发送普通信号,进程只会处理最后一次的信号,因为每次写入都是覆盖式的。

实时信号与普通信号类似,但它们使用的是结构体而非位图,信号被组织在队列中,遵循先入先出的规则。如果连续发送实时信号,进程会依次处理队列中的所有信号。

  1. Core Dump

在《进程控制》一文中,我们解释了wait函数的status参数,其中第7位是core dump标志。程序在运行过程中发生崩溃(如段错误、除零错误等)时,Core dump会记录程序崩溃瞬间的内存状态,包括寄存器的值、调用信息、全局变量局部变量的值等。开发人员可以使用调试工具(如GDB)加载Core dump文件,分析这些信息以找出程序崩溃的原因。

可以通过ulimit -c 10240将core文件的大小限制修改为10240字节。云服务器上通常默认core文件大小限制为0,出现错误时可能导致core文件迅速填满。我们可以根据需要修改其大小限制。生成的文件名为core.pid,其中pid是出错进程的pid。例如,假设test进程发生错误,其pid为12314,我们可以在GDB模式下输入gdb test core.12314来打印错误信息和原因。

二、信号的保存

  1. 前置概念

实际执行信号处理动作称为信号递达。信号从产生到递达之间的状态称为信号未决。被阻塞的信号会保持在未决状态,直到进程解除对此信号的阻塞,才会执行递达的动作。

【Linux】进程信号的发送和保存

  1. 阻塞信号

信号被阻塞时,会进入信号未决状态。阻塞功能通过一个31位的位图block实现,对应1到31号信号。当对应比特位为1时,表示该信号被阻塞,为0则表示不阻塞。如果信号被阻塞,则进入阻塞态;如果没有被阻塞,则进入未决状态。

  1. 保存信号

未决状态的作用是保存信号,通过位图pending实现。pending与block一致,对应的下标和信号编号一一对应。当对应比特位为1时,表示该信号处于未决状态,为0则表示信号已递达。实际上,block和pending都用于保存信号,但由于有两个位图,我们分开讨论。

  1. 信号递达

信号递达后,会执行相应的行为,包括SIG_DFL(默认处理动作)、SIG_IGN(忽略)和自定义处理sighandler。

【Linux】进程信号的发送和保存

  1. 总结

【Linux】进程信号的发送和保存

一个信号首先经过block,如果block为0,则来到pending,如果pending为0,则来到handler执行动作。9号和19号信号是特例,它们不能被阻塞和保存,一旦发出直接执行handler,对应的行为只能是默认动作终止和暂停。

三、信号集操作函数

信号集操作函数用于操作信号集,sigset_t是操作系统提供的数据类型,用于描述位图。以下是信号集操作函数:

#include <signal.h> int sigemptyset(sigset_t *set); // 将位图全部设置为0 int sigfillset(sigset_t *set); // 将位图全部设置为1 int sigaddset(sigset_t *set, int signo); // 将位图中的某一位设置为1 int sigdelset(sigset_t *set, int signo); // 将位图中的某一位设置为0 int sigismember(const sigset_t *set, int signo); // 判断一个信号是否在信号集中,不在返回0,在返回1,出错返回-1 </signal.h>
  1. 设置block位图

sigprocmask是一个重要的系统调用,用于检查和修改进程的信号掩码(阻塞信号集)。

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

返回值:成功返回0,失败返回-1。

how参数指定对信号掩码的操作方式:

how取值 含义 示例说明
SIG_BLOCK 将set所指向的信号集中的信号添加到当前的信号掩码中,即阻塞set中的信号 若当前信号掩码已阻塞SIGINT,使用SIG_BLOCK并传入包含SIGTERM的信号集,SIGTERM也会被阻塞
SIG_UNBLOCK 从当前的信号掩码中移除set所指向的信号集中的信号,即解除对set中信号的阻塞 若当前信号掩码阻塞了SIGINT和SIGTERM,使用SIG_UNBLOCK并传入包含SIGINT的信号集,SIGINT信号的阻塞状态将被解除
SIG_SETMASK 将当前的信号掩码设置为set所指向的信号集,覆盖原来的信号掩码 若原信号掩码阻塞SIGINT,使用SIG_SETMASK并传入包含SIGTERM的信号集,信号掩码将只阻塞SIGTERM

set参数指向一个sigset_t类型的信号集,包含要操作的信号。如果how的值为SIG_BLOCK或SIG_UNBLOCK,set表示要添加或移除的信号集;如果how的值为SIG_SETMASK,set表示要设置的新信号掩码。如果该参数为NULL,则不改变当前的信号掩码,仅获取当前信号掩码,此时oset不能为NULL。

oset参数指向一个sigset_t类型的信号集,用于存储调用sigprocmask之前的信号掩码。如果不需要保存旧的信号掩码,可以将该参数设置为NULL。

  1. 设置pending位图

sigpending用于获取进程当前未决信号集。

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

返回值:成功返回0,失败返回-1。

set参数用于存储当前进程中处于未决状态(即已发送但由于被阻塞而尚未被处理)的信号集。

  1. 设置handler行为

信号处理行为有三种情况:默认、忽略和自定义。自定义处理可以通过signal函数实现,此前已有介绍,不再赘述。

四、验证信号保存行为

#include <iostream> #include <signal.h> #include <unistd.h> using namespace std;  // 打印出位图 void PrintPending(const sigset_t &pset) {     for(int i = 31; i >= 1; i--) {         cout << (sigismember(&pset, i) ? 1 : 0);     }     cout << endl; }  // 信号处理函数 void handler(int sig) {     cout << "Caught signal " << sig << endl; }  int main() {     sigset_t newmask, oldmask, pendmask;      // 初始化信号集     sigemptyset(&newmask);     sigaddset(&newmask, SIGINT);      // 阻塞SIGINT信号     sigprocmask(SIG_BLOCK, &newmask, &oldmask);      // 打印当前未决信号集     while(1) {         sigpending(&pendmask);         PrintPending(pendmask);         sleep(1);     }      // 解除SIGINT信号的阻塞     sigprocmask(SIG_SETMASK, &oldmask, NULL);      return 0; } </unistd.h></signal.h></iostream>

在3秒后按下ctrl+c,捕捉信号函数不会工作,说明信号被阻塞了。15秒后自动解除阻塞,立即打印出handler函数定义要打印的信息,再按ctrl+c就正常执行handler行为。

【Linux】进程信号的发送和保存

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享