Linux 核心筆記 — 訊號

來源:互聯網
上載者:User
Linux 核心筆記 -- 訊號

關鍵詞: Linux    核心    訊號                                          

Linux 核心筆記 -- 訊號

原文連結:http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK&Number=291055&page=9&view=collapsed&sb=5&o=all

劉世勝

1 前言
寫作本文的目的和其它文章略有不同,不是為了系統和全面的介紹”訊號”這個子系統,--雖然它不複雜,其內容也不是一篇短短的文章所能夠覆蓋的,而是要回答自己的疑惑,解決工作中遇到的一些問題,理解那些不能夠馬上瞭解的部分。說到底,可以將這篇文章看作問題的答案。

曾經遇到的問題放在最後一節”問題與答案”中,在閱讀本文之前先掃描一下問題可能更加有助於理解文章中的內容。

歡迎大家對這篇文章提出意見和指正,我的email是:shisheng_liu@yahoo.com.cn。

2 許可協議
本文的許可協議遵循GNU Free Document License。協議的具體內容請參見http://www.gnu.org/copyleft/fdl.html。在遵循GNU Free Document License的基礎上,可以自由地傳播或發行本文,但請保留本文的完整性。

3 什麼是訊號
訊號是UNIX處理序間通訊的一種標準方式,在最早期的UNIX系統中已經存在。訊號的出現允許核心和其它進程通知進程特定事件的發生。現代UNIX中也存在其它的處理序間通訊方式,但由於訊號相對簡單和有效,它們仍然被廣泛使用。

訊號是最簡單的訊息,當一個訊號被發送時,它沒有參數等附加資訊,唯有一個整數來表示訊號的值。訊號的值在所有的UNIX系統中已經標準化了,每一個訊號都有一個名字,它以三個字母SIG開頭,對應於特定的事件。例如:SIGKILL表示終止進程;SIGBUS代表硬體故障;SIGCHLD代表子進程狀態改變。

UNIX中常用的訊號有31個,除了前面提到的三個訊號外,還有如下訊號是文章中用到的,其它的不再一一列出。

SIGSTOP 暫停程式執行

SIGCONT 恢複程式執行

3.1 進程對訊號的處理
進程可以對每一個訊號設定單獨的處理方式,它可以忽略該訊號,也可以設定自己的訊號處理常式(稱為捕捉),或者對訊號什麼也不做,訊號發生的時候執行系統預設動作。

進程還可以設定對某些訊號的阻塞(block)標誌。如果一個訊號被設定為阻塞,當訊號發生的時候,它會與正常的訊號一樣被遞送(deliver)給進程,但只有進程解除對訊號的阻塞時才會被處理。

從一個非阻塞訊號被遞送給進程到訊號得到處理之間的時間間隔,稱為訊號未決(pending)。有的資料將pending翻譯為”訊號掛起”。

所有的訊號中,有兩個訊號SIGSTOP和SIGKILL是特殊的,他們既不能被捕捉,也不能被忽略,也不能被阻塞,這個特性保證了系統管理員在任何時候都可以用訊號暫停和結束程式。

3.2 進程資料結構中與訊號相關的部分
從這一部份開始,我們轉入對linux核心部分的分析。在核心中,一個基本的資料結構sigset_t用來表示訊號。不同的CPU架構中sigset_t的長度略有不同,對Intel i386架構來說,sigset_t是一個64位整數,每一位表示一個訊號。Sigset_t中的後32位表示即時訊號,它和普通訊號的唯一區別是支援同一個訊號的排隊,這保證了發送的多個即時訊號都被接收。即時訊號是POSIX標準的一部分,但linux並沒有對它做單獨的處理,除了支援排隊以外,因此它不是我們關心的重點。

進程結構task_struct中包含下列資料成員與訊號相關

l sigpending

類型為int的一個標誌,如果非0表示進程中存在著未決的非阻塞訊號等待處理。

l pending

類型為struct sigpending的變數。可以看作進程的訊號隊列,它存放所有未決的訊號,對某些訊號來說,還包括相關的資訊。例如SIGCHLD訊號是子進程結束時發送給父進程的,它的附加資訊中包括了子進程的ID和結束值。

