当前位置: 首页 > ops >正文

linux-信号保存和处理

1. 信号其他相关常见概念

实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )(屏蔽)某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的⼀种处理动
作。

2. 在内核中的表示 

每个信号都有两个标志位,分别表示阻塞(block)和未决(pending),这两个标志位本质上是保存收到的的信号的位图,比特位的位置表示的是第几号信号,比特位的内容表示是否阻塞。还有一个函数指针表示处理动作

信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作

- SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

- SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

#include <iostream>
#include <signal.h>
#include <unistd.h>
void handler(int signum)
{std::cout << "signal: " << signum <<  std::endl;signal(2, SIG_DFL);std::cout << "恢复信号" << std::endl;
}
int main()
{signal(2, handler); //自定义信号//signal(2, SIG_IGN); //忽略信号while (true){sleep(1);std::cout << "." << std::endl;}return 0;
}

 

3. 信号集操作函数

3.1 sigset

 - 从上图来看,每个信号只有⼀个bit的未决标志,非0即1, 不记录该信号产生了多少次,阻塞标志也
是这样表示的。因此, 未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号
,这个类型可以表示每个信号的“有效”或“无效”状态。

 - 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

 - 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。

- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置1,表示该信号集的有效信号包括系统支持的所有信号。

- 注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

这四个函数都是成功返回0,出错返回-1

sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

3.2 sigprocmask

在 Linux 中,sigprocmask 是用于 设置或检查进程的信号屏蔽字(信号阻塞集) 的系统调用。它用于 阻塞(屏蔽)或解除阻塞某些信号,以控制进程对特定信号的处理方式。

函数原型:

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

参数说明

 - how:指定如何修改信号屏蔽字,假设当前的信号屏蔽字为mask。

SIG_BLOCK:将 set 指定的信号 加入 到当前进程的信号屏蔽字中(即阻塞这些信号)。
mask = mask | setSIG_UNBLOCK:从当前信号屏蔽字中 移除 set 指定的信号(即解除阻塞)。
mask = mask & ~setSIG_SETMASK:用 set 指定的信号集 直接替换 进程当前的信号屏蔽字。
mask = set

 - set:指定要修改的信号集。如果 NULL,则不改变当前信号屏蔽字,仅用于获取当前信号屏蔽字(结合 oldset 使用)。

 - oldset:如果不为 NULL,则 sigprocmask 在修改信号屏蔽字前,会将进程当前的信号屏蔽字 保存oldset 中。

返回值

成功返回 0

失败返回 -1,并设置 errno 以指示错误原因(如 EINVAL 表示 how 参数无效)。

3.3 sigpending

在 Linux 中,sigpending 用于获取当前 进程未决信号集(pending signals),即那些已经产生但因被阻塞而未能递达的信号。

函数原型

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

参数说明

set:指向 sigset_t 类型的信号集变量,用于存储进程当前未决的信号集合

返回值

成功返回 0

失败返回 -1,并设置 errno 以指示错误原因(通常不会失败)。

产生1到9号命令

9号进程不可被捕捉,不可被屏蔽(SIGSTIOP(19)号进程也是)

#include <iostream>
#include <signal.h>
#include <vector>
#include <functional>
#include <unistd.h>void PrintPend(sigset_t &pending)
{printf("我是一个进程,pid:%d, pending: ", getpid());for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << "1";}else{std::cout << "0";}}std::cout << std::endl;
}
void handler(int signum)
{std::cout << "递达" << signum << "号信号" << std::endl;std::cout << "######################" << std::endl;sigset_t pending;int n = sigpending(&pending);PrintPend(pending);std::cout << "######################" << std::endl;
}
int main()
{signal(2, handler);// 1. 屏蔽所有信号sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock);sigaddset(&block, 2);int n = sigprocmask(SIG_SETMASK, &block, &oblock);int cnt = 0;while (true){sleep(1);// 2. 获取pending信号集sigset_t pending;int m = sigpending(&pending);// 3. 打印PrintPend(pending);cnt++;if(cnt == 10){std::cout << "解除对二号的屏蔽" << std::endl;sigprocmask(SIG_SETMASK, &oblock, NULL);}}return 0;
}

由上面的结果可知,当我们准备递达的时候,要首先清空pending信号集中对应的位图。 

4. 终止信号core vs term

在 Linux 中,终止信号(Termination Signals) 主要用于终止进程,而不同的终止信号可以导致进程以不同的方式退出。其中,coreterm 是两类不同的终止方式。

core 终止信号会导致进程异常退出,并在当前路径或指定目录下生成一个 core dump 文件。进程在退出前,其内存中的核心数据会被拷贝到磁盘,形成 core dump,这一过程称为核心转储(Core Dump)

