linux 信号处理 四

一、信号生命周期

从信号发送到信号处理函数的执行完毕

对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。

下面阐述四个事件的实际意义:

  1. 信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
  2. 信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:

    struct sigpending pending:
    struct sigpending{
    	struct sigqueue *head, **tail;
    	sigset_t signal;
    };

    第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

    struct sigqueue{
    	struct sigqueue *next;
    	siginfo_t info;
    }

    信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。 只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

    注:
    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册); 
    当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。

  3. 信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程的未决信号集中删除该信号(信号注销完毕)。 
    进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
  4. 信号生命终止。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。

    注:
    1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。 
    2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。

回页首

二、信号编程注意事项

  1. 防止不该丢失的信号丢失。如果对八中所提到的信号生命周期理解深刻的话,很容易知道信号会不会丢失,以及在哪里丢失。
  2. 程序的可移植性
    考虑到程序的可移植性,应该尽量采用POSIX信号函数,POSIX信号函数主要分为两类:
    • POSIX 1003.1信号函数: Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
    • POSIX 1003.1b信号函数。POSIX 1003.1b在信号的实时性方面对POSIX 1003.1做了扩展,包括以下三个函数: sigqueue()、sigtimedwait()、sigwaitinfo()。 其中,sigqueue主要针对信号发送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函数,后面有相应实例。

      #include <signal.h>
      int sigwaitinfo(sigset_t *set, siginfo_t *info).

      该函数与sigsuspend()类似,阻塞一个进程直到特定信号发生,但信号到来时不执行信号处理函数,而是返回信号值。因此为了避免执行相应的信号处理函数,必须在调用该函数前,使进程屏蔽掉set指向的信号,因此调用该函数的典型代码是:

      sigset_t newmask;
      int rcvd_sig;
      siginfo_t info;
      sigemptyset(&newmask);
      sigaddset(&newmask, SIGRTMIN);
      sigprocmask(SIG_BLOCK, &newmask, NULL);
      rcvd_sig = sigwaitinfo(&newmask, &info)
      if (rcvd_sig == -1) {
      	..
      }

      调用成功返回信号值,否则返回-1。sigtimedwait()功能相似,只不过增加了一个进程等待的时间。

  3. 程序的稳定性。
    为了增强程序的稳定性,在信号处理函数中应使用可重入函数。

    信号处理程序中应当使用可再入(可重入)函数(注:所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错)。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。

    满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的。The Open Group视下列函数为可再入的:

    _exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。

    即使信号处理函数使用的都是"安全函数",同样要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随时可能被改变。另外,longjmp()以及siglongjmp()没有被列为可再入函数,因为不能保证紧接着两个函数的其它调用是安全的。

回页首

三、深入浅出:信号应用实例

linux下的信号应用并没有想象的那么恐怖,程序员所要做的最多只有三件事情:

  1. 安装信号(推荐使用sigaction());
  2. 实现三参数信号处理函数,handler(int signal,struct siginfo *info, void *);
  3. 发送信号,推荐使用sigqueue()。

实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。

实例一:信号发送及处理
实现一个信号接收程序sigreceive(其中信号安装由sigaction())。

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
	struct sigaction act;
	int sig;
	sig=atoi(argv[1]);

	sigemptyset(&act.sa_mask);
	act.sa_flags=SA_SIGINFO;
	act.sa_sigaction=new_op;

	if(sigaction(sig,&act,NULL) < 0)
	{
		printf("install sigal error\n");
	}

	while(1)
	{
		sleep(2);
		printf("wait for the signal\n");
	}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
	printf("receive signal %d", signum);
	sleep(5);
}

说明,命令行参数为信号值,后台运行sigreceive signo &,可获得该进程的ID,假设为pid,然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时,可验证信号的排队问题。 
注:可以用sigqueue实现一个命令行信号发送程序sigqueuesend,见 附录1

