2.Linux——信号

目录

引言

1.信号概念        

2.信号处理函数的注册

3.信号处理发送函数

4.信号注册函数——(入门版)  

5.信号注册函数——高级版

5.1 信号注册函数 :sigaction

5.2 信号发送函数 :sigqueue 


引言

       信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法,例如,终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序。
 

1.信号概念        

        1.首先,每个信号都有一个名字。这些名字都以3个字符SIG开头。例如,SIGABRT是天折信号,当进程调用abort 函数时产生这种信号。SIGALRM是闹钟信号,由alarm 函数设置的定时器超时后将产生此信号。

        2.很多条件可以产生信号

  •  当用户按某些终端键时,引发终端产生的信号。在终端上按Delete键(或者很多系统中的Ctrl+C键)通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。
  • 硬件异常产生信号:除数为0、无效的内存引用等。这些条件通常由硬件检测到,并通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效内存引用的进程产生SIGSEGV信号。
  • 进程调用kil1(2)函数可将任意信号发送给另一个进程或进程组。自然,对此有所限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
  • 用户可用ki11(1)命令将信号发送给其他进程。此命令只是kil1函数的接口。常用此命令终止一个失控的后台进程。
  • 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。这里指的不是硬件产生条件(如除以0),而是软件条件。例如SIGURG(在网络连接上传来带外的数据)、SIGPIPE(在管道的读进程已终止后,一个进程写此管道)以及STGALRM(进程所设置的定时器已经超时)

        3.在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或与信号相关的动作
        (1)忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。
        (2)捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。例如,若正在编写一个命令解释器,它将用户的输入解释为命令并执行之,当用户用键盘产生中断信号时,很可能希望该命令解释器返回到主循环,终止正在为该用户执行的命令。如果捕捉到SIGCHLD 信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要为SIGTERM信号编写一个信号捕捉函数以清除临时文(SIGTERM是终止信号,kill命令传送的系统默认信号是终止信号)。注意,不能捕捉SIGKILL和SIGsTOP信号。
        (3)执行系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。

        4.系统信号

了解了信号的概述,那么,信号是如何来使用呢?

        其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9)SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

杀死进程

 

2.信号处理函数的注册

信号处理函数的注册不只一种方法,分为入门版和高级版

  1. 入门版:函数 signal
  2. 高级版:函数 sigaction

3.信号处理发送函数

信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:kill
2.高级版:sigqueue

4.信号注册函数——(入门版) 

 4.1 signal函数

        函数原型:

        根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
对于sighandler_t signal(int signum, sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
        同样,typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。我们先来看看简单一个信号注册的代码示例吧。(signal函数的入门理解参考:4.typedef void (*sighandler_t)(int);_单片机爱好者之家的博客-CSDN博客

 4.2 信号注册的代码

#include <signal.h>
#include <stdio.h>

//       typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);

void handler(int signum)
{
	printf("get signum=%d\n",signum);
	switch(signum){
		case 2:
			printf("SIGINT\n");
			break;
		case 9:
			printf("SIGKILL\n");
			break;
		case 10:
			printf("SIGUSR1\n");
			break;

	}
	printf("never quit\n");
}

int main()
{
	signal(SIGINT,SIG_IGN);
	signal(SIGKILL,SIG_IGN);
	signal(SIGUSR1,handler);
	while(1);
	return 0;
}

 4.3 信号的处理还有两种状态,分别是默认处理和忽略,这两种设置很简单,只需要将 handler 设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认动作)即可

#include <signal.h>
#include <stdio.h>

//       typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);

void handler(int signum)
{
	printf("get signum=%d\n",signum);
	switch(signum){
		case 2:
			printf("SIGINT\n");
			break;
		case 9:
			printf("SIGKILL\n");
			break;
		case 10:
			printf("SIGUSR1\n");
			break;

	}
	printf("never quit\n");
}

int main()
{
	signal(SIGINT,SIG_IGN);//参数1:注册(接收)crtl+c这个信号,参数2:宏(忽略)
	signal(SIGKILL,SIG_IGN);
	signal(SIGUSR1,SIG_IGN);
	while(1);
	return 0;
}

4.3 kill函数

函数原型

 kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

 

        信号的处理需要有接受者,显然发送者必须要知道发给谁,根据 kill 函数的远行可以看到,pid 就是接受者的 pid,sig 则是发送的信号的类型。从原型来看,发送信号要比接受信号还要简单些。

//发送信号

#include <signal.h>
#include <stdio.h>
 
//       typedef void (*sighandler_t)(int);
 
//       sighandler_t signal(int signum, sighandler_t handler);
 
void handler(int signum)
{
        printf("get signum = %d\n",signum);
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }
}
 
