信号的响应有四种方式,下面来详细分析。
a、信号的屏蔽
屏蔽信号实际上就是暂缓对信号的响应,采用如下函数进行对信号的屏蔽:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数简析:
- how:操作命令字,比如阻塞、解除阻塞等
- set:当前要操作的信号集
- oldset:若为非空,则将原有阻塞信号集保留到该oldset中
注意到,该函数的操作参数不是单个信号,而是信号集(sigset_t),这意味着我们可以同时对多个信号设置阻塞或解除阻塞。
// 信号集操作函数组
#include <signal.h>
int sigemptyset(sigset_t *set); // 清空信号集set
int sigfillset(sigset_t *set); // 将所有信号加入信号集set中
int sigaddset(sigset_t *set, int signum); // 将信号signum添加到信号集set中
int sigdelset(sigset_t *set, int signum); // 将信号signum从信号集set中剔除
int sigismember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中
另外,how是具体的操作命令字,可以有如下取值:
- SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞)。
- SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号给清除)。
- SIG_UNBLOCK:解除set中的信号。
例如,想要对1、2号信号进行阻塞操作,首先要将这两个信号添加到一个信号集中,操作接口如下:
// 将1、2号信号加入信号集
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig, SIGHUP); // 加入1号信号
sigaddset(&sig, SIGINT); // 加入2号信号
// 阻塞1、2号信号
setprocmask(SIG_SETMASK, &sig, NULL);
b、信号的捕捉
所谓信号的捕捉,实际就是在信号到达之前,给信号关联一个指定的响应函数,让其到达之后自动运行该函数。
给信号指定关联函数的接口是:
#include <signal.h>
void (*signal(int sig, void (*func)(int) ) ) (int);
// 函数指针 函数
该函数接口比较复杂,下面是其返回值和参数详解:
- 返回值类型:void (*)(int);
- 返回值含义:返回一个指向原有的与指定信号关联的函数
- 参数:
- sig: 指定要关联的信号
- func:指定要关联的响应函数
注意到,使用这种方式关联的响应函数的接口是固定的,如下所示:
// 标准信号响应函数接口
void func(int sig)
{
// ...
}
显然,func中的参数sig就是触发该响应函数的信号,以下示例代码展示了如何捕捉信号SIGINT:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void func(int sig)
{
printf("捕获到信号:%d\n", sig);
}
int main(int argc, char **argv)
{
// 指定信号SIGINT关联函数
signal(SIGINT, func);
// 持续响应信号
while(1)
pause();
return 0;
}
提示:
- 函数pause会在信号响应结束后退出,为了让程序可以持续响应信号,上述程序将pause函数放在while循环中。
- 由于上述代码捕捉了SIGINT,因此按ctrl+c将无法中断程序,此时可以按ctrl+\(触发另一个信号SIGQUIT)来退出程序的无限循环。
c、信号的默认处理
如果程序没有对信号做任何预先准备,那么当信号达到时,则会按照信号的默认规则进行响应,具体默认规则可使用如下命令查阅:
gec@ubuntu:~$ man 7 signal # 会得到类似如下的表格:
gec@ubuntu:~$ man 7 signal
# 会得到类似如下的表格:
...
Signal Value Action Comment
──────────────────────────────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating-point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers; see pipe(7)
...
列表中的 Action 一列就是系统对信号的默认处理规则,m默认规则如下:
- Term:中断目标进程。
- Core:中断目标进程,且产生核心转储文件core。
- Stop:暂停目标进程,直到收到信号SIGCONT
- Cont:恢复目标进程运行
- Ign:忽略信号
其中需要说明的是:
- Term和Core都是中断程序,但Core处理方式还会产生转储文件core,core文件即程序在被中断的瞬间其内存映像的快照,用来给后续的调试提供追踪信息。但一般情况下系统是禁止生成所谓转储文件的,放开此项限制的命令是:
# 查看当前系统对 core 文件的限制
gec@ubuntu:~$ ulimit -a
core file size (blocks, -c) 0 # core 文件大小被限制为0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7635
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7635
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
gec@ubuntu:~$
# 将 core 文件的大小设置为“不限制”
gec@ubuntu:~$ ulimit -c unlimited
- Ign是默认就会被忽略的信号,典型的例子是SIGCHLD,此信号是子进程在状态转变时(比如变成僵尸时)自动发给其父进程的信号。
- SIGKILL和SIGSTOP这两个信号只能采取默认处理,不能阻塞、捕捉,也不能忽略。
d、信号的忽略
忽略信号就是直接将收到的信号丢弃,做法如下:
int main()
{
// 忽略信号SIGINT
signal(SIGINT, SIG_IGN);
}