1. 程式人生 > 其它 >20191227甘濘與第六章學習筆記

20191227甘濘與第六章學習筆記

第6章 訊號和訊號處理

6.1訊號和中斷

訊號:發給程序的請求,將程序從正常執行轉移到中斷處理。
中斷:是從I/O裝置或協處理器傳送到CPU的外部請求,它將CPU從正常執行轉移到中斷處理。
“中斷”是傳送給“程序”的事件,它將“程序”從正常活動轉移到其他活動,稱為“中斷處理”。“程序”可在完成“中斷”處理後恢復正常活動。
終端主要有以下幾種型別:

  • 1、人員中斷
  • 2、程序中斷
  • 3、硬體中斷
  • 4、程序的陷阱錯誤

Unix/Linux的訊號處理

(1)按Ctrl+C組合鍵通常會導致當前執行的程序終止。原因如下:Ctrl+C組合鍵會生成一個鍵盤硬體中斷。鍵盤中斷處理程式將Ctrl+C組合鍵轉換為SIGINT(2)訊號,傳送給終端上的所有程序,並喚醒等待鍵盤輸入的程序。在核心模式下,每個程序都要檢查和處理未完成的訊號。程序對大多數訊號的預設操作是呼叫核心的kexit(exitValue)函式來終止。在Linux中,exitValue的低位位元組是導致程序終止的訊號編號。
(2)使用者可使用nohup a.out &命令在後臺執行一個程式。即使在使用者退出後,程序仍將繼續執行。
(3)使用者再次登入時也許會發現(通過ps-u LTD)後臺程序仍在執行。使用者可以使用sh命令kill pid (or kill -s 9 pid)殺死該程序。

訊號處理函式

每一個程序塊都有一個訊號處理陣列int sig【32】。sig[32]陣列的每個條目都指定了如何處理相應的訊號,其中0表示DEFault(預設),1表示 IGNore(忽略)。下圖給出了訊號位向量、遮蔽位向量和訊號處理函式。

如果訊號位向量中的位I為1,則會生成一個訊號I或將其傳送給程序。如果遮蔽位向量的位I為1,則訊號會被阻塞或遮蔽。否則,訊號未被阻塞。只有當訊號存在並且未被阻塞時,訊號才會生效或傳遞給程序。

訊號的傳送

傳送訊號的主要函式有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
1、kill()

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo)

引數pid的值 訊號的接收程序
pid>0 程序ID為pid的程序
pid=0 同一個程序組的程序
pid<0 pid!=-1 程序組ID為 -pid的所有程序
pid=-1 除傳送程序自身外,所有程序ID大於1的程序
Sinno是訊號值,當為0時(即空訊號),實際不傳送任何訊號,但照常進行錯誤檢查,因此,可用於檢查目標程序是否存在,以及當前程序是否具有向目標傳送訊號的許可權(root許可權的程序可以向任何程序傳送訊號,非root許可權的程序只能向屬於同一個session或者同一個使用者的程序傳送訊號)。

Kill()最常用於pid>0時的訊號傳送,呼叫成功返回 0; 否則,返回 -1。 注:對於pid<0時的情況,對於哪些程序將接受訊號,各種版本說法不一,其實很簡單,參閱核心原始碼kernal/signal.c即可,上表中的規則是參考red hat 7.2。

2、raise()

#include <signal.h>
int raise(int signo)

向程序本身傳送訊號,引數為即將傳送的訊號值。呼叫成功返回 0;否則,返回 -1。

3、sigqueue()

#include <sys/types.h>
#include <signal.h> 
int sigqueue(pid_t pid, int sig, const union sigval val)

呼叫成功返回 0;否則,返回 -1。

sigqueue()是比較新的傳送訊號系統呼叫,主要是針對實時訊號提出的(當然也支援前32種),支援訊號帶有引數,與函式sigaction()配合使用。

sigqueue的第一個引數是指定接收訊號的程序ID,第二個引數確定即將傳送的訊號,第三個引數是一個聯合資料結構union sigval,指定了訊號傳遞的引數,即通常所說的4位元組值。
typedef union sigval { int sival_int; void *sival_ptr; }sigval_t;
sigqueue()比kill()傳遞了更多的附加資訊,但sigqueue()只能向一個程序傳送訊號,而不能傳送訊號給一個程序組。如果signo=0,將會執行錯誤檢查,但實際上不傳送任何訊號,0值訊號可用於檢查pid的有效性以及當前程序是否有許可權向目標程序傳送訊號。

在呼叫sigqueue時,sigval_t指定的資訊會拷貝到3引數訊號處理函式(3引數訊號處理函式指的是訊號處理函式由sigaction安裝,並設定了sa_sigaction指標,稍後將闡述)的siginfo_t結構中,這樣訊號處理函式就可以處理這些資訊了。由於sigqueue系統呼叫支援傳送帶引數訊號,所以比kill()系統呼叫的功能要靈活和強大得多。

注:sigqueue()傳送非實時訊號時,第三個引數包含的資訊仍然能夠傳遞給訊號處理函式; sigqueue()傳送非實時訊號時,仍然不支援排隊,即在訊號處理函式執行過程中到來的所有相同訊號,都被合併為一個訊號。

