一 信号是什么
信号就是程序中断的一种方式,属于软件中断。
中断就是终止当前的代码,转而执行其他的代码。中断有软件中断和硬件中断。
Unix/Linux系统中,可以使用信号中断当前代码,转而执行其他的代码。常见的信号:
ctrl+c、ctrl+\、段错误、总线错误、浮点数例外
int/0 浮点数例外(出错,程序被信号中断),double/0 无穷大 (不出错)
信号本质上就是一个整数(非负数),Unix常用信号1-48,Linux常用信号1-64。每个信号都有一个宏名称,便于记忆,宏名称都以SIG开头。比如:
SIGINT就是信号2,ctrl+c 就是信号2
信号是不连续的,有些信号是不存在的。不同的操作系统,对应信号的值有可能不同,因此在开发中,使用信号的宏名字有更好的通用性。
信号的产生是无规律的,不知道什么时候会来,因此对于信号的处理采用异步处理。
kill 命令就是针对信号,可以发送信号:
kill -信号 进程ID - 给某个进程发信号
kill -l 显示所有的信号
信号分为可靠信号和不可靠信号两类:
不可靠信号,1-31都是不可靠信号。这种信号不支持排队,有可能丢失。非实时信号。
可靠信号,34-64都是可靠信号。这种信号支持排队,不会丢失,是 实时信号。
二 信号的处理
Unix/Linux对于信号的处理,有三种方式:
1. 默认处理 - 80%的默认处理都是中断进程。
2. 忽略信号 - 可以忽略信号,不做处理。
3. 自定义处理 - 程序员写 信号处理的代码。
注:
信号9 不能被忽略,也不能被自定义处理。
普通用户只能给自己的进程发信号,但root可以给所用用户发信号。
1 signal()函数
信号的处理方式可以使用signal()/sigaction()注册。
函数指针 signal(int 信号,函数指针)
signal 可以设定信号的处理方式,第一个参数是哪个信号,第二个参数是信号的处理方式,包括:
SIG_IGN - 忽略该信号
SIG_DFL - 默认处理信号
一个程序员自定义的函数名 - 自定义处理信号
错误返回 SIG_ERR
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void fa(int signo){
printf("发送了信号%d\n",signo);
//signal(2,SIG_DFL);
}
int main(){
printf("pid=%d\n",getpid());
signal(2,fa);//信号2的处理方式 调用fa()
if(signal(3,SIG_IGN)==SIG_ERR)
perror("signal"),exit(-1);
signal(9,fa);//9的处理方式无法更改
while(1);
}//练习:修改代码,实现第一次信号2 打印
//从第二次开始,恢复到默认处理
2 子进程的信号处理
fork()创建的子进程完全沿袭父进程对信号的处理方式。父进程忽略子进程也忽略,父进程自定义子进程也自定义。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void fa(int signo){
printf("捕获到了信号%d\n",signo);
}
int main(){
signal(2,fa);
signal(3,SIG_IGN);
pid_t pid = fork();
if(pid == 0){
printf("子进程%d开始运行\n",getpid());
while(1);
}
printf("父进程退出\n");
}
vfork()+exec 创建的子进程,父进程忽略,子进程也会忽略,但父进程是自定义,子进程会改为默认处理。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void fa(int signo){
printf("捕获到了信号%d\n",signo);
}
int main(){
signal(2,fa);
signal(3,SIG_IGN);
pid_t pid = vfork();
if(pid == 0){
printf("子进程%d开始运行\n",getpid());
execl("./proc","proc",NULL);
}
printf("父进程退出\n");
}
proc.c
int main(){
while(1);
}
三 信号的发送
发信号的方式:
1 键盘发送 (少部分信号)
ctrl+c -> 2 SIGINT
ctrl+\ -> 3 SIGQUIT
ctrl+z -> 20 SIGSTP 暂停进程
2 出错 (少部分信号)
段错误 -> 11 SIGSEGV
总线错误 -> 7 SIGBUS
整数除0 -> 8 SIGFPE
3 命令kill (全部信号)
kill -信号 进程ID
信号0 用来测试是否有权限发信号
4 信号发送函数 (全部信号)
raise() kill() alarm() sigqueue() …
1 raise()函数
raise()函数 只能给自己所在的进程发信号。
sleep()和usleep()都可以休眠程序,区别:
sleep()被信号打断会返回剩余秒数,usleep()返回-1。sleep()休眠的单位是秒,usleep()是微秒。
#include <stdio.h>
#include <signal.h>
void fa(int signo){
printf("捕获了信号%d\n",signo);
}
int main(){
signal(SIGINT,fa);
int res = usleep(5000000);//sleep(5);
printf("res=%d\n",res);
raise(3);
while(1);
//pause();//暂停函数,有信号到来时解除暂停
}
2 kill() 函数
int kill(pid_t pid,int sig)
pid 的值可以是以下4种情况:
大于0 发给进程ID=pid的某个进程
= 0 发给和发送进程同组的所有进程
= -1 发给所有有发送权限的进程
< -1 发给进程组ID=-pid的进程
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void fa(int signo){
printf("捕获了信号%d\n",signo);
}
int main(){
pid_t pid = fork();
if(pid == 0){
signal(SIGINT,fa);
while(1);
}
printf("父进程给子进程%d发信号2\n",pid);
kill(pid,SIGINT);
}
killall 进程名称
可以杀死多个同名进程
3 alarm()函数
alarm(int sec)是闹钟函数,sec秒之后产生一个SIGALRM信号。
如果多次调用alarm(),后面的闹钟会替换前面的。
如果sec = 0 ,取消所有的闹钟
返回值:
如果alarm()时,以前设置的闹钟还没有执行,会返回之前的闹钟剩余的秒数;如果以前设置的闹钟执行了,或者没有设置,返回0.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void fa(int signo){
printf("捕获了信号%d\n",signo);
alarm(1);
}
int main(){
signal(SIGALRM,fa);
alarm(5);//闹钟函数,5秒以后发信号SIGALRM
printf("pid=%d\n",getpid());
sleep(2);
int res = alarm(10);
printf("res=%d\n",res);
//sleep(2);
res = alarm(0);//取消
//printf("res=%d\n",res);
while(1);
}