栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

学习笔记——进程间通信之信号详解

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

学习笔记——进程间通信之信号详解

重点来啦!!!

对于Linux来说,信号实际上是软中断,许多重要的程序都需要处理信号。信号(Signals )是Unix系统中使用的最古老的进程间通信的方法,它为Linux提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。

信号简介 信号名称
  1. 每一个信号都有自己的名字和编号,且都以SIG开头。
  2. 信号定义在头文件中,信号名都定义为正整数。
  3. 使用kill -l来查看信号的名字和编号,信号是从1开始编号的,不存在0号信号。
  4. kill对于信号0又特殊的应用。

使用kill -l查看signal

其中:
SIGHUP: 从终端上发出的结束信号;
SIGINT: 来自键盘的中断信号(Ctrl-C);
SIGQUIT:来自键盘的退出信号(Ctrl-);
SIGFPE: 浮点异常信号(例如浮点运算溢出);
SIGKILL:该信号结束接收信号的进程;
SIGALRM:进程的定时器到期时,发送该信号;
SIGTERM:kill 命令发出的信号;
SIGCHLD:标识子进程停止或结束的信号;
SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号;
·······

信号处理

信号的处理有三种方法,分别是忽略、捕捉和默认动作

  1. 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程。
  2. 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,换句话就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  3. 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
信号的产生

信号的来源分为硬件方式和软件方式。

硬件方式

  1. 当用户按下终端键时产生信号

  2. 硬件异常产生信号。这些事件通常由硬件检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时的正在运行的程序。

软件方式

  1. 用户在终端下调用kill命令向进程发送任务信号

  2. 进程调用kill或sigqueue函数发送信号

  3. 当检测到某种软件条件已经具备时发出信号,如SIGALARM信号

信号函数

信号的处理函数不止一种,这里我们介绍signal函数和sigaction函数
信号的发送函数我们这里介绍kill函数和sigqueue函数

所以kill对应signal,sigaction对应sigqueue,二者的不同之处是sigaction和sigaction能传递消息,在向进程传递信号的同时告诉进程本次发来的消息。

比如说A坐在沙发看电视,B突然敲门,向A发送中断信号,A应该放下手中的事,去处理B发来的信号,去给他开门,但是二者没有任何交流,不觉得怪怪的吗?而用sigqueue函数就会携带消息,B在敲门的同时会说:“快开门”,A收到消息,中断过来处理开门

初级函数

发送函数kill,处理函数signal

signal函数
NAME
       signal - ANSI C signal handling

SYNOPSIS
       #include 

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

参数:

  1. signum:指定信号编号
  2. handler:
    SIG_IGN:忽略该信号。
    SIG_DFL:采用系统默认方式处理信号
    自定义的信号处理函数指针

返回值:
成功:设置之前的信号处理方式
出错:‐1

signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。

#include 
#include 
#include
#include 


void handler(int signum){

	printf("signum = %dn",signum);
}

int main(int argc, char** argv){
//      typedef void (*sighandler_t)(int);
//      sighandler_t signal(int signum, sighandler_t handler);
	
	signal(SIGINT,handler);
	while(1);
 
	return 0;
}

如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。

我们通过signal指定信号编号,自定义handler处理函数,得到参数后,我们打印传递来的SIGINT编号,前面我们说到信号名都定义为正整数,所以当我们摁下ctrl C之后,不会退出进程,而是不断地打印这个编号。

kill发送函数
NAME
       kill - send signal to a process

SYNOPSIS
       #include 
       #include 

    函数原型:   int kill(pid_t pid, int sig);

参数:

  1. pid
    正数:要接收信号的进程的进程号
    0:信号被发送到所有和pid进程在同一个进程组的进程
    ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
    pid通过程序getpid或者ps -aux获取

  2. sig:信号编号

函数返回值:成功 0 出错 ‐1

#include 
#include 
#include
#include 


