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

重点来啦!!!

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

信号简介

信号名称
  1. 每一个信号都有自己的名字和编号,且都以SIG开头。
  2. 信号定义在<signal.h>头文件中,信号名都定义为正整数。
  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 <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

参数:

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

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

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

#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


void handler(int signum){

	printf("signum = %d\n",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 <sys/types.h>
       #include <signal.h>

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

参数:

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

  2. sig:信号编号

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

#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


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

//      int kill(pid_t pid, int sig);
	int sig;
	int pid;
	
	if(argc != 3){
		printf("\tUsage:\n");
		printf("\t\t %s <Target_PID> <Signal_Number>\n", argv[0]);
	}
		
	pid = atoi(argv[1]);
	sig = atoi(argv[2]);	
//	printf("pid = %d,sig = %d\n",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 <signal.h> //函数头文件
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;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

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

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


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

在这里插入图片描述

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

sigqueue发送函数
#include <signal.h>
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 <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>

//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 %d\n",info->si_pid);
		printf("the data: %d\n",info->si_value);
		
	}
	
}

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

	while(1);
	return 0;
}

发送端

这是demosend.c  发送消息

#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>

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

	int sig;
	int pid;
	
	if(argc != 3){
		printf("\tUsage:\n");
		printf("\t\t %s <Target_PID> <Signal_Number>\n", argv[0]);
	}else{
		printf("the send pid is %d\n",getpid());
	}
		
	pid = atoi(argv[1]);
	sig = atoi(argv[2]);	
	
	union sigval value;
	/*union sigval {
               int   sival_int;
               void *sival_ptr;
           };
	*/
	value.sival_int = 100;
//      int sigqueue(pid_t pid, int sig, const union sigval value);
	sigqueue(pid,sig,value);

	printf("send signal successful\n");
 
	return 0;
}

在这里插入图片描述

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石子君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值