term 终止信号(如 SIGTERMSIGINT)则仅会使进程退出,不会生成 core dump 文件。

不过在云服务器上,core dump的功能是默认关闭的,我们可以通过ulimit命令查看、修改 。

ulimit -a:查看当前用户的所有资源限制。
ulimit -c:查看 core dump 文件的大小限制(单位:blocks,0 表示不生成 core dump)。

 

设置core dump大小为40960 

int main()
{printf("hello, henu\n");printf("hello, henu\n");printf("hello, henu\n");printf("hello, henu\n");printf("hello, henu\n");printf("hello, henu\n");int a = 10;a /= 0;printf("hello, henu\n");printf("hello, henu\n");printf("hello, henu\n");return 0;
}

 在 gdb 中使用 core-file core 命令加载 core dump 文件,可以直接查看导致程序崩溃的代码行。

正常终止

当进程正常终止时,会设置一个“退出状态”。在Linux中,进程通过 exit 系列函数(如 exit()_exit())来设置退出状态。该状态通常是一个整数值,父进程可以通过 wait()waitpid() 函数获取子进程的退出状态,从而判断子进程的执行结果。

退出状态:在正常终止时,进程的退出状态通常是 0,表示成功执行;如果退出状态是非零值,则表示程序执行过程中出现了错误。图中的“正常终止”右侧的8位设置为 0,这表明这些位不用于存储信号信息。

被信号所杀

当进程由于接收到某个信号而终止时,退出状态包含有关信号的信息。

终止信号编号:图中的右侧8位存储了导致进程终止的信号编号。例如:

SIGINT(信号编号2):通常是由用户按下 Ctrl+C 触发的终止信号。
SIGTERM(信号编号15):用于请求进程正常终止的信号。
SIGKILL(信号编号9):强制终止进程,不能被捕获或忽略。

core dump标志:如果进程因为信号终止,并且系统设置了生成 core dump 文件的标志(通常为 1),系统将保存进程终止时的内存镜像和寄存器状态。这些信息对开发者调试程序异常终止问题非常有帮助。图中提到的“core dump标志”表示这一点。

未使用区域:图中提到的左侧“未用”区域表明,在信号终止的情况下,该区域未用于存储与进程终止相关的其他信息。因为信号终止本身会填充信号编号和相关信息,而这些“未用”区域不需要额外的数据。

    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <functional>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>int main()
    {pid_t id = fork();if (id == 0){sleep(2);printf("hello bit\n");printf("hello bit\n");printf("hello bit\n");printf("hello bit\n");printf("hello bit\n");int a = 10;a /= 0;printf("hello bit\n");exit(1);}int status = 0;waitpid(id, &status, 0);printf("signal: %d, exit code: %d, core dump: %d\n",(status & 0x7F), (status >> 8) & 0xFF, (status >> 7) & 0x1);
    }

     

    http://www.xdnf.cn/news/6204.html

    相关文章:

  • linux-进程信号捕捉
  • 继续预训练 LLM ——数据筛选的思路
  • Linux重定向与缓冲区
  • AI时代的弯道超车之第七章:如何用AI赋能创业?
  • 缺乏自动化测试,如何提高测试效率
  • 酒店旅游类数据采集API接口之携程数据获取地方美食品列表 获取地方美餐馆列表 景点评论
  • CodeBuddy Craft,我的编程搭子
  • element基于表头返回 merge: true 配置列合并
  • Oracle版本、补丁及升级(12)——补丁及补丁集
  • REVERSE学习笔记(攻防世界xxxorrr)
  • 【Java学习笔记】==运算符
  • 解决常见数据库问题:保障数据安全与稳定的全方位指南
  • 模板源码建站、定制建站和SaaS 建站有什么区别?企业建站应该怎么选?
  • C++引用编程练习
  • XILINX-DDR4-自定义componet(x8)-之一
  • 六西格玛觉醒:一场数据思维的启蒙运动​
  • 【江苏省】《信息技术应用创新软件适配改造成本评估规范》(DB32/T 4935-2024)-标准解读系列
  • 【Linux Nano Vim快捷键大全】
  • 基于EFISH-SCB-RK3576/SAIL-RK3576的康复训练机器人技术方案‌
  • Linux下批量提取子文件夹文件到当前目录
  • libmemcached库api接口讲解二
  • 股指期货套期保值怎么操作?
  • 【Linux】shell内置命令fg,bg和jobs
  • tensorflow安装及简单例程学习
  • 字符田字格绘制
  • Java的多线程笔记
  • 企业报表平台如何实现降本增效
  • requestAnimationFrame 与 requestIdleCallback 对比
  • JavaScript中执行上下文和执行栈是什么?
  • Linux `whoami` 命令深度解析与高阶应用指南