实例二:信号传递附加信息
主要包括两个实例:

  1. 向进程本身发送信号,并传递指针参数;

    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    void new_op(int,siginfo_t*,void*);
    int main(int argc,char**argv)
    {
    	struct sigaction act;
    	union sigval mysigval;
    	int i;
    	int sig;
    	pid_t pid;
    	char data[10];
    	memset(data,0,sizeof(data));
    	for(i=0;i < 5;i++)
    		data[i]=‘2‘;
    	mysigval.sival_ptr=data;
    
    	sig=atoi(argv[1]);
    	pid=getpid();
    
    	sigemptyset(&act.sa_mask);
    	act.sa_sigaction=new_op;//三参数信号处理函数
    	act.sa_flags=SA_SIGINFO;//信息传递开关
    	if(sigaction(sig,&act,NULL) < 0)
    	{
    		printf("install sigal error\n");
    	}
    	while(1)
    	{
    		sleep(2);
    		printf("wait for the signal\n");
    		sigqueue(pid,sig,mysigval);//向本进程发送信号,并传递附加信息
    	}
    }
    void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现
    {
    	int i;
    	for(i=0;i<10;i++)
    	{
    		printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
    	}
    	printf("handle signal %d over;",signum);
    }

    这个例子中,信号实现了附加信息的传递,信号究竟如何对这些信息进行处理则取决于具体的应用。

  2. 2、 不同进程间传递整型参数:把1中的信号发送和接收放在两个程序中,并且在发送过程中传递整型参数。 
    信号接收程序:

    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    void new_op(int,siginfo_t*,void*);
    int main(int argc,char**argv)
    {
    	struct sigaction act;
    	int sig;
    	pid_t pid;		
    
    	pid=getpid();
    	sig=atoi(argv[1]);	
    
    	sigemptyset(&act.sa_mask);
    	act.sa_sigaction=new_op;
    	act.sa_flags=SA_SIGINFO;
    	if(sigaction(sig,&act,NULL)<0)
    	{
    		printf("install sigal error\n");
    	}
    	while(1)
    	{
    		sleep(2);
    		printf("wait for the signal\n");
    	}
    }
    void new_op(int signum,siginfo_t *info,void *myact)
    {
    	printf("the int value is %d \n",info->si_int);
    }

    信号发送程序:命令行第二个参数为信号值,第三个参数为接收进程ID。

    #include <signal.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <sys/types.h>
    main(int argc,char**argv)
    {
    	pid_t pid;
    	int signum;
    	union sigval mysigval;
    	signum=atoi(argv[1]);
    	pid=(pid_t)atoi(argv[2]);
    	mysigval.sival_int=8;//不代表具体含义,只用于说明问题
    	if(sigqueue(pid,signum,mysigval)==-1)
    		printf("send error\n");
    	sleep(2);
    }

    注:实例2的两个例子侧重点在于用信号来传递信息,目前关于在linux下通过信号传递信息的实例非常少,倒是Unix下有一些,但传递的基本上都是关于传递一个整数,传递指针的我还没看到。我一直没有实现不同进程间的指针传递(实际上更有意义),也许在实现方法上存在问题吧,请实现者email我。

实例三:信号阻塞及信号集操作

#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
	sigset_t new_mask,old_mask,pending_mask;
	struct sigaction act;
	sigemptyset(&act.sa_mask);
	act.sa_flags=SA_SIGINFO;
	act.sa_sigaction=(void*)my_op;
	if(sigaction(SIGRTMIN+10,&act,NULL))
		printf("install signal SIGRTMIN+10 error\n");
	sigemptyset(&new_mask);
	sigaddset(&new_mask,SIGRTMIN+10);
	if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
		printf("block signal SIGRTMIN+10 error\n");
	sleep(10);
	printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
	if(sigpending(&pending_mask)<0)
		printf("get pending mask error\n");
	if(sigismember(&pending_mask,SIGRTMIN+10))
		printf("signal SIGRTMIN+10 is pending\n");
	if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
		printf("unblock signal error\n");
	printf("signal unblocked\n");
	sleep(10);
}
static void my_op(int signum)
{
	printf("receive signal %d \n",signum);
}

编译该程序,并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。

注:在上面几个实例中,使用了printf()函数,只是作为诊断工具,pringf()函数是不可重入的,不应在信号处理函数中使用。

回页首

结束语:

系统地对linux信号机制进行分析、总结使我受益匪浅!感谢王小乐等网友的支持! 
Comments and suggestions are greatly welcome!

回页首

附录1:

