信號處理

理解概念

​ 能夠用來處理進程間的異步事件——即進程間能夠經過系統調用來發送信號,只是告知某進程發生了什麼事,使得被告知的進程去作對應的事件(信號處理),要注意的是,發送信號的過程並不會傳送任何數據。經過kill -l能夠看到信號的名字和序號。html

能夠經過這個案例來講明:linux

​ 在終端運行top來查看系統運行的一些相關信息,能夠看到終端的數據一直是變化的,同事經過ps -aux|grep top來查看如今系統是否正在運行該指令,能夠獲得運行該指令的進程號,而後用kill -9 進程號將該進程殺掉,咱們此時經過ps -aux|grep top來發現此時top的運行相關信息已經沒有了。這個過程就是一個進程給另外一個進程發送了SIGKILL的信號。(注意:kill,就是送出一個特定的信號給某個進程,而該進程根據信號作出相應的動做(sigqueue也是),-9能夠經過kill -l 看出是SIGKILLios

gqx@gqx-Lenovo-Product:~$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
 6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
 ......

信號處理方式

通常信號的處理能夠分爲三種:c++

  • 忽略信號shell

    ​ 大多數信號可使用這個方式來處理,可是有兩種信號不能被忽略(分別是 SIGKILLSIGSTOP)。由於他們向內核和超級用戶提供了進程終止和中止的可靠方法,若是忽略了,那麼這個進程就變成了沒人能管理的的進程,顯然是內核設計者不但願看到的場景異步

  • 捕捉信號函數

    ​ 須要告訴內核,用戶但願如何處理某一種信號,說白了就是寫一個信號處理函數,而後將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。spa

  • 系統的默認動做操作系統

    ​ 對於每一個信號來講,系統都對應由默認的處理動做,當發生了該信號,系統會自動執行。不過,對系統來講,大部分的處理方式都比較粗暴,就是直接殺死該進程。設計

信號處理的註冊函數

1. signal函數

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);
/* Set the handler for the signal SIG to HANDLER, returning the old
   handler, or SIG_ERR on error.
   By default `signal' has the BSD semantic.  */
extern __sighandler_t signal (int __sig, __sighandler_t __handler);

第一個參數表示信號量類型(對應的kill -l裏的數據),第二個參數則表示該進程被告知該信號後的處理函數。參考案例以下:

#include <iostream>
#include <list>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
void timeout(int sig){
    if(sig == SIGALRM){
        puts("Time out!");
    }
    alarm(2);
}

void keycontrol(int sig){
    if(sig == SIGINT){
        puts("CTRL + C pressed");
    }
}

int main(){
    int  i;
    signal(SIGALRM, timeout);   //到達經過了alarm函數設置的時間,調用函數timeout
    signal(SIGINT, keycontrol); //鍵盤鍵入Ctrl+後,調用keycontrol函數
    alarm(2);

    for(i = 0; i < 6; i++){
        puts("wait...");
        sleep(100);
    }
    return 0;
}

​ 這段代碼要注意的是,在signal中註冊信號函數後,調用信號函數的則是操做系統,但進程處於睡眠狀態的時間爲100s,而alarm函數等待的時間是2秒,即2秒後會產生SIGALRM信號,此時將喚醒處於休眠狀態的進程,並且進程一旦被喚醒,則不會再進入休眠狀態,因此上述程序運行時間比較短。

2. sigaction

該函數已經徹底取代了上述signal函數。

struct sigaction {
   void       (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 爲忽略,SIG_DFL 爲默認動做
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,可以接受額外數據和sigqueue配合使用
   sigset_t   sa_mask;//阻塞關鍵字的信號集,能夠再調用捕捉函數以前,把信號添加到信號阻塞字,信號捕捉函數返回以前恢復爲原先的值。
   int        sa_flags;//影響信號的行爲SA_SIGINFO表示可以接受數據
 };
/* Get and/or set the action for signal SIG.  */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
              struct sigaction *__restrict __oact) __THROW;

第一個參數表示信號量類型;第二個參數信號處理函數;第三個參數:經過此參數獲取以前註冊的信號處理函數指針,若不須要,則傳遞0;

程序示例以下,改程序用來消除由父進程產生的兩個子進程會致使殭屍進程的產生的狀況,當子進程的生命週期結束後,回收子進程的內存信息,而不用等到父進程結束纔去回收銷燬子進程:

#include <iostream>
#include <list>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
void read_childproc(int sig){
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG);   //消滅子進程結束後產生的殭屍進程
    if(WIFEXITED(status)){
        printf("Remove proc id: %d \n", id);
        printf("Child send: %d \n", WEXITSTATUS(status));
    }
}

int main(){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);  //將sa_mask全部位初始化爲0(初始化sa_mask中傳入的信號集,清空其中全部信號)
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);    //SIGCHLD 子進程結束信號
    pid = fork();
    if(pid == 0){
        puts("Hi, I am a child process!");
        sleep(6);
        return 12;
    }else{
        printf("Child proc id: %d \n", pid);
        pid = fork();
        if(pid == 0){
            puts("Hi, I am a child process!");
            sleep(13);
            exit(24);
        }else{
            int i;
            printf("Child proc id: %d \n", pid);
            for(i  = 0; i < 4; i++){
                puts("wait...");
                sleep(5);
            }
        }
    }
    return 0;
}

參考資料

linux signal 用法和注意事項

Linux 信號(signal)

相關文章
相關標籤/搜索