int main(int argc, char** argv){

//      int kill(pid_t pid, int sig);
	int sig;
	int pid;
	
	if(argc != 3){
		printf("tUsage:n");
		printf("tt %s  n", argv[0]);
	}
		
	pid = atoi(argv[1]);
	sig = atoi(argv[2]);	
//	printf("pid = %d,sig = %dn",pid,sig);
	kill(pid,sig);
 
	return 0;
}

这个demo传入dos命令作为main的参数,将pid和sig转为int,直接调用kill。

运行上个demo我们查看他的id号,并在kill这个demo中输入我们要杀死的进程编号。

简单的总结一下,我们通过 signal 函数注册一个信号处理函数,注册了SIGINT信号,随后主程序一直在while。
通过 kill 命令发送信号之前,我们需要先查看到接收者,通过 ps 命令查看了之前所写的程序的 PID,通过 kill 函数来发送。
对于已注册的信号,使用 kill 发送都可以正常接收到,但是如果发送了未注册的信号,则会使得应用程序终止进程。

注意:

  1. 当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
  2. 当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。
高级函数

发送函数sigqueue,处理函数sigaction

这里的处理和发送函数之所以高级,就是因为他们不但可以发送信号,还可以携带消息,这个条件是发送函数kill和处理函数signal满足不了的。那么下面就是简单介绍下这两个高级函数。。

sigaction处理函数
	
#include  //函数头文件
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//函数原型

//struct sigaction 是sigaction函数的结构体参数
struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

sigaction 是一个系统调用函数,层层嵌套,其中第一个参数是整型的signum信号编号,第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复(我们接下来的demo默认null)。

struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。默认是阻塞的。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。

flags设置为SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。另外注意的是sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。

void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。

 siginfo_t {
               int      si_signo;    
               int      si_errno;    
               int      si_code;     
               int      si_trapno;   
               pid_t    si_pid;      
               uid_t    si_uid;      
               int      si_status;   
               clock_t  si_utime;    
               clock_t  si_stime;    
               sigval_t si_value;    
               int      si_int;      
               void    *si_ptr;      
               int      si_overrun;  
               int      si_timerid;  
               void    *si_addr;     
               int      si_band;     
               int      si_fd;       
}

  1. 其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。

  2. 发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。


sigaction 参数比较复杂,我们看看导图可能比较清晰一点。。。。

接收消息介绍完,再来看看发送消息是什么参数。

sigqueue发送函数
#include 
int sigqueue(pid_t pid, int sig, const union sigval value);


union sigval {
   int   sival_int;
   void *sival_ptr;
 };


以下几点需要注意:

  1. 使用 sigaction 函数安装信号处理程序时,选择SA_SIGINFO 的标志。
  2. sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。
  3. sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

下面我们实战看看demo是怎么接收发送消息的。。。


接收端

这是demorec.c  接收消息

#include 
#include 
#include
#include 
#include 

//void     (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum,siginfo_t *info,void *context){
	printf("the signum is %d n",signum);

	if(context != NULL){
		printf("the send pid is %dn",info->si_pid);
		printf("the data: %dn",info->si_value);
		
	}
	
}

int main(){
	
	struct sigaction act;
	act.sa_sigaction = handler;
	act.sa_flags = SA_SIGINFO;
	
	printf("The received id is %dn",getpid());	
//	int sigaction(int signum, const struct sigaction *act,
//	struct sigaction *oldact);
	sigaction(SIGUSR1,&act,NULL);

	while(1);
	return 0;
}

发送端

这是demosend.c  发送消息

#include 
#include 
#include
#include 
#include 

int main(int argc, char** argv){

	int sig;
	int pid;
	
	if(argc != 3){
		printf("tUsage:n");
		printf("tt %s  n", argv[0]);
	}else{
		printf("the send pid is %dn",getpid());
	}
		
	pid = atoi(argv[1]);
	sig = atoi(argv[2]);	
	
	union sigval value;
	
	value.sival_int = 100;
//      int sigqueue(pid_t pid, int sig, const union sigval value);
	sigqueue(pid,sig,value);

	printf("send signal successfuln");
 
	return 0;
}

参考:https://www.jianshu.com/p/f445bfeea40a

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945962.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号