Linux系統程式設計之訊號中斷處理(上)

千鋒教育官方發表於2019-09-05


     什麼是訊號?

訊號是 Linux 程式間通訊的最古老的方式。訊號是軟體中斷,它是在軟體層次上對中斷機制的一種模擬,是一種非同步通訊的方式   。訊號可以導致一個正在執行的程式被另一個正在執行的非同步程式中斷,轉而處理某一個突發事件。

中斷 在我們生活中經常遇到,譬如,我正在房間裡打遊戲,突然送快遞的來了,把正在玩遊戲的我給 中斷 了,我去簽收快遞 ( 處理中斷 ) ,處理完成後,再繼續玩我的遊戲。這裡我們學習的 訊號 就是屬於這麼一種 中斷 。我們在終端上敲 “Ctrl+c” ,就產生一個 中斷 ,相當於產生一個訊號,接著就會處理這麼一個 中斷任務 (預設的處理方式為中斷當前程式)。

訊號可以直接進行使用者空間程式和核心空間程式的互動,核心程式可以利用它來通知使用者空間程式發生了哪些系統事件。

一個完整的訊號週期包括三個部分:訊號的產生,訊號在程式中的註冊,訊號在程式中的登出,執行訊號處理函式。如下圖所示:

 

 

注意:這裡訊號的產生,註冊,登出時訊號的內部機制,而不是訊號的函式實現。

Linux 可使用命令: kill -l "l" 為字母) , 檢視相應的訊號。


 

 

 

列表中,編號為 1 ~ 31 的訊號為傳統 UNIX 支援的訊號,是不可靠訊號(非實時的),編號為 32 ~ 63 的訊號是後來擴充的,稱做可靠訊號(實時訊號)。不可靠訊號和可靠訊號的區別在於前者不支援排隊,可能會造成訊號丟失,而後者不會。非可靠訊號一般都有確定的用途及含義 ,   可靠訊號則可以讓使用者自定義使用。更多詳情,請看《 Linux 訊號列表》。

 

訊號的產生方式

1 )當使用者按某些終端鍵時,將產生訊號。

終端上按“Ctrl+c” 組合鍵通常產生中斷訊號 SIGINT ,終端上按 “Ctrl+\” 鍵通常產生中斷訊號 SIGQUIT ,終端上按 “Ctrl+z” 鍵通常產生中斷訊號 SIGSTOP 等。

2 )硬體異常將產生訊號。

除數為 0 ,無效的記憶體訪問等。這些情況通常由硬體檢測到,並通知核心,然後核心產生適當的訊號傳送給相應的程式。

3 )軟體異常將產生訊號。

當檢測到某種軟體條件已發生,並將其通知有關程式時,產生訊號。

4 )呼叫 kill() 函式將傳送訊號。

注意:接收訊號程式和傳送訊號程式的所有者必須相同,或傳送訊號程式的所有者必須是超級使用者。

5 )執行 kill 命令將傳送訊號。

此程式實際上是使用 kill 函式來傳送訊號。也常用此命令終止一個失控的後臺程式。

 

訊號的常用操作:

 

傳送訊號

所需標頭檔案:

#include <sys/types.h>

#include <signal.h>

 

int kill(pid_t pid, intsignum);

功能:

給指定程式傳送訊號。

注意:使用 kill() 函式傳送訊號,接收訊號程式和傳送訊號程式的所有者必須相同,或者傳送訊號程式的所有者是超級使用者。

 

引數:

pid: 取值有 4 種情況 :

pid > 0: 將訊號傳送給程式 ID pid 的程式。

pid = 0: 將訊號傳送給當前程式所在程式組中的所有程式。

pid = -1: 將訊號傳送給系統內所有的程式。

pid < -1: 將訊號傳給指定程式組的所有程式。這個程式組號等於 pid 的絕對值。

signum: 訊號的編號,這裡可以填數字編號,也可以填訊號的宏定義,可以透過命令 kill -l ("l" 為字母 ) 進行相應檢視。

 

返回值:

成功:0

失敗:-1

下面為測試程式碼,本來父子程式各自每隔一秒列印一句話,3 秒後,父程式透過 kill() 函式給子程式傳送一箇中斷訊號 SIGINT 2 號訊號),最終,子程式結束,剩下父程式在列印資訊:

 

 

#include <stdio.h>  

#include <stdlib.h>  

#include <unistd.h>  

#include <sys/types.h>  

#include <signal.h>  

  

int main(int argc, char *argv[])  

{  

    pid_t pid;  

    int i = 0;  

  

    pid = fork(); // 建立程式  

    if( pid < 0 ){ // 出錯  

        perror("fork");  

    }  

      

    if(pid == 0){ // 子程式  

        while(1){  

            printf("I am son\n");  

            sleep(1);  

        }  

    }else if(pid > 0){ // 父程式  

        while(1){  

            printf("I am father\n");  

            sleep(1);  

              

            i++;  

            if(3 == i){// 3秒後  

                kill(pid, SIGINT); // 給子程式 pid ,傳送中斷訊號 SIGINT  

                // kill(pid, 2); // 等級於kill(pid, SIGINT);  

            }  

        }  

    }  

  

    return 0;  

}  

 

