今天要分享的是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文檔,并且文檔裡有我們寫入的内容