linux 中signal机制如何应用(二)
上一节linux 中signal机制如何应用(一)讲的例子是不带参数的信号处理机制,这一节讲带参数的。我们知道用kill只是发送信号不能携带参数,如果我们想要发送信号给进程并且携带参数,那就得用sigqueue函数了。可以说,sigqueue函数比kill更加强大,经常是与sigaction()函数配合使用。sigqueue函数:int sigqueue(pid_t pid,int signo,
上一节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。
更多推荐
所有评论(0)