今天要分享的是Linux中的信号机制,信号是一种软件中断,是一种处理异步事件的方法,可以很好地在多个进程之间进行同步和简单的数据交换。
一、发送信号
发送信号通常有三种方式,分别是使用kill、raise、sigqueue函数
1、kill函数
int kill(pid_t pid,int sig);
第一个参数代表向谁发送,第二个参数代表发送什么信号。调用成功返回0,调用失败返回-1.
2、raise函数
int raise(int sig);
这个是向自身发送一个信号,等价于
kill(getpid(),sig);
3、sigqueue函数
int sigqueue(pid_t pid,int sig,const union sigval value);
其中第三个参数的形式为
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
这个函数除了能发送信号之外,还能携带一些参数,这些参数就保存在共用体里面。
下面写一个简单的代码
#include<signal.h>
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char* argv[])
{
pid_t pid;
pid=fork();
if(pid==0){
printf("This is child process\n");
sleep(10);
printf("does't receive abort signal\n");
}
else{
printf("This is parent process\n");
sleep(5);
if(kill(pid,SIGABRT)==-1){
printf("kill function failed\n");
}
}
return 0;
}
在这里使用fork创建了一个子进程,子进程本来调用sleep函数要睡眠10秒,但是父进程在睡眠5秒后向子进程发送一个中止的信号,导致子进程在5s时中止了。从下面的图中也可以看到原来两个进程都是处于睡眠态,后面就结束了进程。
二、信号的注册和响应
前面讲了三种发送信号的方式,但是光发送信号还不够,对于接收方来说,还得对信号进行处理。
一般可以使用signal函数和sigaction函数来注册信号。
1、signal函数
void (*signal(int signum,void(*handler)(int)))(int);
这个函数原型看起来很复杂,可以难倒很多人,我们可以对他进行简化:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
这样就清楚很多了。首先看sighandler_t他是一个函数指针,输入参数是int类型,无返回值。
再看signal这个函数,他有两个输入参数,第一个是int类型,第二个是一个函数指针,另外,他返回的也是一个函数指针。
只有对函数指针非常熟悉,才能看懂上面这个表达式。如果还是不懂,可以对比下面这个表达式
char* function(int a,char* b);
这个应该很容易看出是一个函数了吧,两个输入参数,返回一个char*。
如果这个可以看懂,那么上面那个其实是类似的,只不过他不是char* ,而是一个 函数指针类型:void(*)(int).
signal函数的第一个参数是信号类型,第二个参数是函数指针,也就是跳转到哪里去执行。也就是说,当收到第一个参数表示的信号之后,就会跳转到第二个参数指向的代码段去执行。
2、sigaction函数
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
这个函数不比上面的简单,其中第二个参数里的结构体类型展开是:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
在这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。
接下来写一个简单的代码,来应用一下上面的几个函数。实现的需求就是创建一个子进程,父进程每隔一秒钟向子进程发送一个信号,子进程收到信号之后往一个txt文档中写入一句话。
#include<signal.h>
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void signalDeal(int sig,siginfo_t *info,void*t);
int fg=0;
int main(int argc,char* argv[])
{
if(argc!=2){
printf("please input param\n");
return -1;
}
char writebuff[]="This is a test\n";
int count=0,seektemp=0,j=0;
int fd=open(argv[1],O_RDWR | O_CREAT,S_IRWXU); //open a file
pid_t pid=fork();
if(pid==0) //child process
{
struct sigaction act;
act.sa_sigaction=signalDeal;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1)
{
while(!fg);
fg=0;
if(count==0){ //first write
int ret=write(fd,writebuff,strlen(writebuff));
if(ret==-1){
printf("write failed\n");
}
seektemp=lseek(fd,0,SEEK_CUR);
count++;
}
else{ //not first write
j=strlen(writebuff)*count; //offset
seektemp=lseek(fd,j,SEEK_SET);
int ret=write(fd,writebuff,strlen(writebuff));
if(ret==-1){
printf("write failed\n");
}
count++;
}
}
}
else { //parent process
while(1)
{
sleep(1);
int ret=kill(pid,SIGUSR1);
if(ret==-1){
printf("send signal failed\n");
}
else{
printf("send signal success\n");
}
}
}
}
void signalDeal(int sig,siginfo_t *info,void*t)
{
if(sig==SIGUSR1)
{
fg=1;
}
}
在命令行输入
gcc test.c
./a.out a.txt
之后可以看到生成了一个a.txt文档,并且文档里有我们写入的内容