12.3 Linux_进程间通信_信号机制
概述
什么是信号:
信号是在软件层次上对中断机制的模拟(软中断),是一种异步通信方式。
进程对信号的响应方式:
- 缺省方式:根据默认行为响应信号
- 忽略信号:不响应信号
- 捕捉信号:根据指定行为响应信号
常用信号及含义:
见博文"4.Linux_Shell命令"-"进程管理"-"2、向进程发送信号",博文连接如下:
4.Linux_Shell命令-CSDN博客
信号相关命令:
见博文"4.Linux_Shell命令"-"进程管理"-"2、向进程发送信号",博文连接如下:
4.Linux_Shell命令-CSDN博客
信号的状态:
信号递达:代表信号已经被进程接收到,不论进程的处理方式是缺省、忽略还是捕捉。
信号未决:代表信号产生到被进程接收这一段过程。
信号集:
信号屏蔽字mask:设置哪一个信号被屏蔽,bit位与kill -l查询出的相对应。
bit1=1代表SIGHUP被屏蔽
未决信号集:当相应信号被屏蔽时,信号产生后,对应bit位被置1。bit位与kill -l查询出的相对应。
当mask bit1=1时,SIGHUP信号产生后,未决信号集的bit1=1
当信号被取走后,bit位被自动清零
信号相关函数
1、发送信号
//像指定进程发送信号
int kill(pid_t pid, int sig);
//给自己发送信号
int raise(int sig);
返回值:成功返回0,失败返回EOF
pid:进程号
pid值 | 含义 |
> 0 | 发送给指定的进程 |
= 0 | 发送给该进程所在的进程组中的进程 |
= -1 | 取绝对值,发送给指定进程所在的进程组中的所有进程 例如:pid = -2,那么发送给pid=2的进程所在的进程组中的进程 |
< -1 | 发送给所有进程 |
sig:信号类型,由kill -l 查出,比如要发送SIGINT,该值就为2
2、定时器
注意:一个进程中只能设定一个定时器,定时到达时产生SIGALRM信号
注意:有时sleep函数内部实现也会用到定时器,因此使用定时器时需要把sleep注释掉
2.1 一次性定时器
//一次性定时器
unsigned int alarm(unsigned int seconds);
返回值:成功返回上个定时器的剩余时间,失败返回EOF
seconds:定时时间,单位s
2.2 循环发送定时器
//循环发送定时器
useconds_t ualarm(useconds_t usecs, useconds_t interval);
usecs:第一次产生的时间,单位us
interval:之后定时结束的时间间隔,单位us
2.3 更通用的循环发送定时器
//更通用的循环发送定时器
int setitimer(int which,
const struct itimerval *restrict value,
struct itimerval *restrict ovalue);
which:选项,写入ITIMER_REAL,代表真正逝去的时间
value:新的超时时间
ovalue:旧的超时时间,写入NULL即可
struct itimerva结构体成员:
struct itimerval {
struct timeval it_value; /* 闹钟触发时间 */
struct timeval it_interval; /* 闹钟触发周期 */
};
struct timeval {
time_t tv_sec; /* 秒数 */
suseconds_t tv_usec; /* 微秒数 */
};
3、捕捉信号自定义函数
3.1 signal(不建议使用)
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
返回值:成功返回原信号处理函数,失败返回SIG_ERR
signum:改变哪一个信号的行为
handler:信号行为改变成什么,SIG_DFL代表缺省,SIG_IGN代表忽略
示例代码如下:
具体代码实现如下:
#include <stdio.h>
#include <signal.h>
#include <errno.h>
typedef void (*sighandler_t)(int);
void SIG_Handler(int);
int main(){
if(signal(SIGINT,SIG_Handler) == SIG_ERR){
perror("signal");
}
while(1);
return 0;
}
void SIG_Handler(int sig){
printf("this is SIG_Handler,SIG num = %d\n",sig);
if(signal(SIGINT,SIG_DFL) == SIG_ERR){
perror("signal");
}
}
代码执行结果如下:
3.2 sigaction(建议使用)
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
返回值:成功返回0,失败返回-1
signum:改变哪一个信号的行为
act:信号行为改变成什么
oldact:原信号处理函数,不关心可以写NULL
struct sigaction结构体成员:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_handler:信号处理函数指针,这与signal的handler用法完全一致。
sa_mask:屏蔽位,使用 sigemptyset(&act.sa_mask) 去清空它即可。
sa_flags:设置回调函数选择哪一个函数指针
sa_flags值 | 含义 |
SA_SIGINFO | 信号回调函数使用sa_sigaction |
0 | 信号回调函数使用sa_handler |
示例1:实现signal相同的功能
#include <stdio.h>
#include <signal.h>
#include <errno.h>
void SIG_Handler(int);
int main(){
struct sigaction act;
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
//改变信号处理函数
if(sigaction(SIGINT,&act,NULL) != 0){
perror("sigaction");
}
while(1);
return 0;
}
void SIG_Handler(int sig){
struct sigaction act;
act.sa_handler = SIG_DFL;//设为默认形式
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
printf("this is SIG_Handler,SIG num = %d\n",sig);
if(sigaction(SIGINT,&act,NULL) != 0){
perror("sigaction");
}
}
示例2:实现定时器
实现定时器,首先设置好定时器发出的SIGALRM信号该如何处理,之后设置好定时器该如何定时即可。
具体代码实现如下:
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h>
void SIG_Handler(int);
int main(){
struct sigaction act;
struct itimerval value;
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
if(sigaction(SIGALRM,&act,NULL) != 0){
perror("sigaction");
}
//设置定时器
value.it_value.tv_sec = 5;//第一次定时5s
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 1;//之后定时1s
value.it_interval.tv_usec = 0;
printf("now time start\n");
setitimer(ITIMER_REAL,&value,NULL);//启动定时器
while(1);
return 0;
}
void SIG_Handler(int sig){
printf("time over,sig = %d\n",sig);
}
代码运行结果如下:
示例3:通过SIGCHLD信号回收子进程
回收子进程最终都要调用wait函数,但wait会进行阻塞直到子进程退出,通过捕捉SIGCHLD信号,可以使得父进程不进入阻塞,当子进程终止时中断父进程,从而实现不阻塞回收子进程。
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void SIG_Handler(int);
int main(){
pid_t pid;
struct sigaction act;
if((pid = fork())<0){
perror("fork");
}else if(pid == 0){
while(1){
printf("child is running\n");
sleep(1);
}
}else{
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
if(sigaction(SIGCHLD,&act,NULL) != 0){
perror("sigaction");
}
while(1){
printf("father is running\n");
sleep(1);
}
}
return 0;
}
void SIG_Handler(int sig){
int wstatus;
printf("get SIG_Handler\n");
waitpid(-1,&wstatus,WNOHANG);//以非阻塞方式,等待子进程结束
if(WIFEXITED(wstatus)){ //判断子进程是否正常退出
printf("子进程的返回值为%d\n",WEXITSTATUS(wstatus));
}else{
printf("子进程是否被信号结束%d\n",WIFSIGNALED(wstatus));
printf("结束子进程的信号类型%d\n",WTERMSIG(wstatus));
}
}
信号集相关函数
1、设置信号集
//自定义信号集
sigset_t set;
//清空信号集,即:全部写0
int sigemptyset(sigset_t *set);
//填充信号集,即:全部写1
int sigfillset(sigset_t *set);
//向信号集中添加指定信号
int sigaddset(sigset_t *set, int signum);
//从信号集中删除指定信号
int sigdelset(sigset_t *set, int signum);
//判断一个信号是否在集合中
int sigismember(const sigset_t *set, int signum);
set:信号集
signum:信号类型,如:SIGINT
2、屏蔽信号
int sigprocmask(int how,
const sigset_t *restrict set,
sigset_t *restrict oset);
返回值:成功返回0,失败返回-1
how:信号设置后,做什么事情
注意:不能阻塞SIGKILL和SIGSTOP
指令 | 含义 |
SIG_BLOCK | 将set中的信号添加到信号屏蔽字中,即:信号阻塞 添加:最终结果 = set | oldset |
SIG_UNBLOCK | 解除信号阻塞,如果之前有信号产生,也会被检测到 |
SIG_SETMASK | 将信号屏蔽字设置为set 设置:最终结果 = set,与oldset无关 |
set:新的信号集
oset:旧的信号集,不关系可以写NULL
使用示例:
具体代码实现如下:
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
void SIG_Handler(int);
int main(){
struct sigaction act;
sigset_t set;
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
if(sigaction(SIGINT,&act,NULL) != 0){
perror("sigaction");
}
//控制信号集
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT); //添加想要阻塞的信号
sigprocmask(SIG_BLOCK,&set,NULL); //设置信号阻塞
sleep(5);
sigprocmask(SIG_UNBLOCK,&set,NULL);//设置信号不阻塞,即:响应信号
while(1);
return 0;
}
void SIG_Handler(int sig){
printf(" get sig\n");
}
代码运行结果如下:
信号驱动任务
信号驱动任务的方式:
- 捕捉信号,改变信号处理函数(类似中断)
- 使用pause实现(不是中断,信号只是作为开始信号)
- 使用sigsuspend改善pause的实现代码
1、捕捉信号实现
捕捉信号实现就是"信号相关函数"-"3、捕捉信号自定义函数"中的示例代码使用方法。
2、 pause实现
//进程一直阻塞,直到被信号中断
int pause(void);
返回值:-1
pause的行为:
- 若信号默认行为是终止,则进程终止,pause没有机会返回
- 若信号默认行为是忽略,则进程继续阻塞,pause不返回
- 若信号处理动作为捕捉,则调用完信号处理函数后,pause返回-1
- 若信号被屏蔽,则进程继续阻塞,pause不返回(这其实就是没收到信号)
示例代码:
具体代码实现如下:
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
void SIG_Handler(int);
void* Task(void* arg);
int main(){
struct sigaction act;
sigset_t set;
int ret;
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
if(sigaction(SIGINT,&act,NULL) != 0){
perror("sigaction");
}
//控制信号集
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT); //添加想要阻塞的信号
//信号驱动任务
while(1){
printf("now is waiting sig\n");
ret = pause(); //等待信号到达
sigprocmask(SIG_BLOCK,&set,NULL); //设置信号阻塞
Task((void*)ret); //执行相应任务
sigprocmask(SIG_UNBLOCK,&set,NULL);//设置信号不阻塞,即:响应信号
}
return 0;
}
void SIG_Handler(int sig){
printf(" get sig\n");
}
void* Task(void* arg){
printf("Now is Task Fun,arg = %d\n",(int)arg);
sleep(5);
}
代码执行结果如下:
该情况可由"3、sigsuspend改善pause代码"实现
3、sigsuspend改善pause代码
int sigsuspend(const sigset_t *sigmask);
sigmask:想要屏蔽的信号,设置该值使用"信号集相关函数"-"1、设置信号集"中的函数。
该值被sigemptyset设置后代表全部信号都不屏蔽
示例代码:
具体代码实现如下:
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
void SIG_Handler(int);
void* Task(void* arg);
int main(){
struct sigaction act;
sigset_t set,unblockSet;
int ret;
//设置信号处理函数
act.sa_handler = SIG_Handler;//设置信号回调函数
sigemptyset(&act.sa_mask); //清空屏蔽位
act.sa_flags = 0; //0代表使用sa_handler作为信号回调函数
if(sigaction(SIGINT,&act,NULL) != 0){
perror("sigaction");
}
//控制信号集
sigemptyset(&set); //清空信号集
sigaddset(&set,SIGINT); //添加想要阻塞的信号
//信号驱动任务
printf("now is waiting sig\n");
ret = pause(); //在while外使用pause等待第一次的信号
sigemptyset(&unblockSet); //清空信号集,该信号集用于解除信号阻塞
while(1){
sigprocmask(SIG_BLOCK,&set,NULL); //设置信号阻塞
Task((void*)ret);
sigsuspend(&unblockSet); //解除信号阻塞并且可以让pause接收到阻塞的信号
//该函数相当于sigprocmask和pause
}
return 0;
}
void SIG_Handler(int sig){
printf(" get sig\n");
}
void* Task(void* arg){
printf("Now is Task Fun,arg = %d\n",(int)arg);
sleep(5);
}
代码执行结果如下: