目录
1.基本概念
什么是信号:
信号是内容受限的一种异步通信机制
(1)信号的目的:用来通信(进程与进程之间的通信)
(2)信号是异步的(对比硬件中断),不确定信号到来时间和内容
(3)信号本质上是int型数字编号(事先定义好的)
信号由谁发出:
(1)用户在终端按下按键
(2)硬件异常后由操作系统内核发出信号
(3)用户使用kill命令向其他进程发出信号
(4)某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write时会产生SIGPIPE信号
信号由谁处理、如何处理:
(1)忽略信号
(2)默认处理(当前进程没有明显的管这个信号,默认:忽略或终止进程)
(3)捕获信号(信号绑定了一个函数)
常用的信号的编号及简介:
编号 | 名称 | 默认动作 | 对应事件 |
---|---|---|---|
2 | SIGINT | 终止 | 用户输入 ctrl+c |
9 | SIGKILL | 终止 | 终止程序(不能重写或忽略) |
11 | SIGSEGV | 终止且 Dump | 段冲突 Segmentation violation |
14 | SIGALRM | 终止 | 时间信号 |
17 | SIGCHLD | 忽略 | 子进程停止或终止 |
2.发送信号
进程组:
每个进程都都只属于一个进程组
//返回当前进程的进程组
pid_t getpgrp(void);
//改变一个进程的进程组 -- 若成功返回 0 ,若错误则为 -1
int setpgid(pid_t pid, pid_t pgid);
用 /bin/kill 程序发送信号:
# 发送信号 9 (SIGKILL)给进程组15213
/bin/kill -9 15213
从键盘发送信号:
通过键盘让内核向每个前台进程发送 SIGINT(SIGTSTP) 信号
Ctrl+C (SIGINT) #默认终止进程
Ctrl+Z (SIGTSTP) #默认挂起进程
用alarm函数发送信号:
进程可以调用 alarm 函数向它自己发送 SIGALARM 信号
alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程
unsigned int alarm(unsigned int seconds);
用kill函数发送信号:
//使用kill发送信号号码 sig 给进程 pid
int kill(pid_t pid, int sig);
如下程序为 父进程发送 SIGKILL 信号给它的子进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
/* Child sleeps until SIGKILL signal received, then dies */
if ((pid = fork()) == 0)
{
pause(); /* Wait for a signal to arrive */
printf("control should never reach here!\n");
exit(0);
}
/* Parent sends a SIGKILL signal to a child */
kill(pid, SIGKILL);
exit(0);
}
3.接收信号
每个信号类型都有一个预定义的『默认动作』,可能是以下的情况:
- 终止进程
- 终止进程 并 转储内存(dump core)
- 停止进程,收到
SIGCONT
信号之后重启 - 忽略信号
signal
函数可以修改默认的动作,函数如下:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal 函数可以通过下列三种方法之一来改变和信号 signum 相关联的行为:
- 如果 handler 是 SIG_IGN,那么忽略类型为 signum 的信号
- 如果 handler 是 SIG_DFL,那么类型为 signum 的信号行为恢复为默认行为
- 否则,handler就是用户定义的函数的地址,这个函数被称为 信号处理程序,只要进程接收到一个类型为signum,就会调用这个程序。
如下程序为捕获用户在键盘上的输入Ctrl + C时发送的SIGINT信号,SIGINT的默认行为是立即终止该程序,在这个程序中,我们将默认行为修改为捕获信号,输出一条消息,然后终止该进程。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigint_handler(int sig) /* SIGINT handler */
{
printf("\nCaught SIGINT!\n");
exit(0); //终止进程
}
int main()
{
/* Install the SIGINT handler */
if (signal(SIGINT, sigint_handler) == SIG_ERR)
printf("signal error");
pause(); /* Wait for the receipt of a signal */
return 0;
}
执行程序,按下Ctrl + C,输出:
^C
Caught SIGINT!
4.阻塞信号
内核会阻塞与当前在处理的信号同类型的其他正待等待的信号,也就是说,一个 SIGINT 信号处理器是不能被另一个 SIGINT 信号中断的。(阻塞相同信号)
如果想要显式阻塞(阻塞特定信号),就需要使用sigprocmask
函数了,以及其他一些辅助函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:
SIG_BLOCK 把 set 中的信号添加到 blocked 中
SIG_UNBLOCK 从 blocked 中删除 set 中的信号
SIG_SETMASK 阻塞信号集设置为参数集。
#其他辅助函数
sigemptyset - 创建空集
sigfillset - 把所有的信号都添加到集合中(因为信号数目不多)
sigaddset - 添加指定信号到集合中
sigdelset - 删除集合中的指定信号
我们可以用下面这段代码来临时阻塞特定的信号:
sigset_t mask, prev_mask;
Sigemptyset(&mask); # 初始化 set 为空集合
Sigaddset(&mask, SIGINT); # 把 SIGINT 信号加入 set 中
# 阻塞对应信号,并保存之前的集合作为备份
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);
...
... # 这部分代码不会被 SIGINT 中断
...
# 取消阻塞信号,恢复原来的状态
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);