本文是一篇科普文章,介紹什么是 Linux 信號,以及它的基本用法,原文鏈接見底部參考,

Linux中有許多處于不同狀態的行程,這些行程屬于用戶應用程式或作業系統,我們需要一種機制讓內核和這些行程協調它們的活動,其中一種方式是在一個行程有重大改變時通知其他行程,因此我們有了 信號 的概念,
信號基本上是一種單向通知,信號可以由內核發送給一個行程,或由一個行程發送給另一個行程,或者一個行程發送給它自己,
Linux信號的概念來源于Unix,在后來的Linux版本中,加入了實時(real-time)信號,信號是一種簡單和輕量級的行程間通信形式,因此適用于嵌入式系統,
有關信號的討論
什么是信號?

總共有 31 個標準信號,編號為 1-31,每個信號命名為“SIG”開頭,后跟一個后綴(如INT、HUP、KILL等),從 2.2 版開始,Linux 內核支持 33 種不同的實時信號,編號為 32-64,但應用程式應改為使用 SIGRTMIN + n 表示法,標準信號有特定用途,但 SIGUSR1 和 SIGUSR2 的使用可以由程式自定義,實時信號也可由程式定義,
0號信號,即 POSIX.1 標準中所說的null信號,一般不使用,但在 kill 函式中有個特殊的用途,使用時沒有信號被發送,但可以用來(相當不可靠)檢查行程是否仍然存在,
Linux中的信號實作完全符合 POSIX 標準,最新的實作應該傾向于使用 sigaction 而不是傳統的信號介面,
正如硬體子系統可以中斷處理器一樣,信號可以中斷行程的執行,因此,它們被看作是軟體中斷,一般來說,中斷處理程式(interrupt handlers)處理硬體中斷,而信號處理程式(signal handlers)則處理信號導致的中斷,
通常信號被映射到特定的按鍵輸入,比如,SIGINT代表ctrl+c,SIGSTOP代表ctrl+z,SIGQUIT代表ctrl+\,
信號如何影響行程的狀態?

一些信號會終止正在接受信號的行程:SIGHUP、SIGINT、SIGTERM、SIGKILL,有一些信號不僅可以終止行程還會輸出一些內核資訊,以幫助程式員除錯出錯的地方,如SIGABRT(abort)、SIGBUS(bus error)、SIGILL(illegal instruction)、SIGSEGV(invalid memory reference無效記憶體參考)、SIGSYS(bad system call錯誤的系統呼叫) ),用于停止行程的信號有:SIGSTOP、SIGTSTP, SIGCONT 是恢復已停止的行程,
一個程式可以覆寫信號的默認行為,例如,一個互動式程式可以忽略SIGINT(由ctrl+c輸入產生),不過有兩個例外需要注意,SIGKILL和SIGSTOP,它們不能被忽略、阻止或用這種方式覆寫,
讓我們看一個父行程和其子行程的例子,假設子行程向自己發送了SIGSTOP,子行程將被停止,這反過來又會觸發SIGCHLD到父行程,然后,父行程可以使用SIGCONT向子行程發出繼續運行的信號,當子行程從停止狀態重新運行時,另一個SIGCHLD被發送到父行程,如果后來,子行程退出了,最后的SIGCHLD會被發送到父行程,
信號類似于例外(exception)嗎?
一些編程語言能夠使用諸如try-throw-catch這樣的結構進行例外處理,
但信號與例外并不類似,相反,失敗的系統或庫呼叫會回傳非零的退出代碼,當一個行程被終止時,它的退出代碼是128加信號編號,例如,一個被SIGKILL殺死的行程將回傳137(128+9),
信號是同步還是異步的?
信號既可以是同步,也可以是異步,
同步信號的出現是由于指令導致了一個無法恢復的錯誤,如非法地址訪問,這些信號被發送到導致它的執行緒,這些信號也被稱為陷阱(trap),因為它們也會導致陷阱進入內核的陷阱處理程式(trap handler),
異步信號是對當前執行環境的外部信號,從另一個行程中發送 SIGKILL 就是這樣一個例子,這些也被稱為軟體中斷,
信號的生命周期是什么?