4、alarm()

#include <unistd.h> 
unsigned int alarm(unsigned int seconds) 

專門為SIGALRM訊號而設,在指定的時間seconds秒後,將向程序本身傳送SIGALRM訊號,又稱為鬧鐘時間。程序呼叫alarm後,任何以前的alarm()呼叫都將無效。如果引數seconds為零,那麼程序內將不再包含任何鬧鐘時間。
返回值,如果呼叫alarm()前,程序中已經設定了鬧鐘時間,則返回上一個鬧鐘時間的剩餘時間,否則返回0。

5、setitimer()

#include <sys/time.h> 
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); 

setitimer()比alarm功能強大,支援3種類型的定時器:

ITIMER_REAL: 設定絕對時間;經過指定的時間後,核心將傳送SIGALRM訊號給本程序;
ITIMER_VIRTUAL 設定程式執行時間;經過指定的時間後,核心將傳送SIGVTALRM訊號給本程序;
ITIMER_PROF 設定程序執行以及核心因本程序而消耗的時間和,經過指定的時間後,核心將傳送ITIMER_VIRTUAL訊號給本程序;
Setitimer()第一個引數which指定定時器型別(上面三種之一);第二個引數是結構itimerval的一個例項,結構itimerval形式見附錄1。第三個引數可不做處理。

Setitimer()呼叫成功返回0,否則返回-1。

6、abort()

#include <stdlib.h> 
void abort(void);

向程序傳送SIGABORT訊號,預設情況下程序會異常退出,當然可定義自己的訊號處理函式。即使SIGABORT被程序設定為阻塞訊號,呼叫abort()後,SIGABORT仍然能被程序接收。該函式無返回值。

訊號捕捉函式

程序可使用系統呼叫:int r =signal(int signal_numberr void *handler);來修改選定訊號編號的處理函式,SIGKILL(19)除外,它們不能修改。signal()系統呼叫在所有類Unix系統中均可用,但它有一些缺點:

  • 在執行已安裝的訊號捕捉函式之前,通常將訊號處理函式重置為DEFault。為捕捉下一次出現相同的訊號,必須重新安裝捕捉函式。
  • signal()不能阻塞其他訊號
  • 不同Unix版本的signal。可能會有所不通不同。
    所以現在signal()已經被sigaciton()所代替,它的原型是int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);,sigaction結構體的定義為


其中重要的欄位如下:

sa_handler :該欄位是指向處理函式的指標,該函式與signal()的處理函式有相同的原型。
sa_sigaction:該欄位是執行訊號處理函式的另一種方法。它的訊號編號旁邊有兩個額外引數,其中siginfo t *提供關於所接收訊號的更多資訊。
sa_mask:可在處理函式執行期間設定要阻塞的訊號。
sa_flags :可修改訊號處理程序的行為。若要使用sa_sigaction處理函式,必須將sa_flags設定為SA_SIGINFO。

實踐內容

/* sigset.c */
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>

    /* 自定義的訊號處理函式 */
    void my_func(int signum)
    {
        printf("If you want to quit,please try SIGQUIT\n");
    }

    int main()
    {
        sigset_t set,pendset;
        struct sigaction action1,action2;

        /* 初始化訊號集為空 */
        if (sigemptyset(&set) < 0)
        {
            perror("sigemptyset");
            exit(1);
        }

        /* 將相應的訊號加入訊號集 */
        if (sigaddset(&set, SIGQUIT) < 0)
        {
            perror("sigaddset");
            exit(1);
        }

        if (sigaddset(&set, SIGINT) < 0)
        {
            perror("sigaddset");
            exit(1);
        }

        if (sigismember(&set, SIGINT))
        {
            sigemptyset(&action1.sa_mask);
            action1.sa_handler = my_func;
            action1.sa_flags = 0;
            sigaction(SIGINT, &action1, NULL);
        }

        if (sigismember(&set, SIGQUIT))
        {
            sigemptyset(&action2.sa_mask);
            action2.sa_handler = SIG_DFL;
            action2.sa_flags = 0;
            sigaction(SIGQUIT, &action2,NULL);
        }

        /* 設定訊號集遮蔽字,此時set中的訊號不會被傳遞給程序,暫時進入待處理狀態 */
        if (sigprocmask(SIG_BLOCK, &set, NULL) < 0)
        {
            perror("sigprocmask");
            exit(1);
        }
        else
        {
            printf("Signal set was blocked, Press any key!");
            getchar();
        }
        /* 在訊號遮蔽字中刪除set中的訊號 */
        if (sigprocmask(SIG_UNBLOCK, &set, NULL) < 0)
        {
            perror("sigprocmask");
            exit(1);
        }
        else
        {
            printf("Signal set is in unblock state\n");
        }

        while(1);
        exit(0);
    }

該程式的執行結果如下,可以看見,在訊號處於阻塞狀態時,所發出的訊號對程序不起作用,並且該訊號進入待處理狀態。讀者按任意鍵,並且訊號脫離了阻塞狀態後,使用者發出的訊號才能正常執行。這裡SIGINT已按照使用者自定義的函式執行,請讀者注意阻塞狀態下SIGINT的處理和非阻塞狀態下SIGINT的處理有何不同。

問題與解決