用sigqueue实现的命令行信号发送程序sigqueuesend,命令行第二个参数是发送的信号值,第三个参数是接收该信号的进程ID,可以配合实例一使用:

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char**argv)
{
	pid_t pid;
	int sig;
	sig=atoi(argv[1]);
	pid=atoi(argv[2]);
	sigqueue(pid,sig,NULL);
	sleep(2);
}
时间: 2024-10-21 22:41:41

linux 信号处理 四的相关文章

linux第四章实验报告

Linux第四章 用户和文件权限管理 实验环境 公司的linux主机即将提供给技术部作为开发服务器使用,根据部门内项目组的构成情况,首先需要建立相应的组账号.用户账号,并作为目录设置权限,还需要设置一个共用的数据存储目录,便于同事之间的数据交换. 需求描述 1.建立用户目录 创建目录/tech/benet和/tech/accp,分别用于存放各项目组中用户账号的宿主文件夹.例如,kylin用户的宿主目录应位于/tech/benet/kylin. 2.添加组账户 为两个项目组添加组账号benet,a

Linux中四种进程或线程同步互斥控制方法

原文地址:http://blog.itpub.net/10697500/viewspace-612045/ 一.Linux中 四种进程或线程同步互斥的控制方法: 1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问. 2.互斥量:为协调共同对一个共享资源的单独访问而设计的. 3.信号量:为控制一个具有有限数量用户资源而设计. 4.事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始. 二.临界区(Critical Section) 保证在某一时刻只有一个线程

linux 信号处理 二

[摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核对于信号的处理流程包括信号的触发/注册/执行及注销等.最后介绍了应用层的相关处理,主要包括信号处理函数的安装.信号的发送.屏蔽阻塞等,最后给了几个简单的应用实例. [关键字]软中断信号,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t 1       信

Linux 第四天 vim shell

案例一:配置主机名称:Vim /etc/hostname 部署网卡配置:Rhel 5/6 网卡配置文前缀eth0 eth1RHEL 7 ifcfg-ens1 设备类型:TYPE=Ethernet地址分配模式:BOOTPROTO=staticDEFROUTE=yesPEERDNS=yesPEERROUTES=yesIPV4_FAILURE_FATAL=noIPV6INIT=yesIPV6_AUTOCONF=yesIPV6_DEFROUTE=yesIPV6_PEERDNS=yesIPV6_PEERR

linux信号处理相关知识

因为要处理最近项目中碰上的多个子进程退出信号同时到达,导致程序不当产生core的情况,今天我花了时间看了一些关于linux信号处理的博客. 总结一下:(知识未经实践) 1 1 linux信号分两种,一种实时信号(也叫可靠信号),一种非实时信号(也叫不可靠信号). 2 2 实时信号之所以是可靠的,因为在进程阻塞该信号的时间内,发给该进程的所有实时信号会排队,而非实时信号则会合并为一个信号.(可以这样理解,同一个不可靠信号同时产生时,只有一个信号会被处理,其它的丢弃) 3 3 早期设计的信号都是不可

学习linux/unix编程方法的建议,学习Linux的四个步骤(转)

解答:学习Linux的四个步骤假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux系统编程=>内核开发阅读内核源码其中学习linux常用命令时就要学会自己编译内核,优化系统,调整参数安装和常用命令书太多了,找本稍微详细点的就ok,其间需要学会正则表达式系统编程推荐<高级unix环境编程>,黑话叫APUE还有<unix网络编程>这时候

linux 信号处理 一

信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使用信号,以及有关信号的几个系统调用. 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断.从它的命名可以看出,它的实质和使用很象中断.所以,信号可以说是进程控制的一部分. 一.信号的基本概念 本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件.基本概念对于理解和使用信号,对于理解信号机制都特别重要.下面就来看看什么是信号. 1.基本

linux 信号处理 三

一.信号及信号来源 信号本质 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达. 信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了.信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息. 信号来源 信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它

Linux下四款Web服务器压力测试工具(http_load、webbench、ab、siege)介绍

一.http_load程序非常小,解压后也不到100Khttp_load以并行复用的方式运行,用以测试web服务器的吞吐量与负载.但是它不同于大多数压力测试工具,它可以以一个单一的进程运行,一般不会把客户机搞死.还可以测试HTTPS类的网站请求. 下载地址:http://soft.vpser.net/test/http_load/http_load-12mar2006.tar.gz 安装#tar zxvf http_load-12mar2006.tar.gz#cd http_load-12mar