linux 中signal机制如何应用(二)

来源:互联网 发布:联通网络客服人工电话 编辑:程序博客网 时间:2024/05/21 09:33

上一节linux 中signal机制如何应用(一)讲的例子是不带参数的信号处理机制,这一节讲带参数的。

我们知道用kill只是发送信号不能携带参数,如果我们想要发送信号给进程并且携带参数,那就得用sigqueue函数了。可以说,sigqueue函数比kill更加强大,经常是与sigaction()函数配合使用。
sigqueue函数:

int sigqueue(pid_t pid,int signo,const union sigval value);  //pid:    指定接收信号的进程pid//signo:  要发送的信号,//sigval: 联合数据结构 sigval,指定了信号传递的参数//返回值:  成功返回0,失败返回-1

union sigval的定义:

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

sigqueue()比kill()传递了更多的附加信息,可以向应用程序传递整数或者指向包含更多信息的缓冲区指针。但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

来思考一个问题,C++的模板函数是怎么实现的?
上一节用sigaction实现的signal函数是不携带参数的,这是原来的函数signal_test(int signo,void (*func)(int)),第二个参数是函数指针,函数有一个int参数,返回void类型。而现在,我们想要加上携带参数的情形。那么第二个参数的类型就不是固定的,因为需要void (*func)(int signo, siginfo_t *info,void *context)这种类型的函数指针。我们可能会想到C++的模板函数,但是我们要用C语言实现又该怎么办呢?办法还是有的,这时可以把参数定义为void*类型,参数传进函数后,在函数体里面强制转化为函数指针就可以了。
我们在下面定义了两个函数指针,用于在信号处理函数中强制转化指针类型。具体采用哪一个需要根据flag类型来确定。

typedef void (*pFunc)(int);typedef void (*pSignalFunParam)(int, siginfo_t *, void *);

通常情况按照下列方式调用信号处理函数:
void handler(int signo);
需要填充sigaction结构体成员act.sa_handler = handler;
但是,如果设置了SA_SIGINFO标志,也就是act.sa_flags = SA_SIGINFO时,要按照下列方式填充sigaction结构体成员和调用信号处理程序:
void handler(int signo, siginfo_t *info,void *context);
需要填充sigaction结构体成员act.sa_sigaction = handler;
这两种调用方式在接下来的程序中都会实践。这两种中断处理函数区别在于,一个是不携带参数,一个携带参数,并且保存在siginfo_t结构体中,信号处理函数可以解析这些数据。

接下来用一个例子来讲带参数的信号处理机制怎么实现。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>#include <sys/wait.h>#include <signal.h>int logFlag = 0;//全局变量,是否打印log的标志//信号没有带参数时使用的中断处理函数void signal_handler(int signo){    switch(signo)    {        case SIGINT:            //SIGINT默认行为是退出进程            printf("in signal_handler() SIGINT signal\n");            exit(0);            break;        case SIGALRM:            //SIGALRM默认行为是退出进程            printf("SIGALRM signal\n");            break;    }   }//信号携带参数时使用的中断处理函数void signal_handler_param(int signo,siginfo_t *info,void *context){    switch(signo)    {        case SIGINT:            //SIGINT默认行为是退出进程            printf(" SIGINT signal \n");            exit(0);            break;        case SIGUSR1:            //当logFlag等于1的时候表示打开log标志            logFlag = 1;            printf("SIGUSR1 , signo=%d ,logFlag = %d,recv data :%d\n",signo,logFlag,info->si_value.sival_int);            break;        case SIGUSR2:            printf("SIGUSR2 ,signo=%d , recv data :%d\n",signo,info->si_value.sival_int);            break;    }       return ;}typedef void (*pFunc)(int);typedef void (*pSignalFunParam)(int, siginfo_t *, void *);int signal_sigaction(int signo,void *func,int flag){    struct sigaction act,oact;    //填充软中断函数    switch(flag)    {        case 0:            act.sa_handler = (pFunc)func;            break;        case SA_SIGINFO:            act.sa_sigaction = (pSignalFunParam)func;            break;    }    //act.sa_handler=func;    //将act的属性sa_mask设置一个初始值    sigemptyset(&act.sa_mask);    act.sa_flags=flag;    int ret = sigaction(signo,&act,&oact);    if (ret !=0 )    {        printf("sigaction() failed !\n");    }    return ret;}int main(int arg, char *args[]){    pid_t pid=0;    pid=fork();    if(pid==-1)    {        printf("fork process failed!\n");        return -1;    }    if(pid>0)//父进程    {        printf("in father!\n");        //linux内核为用户程序保留了两个信号,一个是10 SIGUSR1还有12 SIGUSR2        //要想接受信号带来的参数,必须使用了SA_SIGINFO选项        signal_sigaction(SIGUSR1,signal_handler_param,SA_SIGINFO);        signal_sigaction(SIGUSR2,signal_handler_param,SA_SIGINFO);        //不带参数        signal_sigaction(SIGINT,signal_handler,0);        //等待子进程发送信号        while(1)        {            //pause()会令目前的进程暂停(进入睡眠状态), 直到被信号(signal)所中断            pause();                }        printf("recv signal from child\n");    }    if(pid==0)//子进程    {        printf("in child!\n");        //因为我们不知道子进程还是父进程先执行,这里等待是为了父进程执行完信号安装        sleep(2);        //向父进程发送带数据的信号        union sigval sigvalue;        sigvalue.sival_int=110;        //发送信号SIGUSR1        if(sigqueue(getppid(),SIGUSR1,sigvalue)==-1)        {            printf("sigqueue() failed!\n");            exit(0);        }        //发送信号SIGUSR2        if(sigqueue(getppid(),SIGUSR2,sigvalue)==-1)        {            printf("sigqueue() failed!\n");            exit(0);        }        printf("child process send signal successfully\n");        exit(0);    }    return 0;}

编译:

ubuntu:~/test/signal_test$ gcc 2signal.c -o 2signal

后台运行:

ubuntu:~/test/signal_test$ ./2signal &

实验结果:

[1] 46814ubuntu:~/test/signal_test$ in father!in child!child process send signal successfullySIGUSR2 ,signo=12 , recv data :110SIGUSR1 , signo=10 ,logFlag = 1,recv data :110

kill发送SIGINT信号:

ubuntu:~/test/signal_test$ kill -INT 46814in signal_handler() SIGINT signal

从上面的程序和实验结果理解程序的流程,先调用fork函数,创建子进程,子进程先睡眠两秒等待父进程完成信号的安装,然后父进程调用pause函数,令目前的进程暂停(进入睡眠状态), 直到被信号(signal)所中断,接着子进程向父进程发送SIGUSR1,SIGUSR2的信号,触发了中断,进程根据信号的类型调用了不同的中断处理函数。当发送的是SIGUSR1和SIGUSR2信号,会调用signal_handler_param函数,当发送的是SIGINT信号,则调用的是signal_handler。