l blocked

類型為sigset_t的變數。表示當前進程中哪些訊號被阻塞。

l sig

類型為struct signal_struct的變數。描述進程怎樣處理每個訊號。

3.3 相關函數
下面是進程處理訊號時經常用到的系統調用

l sigaction

設定或讀取進程對某個訊號的處理方式。進程可以忽略或用預設處理該訊號,也可以設定自己的訊號處理常式。

l Sigprocmask

設定或讀取進程的訊號阻塞掩碼。

l Sigpending

返回當前未決的訊號集。被阻塞的未決訊號並不返回。

4 訊號的發送
訊號是非同步事件,當一個訊號被發送給進程時,接收進程可能運行在任何時刻,處於任何一種狀態。為了使程式處於不同狀態時都能夠正確的處理訊號,系統在訊號發送的時候需要進行適當的預先處理。我們討論的過程實際上包括訊號的產生和訊號的遞送,下面會進行詳細的分析。

4.1 進程的狀態
從進程本身的狀態來看,它有可能處在運行態(RUNNING),等待態(INTERRUPTBLE &UNINTERRUPTBLE), 停止態(STOPPED)和僵死態(ZOMBIE)。而從進程啟動並執行模式(mode)來看,又可能位於核心模式和使用者模式的任一種,

4.2 程式分析
有幾種方法可以將訊號發送給某個進程,但它們最終會調用核心中的一個函數send_sig_info來完成發送。在這個函數裡,核心會進行一系列檢查,只有滿足適當條件*的訊號才會被放在進程的訊號隊列中;接著檢查程式是否處於INTERRUPTABLE態,如果是就喚醒該進程,將進程的狀態改為RUNNING態,並且放在系統運行隊列內。

系統對發送訊號的處理有兩點比較有意思:

1) 只有SIGKILL和SIGCONT訊號能夠被狀態為STOPPED進程接收。

所有其他訊號都被忽略。這是由STOPPED狀態本身的特性決定的,它被設定來控制進程的執行和暫停,SIGCONT訊號能夠使暫停程式恢複執行,而SIGKILL被接收則提供殺死暫停進程的能力。

2)所謂滿足適當的訊號是滿足一系列檢查條件的訊號:

.a)發送訊號的進程滿足POSIX.1中對寄件者的要求

.b)該進程沒有顯式/隱含忽略該訊號。

.c)該進程沒有阻塞該訊號。

.d)同樣的訊號沒有已經在進程中掛起。同一進程的同一訊號是不排隊的,如果一個訊號被連續發送多次,而它已經在接收進程中被掛起時,後續的訊號被簡單丟棄。

4.3 相關函
4.3.1 核心部分
l send_sig_info(kernel/signal.c)

完成訊號發送的入口函數,其他所有函數最終都通過它完成訊號的發送。

l force_sig_info(kernel/signal.c)

僅供核心功能使用的強制訊號發送函數。它做了一些努力以確保進程既不能忽略,又不能阻塞該訊號,然後調用send_sig_info來完成訊號的發送。

4.3.2 使用者部分
l kill系統調用

進程通過kill系統調用向其他進程發送訊號,kill系統調用在核心中的實現參見kernel/signal.c中的sys_kill() 函數,它調用send_sig_info來完成訊號的發送。

Kill能夠向一個進程或整個進程組發送訊號。通過在kill系統調用時指定負的接收進程ID(”pid”),訊號被發送給ID為”-pid”的進程組;如果將接收進程ID指定為-1,則訊號被發送給除自己外的全體進程,這顯然是一個不應該經常使用的功能。

5 訊號的處理
5.1 訊號處理常式
訊號處理常式是進程本身的執行程式的一部分,只有進程正在CPU上啟動並執行時候才能得到執行,也就是說,如果進程得不到執行的機會,例如狀態是UNINTERRUPTABLE, STOPPED(如前面所說,可以接收SIGKILL, SIGCONT訊號)等狀態,發給進程的訊號永遠不會得到處理的機會。

