七、进程间通信-信号
1、信号
(1)概述
信号是软件中断,进程接收信号后做出相应响应,它提供了一种处理异步事件的方法。每个信号都有名字,这些名字以SIG开头,信号都定义在<signal.h>头文件中,并且都是正整数常量。
(2)怎么产生信号
-
硬件
- 执行非法指令
- 访问非法内存
- 驱动程序
-
软件
-
Ctrl + C:中断信号。
-
Ctrl + | :退出信号。
-
Ctrl + Z:停止信号。
-
kil命令:程序调用kill()函数。
-
(3)信号的处理方式
-
忽略:进程当信号从来没有发生过。
-
捕获:进程会调用相应的处理函数,进行相应的处理。
-
默认:使用系统默认处理方式处理信号。
(4)常用信号分析
信号名 | 信号编号 | 产生原因 | 默认处理方式 |
SIGHUP | 1 | 关闭中断 | 终止 |
SIGINT | 2 | ctrl+c | 终止 |
SIGQUIT | 3 | ctrl+\ | 终止+转储 |
SIGABRT | 6 | abort() | 终止+转储 |
SIGPE | 8 | 算术错误 | 终止 |
SIGKILL | 9 | kill -9 pid | 终止,不可捕获/忽略 |
SIGUSR1 | 10 | 自定义 | 忽略 |
SIGSEGV | 11 | 段错误 | 终止+转储 |
SIGUSR2 | 12 | 自定义 | 忽略 |
SIGALRM | 14 | alarm() | 终止 |
SIGTERM | 15 | kill pid | 终止 |
SIGCHLD | 17 | (子)状态变化 | 忽略 |
SIGSTOP | 19 | ctrl+z | 暂停,不可捕获/忽略 |
(5)设置信号处理方式
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
- 参数:
-
signum: 要设置的信号
-
handler:SIG_IGN:忽略;SIG_DFL:默认
-
void (*sighandler_t)(int):自定义
-
- 返回值:
- 成功:返回上一次设置的handler
- 失败:SIG_ERR.
-
实例:CTRL+C触发SIGINT信号,处理信号回调函数
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
printf("\nthis signa number is %d\n",sig);
if(sig == SIGINT)
{
printf("I have get SIGINT!\n\n");
printf("The signal has been restored to the default processing mode!\n\n");
/*恢复信号为默认情况*/
signal(SIGINT,SIG_DFL);
}
}
int main(void)
{
printf("\nthis is an alarm test function\n\n");
/*设置信号处理的回调函数*/
signal(SIGINT,signal_handler);
while(1)
{
printf("waiting for the SIGINT signal,please enter\"Ctrl+C\"...\n");
sleep(1);
}
exit(0);
return 0;
}
执行结果:
(6)kill函数:给其他进程发送特定的信号
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig);
- 参数:
- pid:进程id.
- sig:要发送的信号。
- 返回值:
- 成功:0
- 失败:-1
(7)raise函数:把信号发给进程自身
#include <signal>
int raise(int sig);
- 参数:
- sig:要发送的信号。
- 返回值:
- 成功:0
- 失败:非0
(8)kiill和raise使用实例
子进程使用rasie给自身进程发送SIGSTOP暂停信号,然后父进程通过kill发送SIGKILL给子进程杀死子进程。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
int main(void)
{
pid_t pid;
int ret;
pid = fork();
if(pid<0)
{
printf("create process error\n");
exit(1);
}
else if(pid == 0)
{
printf("child is waiting for SIGSTOP signal!!\n\n");
/*子进程停在这里*/
raise(SIGSTOP); //给子进程本身发暂停信号,阻塞到这。
/*子进程没有机会运行到这里*/
printf("child won't run here forever");
exit(0);
}
else
{
/*睡眠3s,让子进程先执行*/
sleep(3);
/*发送SIGKILL信号杀死子进程*/
if((ret=kill(pid,SIGKILL))==0) //kill给其他进程发信号
{
printf("Parent kill %d!\n\n",pid);
}
/*等待子进程退出*/
wait(NULL);
/*父进程退出运行*/
printf("parent exit!\n");
exit(0);
}
return 0;
}
2、信号集处理函数
内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。综上:自定义信号集set(也为一个字,64位)通过信号集操作函数来改变信号屏蔽字mask,然后mask进一步来影响未决信号集,从而控制信号是否应该被屏蔽。后面我们只是研究常规信号,即1~31号!
(1)屏蔽信号集
- 手动
- 自动
(2)未处理信号集
信号如果被屏蔽,则记录在未处理信号集中。
- 非实时信号(1~31):不排队,只留一个。
- 实时信号(34~64):排队,保留全部。
(3)信号集相关
sigset_t set; // typedef unsigned long sigset_t; 声明一个信号集变量set,信号集类型为sigset_t,为unsigned long(无符号长整型),64位。
int sigemptyset(sigset_t *set); //将set所指信号集清0 成功:0;失败:-1
int sigfillset(sigset_t *set); //将set所指信号集置1 成功:0;失败:-1
int sigaddset(sigset_t *set, int signum); //将某个信号加入信号集(即将其置1) 成功:0;失败:-1
int sigdelset(sigset_t *set, int signum); //将某个信号清出信号集(即将其置0) 成功:0;失败:-1
int sigismember(const sigset_t *set, int signum); 判断某个信号是否在信号集中(是否为1)返回值:在集合:1;不在:0;出错:-1
sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
- how:
- SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
- SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
- SIG_SETMASK:屏蔽集 = set
- oldset:保存就的屏蔽集的值,NULL表示不保存
(4)实例:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
printf("hello\n");
sleep(5);
printf("world\n");
}
int main(void)
{
/*设置信号处理的回调函数*/
signal(SIGINT,signal_handler);
while(1);
return 0;
}
执行结果:
由上面的结果,可知我们发送了一个SIGINT信号后,在回调函数里面打印helloworld,我们按Ctrl+C后执行hello,在这期间,多次按ctrl+C发送SIGINT信号,signal并没有每个信号都进行处理,如果他第一个信号没有处理完,就不会再响应其他信号。
由于SIGINT信号编号为2,是一种非实时信号,所以只保留一个信号响应。
我们发送编号为35的信号,此信号为实时信号,系统排队响应。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
printf("hello\n");
sleep(5);
printf("world\n");
}
int main(void)
{
/*设置信号处理的回调函数*/
signal(35,signal_handler);
while(1);
return 0;
}
在一个终端执行程序,在另一个终端通过kill发送编号为35的信号给signalsets的进程,然后就打印出了hello world.
由于编号为35的信号是实时信号,排队处理,所以每个信号都被排队响应
如果我们想实时响应非实时信号,可将信号集的这一信号置为1,如SIGINT信号,修改代码如下:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void signal_handler(int sig)
{
sigset_t set;
sigemptyset(&set); //信号集清0
sigaddset(&set,SIGINT); //将SIGINT加入信号集
sigprocmask(SIG_UNBLOCK,&set,NULL); //使用新设置的信号集去修改信号屏蔽集
printf("hello\n");
sleep(5);
printf("world\n");
}
int main(void)
{
/*设置信号处理的回调函数*/
signal(SIGINT,signal_handler);
while(1);
return 0;
}
执行结果如下:每个SIGINT都会被响应