天天看點

Linux筆記(15)| Linux的信号

今天要分享的是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時中止了。從下面的圖中也可以看到原來兩個程序都是處于睡眠态,後面就結束了程序。

Linux筆記(15)| Linux的信号

二、信号的注冊和響應

前面講了三種發送信号的方式,但是光發送信号還不夠,對于接收方來說,還得對信号進行處理。

一般可以使用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文檔,并且文檔裡有我們寫入的内容

Linux筆記(15)| Linux的信号
Linux筆記(15)| Linux的信号