5.2 何時執行
核心會在一些特定的時間點,-- 具體的說,在每一個系統調用結束,程式從核心態返回到使用者態之前和程式從中斷和異常代碼中返回的時候[參見arch/kernel/entry.S檔案中的ret_from_sys_call]代碼 --, 檢查當前進程是否有訊號未被處理,然後調用訊號處理的主函數do_signal。

5.3 程式分析
作為一個主函數,do_signal有相當多值得注意的地方。它迴圈地工作,每一個迴圈都從進程中取出一個未決的訊號,取訊號的順序是由小到大,然後加以處理。處理的過程如下:

1) 如果程式正在被跟蹤,程式會被轉入STOPPED態,同時一個SIGCHLD訊號被發送給跟蹤者。一個有意思的事情是:當程式再次得到運行權後,它是怎樣啟動並執行呢?

2) 如果訊號的處理操作是忽略該訊號,則立刻完成本次迴圈,但SIGCHLD訊號是一個例外,它調用sys_wait4函數,強迫這個進程讀子進程的資訊,藉此清理由終止的子進程所留下的記憶體。

3) 如果訊號的處理操作是預設處理,do_signal會執行訊號的預設操作,一個例外當接收進程是init時,訊號被丟棄,本次迴圈會立即完成。

不同訊號的預設操作是不同的,可以分為四類,第一類訊號的預設操作是忽略,例如SIGCHLD訊號;第二類訊號會將進程轉入停止態(注意,並不是結束進程),例如SIGSTOP訊號;第三類訊號會結束進程並將結束前的狀態寫入core檔案,例如表示非法記憶體訪問的SIGSEGV訊號;而另一些訊號僅僅簡單的結束進程(do_exit函數),例如SIGKILL訊號。

4)最後,調用進程本身的訊號處理常式來處理訊號,在完成了這個調用後,do_signal直接返回,也就是說,與其他被通過迴圈處理的訊號不同,帶有自己的訊號處理函數的訊號在一次do_signal調用中只能處理一個。這樣做的原因是,與其它訊號處理方式不同,進程的訊號處理函數為使用者態函數,它不可能直接在do_signal迴圈中被調用,否則會帶來嚴重的安全性問題,do_signal能做的是設定程式的執行寄存器環境和堆棧代碼,使進程回到使用者態首先執行訊號處理函數。設定進程的棧環境是一項相當複雜的工作,值得用另一篇文章來單獨說明了,在此不再贅述,可以參考具體代碼arch/kernel/signal.c中的handle_signal函數。

5.4 相關函數
所有的相關函數都位於核心態。

l ret_from_syscall

系統調用結束,返回到使用者態前執行的函數。它檢查進程中是否存在訊號未決,並調用do_signal函數來處理未決的訊號。Ret_from_syscall位於彙編檔案arch/kernel/entry.S中,它其實並不是一個函數,而是一個組合語言程式點,調用者用jmp指令來進入它。

l do_signal

訊號處理的主函數。位於arch/kernel/signal.c檔案中。

l handle_signal

處理有”自訂訊號處理函數”的訊號,完成建立堆棧等複雜工作。

6 訊號與系統調用
6.1 允許被訊號中斷的系統調用
正常執行的系統調用是不會被訊號中斷的,但某些系統調用可能需要很長的等待時間來完成,對於這種情況,允許訊號中斷它的執行提供了更好的程式控制能力。要達到允許被訊號中斷的功能,系統調用的實現上需要滿足如下條件:

系統調用必須在需要等待的時候將進程轉入睡眠狀態,主動讓出CPU。因為作為核心態的程式,系統調用的執行是不可搶佔的,不主動放棄CPU的系統調用會一直運行直到結束。而且當前進程的睡眠狀態必須設定為TASK_INTERRUPTABLE,只有在這個狀態下,當訊號被發送給進程時,進程的狀態被(訊號發送函數)喚醒,並重新在運行隊列中排隊等待調度。

