三.信号(Signal)

信号是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知某一进程发生了某一种预定好的事件;接收到信号的进程可以选择不同的方式处理该信号,一是可以采用默认处理机制—进程中断或退出,一是忽略该信号,还有就是自定义该信号的处理函数,执行相应的动作。

内核为进程生产信号,来响应不同的事件,这些事件就是信号源。信号源可以是:异常,其他进程,终端的中断(Ctrl-C,Ctrl+\等),作业的控制(前台,后台进程的管理等),分配额问题(cpu超时或文件过大等),内核通知(例如I/O就绪等),报警(计时器)。

一、信号生命周期

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

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

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

  1. 信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
  2. 信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:
  3. struct sigpending pending:
  4. struct sigpending{
  5. struct sigqueue *head, **tail;
  6. sigset_t signal;
  7. };

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

    struct sigqueue{

    struct sigqueue *next;

    siginfo_t info;

    }

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

    注:

    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);

    )、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。

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

    )信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。

    )在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。

二、信号编程注意事项

  • 防止不该丢失的信号丢失。如果对八中所提到的信号生命周期理解深刻的话,很容易知道信号会不会丢失,以及在哪里丢失。
  • 程序的可移植性
  • 考虑到程序的可移植性,应该尽量采用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()功能相似,只不过增加了一个进程等待的时间。

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

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

    )使用静态的数据结构,如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验证信号的发送接收及处理。同时,可验证信号的排队问题。

实例二:信号传递附加信息

主要包括两个实例:

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

}

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

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

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

}

的两个例子侧重点在于用信号来传递信息,目前关于在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 42pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。

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

3.4. 结束语

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

Comments and suggestions are greatly welcome!

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

}

Linux进程间通信—信号的更多相关文章

  1. Linux进程间通信——信号集函数

    一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中 ...

  2. 详解linux进程间通信-信号

    前言:之前说看<C++ Primer >暂时搁浅一下,迷上公司大神写的代码,想要明白,主要是socket.进程间通信! 知道进程间通信:信号.信号量.管道.消息队列.共享内存(共享存储), ...

  3. Linux进程间通信——信号

    一.认识信号 信号(Signals)是Unix.类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式.它是一种异步的通知机制,用来提醒进程一个事件已经发生.当一个信号发送给一个进程 ...

  4. Linux进程间通信-信号

    1.什么是信号信号是Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会执行相应的操作. 2.信号的产生1)由硬件产生,如从键盘输入Ctrl+C可以终止当前进程2)由其他进程发送,如可在s ...

  5. Linux 进程间通信 信号(signal)

    1. 概念: 1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式 2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件. 3)如果 ...

  6. Linux进程间通信(一): 信号 signal()、sigaction()

    一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中 ...

  7. [转]Linux进程间通信——使用信号

    转载于:http://blog.csdn.net/ljianhui/article/details/10128731         经典!!! Linux进程间通信——使用信号 一.什么是信号 用过 ...

  8. Linux进程间通信——使用信号

    一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中 ...

  9. Linux进程间通信(五):信号量 semget()、semop()、semctl()

    这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信 -- 信号.下面 ...

随机推荐

  1. 【LOJ】 #2015. 「SCOI2016」妖怪

    题解 这道题教会我很多东西,虽然它是个傻逼三分 1.long double的运算常数是巨大的 2.三分之前的界要算对!一定要算准,不要想一个直接写上! 3.三分100次也就只能把精度往里推20多位,可 ...

  2. VuGen录制选项Recording Options

  3. AOSP编译

    AOSP编译 1 编译环境Win10系统安装VMplayer14,主机16G内存,I3-4170 ,虚拟机ubuntu-16.04.1-desktop-amd64,12G内存. 2 软件安装sudo ...

  4. shell-sed命令详解(转)

    (转自http://blog.csdn.net/wl_fln/article/details/7281986) Sed简介 sed是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时 ...

  5. 洛谷P2234 [HNOI2002] 营业额统计 [splay]

    题目传送门 营业额统计 题目描述 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每天 ...

  6. Python-函数总结

    把程序分解成较小的部分,主要有3种方法. 函数(function) 对象(object) 模块(module) 本节我们先学习函数.函数是带名字的代码块,可以把多个逻辑封装起来.这样就可以在程序中可以 ...

  7. scrapy抓取拉勾网职位信息(八)——使用scrapyd对爬虫进行部署

    上篇我们实现了分布式爬取,本篇来说下爬虫的部署. 分析:我们上节实现的分布式爬虫,需要把爬虫打包,上传到每个远程主机,然后解压后执行爬虫程序.这样做运行爬虫也可以,只不过如果以后爬虫有修改,需要重新修 ...

  8. 转载:tar命令批量解压方法总结

    由于linux的tar命令不支持批量解压,所以很多网友编写了好多支持批量解压的shell命令,收集了一下,供大家分享: 第一:for tar in *.tar.gz;  do tar xvf $tar ...

  9. Java 请求webServce接口 带参数

    public String getWebServiceByParams(String param){ //获取基金缴付记录 // Post请求的url,与get不同的是不需要带参数 URL postU ...

  10. 9.3n+1问题

    时间限制: 1 s 空间限制: 32000 KB 题目等级 : 白银 Silver 题解 查看运行结果 题目描述 Description 3n+1问题是一个简单有趣而又没有解决的数学问题.这个问题是由 ...