int main()
{
        signal(SIGINT,handler);    //参数1:注册(接收)crtl+c这个信号,参数2:调用handler函数处理信号
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
 
        while(1);
        return 0;
}

接收信号


#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
 
//int kill(pid_t pid, int sig);
 
int main(int argc,char **argv)
{
        int signum;
        int pid;
 
        signum = atoi(argv[1]);    //atoi() 将argv[i]的字符串,转换为 整型数 
        pid = atoi(argv[2]);
 
        printf("signum = %d , pid = %d \n",signum,pid);
 
        //int kill(pid_t pid, int sig);
        kill(pid,signum);
 
        printf("send siganal ok\n");
 
        return 0;
}

 4.4 除了 kill()函数 还可以用 sprintf() 配合 system() 函数 来做到同样的信号处理发送

 

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


int main(int argc ,char **argv)
{
	int signum;
	int pid;

	char cmd[128]={0};

	signum = atoi(argv[1]);
	pid = atoi(argv[2]);

	printf("num=%d,pid=%d\n",signum,pid);	

//	kill(pid, signum);
	sprintf(cmd,"kill -%d %d",signum,pid);

	
	system(cmd);

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

5.信号注册函数——高级版

        5.1 信号注册函数 :sigaction

        函数原型:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
The sigaction structure is defined as something like:

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

这个函数的原版帮助信息,可以通过man sigaction来查看。

sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

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

sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。

关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。

关于void (*sa_sigaction)(int, siginfo_t *, void *);处理函数来说还需要有一些说明。void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。

 

其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

那么,kill 函数发送的信号是无法携带数据的,我们现在还无法验证发送收的部分,那么,我们先来看看发送信号的高级用法后,我们再来看看如何通过信号来携带数据吧。

5.2 信号发送函数 :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 成员,那么将无法获取额外携带的数据。

sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

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

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

那么我们来尝试一下,发送信号如何携带信息

send.c

#include <stdio.h>
#include <signal.h>
 
//int  sigqueue(pid_t  pid,  int  sig,  const  union  sigval value);
 
int main(int argc,char **argv)
{
        int signum;
        int pid;
 
        printf("%d\n",getpid());
 
        signum = atoi(argv[1]);    //atoi() 将argv[i]的字符串,转换为 整型数
        pid = atoi(argv[2]);
 
        union  sigval value;
        value.sival_int = 100;
    
        sigqueue(pid,signum,value);    //参数1:进程pid,参数2:信号值,参数3:你要发送的信息
 
        printf("done\n");
        return 0;
}

 get.c

#include <signal.h>
#include <stdio.h>
 
//       int sigaction(int signum, const struct sigaction *act,
//              struct sigaction *oldact);
 
//void     (*sa_sigaction)(int, siginfo_t *, void *);
 
void handler(int signum,siginfo_t *info,void *context)    //参数1:信号值,参数2:内容,参数3:是否为NULL决定有没有内容
{
        printf("get signum %d\n",signum);
 
        if(context != NULL){                              //非空,代表有内容
                printf("get data = %d\n",info->si_int);
                printf("get data = %d\n",info->si_value.sival_int);
                printf("from = %d\n",info->si_pid);
        }
}
 
int main()
{
        struct sigaction act;
        act.sa_sigaction = handler;      //调用handler函数处理信号
        act.sa_flags = SA_SIGINFO;      //要想接收信号数据,必须指定这个flag 是 SA_SIGINFO  
 
        printf("%d\n",getpid());
 
        sigaction(SIGUSR1,&act,NULL);    //参数1:接收哪个信号,参数2:收到信号后想怎么处理它,参数3:备份原信号的操作。一般设置为NULL,不关心
 
        while(1);
        return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值