當進程獲得了一個CPU時間片後,它接著睡眠時的下一條指令開始運行(還在系統調用內),系統調用判斷出收到了訊號,會設定一個與訊號有關的退出標誌並迅速結束。退出標誌為:ERESTARTNOHAND,ERESTARTSYS,ERESTARTNOINTR中的一種,這些標誌都是和接收訊號程式do_signal通訊用的,它們和進程對特定訊號的處理標誌一起,決定了系統調用中斷後是否重新執行。

ERESTARTNOINTR:要求系統調用被無條件重新執行。

ERESTARTSYS: 要求系統調用重新執行,但允許訊號處理常式根據自己的標誌來做選擇。

ERESTARTNOHAND:在訊號沒有設定自己的訊號處理函數時系統調用重新執行。

6.2 系統調用的重新執行
在系統調用結束的時候,do_signal函數得以執行,它會與系統調用的退出標誌一起來決定是否重新執行系統調用。do_sigal函數需要對兩種情況進行處理,一種是進程對收到的訊號設定了自己的訊號處理常式,而另一種是進程沒有設定收到的訊號的訊號處理常式,而且在處理完所有訊號後,進程沒有被停止或者結束掉。在後一種情況中,當系統調用退出的標誌為ERESTARTNOHAND,ERESTARTSYS,ERESTARTNOINTR中的任意一個時,do_signal會使系統調用重新啟動。而對前一種情況,只有系統調用的退出標誌為ERESTARTNOINTR或者ERESTARTSYS並且訊號處理標誌也滿足條件的時候系統調用才能重新啟動。

6.3 相關函數
l sys_sigaction

系統調用sigaction的實現。通過sigaction系統調用,進程可以設定特定訊號的處理常式和處理標誌,其中一個標誌SA_RESTART影響是否允許系統調用的重新執行。

7 問題與答案
l 為什麼有時候進程阻塞於系統調用中時,該阻塞可以被訊號中斷;而阻塞於另一些系統調用的時候就不可以被中斷?

如”訊號與系統調用”一節所述,只有將進程以TASK_INTERRUPTABLE方式阻塞的系統調用才能夠被中斷。而設定以TASK_UNINTERRUPTABLE方式阻塞的系統調用不能中斷。

l 系統是如何確保SIGKILL/SIGSTOP訊號不能被捕捉和忽略的?

進程通過系統調用sigaction來設定對訊號的處理方式,sigaction在核心中的實現函數是sys_sigaction(kernel/signal.c),它對進程傳遞訊號值進行檢查,確保訊號SIGKILL和SIGSTOP不能被設定。

另外兩個系統調用sigprocmask和sigsuspend會更改訊號的屏蔽字,與sigaction類似,它們在核心中的實現函數會檢查參數,確保SIGKILL和SIGSTOP不被屏蔽。

l 阻塞訊號和忽略訊號兩種操作有何不同?

阻塞訊號允許訊號被發送給進程,但不進行處理,需要等到阻塞解除後再處理。而忽略訊號是進程根本不接收該訊號,所有被忽略的訊號都被丟棄。

系統調用sigprocmask和sigsuspend被用來設定訊號的阻塞與否;而系統調用sigaction則設定進程是否忽略一個訊號。

8 參考文獻:
1. linux核心原始碼版本2.4.14

在任何時候真實的代碼總是提供給我們最準確和詳細的資料。感謝Linus Torvalds,Alan Cox和其它linux開發人員的辛勤勞動。

2.DANIEL P.BOVET & MARCO CESATI

<<UNDERSTANDING THE LINUX KERNEL>> ISBN: 0-596-00002-2 O’REILLY 2001

中譯版 《深入理解Linux核心》 陳莉君等譯 ISBN: 7-5083-0719-4 中國電力出版社 2001。

本書是專門介紹linux核心結構的書中最詳盡的一本,對程式碼分析講解的也比較深入,基於2.2的核心版本

3.W.Richard Stevens

《UNIX環境進階編程》 尤晉元譯 ISBN: 7-111-07579-X 機械工業出版社 2000

UNIX編程聖經,程式員手頭必備的書籍之一,對所有UNIX開發人員,無論水平高低,都有參考價值。翻譯的水準也難得一見的高明。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.