信号SIGNAL
信号出现在早期的Unix中,早期的信号模型是不可靠的。BSD和System V分别对早期信号进行扩展,但是互不兼容。POSIX统一了上述的两种模型,由此提供了可靠的信号模型。
1、信号的基本概念
- 用于进程之间的通信,可以中断进程的运行过程,改变处理流程
进程1 向 进程2 发送一个信号,进程2 停下当前工作,转而处理信号,执行相应的工作
- 信号是软件中断
- 信号是异步事件
进程1 发信号 给进程2,进程1 不用等待进程2回应,会继续执行自己的事情。
- 不可预见
- 信号有自己的名称和编号,如 SIGINT
- 信号和异常处理机制
查看linux系统中的信号的名称和编号,使用kill -l命令。
信号无优先级。
1~31 非实时信号,发送的信号可能会丢失,不支持信号排队。(linux操作系统中使用到,记录在/usr/include/bits/signum.h文件中,每个信号都有一定的解释)
34~64 实时信号,支持信号排队,发送的多个实时信号都会被接收。
- 信号发生的来源
硬件来源(由硬件驱动)
1. 按下键盘。CTRL+C CTRL+Z等等。
2. 者其他的一些硬件故障,由硬件驱动产生。
软件来源(由内核产生)
1. 常见的发送信号的系统函数:kill(),raise(),alarm(),settimer()等。
2. 一些非法运算等操作。除0,浮点数运算错误等等。
3. 软件设置条件(如gdb)。
2、信号的三个处理方法
2.1. 忽略信号 signal(signo,SIG_IGN)
SIGKILL和SIGSTOP永远不能被忽略,且不能被捕获。设置了也没用。
可以用于忽略硬件异常信号。
进程启动时,SIGUSR1和SIGUSR2两个信号默认被忽略。登记一下,可以被捕获处理。
2.2. 执行默认操作 signal(signo,SIG_DEF)
每个信号都有自己的默认动作,大部分信号默认动作是终止进程。
2.3. 捕获信号 signal(signo,func)
告诉内核出现信号时,调用自己的处理函数。signal 用于向内核登记信号和处理函数。
SIGKILL和SIGSTOP不能被捕获,且不能被忽略,只能执行默认操作。
2.4 signal函数
查看signal函数,使用man signal命令。
#include <signal.h>
void (*signal( int signo, void (*func)(int) ))(int);
功能:向内核登记安装信号处理函数。
返回:成功返回信号处理函数指针,错误返回SIG_ERR
参数:signo 要登记的信号值,func 信号处理函数指针 或者SIG_IGN忽略信号 或者SIG_DEF执行默认操作。
2.5 参考例子
对SIGTSTP和SIGINT 进行捕获登记
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
printf("%d,%d occured\n",getpid(),signo);
}
int main(void)
{
//向内核登记信号处理函数以及信号值
if(signal(SIGTSTP,sig_handler) == SIG_ERR){
perror("signal sigtstp error");
}
if(signal(SIGINT,sig_handler) == SIG_ERR){
perror("signal sigint error");
}
int i=0;
while(i<30)
{
printf("%d out %d\n",getpid(),i++);
sleep(1);//1秒
}
}
编译运行之后,键盘组合键 CTRL+C CTRL+Z 不再能够退出打断。
kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin
6116 out 0
6116 out 1
6116 out 2
6116 out 3
6116 out 4
6116 out 5
6116 out 6
6116 out 7
6116 out 8
6116 out 9
6116 out 10
^C6116,2 occured //CTRL+C 终止
6116 out 11
6116 out 12
6116 out 13
6116 out 14
^C6116,2 occured //CTRL+C 终止
6116 out 15
6116 out 16
6116 out 17
6116 out 18
^Z6116,20 occured //CTRL+Z 暂停
6116 out 19
6116 out 20
6116 out 21
6116 out 22
6116 out 23
6116 out 24
6116 out 25
^C6116,2 occured //CTRL+C 终止
6116 out 26
6116 out 27
6116 out 28
6116 out 29
2.6 信号还可以怎么使用
可以使用 kill -SIGCONT 进程号 ,来使得一个进程恢复运行。
也可以使用 kill -SIGSTP 进程号,来暂停 一个进程。和CTRL+Z功能一样。
signal(SIGINT,SIG_IGN); //可以用来忽略CTRL+C信号
signal(SIGINT,SIG_DFL); //CTRL+C信号按照默认的方式,用来终止进程
2.7 SIGCHLD信号
在学习进程时讲到,为了避免僵尸进程,有两种方式关闭子进程。一是父进程使用wait() 阻塞等待子进程结束并回收。二是父进程结束,由1号进程领养子进程并回收。
这里我们还可以使用SIGCHILD信号来及时回收子进程。这样可以让父进程继续运行,不用等待子进程结束。
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
printf("child processs dead, signo:%d\n",signo);
//当子进程结束时,会产生SIGCHID信号。父进程捕获并回收(wait)子进程资源。
wait(0);
}
void out(int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d out %d\n",getpid(),i);
sleep(2);
}
}
int main(void)
{
//向内核登记信号处理函数以及信号值
if(signal(SIGCHLD,sig_handler) == SIG_ERR){
perror("signal sigchld error");
}
pid_t pid = fork();
if(pid<0){
perror("fork error");
exit(1);
}else if(pid>0){//父进程
out(100);
}else if(pid==0){//子进程
out(10);
}
}
运行结果:
kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin
9078 out 0
9079 out 0
9078 out 1
9079 out 1
9078 out 2
9079 out 2
9078 out 3
9079 out 3
9078 out 4
9079 out 4
9078 out 5
9079 out 5
9078 out 6
9079 out 6
9078 out 7
9079 out 7
9078 out 8
9079 out 8
9078 out 9
9079 out 9
9078 out 10
child processs dead, signo:17
9078 out 11
9078 out 12
9078 out 13
9078 out 14
9078 out 15
^C
kshine@kshine-virtual-machine:~/桌面$
3. 信号发送
3.1 基本概念
- 除了内核和超级用户root,并不是所有进程都可以向其他进程发送信号。
- 一般的进程 只能想相同的 uid(用户ID,同一个用户创建的) 和gid(用户组ID,属于同一个用户组) 的进程发送信号,或者向相同进程组的其他进程发送信号。
3.1.1 发送信号的函数
- kill() 向其他进程或者自己发送信号 ,可以使用man 2 kill 查看函数介绍。
#include <sys/types.h>
#include <signal.h>
/*
sig 可以为0 空信号,用来检测特定的信号是否存在。
pid 进程编号。
(1)pid>0 进程ID
(2)pid ==0,将信号发送给同一进程组的所有进程。
(3)pid < 0 将信号发送给进程组ID等于pid的绝对值的所有进程
(4)pid==-1 将信号发送给 发送进程有权限发送信号的系统上的所有进程。
*/
int kill(pid_t pid, int sig);
- raise() 只能向自己发送信号
#include <signal.h>
int raise(int sig);
参考例子:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
printf("signo:%d\n",signo);
}
int main(void)
{
//向内核登记信号处理函数以及信号值
if(signal(SIGUSR1,sig_handler) == SIG_ERR){
perror("signal sigusr1 error");
}
if(signal(SIGUSR2,sig_handler) == SIG_ERR){
perror("signal sigusr2 error");
}
int i=0;
while(i<10){
printf("%d out %d\n",getpid(),i++);
//if(i==5) kill(getpid(),SIGKILL); //自己杀死自己
sleep(1);
}
//向进程自己发送信号
raise(SIGUSR1);
kill(getpid(),SIGUSR2);
}
kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin
9567 out 0
9567 out 1
9567 out 2
9567 out 3
9567 out 4
9567 out 5
9567 out 6
9567 out 7
9567 out 8
9567 out 9
signo:10
signo:12
kshine@kshine-virtual-machine:~/桌面$
- alarm() 定时信号
设置定时器,当定时器超时,产生SIGALRM信号。
不是周期性的定时信号,是一次性的信号。
精确度只到秒,要想使用更高的精度可以使用 ualarm()函数。
#include <unistd.h>
/*
seconds ==0 时,可以取消以前设置的定时器。
*/
unsigned int alarm(unsigned int seconds);
参考例子:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <math.h>
//定义信号处理函数
//signo:进程捕获到的信号
void sig_handler(int signo)
{
if(signo == SIGALRM){
printf("clock time out\n");
//重新设置定时器(周期性定时)
alarm(5);
}
}
void out(void)
{
int i=0;
while(i<20)
{
double d = drand48();
printf("%-10d:%lf\n",i++,d);
//if(i==16) alarm(0);//取消定时器
sleep(1);
}
}
int main(void)
{
//向内核登记信号处理函数以及信号值
if(signal(SIGALRM,sig_handler) == SIG_ERR){
perror("signal sigalrm error");
}
//设置定时器
alarm(5);
printf("begin running main\n");
out();
printf("end running main\n");
return 0;
}
运行结果:
kshine@kshine-virtual-machine:~/桌面$ gcc test.c -o bin
kshine@kshine-virtual-machine:~/桌面$ ./bin
begin running main
0 :0.000000
1 :0.000985
2 :0.041631
3 :0.176643
4 :0.364602
clock time out
5 :0.091331
6 :0.092298
7 :0.487217
8 :0.526750
9 :0.454433
clock time out
10 :0.233178
11 :0.831292
12 :0.931731
13 :0.568060
14 :0.556094
clock time out
15 :0.050832
16 :0.767051
17 :0.018915
18 :0.252360
19 :0.298197
clock time out
end running main
kshine@kshine-virtual-machine:~/桌面$
- settimer()
- abort() 异常终止 产生SIGABRT
4. 高阶提升
4.1 信号的可靠性
- 不可靠信号问题一
发生信号时,关联动作中,再次绑定该信号。
- 在进入关联动作 与关联动作中再次调用signal绑定之间的时间内,信号可能会丢失,即同样的信号不会被捕获
- 导致进程终止
- 不可靠信号问题二
无法暂停阻塞信号。