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); 
}

代码执行结果如下:

openSUSE 12.3 (x86_64) 是一款开源的 Linux 操作系统,适用于 x86_64 架构的计算机。它由 openSUSE 项目团队开发,提供了稳定和强大的功能,适合个人和企业用户使用。 下载 openSUSE 12.3 (x86_64) 可以通过 openSUSE 官方网站或其他可信的镜像站点进行。你可以在 openSUSE 的官方网站上找到下载链接,并选择你相应的硬件架构(x86_64)。下载过程可能需要一些时间,因此请确保你有一个可靠的网络连接。 一旦下载完成,你可以选择将 openSUSE 12.3 (x86_64) 刻录到光盘或制作成 USB 启动盘。在安装之前,建议你备份你的重要数据,并确保你的计算机满足 openSUSE 12.3 (x86_64) 的系统要求。 在安装过程中,你将被引导通过一系列的设置和配置选项。你可以自定义你的安装选择,包括硬盘分区、安装软件包和选择你想要的桌面环境。openSUSE 12.3 (x86_64) 提供了几种桌面环境可供选择,如 KDE、GNOME 和 Xfce 等。 安装完成后,你将能够享受到 openSUSE 12.3 (x86_64) 提供的各种功能和应用程序。它拥有强大的稳定性和安全性,以及广泛的软件库和社区支持。你可以使用它来进行日常任务,如浏览网页、编辑文档、播放媒体和进行开发等。 总之,openSUSE 12.3 (x86_64) 是一款功能强大的开源操作系统,适合各种使用场景。希望你能通过下载和安装它,体验到它带来的卓越性能和自由。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值