一,信号
信号是软件中断
1,中断
中止当前正在执行的程序,转而去处理别的程序代码
分为软件中断和硬件中断
2,信号
一种中断方式,软件中断方式,可以作为进程间通信的方式
常见的信号:段错误 总线错误 浮点数例外
信号是异步的,就是说程序不知道信号会什么时候来。
进程可以处理信号,还可以进程间发送信号,类似进程间通信。
不同的进程是不能相互访问对象的内存空间的
每一个信号都有一个名字,这些名字都是以SIG开头的,信号本质上是一个整数,信号的名字
是这个整数的宏定义
3,信号从哪里来
1)硬件异常:除零,无效的存储空间,这些条件通常由硬件检测到,并将其通知内核,然后内核
为该条件发生时正在运行的进程产生适当的信号
2)软件产生异常信号---调用函数产生
kill raise alarm等
4,信号的分类
1)不可靠信号,这种信号会丢失。主要特点是不支持排队。
所有不可靠信号都是非实时信号。
2)可靠信号,不会丢失,支持排队
5,进程收到信号如何处理
1)默认处理,80%的情况是退出进程
2)忽略
3)调用信号处理函数
signal.c
#include<stdio.h>
#include<signal.h>
int main(){
signal(2,SIG_IGN);//忽略2信号
while(1);
return 0;
}
------------------------------------------------------------------
#include<stdio.h>
#include<signal.h>
void fa(int signo){
printf("你发信号%d\n",signo);
}
int main(){
signal(SIGINT,fa);//注册一个信号处理函数
while(1);
return 0;
}
//signal(SIGINT,SIG_DFL);恢复指定信号的默认行为
注:9信号不能忽略,也不能给9信号注册信号处理函数,只能默认处理)
9信号是SIGKILL.
当前用户只能给自己的进程发信号,不能给别的用户的进程发信号。root用户可以给所有用户发信号
二,信号注册函数----signal
给类型起别名
1,用此类型定义一个变量
2,前面加typedef,变量名就是类型的别名
int main(){
typedef int INT32;
INT32 x;
x = 100;
int a[10];
sizeof(int[10]);
typedef int INT10[10];
INT10 b;
b[0] = 100;
b[1] = 200;
typedef void* (*F)(int*,int);
F f1 = fa;
}
三,子进程的信号处理
子进程会继承父进程的信号处理方式,直到子进程调用exec函数
子进程调用exec函数后,exec将父进程中设置为捕获的信号恢复为默认处理方式,其余不变
child.c
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fa(int signo){//信号处理函数返回值必须是void,参数int
printf("捕获到信号%d\n",signo);
}
int main(){
if(signal(SIGINT,fa)==SIG_ERR){
perror("signal failed.");
exit(-1);
}
if(signal(SIGQUIT,SIG_IGN) == SIG_ERR){
perror("signal ignore failed.");
exit(-1);
}
int pid = fork();
if(pid == -1){
perror("fork failed."):
exit(-1);
}
if(pid == 0){
printf("子进程pid=%d\n",getpid());
while(1);
}else{
printf("父进程退出\n");
}
return 0;
}
======================================================================
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fa(int signo){//信号处理函数返回值必须是void,参数int
printf("捕获到信号%d\n",signo);
}
int main(){
if(signal(SIGINT,fa)==SIG_ERR){
perror("signal failed.");
exit(-1);
}
if(signal(SIGQUIT,SIG_IGN) == SIG_ERR){
perror("signal ignore failed.");
exit(-1);
}
int pid = vfork();
if(pid == -1){
perror("fork failed."):
exit(-1);
}
if(pid == 0){
execlp("pro1","pro1",NULL);
}else{
printf("父进程退出\n");
}
return 0;
}
pro1.c
#include<stdio.h>
#include<unistd.h>
int main(){
printf("子进程pid=%d\n",getpid());
while(1);
return 0;
}
四,发送信号
1,键盘
CTRL+C ==> 给当前进程发送2信号 SIGINT
CTRL+\ ==>发3信号 SIGQUIT
CTRL+Z ==>发20信号 SIGTSTP
2,出错
访问不存在的地址==>发11信号 SIGSEGV
除0==>发8信号 SIGFPE
总线错误==>发7信号 SIGBUS
......
3,命令发送
kill -信号 进程号
4,程序中用函数发送
kill函数
raise函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void child(int signo){
printf("进程%d捕获到信号%d\n",getpid(),signo);
}
int main(){
int pid = fork();
if(pid == 0){
signal(50,child);
while(1);
}
printf("父进程给子进程发送50信号\n");
kill(pid,50);
return 0;
}
pause函数:等待信号到来,信号处理完后返回-1
sleep函数:可以被任何信号打断,当信号处理完后,不会再继续sleep,而是直接返回睡眠剩下几秒
raise函数:自己给自己发信号
alarm函数:自己给自己发信号。当有多个alarm的时候,那么前面的alarm未执行的时候,
那么后年的alarm有效,前面的无效,后面的执行返回值是前面还剩余的时间是多少
sigqueue函数
五,信号集和信号屏蔽
信号集:一个变量,可以同时表示多个信号
int sigemptyset//清空
int sigfillset//添加
#include<stdio.h>
#incldue<signal.h>
int main(){
printf("sizeof(sigset_t)=%d\n",sizeof(sigset_t));
sigset_t set;//set是信号集变量
sigempty(&set);
sigfillset(&set):
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigaddset(&set,SIGALARM);
sigdelset(&set,SIGINT);
int res = sigismember(&set,SIGQUIT);
return 0;
}
信号屏蔽字
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset* oldset);
how:SIG_BLOCK:把指定的信号添加到原来的里面中
SIG_UNBLOCK:在原来的基础上减去新添加的
SIG_SETMASK:不管以前的,按照新加的
set:
oldset:
每个进程都会有一个信号屏蔽字,它规定了当前那些信号可以递送,那些信号需要阻塞。
当程序执行敏感任务时(比如数据库更新),不希望外界信号中断程序的运行,在这个阶段
并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号
在信号处理函数中,当前的信号总是加入信号屏蔽字中
信号屏蔽字的类型是信号集。默认情况下,信号屏蔽字中不包括任何信号
sigmask.c
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<stdlib.h>
void sig_fun(int signo){
printf("捕获到信号%d\n",signo);
}
int main(){
signal(SIGINT,sig_fun);
signal(SIGQUIT,sig_fun);
printf("程序执行普通代码,试试发信号2,3\n");
sleep(10);
printf("程序开始执行关键代码,屏蔽掉一些信\n");
//准备信号集变量,放入要屏蔽的信号
sigset_t set,old;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//设置新的信号屏蔽字
if(sigpromask(SIG_SETMASK,&set,&old)==-1){
perror("设置信号屏蔽字失败"),exit(-1);
}
//执行关键代码
sleep(6);
printf("程序睡足6秒时间,关键代码执行完\n");
//恢复原先的信号屏蔽字
sigpromask(SIG_SETMASK,&old,NULL);
while(1);
return 0;
}
sigpending:可以获取到被屏蔽掉的信号
注:被屏蔽掉的信号如果是不可靠,发送多次,可能只处理一个,因为前面的
丢弃了,如果是可靠信号,发送几个处理几个
六,sigaction
功能和signal相同的,用来注册一个信号处理方式的
int sigaction(int signum,const struct sigaction* act,struct sigaction* oldact);
C语言中,结构体中不能有函数,但是可以有函数指针
sigaction.c
#include<stdio.h>
#include<signal.h>
void fa(int signo){
printf("捕获到信号%d\n",signo);
sleep(5);
}
int main(){
struct sigaction action = {};
action .sa_handler = fa;
sigaction(SIGINT,&action,NULL);
pause();
return 0;
}
七,发送信号,特点发送信号的同时,还可以带值
sigsequeue
八,Linux为每个进程维护3个计时器,分别是真实计时器,虚拟计时器和实用计时器
真实计时器计算的是程序的运行时间 SIGALARM
虚拟计时器SIGVTALRM
实用计时器SIGPROF
信号是软件中断
1,中断
中止当前正在执行的程序,转而去处理别的程序代码
分为软件中断和硬件中断
2,信号
一种中断方式,软件中断方式,可以作为进程间通信的方式
常见的信号:段错误 总线错误 浮点数例外
信号是异步的,就是说程序不知道信号会什么时候来。
进程可以处理信号,还可以进程间发送信号,类似进程间通信。
不同的进程是不能相互访问对象的内存空间的
每一个信号都有一个名字,这些名字都是以SIG开头的,信号本质上是一个整数,信号的名字
是这个整数的宏定义
3,信号从哪里来
1)硬件异常:除零,无效的存储空间,这些条件通常由硬件检测到,并将其通知内核,然后内核
为该条件发生时正在运行的进程产生适当的信号
2)软件产生异常信号---调用函数产生
kill raise alarm等
4,信号的分类
1)不可靠信号,这种信号会丢失。主要特点是不支持排队。
所有不可靠信号都是非实时信号。
2)可靠信号,不会丢失,支持排队
5,进程收到信号如何处理
1)默认处理,80%的情况是退出进程
2)忽略
3)调用信号处理函数
signal.c
#include<stdio.h>
#include<signal.h>
int main(){
signal(2,SIG_IGN);//忽略2信号
while(1);
return 0;
}
------------------------------------------------------------------
#include<stdio.h>
#include<signal.h>
void fa(int signo){
printf("你发信号%d\n",signo);
}
int main(){
signal(SIGINT,fa);//注册一个信号处理函数
while(1);
return 0;
}
//signal(SIGINT,SIG_DFL);恢复指定信号的默认行为
注:9信号不能忽略,也不能给9信号注册信号处理函数,只能默认处理)
9信号是SIGKILL.
当前用户只能给自己的进程发信号,不能给别的用户的进程发信号。root用户可以给所有用户发信号
二,信号注册函数----signal
给类型起别名
1,用此类型定义一个变量
2,前面加typedef,变量名就是类型的别名
int main(){
typedef int INT32;
INT32 x;
x = 100;
int a[10];
sizeof(int[10]);
typedef int INT10[10];
INT10 b;
b[0] = 100;
b[1] = 200;
typedef void* (*F)(int*,int);
F f1 = fa;
}
三,子进程的信号处理
子进程会继承父进程的信号处理方式,直到子进程调用exec函数
子进程调用exec函数后,exec将父进程中设置为捕获的信号恢复为默认处理方式,其余不变
child.c
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fa(int signo){//信号处理函数返回值必须是void,参数int
printf("捕获到信号%d\n",signo);
}
int main(){
if(signal(SIGINT,fa)==SIG_ERR){
perror("signal failed.");
exit(-1);
}
if(signal(SIGQUIT,SIG_IGN) == SIG_ERR){
perror("signal ignore failed.");
exit(-1);
}
int pid = fork();
if(pid == -1){
perror("fork failed."):
exit(-1);
}
if(pid == 0){
printf("子进程pid=%d\n",getpid());
while(1);
}else{
printf("父进程退出\n");
}
return 0;
}
======================================================================
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void fa(int signo){//信号处理函数返回值必须是void,参数int
printf("捕获到信号%d\n",signo);
}
int main(){
if(signal(SIGINT,fa)==SIG_ERR){
perror("signal failed.");
exit(-1);
}
if(signal(SIGQUIT,SIG_IGN) == SIG_ERR){
perror("signal ignore failed.");
exit(-1);
}
int pid = vfork();
if(pid == -1){
perror("fork failed."):
exit(-1);
}
if(pid == 0){
execlp("pro1","pro1",NULL);
}else{
printf("父进程退出\n");
}
return 0;
}
pro1.c
#include<stdio.h>
#include<unistd.h>
int main(){
printf("子进程pid=%d\n",getpid());
while(1);
return 0;
}
四,发送信号
1,键盘
CTRL+C ==> 给当前进程发送2信号 SIGINT
CTRL+\ ==>发3信号 SIGQUIT
CTRL+Z ==>发20信号 SIGTSTP
2,出错
访问不存在的地址==>发11信号 SIGSEGV
除0==>发8信号 SIGFPE
总线错误==>发7信号 SIGBUS
......
3,命令发送
kill -信号 进程号
4,程序中用函数发送
kill函数
raise函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void child(int signo){
printf("进程%d捕获到信号%d\n",getpid(),signo);
}
int main(){
int pid = fork();
if(pid == 0){
signal(50,child);
while(1);
}
printf("父进程给子进程发送50信号\n");
kill(pid,50);
return 0;
}
pause函数:等待信号到来,信号处理完后返回-1
sleep函数:可以被任何信号打断,当信号处理完后,不会再继续sleep,而是直接返回睡眠剩下几秒
raise函数:自己给自己发信号
alarm函数:自己给自己发信号。当有多个alarm的时候,那么前面的alarm未执行的时候,
那么后年的alarm有效,前面的无效,后面的执行返回值是前面还剩余的时间是多少
sigqueue函数
五,信号集和信号屏蔽
信号集:一个变量,可以同时表示多个信号
int sigemptyset//清空
int sigfillset//添加
#include<stdio.h>
#incldue<signal.h>
int main(){
printf("sizeof(sigset_t)=%d\n",sizeof(sigset_t));
sigset_t set;//set是信号集变量
sigempty(&set);
sigfillset(&set):
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigaddset(&set,SIGALARM);
sigdelset(&set,SIGINT);
int res = sigismember(&set,SIGQUIT);
return 0;
}
信号屏蔽字
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset* oldset);
how:SIG_BLOCK:把指定的信号添加到原来的里面中
SIG_UNBLOCK:在原来的基础上减去新添加的
SIG_SETMASK:不管以前的,按照新加的
set:
oldset:
每个进程都会有一个信号屏蔽字,它规定了当前那些信号可以递送,那些信号需要阻塞。
当程序执行敏感任务时(比如数据库更新),不希望外界信号中断程序的运行,在这个阶段
并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号
在信号处理函数中,当前的信号总是加入信号屏蔽字中
信号屏蔽字的类型是信号集。默认情况下,信号屏蔽字中不包括任何信号
sigmask.c
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<stdlib.h>
void sig_fun(int signo){
printf("捕获到信号%d\n",signo);
}
int main(){
signal(SIGINT,sig_fun);
signal(SIGQUIT,sig_fun);
printf("程序执行普通代码,试试发信号2,3\n");
sleep(10);
printf("程序开始执行关键代码,屏蔽掉一些信\n");
//准备信号集变量,放入要屏蔽的信号
sigset_t set,old;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//设置新的信号屏蔽字
if(sigpromask(SIG_SETMASK,&set,&old)==-1){
perror("设置信号屏蔽字失败"),exit(-1);
}
//执行关键代码
sleep(6);
printf("程序睡足6秒时间,关键代码执行完\n");
//恢复原先的信号屏蔽字
sigpromask(SIG_SETMASK,&old,NULL);
while(1);
return 0;
}
sigpending:可以获取到被屏蔽掉的信号
注:被屏蔽掉的信号如果是不可靠,发送多次,可能只处理一个,因为前面的
丢弃了,如果是可靠信号,发送几个处理几个
六,sigaction
功能和signal相同的,用来注册一个信号处理方式的
int sigaction(int signum,const struct sigaction* act,struct sigaction* oldact);
C语言中,结构体中不能有函数,但是可以有函数指针
sigaction.c
#include<stdio.h>
#include<signal.h>
void fa(int signo){
printf("捕获到信号%d\n",signo);
sleep(5);
}
int main(){
struct sigaction action = {};
action .sa_handler = fa;
sigaction(SIGINT,&action,NULL);
pause();
return 0;
}
七,发送信号,特点发送信号的同时,还可以带值
sigsequeue
八,Linux为每个进程维护3个计时器,分别是真实计时器,虚拟计时器和实用计时器
真实计时器计算的是程序的运行时间 SIGALARM
虚拟计时器SIGVTALRM
实用计时器SIGPROF