目录
三、信号(signal)
3.1 概念
信号本质上是一种软件中断
软件触发的中断、和硬件的处理机制一样
当收到信号时, 停下来手头的事情,去处理信号处理函数,处理完后,再继续做原来的事情
信号处处都在
其实我们已经看到过
1、程序在运行时, 我们想退出, 按“ctrl + c”时就已经发送了一个信号出去
2、段错误
3、除0异常信号(代码中出现整数除以0)
4、杀死一个进程的指令“kill -9 pid” , kill 就是 发送信号的指令, -9 是9号信号, pid是进程号
5、子进程结束后, 发送信号给父进程,来回收它的资源
如果你觉得只有那么一点, 那就错了
来!
执行下面这个指令, 可以查看本系统支持的所有的信号
kill -l
Linux 有64个信号
每个信号会给一个宏名, 开发中, 尽量使用宏名, 不使用数字。宏名以 SIG开头, 例如9号信号的名字 为 SIGKILL
Linux 的信号分两类: 可靠信号(支持排队) 、 不可靠信号(不支持排队)
当一个进程同时收到多个相同信号时, 可靠信号都可以收到 , 而不可靠信号会丢失
1-31属于不可靠信号, 34 -64属于可靠信号。
3.2 信号的处理
当一个进程收到信号的时候
它有三种处理方式:
1 ) 默认(缺省)处理
默认处理, 而大多数信号的默认处理方式为 退出进程
2 ) 忽略信号
收到的信号不做任何处理
3 ) 捕获信号
收到信号后会去执行信号处理函数,而这个函数可由程序猿自己编写
Linux提供了一个函数来设置信号的处理方式 : signal 函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数如下:
signum - 设置哪个信号
handler - 信号的处理方式
SIG_DFL - 默认处理
SIG_IGN - 忽略信号
函数 - 捕获信号
前面两种方式忽略不讲, 主要讲解第三种方式, 即
当设置为 捕获信号 的方式时
需要自己额外写个函数(信号处理函数)
收到信号后, 程序会停下当前的事情去处理这个函数
信号处理函数 如何定义?
它的格式为 无返回值, 一个int 型的参数, 函数名自定义
代码如下(signal、捕获信号方式):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void func(int sig)
{
printf("sig %d coming!\n",sig);
}
int main()
{
printf("pid = %d\n",getpid());
//默认处理
//signal(SIGINT,SIG_DFL);
//忽略信号
//signal(SIGINT,SIG_IGN);
//signal(SIGKILL,SIG_IGN);//忽略信号9是无效的, 如果这样的话, 电脑病毒不就很嚣张了?关不掉
//捕获信号
signal(SIGINT,func);
while(1);
return 0;
}
把程序运行起来, 键盘按“ctrl + c”, 即发送了信号2 (SIGINT), 等价于指令“kill -2 pid”
输出结果如下:
pid = 3000
^Csig 2 coming!
但并不是所有的信号都可以处理,有两个特殊的信号
信号9(SIGKILL)和信号19(SIGSTOP)既不能忽略, 也不能捕获, 只能默认处理
信号9默认退出
信号19默认暂停
另外
假如父进程的信号处理方式设置好了
那么它的子进程是如何继承父进程的信号处理方式呢?
1、fork / vfork 创建的子进程, 是完全继承父进程的处理方式
2、(fork / vfork) + exec系列函数创建的子进程, 只有捕获信号的方式不继承,由于exec替换了进程内容,这个函数就不存在了
关于fork/vfork/exec 多进程编程 在之前的博客有系统进行讲解(链接)
3.3 信号的发送
1、用键盘发送信号
ctrl + c ---> 信号2(SIGINT)
ctrl + \ ---> 信号3(SIGQUIT)
ctrl + z ---> 信号20(SIGTSTP)
2、硬件故障、程序错误
段错误
总线错误
除以0
3、kill/killall 命令
4、通过函数发送信号
4.1 kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
给进程号为pid的进程发送sig信号, 代码如下
/*
这个程序是用来杀死其它进程的
执行这个程序的时候,应该这么执行:
./可执行文件 pid
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(int argc,char **argv)
{
printf("pid = %d\n",getpid());
if(argc!=2){
printf("Usage:%s PID\n",argv[0]);
exit(-1);
}
kill(atoi(argv[1]),SIGINT);
return 0;
}
4.2 raise 函数 给自己发送信号
4.3 alarm 函数 让进程等待一段时间(second) , 然后发送指定信号(14)SIGALRM给自己。
参数为设置的秒数。
返回值为 当前存在闹钟剩余的时间。
SIGALRM 默认让进程结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void fuc(int sig)
{
printf("Remember to fetch vegetable!\n");
alarm(1);
}
int main()
{
signal(SIGALRM,fuc);
alarm(10);
while(1);
return 0;
}
4.4 pause 函数 会导致进程睡眠, 不再使用cpu, 和while( 1) 有本质区别。
功能是等待, 只有非忽略信号( 捕获信号或者终止信号)才会唤醒。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void func(int sig)
{
printf("收到信号%d!\n",sig);
}
int main(int argc,char **argv)
{
signal(SIGINT,func);
printf("pid = %d\n",getpid());
//等待一个非忽略信号来唤醒进程,此处唤醒的信号为SIGINT
int res = pause();
printf("res = %d\n",res);
return 0;
}
3.4 信号的屏蔽
信号会打断原来的程序 去执行其它程序。
但是在执行关键代码( 例如初始化)时, 收到信号被打断 ,可能会导致错误的发生。
进程无法阻止信号的到来, 但是可以暂时不处理它, 即屏蔽信号, 就是信号到来后暂时不处理, 当关键代码执行完毕后, 再解除屏蔽, 最后再去处理来了的信号。
信号屏蔽中使用了信号集
信号集
就是一个信号的集合
类型 sigset_t (我前面也有提到 ,什么什么下划线t的数据类型都是正整数)
这个数的二进制形式中, 每一位对应一个信号(0表示没有、1表示有)
通过这个数, 就可以记录要屏蔽的信号了
Linux也提供了相关的函数接口:
我们通过代码熟悉一下函数的用法:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main()
{
printf("sigset_t有%d 字节\n",sizeof(sigset_t));
sigset_t set;
sigemptyset(&set);//清空
printf("%lld\n",set);
//添加信号
sigaddset(&set,SIGINT);//0000 0010 信号2记录在了第2位(置1)
printf("%lld\n",set);
sigaddset(&set,SIGQUIT);//0000 0110 信号3记录在了第3位(置1)
printf("%lld\n",set);
sigaddset(&set,6);//0010 0110 信号6记录在了第6位(置1)
printf("%lld\n",set);
//删除信号
sigdelset(&set,SIGQUIT);//将第3位 复位为0
printf("%lld\n",set);//0010 0010 34
//查找信号
if(sigismember(&set,SIGINT))
printf("信号集中有信号2!\n");
if(sigismember(&set,SIGQUIT))
printf("信号集中有信号3!\n");
return 0;
}
我们学会了记录, 但这还没完
我们要利用它来开始屏蔽信号啦
屏蔽信号期间, 是可以收信号, 但是是不处理的,等屏蔽结束后才会去处理
屏蔽信号期间, 假如收到多个同样的信号, 等屏蔽结束后, 相当于同时一起来. 这对于可靠信号来说, 它是支持排队的, 有多少就处理多少, 而对于不可靠信号来说, 这会丢失信号
代码如下,当执行普通代码时, 可以收到指定信号, 但是执行关键代码时需要屏蔽,屏蔽期间收到的指定信号不处理,屏蔽结束后再去处理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//自定义信号处理函数
void func(int sig)
{
printf("捕获了信号%d\n",sig);
}
int main()
{
printf("pid = %d\n",getpid());
//设置信号处理方式 为捕获信号
signal(SIGINT,func);
signal(45,func);
//假设执行普通代码
printf("执行普通代码,不屏蔽信号!\n");
sleep(8);//sleep休眠期间, 不占cpu,但是一旦非忽略信号来,便唤醒, 不再休眠(不再等待)
printf("普通代码执行完毕!\n");
//记录要屏蔽的信号
sigset_t set,oldset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,45);
//开始屏蔽
printf("执行关键代码,信号屏蔽!\n");
sigprocmask(SIG_SETMASK,&set,&oldset);
sleep(6);
//屏蔽期间可以查看收到的信号
sigset_t ar_set;
sigpending(&ar_set);
if(sigismember(&ar_set,SIGINT))
printf("收到了信号2\n");
if(sigismember(&ar_set,45))
printf("收到了信号45\n");
sleep(6);
//结束屏蔽
printf("关键代码执行完毕,解除信号屏蔽!\n");
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1);
return 0;
}