學習一下信號的處理機制。
一、信號的產生
信號是有可能來自內核,也有可能來自進程。當然,最根本的來源是信號產生函數。
其實就是通過內核更新目標進程的數據結構以表示一個信號已經被髮送。
其中爲進程產生信號的函數有:
函數名 |
說明 |
send_sig() |
向單一進程發送信號 |
send_sig_info() |
與send_sig()類似,只是還使用siginfo_t結構中的擴展信息 |
force_sig() |
發送既不能被進程顯示忽略,也不能被進程阻塞的信號 |
force_sig_info() |
與force_sig()類似,只是還使用siginfo_t結構中的擴展信息 |
force_sig_specific() |
與force_sig()類似,但是優化了對SIGSTOP和SIGKILL信號的處理 |
sys_tkill() |
tkill()的系統調用處理函數 |
sys_tgkill() |
tgkill()的系統調用處理函數 |
爲線程組產生信號的函數有:
函數名 |
說明 |
send_group_sig_info() |
向某一個線程組發送信號,該線程組由它的一個成員進程的描述符來識別 |
kill_pg() |
想一個進程組中的所有線程組發送信號 |
kill_pg_info() |
與kill_pg()類似,只是還使用siginfo_t結構中的擴展信息 |
kill_pg_proc() |
向某一個線程組發送信號,該線程組由它的一個成員進程的pid來識別 |
kill_pg_proc_info() |
與kill_pg_proc ()類似,只是還使用siginfo_t結構中的擴展信息 |
sys_kill() |
kill()的系統調用處理函數 |
sys_rt_sigqueueinfo() |
rt_sigqueueinfo()的系統調用處理函數 |
二、信號的傳遞
當內核注意到一個信號到來,並調用相關函數爲接受此信號的進程準備描述符。但是萬一這個進程那一刻並不在CPU上運行,內核就只能延遲傳遞信號的任務。因此這裏就有兩個queue,分別儲存私有信號和共享信號。每當內核處理完一箇中斷或異常時,就檢查是否存在掛起信號(即檢查TIF_SIGPENDING)。如果存在掛起信號,那麼內核就會調用do_signal函數。這個函數會循環執行,直到將兩個隊列中的所有非阻塞信號都處理完才退出。
既然直到do_signal()整個過程幹了什麼,那麼接下來就描述一下對於每一個信號,do_signal會進行怎麼樣的處理。首先,它會檢查current接收進程是否受到其他一些進程的監控;在肯定的情況下,do_signal函數調用相關函數讓監控進程知道current接收進程的信號處理;然後do_signal()要把處理信號的k_sigaction數據結構的地址賦值給局部變量ka,再根據ka內容來執行三種操作:忽略信號、執行缺省操作或執行信號處理程序。
三、信號的處理
在第二點中已經提到了進程收到信號之後的可能三種操作。這裏只描述執行信號處理程序的過程。這個過程比較複雜。
如果信號有一個專門的處理程序,那麼do_signal()函數就會通過調用handle_signal()來強迫該處理程序執行。但是信號處理程序都是用戶態進程所定義的,幷包含在用戶態的代碼段中。handle_signal()函數運行在內核態,而信號處理程序運行在用戶態,這就意味着在當前進程恢復“正常”執行之前,它首先必須執行用戶態的信號處理程序。而且當內核打算恢復進程的正常執行時,內核態堆棧不再包含被中斷程序的硬件上下文,因爲每當從內核態向用戶態轉換時內核態堆棧都會被清空。而如果信號處理程序調用了系統調用,那麼這個執行過程的複雜程度就更高了。
Linux對此過程的解決方案是把保存在內核態堆棧中的硬件上下文拷貝到當前進程的用戶態堆棧中。用戶態堆棧也以這樣的方式被修改,當信號處理程序終止時,自動調用sigreturn()系統調用把這個硬件上下文拷貝回內核態堆棧中,並回複用戶態堆棧中原來的內容。
該過程流程圖如下:
更多的細節不在此描述了。