一個信號經歷三個階段:
-
Generation:信號可以由內核或任何行程生成,生成后會將其發送給特定的行程,信號由其編號表示,沒有額外的資料或引數,因此,信號是輕量級的,但是,POSIX 實時信號傳遞額外的資料,可以生成信號的系統呼叫和函式包括 raise、kill、killpg、pthread_kill、tgkill 和 sigqueue,
-
Delivery:信號在傳遞之前一直處于待處理狀態,通常,內核會盡快將信號傳遞給行程,但是,如果對應的行程阻塞了信號,它將保持未處理狀態直到解除阻塞,
-
Processing:一旦信號被傳遞到,就會以多種方式中其中一種進行處理,每個信號都有一個默認的行為:忽略信號;或終止行程,有時使用核心轉儲(core dump);或停止/繼續該程序,對于非默認行為,對應的處理函式會被呼叫,通過 sigaction 函式指定究竟采用哪一種處理方式,
什么是信號阻塞和解除阻塞?

信號打斷了程式執行的正常流程,當行程正在執行一些關鍵代碼或更新與信號處理程式共享的資料時,這是不希望看到的,阻斷的引入解決了這個問題,不過代價是,信號處理被延遲了,
每個行程都可以指定它是否要阻塞一個特定的信號,如果被阻斷,而信號確實發生了,作業系統將把該信號作為待處理信號,一旦行程解除阻斷,該信號將被傳遞,當前被屏蔽的信號集合被稱為信號屏蔽(signal mask),
無限期地阻斷一個信號是沒有意義的,為了這個目的,行程可以在接受到信號后選擇忽略它,被一個行程屏蔽的信號不會影響其他行程,他們可以正常接收信號,
信號屏蔽(Signal mask)可以用 sigprocmask(單執行緒)或 pthread_sigmask(多執行緒)來設定, 當一個行程有多個執行緒時,信號可以針對每個執行緒分別設定是否屏蔽,信號將被傳遞給任何一個沒有阻斷它的執行緒,從本質上講,信號處理程式是針對某個行程的,信號掩碼是針對某個執行緒的,
一個行程可以有多個待處理的信號嗎?
是的,許多標準信號可以在行程中被掛起,然而,一個給定的信號型別只能有一個實體被掛起,這是因為信號的掛起和阻塞是作為位掩碼(bitmask)實作的,每個信號型別只有一個位,例如,我們可以讓 SIGALRM 和 SIGTERM 同時掛起,但我們不能有兩個 SIGALRM 信號掛起,行程將只收到一個SIGALRM信號,即使是多次拋出,
通過實時信號,信號可以和資料一起排隊,這樣每個信號的實體都可以單獨傳遞和處理,
POSIX沒有規定標準信號的傳遞順序,也沒有規定如果標準信號和實時信號都在等待中會如何處理,然而在Linux中,會優先處理標準信號,對于實時信號,編號較低的信號首先被傳遞,如果一個信號型別有很多在排隊,最早的一個會被首先傳遞,
大事記
- 1990 信號在 POSIX.1-1990 標準中得到了描述,可以追溯至 IEEE標準1003.1-1988,
- 1993 實時擴展作為 POSIX.1b 發布,其中包含實時信號,
- 1999 隨著內核版本 2.2 的發布,Linux 開始支持實時信號,
- 2001 POSIX.1-2001 標準中增加了更多信號:SIGBUS、SIGPOLL、SIGPROF、SIGSYS、SIGTRAP、SIGURG、SIGVTALRM、SIGXCPU、SIGXFSZ,

示例代碼
// Example shows a custom handler for SIGINT
// but the handler reverts to default action for future signals.
// Thus, first ctrl+c will allow program to continue
// and second ctrl+c will terminate the program.
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sig_handler1(int num)
{
printf("You are here becoz of signal: %d\n", num);
signal(SIGINT, SIG_DFL);
}
int main()
{
signal(SIGINT, sig_handler1);
while(1)
{
printf("Hello\n");
sleep(2);
}
}
參考
- 原文鏈接:linux-signals
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/462841.html
標籤:Linux
