信号(signal)
信号是一种软件中断的方式,也是Unix/Linux系统最常用的软件中断方式。
中断就是中止当前正在执行的代码,转而去执行其他代码。中断分为软件中断和硬件中断。
死循环了,正常的代码就是 死循环
while(1) printf("zhangfei\n");
中断死循环 - 信号中断(ctrl+c -> 信号2)
信号本质就是一个非负整数,Unix系统信号到48,Linux系统信号到64,中间不保证连续。信号为了更好的辨识,都定义了一个宏名称,编程时要求使用宏名称,而不是整数。宏名称都以SIG开头(信号signal)。
kill 命令用来发送信号。
kill -9 PID 其实是给进程发9信号,而9信号本身是用来杀死进程的。
kill -l 可以查看当前系统所有的信号。
常见信号:
信号2 SIGINT ctrl+c
信号3 SIGQUIT ctrl+\
信号9 SIGKILL 杀进程的
段错误、总线错误、整数除以0都是发送信号终止程序。
SIGBUS 对应的值 在不同系统中不同,因此编程时使用 SIGBUS(宏名称) 有更好的通用性
信号分为可靠信号和不可靠信号,早期的信号都是不可靠信号。
不可靠信号,Linux系统中,1-31都是不可靠信号,不可靠信号特点不支持排队,因此当多个信号堆积时,会造成信号的丢失。非实时信号。
可靠信号,Linux系统中,34-64都是可靠信号,可靠信号特点支持排队,不会丢失信号。实时信号。
信号的处理方式:
1 默认处理,每个信号都有默认处理,80%的默认处理都是 退出进程。
2 忽略信号,信号来了不做处理。
3 自定义信号处理函数,然信号处理时执行程序员的代码。
4 信号9无法忽略,无法自定义。
5 信号受用户的限制,当前用户只能给自己的进程发信号,但root可以给所有进程发信号。
如何设置信号的处理方式? - 信号注册
signal() - 重点
sigaction() - 了解
让某一个信号执行某一个函数(自定义)
signal(int signum,函数指针)
参数:signum 就是信号
函数指针 就是自定义的函数,格式如下:
void (*fa) (int)
函数指针也可以用宏做参数:
SIG_DFL - 采用默认处理
SIG_IGN - 采用忽略处理
返回函数指针,出错 返回 SIG_ERR。
子进程的信号处理和父进程之间的关系?
fork()创建的子进程完全继承父进程对信号的处理方式,父进程自定义处理函数,子进程也使用相同的处理函数,父进程忽略,子进程也忽略。
vfork()+execl()创建的子进程部分继承父进程对信号的处理方式,默认和忽略继承,如果父进程自定义处理函数,子进程会改为默认(因为excel()会使用全新的代码区)。
发送信号的方式:
1 键盘发送(部分信号)
ctrl+c -> 信号2
ctrl+\ -> 信号3
2 出错,系统发送(部分信号)
段错误 -> 信号11
总线错误 -> 信号7
3 kill 命令发送(所有信号)
kill -信号 进程的PID
4 系统提供了信号发送函数,比如:
raise() kill() alarm() sigqueue()
重点是kill()
raise() 只能给本进程(自己)发信号
kill() 可以给所有进程发信号,但发送进程得有发送权限(受用户影响)。
kill -0 进程PID 可以测试是否有发送信号的权限。信号0 是用于测试,本身不做任何的处理。
sleep(int n) - 休眠n秒,但会被信号打断,并返回剩余秒数。
usleep()也是用于休眠,单位是微秒,1000000微秒 = 1秒。usleep()的手册有问题。
kill(pid_t pid,int signo)
参数:signo 就是信号
pid就是发送给哪些进程,值有4种情况:
>0 发送给指定的进程(pid对应进程)
-1 发送给所有的进程,只要有权限
0 发送给本组的所有进程
<-1发送给指定进程组中的所有进程(进程组ID 等于 -pid)
常用的是 大于0 。
练习: kill()函数的使用
用fork()创建一个子进程,然后父进程发信号2给子进程,子进程自定义信号2的处理函数。信号处理函数参考fa()即可。子进程不要退出,父进程发完信号后退出。
alarm()严格来说,不算信号发送函数。
alarm()通过信号SIGALRM实现闹钟的功能,闹钟就是n秒之后,产生一个信号,完成某项工作。当alarm()设置闹钟时,如果之前有没有完成的闹钟,会替换没有完成的闹钟(之前的闹钟就不会再产生信号了),并返回之前的闹钟的剩余秒数。
alarm(0)是取消所有闹钟。
信号集 - 信号组成的集合,本质是个超大的整数,信号集的类型 sigset_t。集合提供的必须函数:
1 初始化函数
2 增加元素函数
3 删除元素函数
4 查找元素函数
sigemptyset(sigset_t* set)-清0(无信号)
sigfillset(sigset_t* set)-置1(满信号)
新增信号/删除信号
sigaddset(sigset_t*,int signo)
sigdelset(sigset_t*,int signo)
查找信号在不在信号集中
sigismember(sigset_t*,int signo)
一个二进制位代表一个信号的有无,二进制的倒数第n位 代表信号n。
信号集应用 - 信号屏蔽
信号无法确定 什么时候来,程序员 控制不了 信号 什么时候来。
当程序在执行关键代码时,如果信号来了可能会造成严重的后果,信号没法控制不要来,可以采用信号屏蔽技术让信号来,但不做处理,等解除屏蔽后再处理。
信号屏蔽时,信号放入 信号集中再屏蔽。
sigprocmask()函数用于屏蔽信号
int sigprocmask(int how,
sigset_t* new,sigset_t* old)
参数:new 是 需要屏蔽的信号所在信号集
old是传出参数,会把之前的屏蔽带出来
当信号屏蔽解除时,再把old放回去即可
how有三个宏:
SIG_BLOCK: A B C + C D E->A B C D E
SIG_UNBLOCK:A B C - C D E->A B
SIG_SETMASK:A B D = C D E->C D E
一般使用SIG_SETMASK。
返回 -1 代表错误。
信号是一种软件中断的方式,也是Unix/Linux系统最常用的软件中断方式。
中断就是中止当前正在执行的代码,转而去执行其他代码。中断分为软件中断和硬件中断。
#include <stdio.h>
#include <signal.h>
void fa(int signo){//信号处理函数,注册后生效
//以后开发信号处理函数中,代码很复杂
printf("捕获了信号%d\n",signo);
}
int main(){
printf("pid=%d\n",getpid());
signal(SIGINT,fa);//信号2处理方式就是调用fa()
signal(SIGQUIT,SIG_IGN);//信号3被忽略
//signal(SIGKILL,fa);//信号9无法忽略和自定义
while(1);
}
死循环了,正常的代码就是 死循环
while(1) printf("zhangfei\n");
中断死循环 - 信号中断(ctrl+c -> 信号2)
信号本质就是一个非负整数,Unix系统信号到48,Linux系统信号到64,中间不保证连续。信号为了更好的辨识,都定义了一个宏名称,编程时要求使用宏名称,而不是整数。宏名称都以SIG开头(信号signal)。
kill 命令用来发送信号。
kill -9 PID 其实是给进程发9信号,而9信号本身是用来杀死进程的。
kill -l 可以查看当前系统所有的信号。
常见信号:
信号2 SIGINT ctrl+c
信号3 SIGQUIT ctrl+\
信号9 SIGKILL 杀进程的
段错误、总线错误、整数除以0都是发送信号终止程序。
SIGBUS 对应的值 在不同系统中不同,因此编程时使用 SIGBUS(宏名称) 有更好的通用性
信号分为可靠信号和不可靠信号,早期的信号都是不可靠信号。
不可靠信号,Linux系统中,1-31都是不可靠信号,不可靠信号特点不支持排队,因此当多个信号堆积时,会造成信号的丢失。非实时信号。
可靠信号,Linux系统中,34-64都是可靠信号,可靠信号特点支持排队,不会丢失信号。实时信号。
信号的处理方式:
1 默认处理,每个信号都有默认处理,80%的默认处理都是 退出进程。
2 忽略信号,信号来了不做处理。
3 自定义信号处理函数,然信号处理时执行程序员的代码。
4 信号9无法忽略,无法自定义。
5 信号受用户的限制,当前用户只能给自己的进程发信号,但root可以给所有进程发信号。
如何设置信号的处理方式? - 信号注册
signal() - 重点
sigaction() - 了解
让某一个信号执行某一个函数(自定义)
signal(int signum,函数指针)
参数:signum 就是信号
函数指针 就是自定义的函数,格式如下:
void (*fa) (int)
函数指针也可以用宏做参数:
SIG_DFL - 采用默认处理
SIG_IGN - 采用忽略处理
返回函数指针,出错 返回 SIG_ERR。
子进程的信号处理和父进程之间的关系?
fork()创建的子进程完全继承父进程对信号的处理方式,父进程自定义处理函数,子进程也使用相同的处理函数,父进程忽略,子进程也忽略。
vfork()+execl()创建的子进程部分继承父进程对信号的处理方式,默认和忽略继承,如果父进程自定义处理函数,子进程会改为默认(因为excel()会使用全新的代码区)。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void fa(int signo){
printf("捕获了信号%d\n",signo);
}
int main(){
signal(SIGINT,fa); // 信号2自定义
signal(SIGQUIT,SIG_IGN);//信号3被忽略
pid_t pid = vfork();
if(pid == 0){
printf("pid=%d\n",getpid());
execl("./proc","proc",NULL);
}
printf("父进程结束\n");
return 0;
}
发送信号的方式:
1 键盘发送(部分信号)
ctrl+c -> 信号2
ctrl+\ -> 信号3
2 出错,系统发送(部分信号)
段错误 -> 信号11
总线错误 -> 信号7
3 kill 命令发送(所有信号)
kill -信号 进程的PID
4 系统提供了信号发送函数,比如:
raise() kill() alarm() sigqueue()
重点是kill()
raise() 只能给本进程(自己)发信号
kill() 可以给所有进程发信号,但发送进程得有发送权限(受用户影响)。
kill -0 进程PID 可以测试是否有发送信号的权限。信号0 是用于测试,本身不做任何的处理。
sleep(int n) - 休眠n秒,但会被信号打断,并返回剩余秒数。
usleep()也是用于休眠,单位是微秒,1000000微秒 = 1秒。usleep()的手册有问题。
kill(pid_t pid,int signo)
参数:signo 就是信号
pid就是发送给哪些进程,值有4种情况:
>0 发送给指定的进程(pid对应进程)
-1 发送给所有的进程,只要有权限
0 发送给本组的所有进程
<-1发送给指定进程组中的所有进程(进程组ID 等于 -pid)
常用的是 大于0 。
练习: kill()函数的使用
用fork()创建一个子进程,然后父进程发信号2给子进程,子进程自定义信号2的处理函数。信号处理函数参考fa()即可。子进程不要退出,父进程发完信号后退出。
alarm()严格来说,不算信号发送函数。
alarm()通过信号SIGALRM实现闹钟的功能,闹钟就是n秒之后,产生一个信号,完成某项工作。当alarm()设置闹钟时,如果之前有没有完成的闹钟,会替换没有完成的闹钟(之前的闹钟就不会再产生信号了),并返回之前的闹钟的剩余秒数。
alarm(0)是取消所有闹钟。
信号集 - 信号组成的集合,本质是个超大的整数,信号集的类型 sigset_t。集合提供的必须函数:
1 初始化函数
2 增加元素函数
3 删除元素函数
4 查找元素函数
sigemptyset(sigset_t* set)-清0(无信号)
sigfillset(sigset_t* set)-置1(满信号)
新增信号/删除信号
sigaddset(sigset_t*,int signo)
sigdelset(sigset_t*,int signo)
查找信号在不在信号集中
sigismember(sigset_t*,int signo)
#include <stdio.h>
#include <signal.h>
void fa(int signo){
printf("捕获到了信号%d\n",signo);
}
int main(){
signal(SIGINT,fa); signal(SIGQUIT,fa);
signal(50,fa);
printf("pid=%d\n",getpid());
printf("执行普通代码,没有屏蔽信号\n");
sleep(15);
printf("执行关键代码,开始信号屏蔽\n");
sigset_t set,old;//set屏蔽,old解除
sigemptyset(&set); sigaddset(&set,50);
sigaddset(&set,2); sigaddset(&set,3);
sigprocmask(SIG_SETMASK,&set,&old);//开始屏蔽
sleep(15); printf("屏蔽被解除\n");
sigprocmask(SIG_SETMASK,&old,0);//解除屏蔽
}//50是可靠信号,所以不丢失;2 3是不可靠信号 会丢失
一个二进制位代表一个信号的有无,二进制的倒数第n位 代表信号n。
信号集应用 - 信号屏蔽
信号无法确定 什么时候来,程序员 控制不了 信号 什么时候来。
当程序在执行关键代码时,如果信号来了可能会造成严重的后果,信号没法控制不要来,可以采用信号屏蔽技术让信号来,但不做处理,等解除屏蔽后再处理。
信号屏蔽时,信号放入 信号集中再屏蔽。
sigprocmask()函数用于屏蔽信号
int sigprocmask(int how,
sigset_t* new,sigset_t* old)
参数:new 是 需要屏蔽的信号所在信号集
old是传出参数,会把之前的屏蔽带出来
当信号屏蔽解除时,再把old放回去即可
how有三个宏:
SIG_BLOCK: A B C + C D E->A B C D E
SIG_UNBLOCK:A B C - C D E->A B
SIG_SETMASK:A B D = C D E->C D E
一般使用SIG_SETMASK。
返回 -1 代表错误。
在信号屏蔽时,如果来的是可靠信号,发送几次,解除屏蔽后处理几次;如果来的是不可靠信号,发送几次,都只处理一次。相同的不可靠信号在屏蔽期间 会丢失。
sigaction()函数是signal()的增强版,因此sigaction()过于复杂,Unix/Linux系统把它的主体功能抽取出来,做了signal()函数。
sigaction()比signal()强在 它能额外的传参,还能拿到信号更多的信息。
基于信号的计时器(定时器)
Linux系统对每个进程都支持3种计时器:
真实计时器、虚拟计时器、实用计时器
真实计时器最常用,存储程序运行的真实总时间。
计时器就是M秒之后每隔N秒就产生一个信号,完成某种功能。计时器有一个开始时间,会产生第一个信号;还有一个间隔时间,第一个信号之后的每个信号都是间隔时间决定。
计时器相关函数:
setitimer()、getitimer()
setitimer(int which,struct itimerval*
time,struct itimerval* oldtime)
which 选 ITIMER_REAL,代表真实计时器
time包括开始时间和间隔时间
oldtime 基本不用,给NULL即可
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void fa(int signo){
printf("每隔1.1秒执行一次\n");
}
int main(){
signal(SIGALRM,fa);
struct itimerval timer;
//间隔时间
timer.it_interval.tv_sec = 1;//秒数
timer.it_interval.tv_usec = 100000;//微秒
//开始时间
timer.it_value.tv_sec = 5;
timer.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&timer,0);//启动计时器
while(1);
}