執行結果:

等待訊號

所需標頭檔案:

#include <unistd.h>

int pause(void);

功能:

等待訊號的到來(此函式會阻塞)。將呼叫程式掛起直至捕捉到訊號為止,此函式通常用於判斷訊號是否已到。

引數:無。

返回值:

直到捕獲到訊號才返回 -1 ,且 errno 被設定成 EINTR

測試程式碼如下:

 

 

#include <unistd.h>  

#include <stdio.h>  

  

int main(int argc, char *argv[])  

{  

    printf("in pause function\n");  

    pause();  

      

    return 0;  

}  

 

沒有產生訊號前,程式一直阻塞在 pause() 不會往下執行,假如,我們按 “Ctrl+c” pause() 會捕獲到此訊號,中斷當前程式

 

 

處理訊號

一個程式收到一個訊號的時候,可以用如下方法進行處理:

1 )執行系統預設動作

對大多數訊號來說,系統預設動作是用來終止該程式。  

2 )忽略此訊號

接收到此訊號後沒有任何動作。

3 )執行自定義訊號處理函式

用使用者定義的訊號處理函式處理該訊號。

注意:SIGKILL SIGSTOP 不能更改訊號的處理方式,因為它們向使用者提供了一種使程式終止的可靠方法。

產生一個訊號,我們可以讓其執行自定義訊號處理函式。假如有函式 A, B, C ,我們如何確定訊號產生後只呼叫函式 A ,而不是函式 B C 。這時候,我們需要一種規則規定,訊號產生後就呼叫函式 A ,就像交通規則一樣,紅燈走綠燈行,訊號註冊函式 signal() 就是做這樣的事情。

 

所需標頭檔案:

#include <signal.h>

 

 

typedef void (*sighandler_t)(int);// 回撥函式的宣告

sighandler_t signal(int signum,sighandler_t handler);

功能:

註冊訊號處理函式(不可用於 SIGKILL SIGSTOP 訊號),即確定收到訊號後處理函式的入口地址。此函式不會阻塞。

引數:

signum :訊號的編號,這裡可以填數字編號,也可以填訊號的宏定義,可以透過命令 kill -l ("l" 為字母 ) 進行相應檢視。

handler: 取值有 3 種情況:

SIG_IGN :忽略該訊號

SIG_DFL :執行系統預設動作

訊號處理函式名:自定義訊號處理函式,如:fun

回撥函式的定義如下:

void fun(int signo)

{

// signo 為觸發的訊號,為 signal() 第一個引數的值

}

注意:訊號處理函式應該為可重入函式,關於可重入函式的更多詳情,請《淺談可重入函式與不可重入函式》。

返回值:

成功:第一次返回 NULL ,下一次返回此訊號上一次註冊的訊號處理函式的地址。如果需要使用此返回值,必須在前面先宣告此函式指標的型別。

失敗:返回 SIG_ERR

例項1

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

 

// 訊號處理函式

void signal_handler(int signo)

{

if(signo == SIGINT){

printf("recv SIGINT\n");

}else if(signo == SIGQUIT){

printf("recv SIGQUIT\n");

}

}

 

int main(int argc, char *argv[])

{

printf("wait for SIGINT OR SIGQUIT\n");

/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */

// 訊號註冊函式

signal(SIGINT, signal_handler);

signal(SIGQUIT, signal_handler);

// 等待訊號

pause();

pause();

return 0;

}

 

執行結果

 

例項2

 

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

 

// 回撥函式的宣告

typedef void (*sighandler_t)(int);

 

void fun1(int signo)

{

printf("in fun1\n");

}

 

void fun2(int signo)

{

printf("in fun2\n");

}

 

int main(int argc, char *argv[])

{

sighandler_t previous = NULL;

// 第一次返回 NULL

previous = signal(SIGINT,fun1);

if(previous == NULL)

{

printf("return fun addr is NULL\n");

}

// 下一次返回此訊號上一次註冊的訊號處理函式的地址。

previous = signal(SIGINT, fun2);

if(previous == fun1)

{

printf("return fun addr is fun1\n");

}

// 還是返回 NULL,因為處理的訊號變了

previous = signal(SIGQUIT,fun1);

if(previous == NULL)

{

printf("return fun addr is NULL\n");

}

return 0;

}

執行結果:

 

 

 

 

最後:

關注回覆“物聯網”即可獲取物聯網全套影片教程

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69914734/viewspace-2656130/,如需轉載,請註明出處,否則將追究法律責任。

相關文章