Linux---进程信号
一、预备
1.信号
①物理:闹钟,红绿灯,上课铃声,狼烟,电话铃声,敲门声......
②什么叫信号:终端人正在做的事情,是一种事件的异步通知机制
③技术层面:信号是一种给进程发送的,用来进行事件异步通知的机制
④异步:两件事情同步进行互不干扰,信号的产生,先对于进程的运行,是异步的!
2.基本结论
①信号处理,进程在信号没有产生的时候,早就直到信号该如何处理了
②信号的处理,不是立即处理,二十可以等一会再处理,合适的时候,进行信号的处理
③人能识别信号,是被提前“教育”过的,进程也是如此,OS程序员设计的进程,进程早已内置了对于信号的识别和处理方式!
④信号源非常多->给进程产生信号的信号源也非常多。
二、信号的产生
1.键盘产生信号
①信号有哪些
其中1~31是普通信号,34~64是实时信号,无需我们考虑
②ctrl+c:向目标进程发送二号信号(SIGINT)
Ⅰ代码演示
Ⅱ ctrl+c是给目标进程发送信号,相当一部分信号的处理动作,就是让自己终止
③信号的三种操作:进程收到信号之后,合适的时候,处理信号的动作有三种
Ⅰ默认处理动作(大部分信号默认处理动作是终止)
Ⅱ自定义处理动作
Ⅲ忽略处理
⑤前台进程和后台进程
Ⅰ前台进程(./test):键盘产生的进程,只能发给前台进程
当运行程序时,输入ls,pwd等指令没有结果,因为前台进程只能有一个,系统运行./test就不能运行bash进程了
Ⅱ后台进程(./test &):ctrl+c无法相应
运行程序时,bash为前台进程,所以输入命令时可以执行
Ⅲ区别与共性
前台进程能从键盘上获取标准输入,后台进程无法从标准输入中获取内容;但是都可以向标准输出上打印
Ⅳ原因
键盘只有一个,输入数据一定是给确定的进程的,前台进程必须只有一个,后台进程可以有多个,
前台的本质就是要从键盘获取数据的
Ⅴ相互转变
jobs:查询后台任务号
fg+任务号:后台变前台
ctrl+z:暂停前台任务,自动变后台
bg+任务号:暂停后运行后台任务
⑥什么叫做给进程发送信号
信号产生后,并不是立即处理,所以要求进程必须把信号记录下来,在合适的时候处理!
再task_struct中使用位图结构:收到几号信号将那位置1
发送信号本质就是向目标进程写信号(修改位图,pid和信号编号)
2.系统调用
①相关接口
Ⅰsignal:修改信号操作
Ⅱ kill: 杀死目标进程中的信号
Ⅲ raise:指明发送给信号
Ⅳ abort:使当前进程接收到信号而异常终止,使用6号信号,要求进程必须处理,忽视信号捕捉
②演示
sigal
mykill
二者同时运行
③9号和19号信号无法被捕捉
3.异常产生信号
①示例
Ⅰ除0
8号SIGFPE:浮点数错误
Ⅱ野指针
11号SIGSEGV:段错误
②本质使硬件无法产生页表无法对应从而返回错误
4.软件条件(SIGPIPE)
①alarm:闹钟,时间到向OS发送信号,默认终止
alarm(0):取消闹钟
14号信号SIGALRM
②代码演示
操作系统的执行方法,while循环从开机开始就不停
#include <functional>
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>void func1() {std::cout << "方法一" << std::endl;
}
void func2() {std::cout << "方法二" << std::endl;
}
void func3() {std::cout << "方法三" << std::endl;
}using func_t = std::function<void()>;
std::vector<func_t> funcs;void handlerSig(int sig) {std::cout << "###############" << std::endl;for (auto f : funcs)f();std::cout << "###############" << std::endl;int n = alarm(1);
}
int main() {funcs.push_back(func1);funcs.push_back(func2);funcs.push_back(func3);signal(SIGALRM,handlerSig);alarm(2);while (true) {pause();//暂停}
}
三、信号的保存
1.预备
①实际执⾏信号的处理动作称为信号递达(Delivery)
②信号从产⽣到递达之间的状态,称为信号未决(Pending)。
2.理解
在一个进程pcb中有三张表
①pending表(未决表):保存收到的信号的位图,bit位的位置表示第几个信号,内容(0或1)表示是否收到
②block表:bit位的位置表示第几个信号,bit的内容表示是否阻塞
③handler表:是一个函数指针数组,下标表示信号,当使用signal函数时,第二个参数就是传入这个表,有默认,自定义,忽略三种方式,使用signal就是自定义
3.sigprocmask
调用函数可以读取或者更改进程的信号屏蔽字
①how
how
是核心控制参数,用于指定 sigprocmask
如何修改当前进程的信号掩码,必须传入以下 3 个预定义常量之一,决定了 set
中信号集的使用逻辑:
②set
set
是一个指向 sigset_t
类型的指针,sigset_t
是系统定义的信号集数据结构(可理解为 “存储多个信号的容器”),用于指定要 “阻塞 / 解除阻塞 / 替换” 的具体信号。
关键说明:
set
的值取决于how
的作用:- 当
how
为SIG_BLOCK
/SIG_UNBLOCK
时,set
需包含要 “添加阻塞” 或 “解除阻塞” 的信号; - 当
how
为SIG_SETMASK
时,set
就是新的完整信号掩码; - 若
set
为NULL
,表示 “不修改信号集”,仅通过oldset
获取当前掩码(此时how
参数会被忽略)。
- 当
③oldset(输出型参数)
oldset
是一个指向 sigset_t
类型的指针,用于保存修改前的 “旧信号掩码”(即调用 sigprocmask
前进程的信号掩码状态)。
关键说明:
- 若
oldset
不为NULL
:系统会将修改前的信号掩码写入oldset
指向的内存,便于后续恢复原状态(例如临时阻塞信号后,需还原为原来的阻塞规则); - 若
oldset
为NULL
:表示 “不需要保存旧掩码”,仅执行修改操作。
4.sigpending
①功能
1. 核心概念:待处理信号(Pending Signal)
在理解 sigpending
前,需先明确 “待处理信号” 的定义:
当一个信号被发送给进程时,系统会先检查该信号是否在进程的信号掩码中(即是否被阻塞);
若未被阻塞:进程会立即处理该信号(执行信号处理函数或默认行为);
若已被阻塞:信号不会被立即处理,而是被标记为 “待处理” 状态,暂存于进程的 “待处理信号队列” 中;
当该信号从信号掩码中被解除阻塞(如通过
sigprocmask(SIG_UNBLOCK)
)后,进程会立即处理这个待处理信号。
注意:每个信号类型(如 SIGINT、SIGQUIT)在待处理队列中最多只有一个实例—— 若同一信号被多次发送且均被阻塞,只会记录一次,解除阻塞后也仅处理一次。
②参数:sigset_t *set(输出型参数)
set
是一个指向 sigset_t
类型(信号集)的指针,其作用是接收进程当前所有待处理信号的集合。
5.sig集合
①sigemptyset
- 功能:初始化一个信号集,将该信号集中所有信号的对应标志位都设置为 0,即清空信号集,使其不包含任何信号。
- 返回值:成功时返回 0;若出现错误,返回 - 1。
②sigfillset
- 功能:将信号集中的所有信号对应的标志位都设置为 1,也就是使该信号集包含系统支持的所有信号。
- 返回值:成功时返回 0;若出现错误,返回 - 1。
③sigaddset
- 功能:向指定的信号集
set
中添加一个特定的信号。signum
参数表示要添加的信号编号,比如常见的SIGINT
(信号编号通常为 2,代表终端中断信号)、SIGTERM
(终止信号)等。 - 返回值:成功时返回 0;若出现错误(例如
signum
不是一个有效的信号编号,或者set
指针无效等情况),返回 - 1。
④sigdelset
- 功能:从指定的信号集
set
中删除一个特定的信号。signum
是要删除的信号编号。 - 返回值:成功时返回 0;若出现错误(比如
signum
不是一个有效的信号编号,或者set
指针无效,又或者要删除的信号原本就不在信号集中 ),返回 - 1。
⑤sigismember
- 功能:检查指定的信号
signum
是否存在于信号集set
中。这是一个查询函数,用于判断某个信号是否被包含在特定的信号集中。 - 返回值:如果信号
signum
在信号集set
中,返回 1;如果信号signum
不在信号集set
中,返回 0 ;若出现错误(比如set
指针无效),返回 - 1。
⑥代码使用
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void PrintPending(sigset_t &pending) {printf("我是一个进程(%d), pending: ", getpid());for (int signo = 31; signo > 0; signo--) {if (sigismember(&pending, signo)) {std::cout << "1";} else {std::cout << "0";}}std::cout << std::endl;
}// 先将2号信号进行屏蔽,然后不断获取pending表,然后向目标进程发送信号int main() {// 1.屏蔽二号信号sigset_t block, oblock;sigemptyset(&block);sigemptyset(&oblock); // 清空sigaddset(&block, SIGINT); // 添加信号sigprocmask(SIG_SETMASK, &block, &oblock);int cnt = 0;while (true) {// 获取pending集合sigset_t pending;sigpending(&pending);PrintPending(pending);sleep(1);cnt++;}return 0;
}
6.核心转储
Action中有些时Trem有些时Core,他们都是终止信号,但是有差异
Term:单纯的终止进程,没有多余的动作
;Core:先进性核心转储,再终止进程
;
核心转储及其作用
核心转储(core dump)是指在程序发生严重错误导致异常终止时,操作系统将程序的内存内容以及相关的调试信息保存到一个特殊的文件中,以供后续分析和调试使用。这个文件通常被称为核心转储文件或核心文件。
核心转储文件包含了程序崩溃时内存的快照,以及与进程相关的其他信息,如寄存器状态、调用栈、变量值等。这对于开发人员来说是非常有价值的,因为它提供了关于程序崩溃原因的详细信息,有助于识别和调试问题。
核心转储文件通常以 “core” 或者在某些系统中以进程ID为文件名的形式保存在程序当前工作目录或系统指定的核心转储文件目录中。它们对于排查由于程序错误、内存损坏或其他异常情况引起的问题非常有用。
有一些关键的概念和注意事项与核心转储相关:
ulimit 设置: 操作系统可能会设置 ulimit(用户资源限制)来限制核心转储文件的大小,以避免占用过多磁盘空间。
调试符号: 为了更好地解析核心转储文件,通常需要保留程序的调试符号。调试符号是编译时信息,包含了程序源代码的映射关系,有助于将内存地址映射回源代码。
调试工具: 使用调试工具(如gdb)可以加载核心转储文件,并允许开发人员分析崩溃时的状态、查看堆栈跟踪,以及检查变量值等。
产生核心转储: 在Unix-like系统中,可以通过在程序中调用 ulimit 设置允许生成核心转储文件,或者在终端运行程序时使用 ulimit -c unlimited 临时修改。