进程信号简述
01. 信号产生
生活中的信号类比(交通信号灯、警报),当产生这些信号时,我们会立马想到对应的动作。在Linux中,信号是事件发生对进程的通知机制亦称软件中断,由操作系统内核、进程本身或者其他进程向目标进程异步事件发送机制(即收到某种信号,并不会立马去执行)。通知进程发生某种预定义的时间,要求进程作出相应响应。信号与硬件中断相似之处在于会打断程序执行流程。
1.1 认识常见信号
- 硬件异常:
- 如内存越界(
SIGSEGV
) - 除零错误(
SIGFPE
) - 由内核自动生成
- 如内存越界(
- 系统调用:
kill()
:向指定进程发送信号raise()
:进程向自身发送信号alarm()
:设置定时器,超时后发送SIGALRM
- 终端输入:
Ctrl+C
:SIGINT
(终止进程)Ctrl+Z
:SIGTSTP
(暂停进程)Ctrl+\
:
- 软件条件:
- 子进程退出时,父进程收到
SIGCHLD
- 定时器到期(如
alarm()
)触发信号
- 子进程退出时,父进程收到
- 核心转储 :
- …
1.2 信号的分类
信号分为两大类。(编号1-31)为传统信号信号,内核向进程通知且递送一次,(编号34-64)为实时信号使信号按序递送。
1.3 信号处理方式
- 默认动作: 部分是终止自己,暂停等
- 忽略动作: 是一种信号处理的方式,只不过动作就是什么都不干
- 自定义动作: 使用signal方法修改信号的处理动作。(即用户程序员编写的函数:将默认动作转化为自定义动作)
1.4 改变信号处理方式
02. 信号阻塞
2.1 概念悉知
- 实际执行信号的处理动作称为信号递达(Delivery)
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
- 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
信号产生后会稍后递达给某进程,信号在产生和到达期间会一直处于pending等待状态。
2.2 信号在内核中的表示
每个进程都有一个信号屏蔽字亦称阻塞信号集,一个 sigset_t
类型的位图,每一位对应一个信号(如 SIGINT
、SIGQUIT
等)。用来标记哪些信号当前被阻塞。被阻塞的信号若已产生,会进入未决状态,直到阻塞被解除才会触发处理。位为1生效,0不生效。
2.2.1 block与pending之间联系
pending集是信号产生后的暂存区,block是控制信号是否能从pending暂存区递交给进程的开关
- block:决定哪些信号会被阻塞,近而将其放入未决信号集
- pending:记录已产生但未被处理的信号(因被阻塞或正在处理其他信号),本质是这个信号被暂存在
task_struct
信号为途中?
其中block
位图用于表示进程是否阻塞。pending
位图用于是否有信号写入(信号是否产生,信号产生时,内核在进程控制块中设置该信号的pending
位图,直到信号抵达才消失)。handler
函数指针数组用于进程执行何种动作(其中存放的是动作函数指针,每个信号的编号就是其数组下标)。
2.3 关键系统调用
2.3.1 信号集操作函数
sigset_t 本质上是一个位图,每一位代表一个信号。当某一位被设置为 1 时,表示对应的信号被包含在信号集中;为 0 则表示不包含。使用者只能调用以下函数来操作sigset_t
变量,不用关注内部数据。
在使用sigset_t
类型的变量之前,一定要调用sigemptyset()
函数初始化一个未包含任何成员的信号集或者sigfillset()
函数则初始化一个信号集,使其包含所有信号(包括所有实时信号)。
#include <signal.h>
int sigemptyset(sigset_t *set); // 清空信号集,置0
int sigfillset(sigset_t *set); // 填充所有信号, 置有效状态1?
信号集初始化后,可以分别使用 sigaddset()
和sigdelset()
函数向一个集合中添加或者移除单个信号。使用sigismember()
测试信号是否是信号集set
的成员。
#include <signal.h>
int sigaddset(sigset_t *set, int signo); // 添加单个信号
int sigdelset(sigset_t *set, int signo); // 删除单个信号
int sigismember(const sigset_t *set, int signo); // 判断信号是否存在
2.3.2 信号掩码(阻塞信号传递)
内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。如果将遭阻塞的信号发送给某进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数 :
how
:指定了sigprocmask()函数想给信号掩码带来的变化。SIG_BLOCK
:set包含了我们希望添加到当前信号屏蔽字的信号(逻辑或)SIG_UNBLOCK
:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号(逻辑与非)SIG_SETMASK
:设置当前信号屏蔽字为set所指向的值(直接赋值)
set
- 要操作的信号集,(为NULL则忽略)
oldset
- 保存旧的信号掩码(可为NULL)
返回值:成功返回 0,失败返回 -1(设置 errno
)
- 阻塞:信号暂存到未决信号集直到解除阻塞
- 忽略:直接丢弃
2.3.3 sigpending获取未决信号集
如果某进程接受了一个该进程正在阻塞的信号,那么会将该信号填加到进程的等待信号集中。当之后解除了对该信号的锁定时,会随之将信号传递给此进程。
#include <signal.h>
int sigpending(sigset_t *set);
作用: 返回处于等待状态的信号集,并将其置于 set
指向的sigset_t
结构中。
2.3.4 sigaction() - 设置信号处理
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
-
signum
: 目标信号 -
act
新的信号处理方式sa_handler
:信号处理函数指针(或SIG_IGN
、SIG_DFL
)。sa_mask
:处理该信号时额外阻塞的信号集。sa_flags
:控制选项(如SA_RESTART
、SA_NODEFER
)NULL
-
oldact
- 保存旧的处理配置(可为
NULL
)
- 保存旧的处理配置(可为
返回值:成功返回 0,失败返回 -1(设置 errno
)
struct sigaction {void (*sa_handler)(int); // 简单处理函数void (*sa_sigaction)(int, siginfo_t *, void *); // 高级处理函数sigset_t sa_mask; // 处理期间屏蔽的信号集int sa_flags; // 控制标志void (*sa_restorer)(void); // 内部使用(已废弃)
};
现在我们使用sigprocmask
函数和sigpending
函数完成一个函数。思路是
- 对2号信号写入信号集
- 将该信号集使用
sigprocmask
函数将2号
信号block住 - 我们键盘输入
ctrl+c
,产生2号
信号,发送至该进程。 - 使用
sigpending
函数输出pending位图,
因为我们将2号
进程block
住了,该信号并不能抵达。我们再向其发送2号
信号,信号被block住了并不会影响信号的写入。因此我们再发送2号
进程之前pending
位图应该全为0
,发送2号
进程之后会看见pending
位图发送由0
至1
的变化。
执行流程:
- 前 5 秒内,
SIGINT
(Ctrl+C)被阻塞,信号进入未决状态但不触发处理。 - 5 秒后解除阻塞,若之前有未决的
SIGINT
,会立即触发handle
函数。
#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
using namespace std;
static void handler(int signo){cout << signo << " 号信号确实递达了" << endl;//最终不退出进程
}void DisplayPending(const sigset_t pending){// 打印 pending 表int i = 1;while (i < 32){if (sigismember(&pending, i)) cout << "1";else cout << "0";i++;} cout << endl;
}
int main(){// 更改 2 号信号的执行动作signal(2, handler);// 创建信号集sigset_t set, oset;// 信号集清空 0sigemptyset(&set);sigemptyset(&oset);sigaddset(&set, 2); //将2号信号写入set// 设置当前进程的屏蔽信号集sigprocmask(SIG_BLOCK, &set, &oset);//0给进程// 死循环int n = 0;while (true){if (n == 5){// 采用 SIG_SETMASK 的方式,覆盖进程的 block 表sigprocmask(SIG_SETMASK, &oset, NULL); // 不接收进程的 block 表}// 获取进程的 未决信号集sigset_t pending;sigemptyset(&pending);int ret = sigpending(&pending);assert(ret == 0);(void)ret; // 避免 release 模式中出错DisplayPending(pending);n++;sleep(1);}return 0;
}
03. 信号处理
loading…