上一节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] 46814
ubuntu:~/test/signal_test$ in father!
in child!
child process send signal successfully
SIGUSR2 ,signo=12 , recv data :110
SIGUSR1 , signo=10 ,logFlag = 1,recv data :110

kill发送SIGINT信号:

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

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

Logo

更多推荐