本文簡(jiǎn)單介紹下Linux信號(hào)處理機(jī)制,為介紹二進(jìn)制翻譯下信號(hào)處理機(jī)制做一個(gè)鋪墊。
本文主要參考書(shū)目《Linux內(nèi)核源代碼情景分析》《獨(dú)辟蹊徑品內(nèi)核:Linux內(nèi)核源代碼導(dǎo)讀》
首先,先說(shuō)一下什么是信號(hào)。信號(hào)本質(zhì)上是在軟件層次上對(duì)中斷機(jī)制的一種模擬,其主要有以下幾種來(lái)源:
程序錯(cuò)誤:除零,非法內(nèi)存訪問(wèn)…
外部信號(hào):終端Ctrl-C產(chǎn)生SGINT信號(hào),定時(shí)器到期產(chǎn)生SIGALRM…
顯式請(qǐng)求:kill函數(shù)允許進(jìn)程發(fā)送任何信號(hào)給其他進(jìn)程或進(jìn)程組。
在Linux下,可以通過(guò)以下命令查看系統(tǒng)所有的信號(hào):
kill-l可以通過(guò)類(lèi)似下面的命令顯式的給一個(gè)進(jìn)程發(fā)送一個(gè)信號(hào):
kill-2pid上面的命令將2號(hào)信號(hào)發(fā)送給進(jìn)程id為pid的進(jìn)程。不存在編號(hào)為0的信號(hào)。
目前Linux支持64種信號(hào)。信號(hào)分為非實(shí)時(shí)信號(hào)(不可靠信號(hào))和實(shí)時(shí)信號(hào)(可靠信號(hào))兩種類(lèi)型,對(duì)應(yīng)于 Linux 的信號(hào)值為 1-31 和 34-64。信號(hào)是異步的,一個(gè)進(jìn)程不必通過(guò)任何操作來(lái)等待信號(hào)的到達(dá),事實(shí)上,進(jìn)程也不知道信號(hào)到底什么時(shí)候到達(dá)。本文著重于Linux的信號(hào)處理機(jī)制,對(duì)信號(hào)更多的介紹可以參考這里。
一般情況下一個(gè)進(jìn)程接受到信號(hào)后,會(huì)有如下的行為:
進(jìn)程對(duì)信號(hào)的響應(yīng)
忽略信號(hào):大部分信號(hào)可被忽略,除SIGSTOP和SIGKILL信號(hào)外(這是超級(jí)用戶(hù)殺掉或停掉任意進(jìn)程的手段)。
捕獲信號(hào):注冊(cè)信號(hào)處理函數(shù),它對(duì)產(chǎn)生的特定信號(hào)做處理。
讓信號(hào)默認(rèn)動(dòng)作起作用:unix內(nèi)核定義的默認(rèn)動(dòng)作,有5種情況:
a) 流產(chǎn)abort:終止進(jìn)程并產(chǎn)生core文件。
b) 終止stop:終止進(jìn)程但不生成core文件。
c) 忽略:忽略信號(hào)。
d) 掛起suspend:掛起進(jìn)程。
e) 繼續(xù)continue:若進(jìn)程是掛起的,則resume進(jìn)程,否則忽略此信號(hào)。
注冊(cè)信號(hào)處理函數(shù)
如果想要進(jìn)程捕獲某個(gè)信號(hào),然后作出相應(yīng)的處理,就需要注冊(cè)信號(hào)處理函數(shù)。同中斷類(lèi)似,內(nèi)核也為每個(gè)進(jìn)程準(zhǔn)備了一個(gè)信號(hào)向量表,信號(hào)向量表中記錄著每個(gè)信號(hào)所對(duì)應(yīng)的處理機(jī)制,默認(rèn)情況下是調(diào)用默認(rèn)處理機(jī)制。當(dāng)進(jìn)程為某個(gè)信號(hào)注冊(cè)了信號(hào)處理程序后,發(fā)生該信號(hào)時(shí),內(nèi)核就會(huì)調(diào)用注冊(cè)的函數(shù)。
注冊(cè)信號(hào)處理函數(shù)是通過(guò)系統(tǒng)調(diào)用signal()、sigaction()。其中signal()在可靠信號(hào)系統(tǒng)調(diào)用的基礎(chǔ)上實(shí)現(xiàn), 是庫(kù)函數(shù)。它只有兩個(gè)參數(shù),不支持信號(hào)傳遞信息,主要是用于前32種非實(shí)時(shí)信號(hào)的安裝;而sigaction()是較新的函數(shù)(由兩個(gè)系統(tǒng)調(diào)用實(shí) 現(xiàn):sys_signal以及sys_rt_sigaction),有三個(gè)參數(shù),支持信號(hào)傳遞信息,主要用來(lái)與 sigqueue() 系統(tǒng)調(diào)用配合使用,當(dāng)然,sigaction()同樣支持非實(shí)時(shí)信號(hào)的安裝。sigaction()優(yōu)于signal()主要體現(xiàn)在支持信號(hào)帶有參數(shù)。關(guān)于這方面的內(nèi)容,如果想獲取更多,也可參考這里。
Linux下信號(hào)處理機(jī)制
進(jìn)程如何發(fā)現(xiàn)和接受信號(hào)?
我們知道,信號(hào)是異步的,一個(gè)進(jìn)程不可能等待信號(hào)的到來(lái),也不知道信號(hào)會(huì)到來(lái),那么,進(jìn)程是如何發(fā)現(xiàn)和接受信號(hào)呢?實(shí)際上,信號(hào)的接收不是由用戶(hù)進(jìn)程來(lái)完成的,而是由內(nèi)核代理。當(dāng)一個(gè)進(jìn)程P2向另一個(gè)進(jìn)程P1發(fā)送信號(hào)后,內(nèi)核接受到信號(hào),并將其放在P1的信號(hào)隊(duì)列當(dāng)中。當(dāng)P1再次陷入內(nèi)核態(tài)時(shí),會(huì)檢查信號(hào)隊(duì)列,并根據(jù)相應(yīng)的信號(hào)調(diào)取相應(yīng)的信號(hào)處理函數(shù)。如下圖所示:
其中,動(dòng)作c:發(fā)現(xiàn)和捕捉信號(hào)
信號(hào)檢測(cè)和響應(yīng)時(shí)機(jī)
剛才我們說(shuō),當(dāng)P1再次陷入內(nèi)核時(shí),會(huì)檢查信號(hào)隊(duì)列。那么,P1什么時(shí)候會(huì)再次陷入內(nèi)核呢?陷入內(nèi)核后在什么時(shí)機(jī)會(huì)檢測(cè)信號(hào)隊(duì)列呢?
當(dāng)前進(jìn)程由于系統(tǒng)調(diào)用、中斷或異常而進(jìn)入系統(tǒng)空間以后,從系統(tǒng)空間返回到用戶(hù)空間的前夕。
當(dāng)前進(jìn)程在內(nèi)核中進(jìn)入睡眠以后剛被喚醒的時(shí)候(必定是在系統(tǒng)調(diào)用中),或者由于不可忽略信號(hào)的存在而提前返回到用戶(hù)空間。
進(jìn)入信號(hào)處理函數(shù)
發(fā)現(xiàn)信號(hào)后,根據(jù)信號(hào)向量,知道了處理函數(shù),那么該如何進(jìn)入信號(hào)處理程序,又該如何返回呢?
我們知道,用戶(hù)進(jìn)程提供的信號(hào)處理函數(shù)是在用戶(hù)態(tài)里的,而我們發(fā)現(xiàn)信號(hào),找到信號(hào)處理函數(shù)的時(shí)刻處于內(nèi)核態(tài)中,所以我們需要從內(nèi)核態(tài)跑到用戶(hù)態(tài)去執(zhí)行信號(hào)處理程序,執(zhí)行完畢后還要返回內(nèi)核態(tài)。這個(gè)過(guò)程如下圖所示:
如圖中所見(jiàn),處理信號(hào)的整個(gè)過(guò)程是這樣的:進(jìn)程由于 系統(tǒng)調(diào)用或者中斷 進(jìn)入內(nèi)核,完成相應(yīng)任務(wù)返回用戶(hù)空間的前夕,檢查信號(hào)隊(duì)列,如果有信號(hào),則根據(jù)信號(hào)向量表找到信號(hào)處理函數(shù),設(shè)置好“frame”后,跳到用戶(hù)態(tài)執(zhí)行信號(hào)處理函數(shù)。信號(hào)處理函數(shù)執(zhí)行完畢后,返回內(nèi)核態(tài),設(shè)置“frame”,再返回到用戶(hù)態(tài)繼續(xù)執(zhí)行程序。
在上面這段話中,我提到“frame”,frame是什么?那么為什么要設(shè)置frame?為什么在執(zhí)行完信號(hào)處理函數(shù)后還要返回內(nèi)核態(tài)呢?
什么叫Frame?
在調(diào)用一個(gè)子程序時(shí),堆棧要往下(邏輯意義上是往上)伸展,這是因?yàn)樾枰诙褩V斜4孀映绦虻姆祷氐刂罚€因?yàn)樽映绦蛲芯植孔兞浚惨加枚褩V械目臻g。此外,調(diào)用子程序時(shí)的參數(shù)也是在堆棧中。子程序調(diào)用嵌套越深,則堆棧伸展的層次也越多。在堆棧中的每一個(gè)這樣的層次,就稱(chēng)為一個(gè)”框架”,即frame。
一般來(lái)說(shuō),當(dāng)子程序和調(diào)用它的程序在同一空間中時(shí),堆棧的伸展,也就是堆棧中框架的建立,過(guò)程主要如下:
call指令將返回地址壓入堆棧(自動(dòng))
用push指令壓入調(diào)用參數(shù)
調(diào)整堆棧指針來(lái)分配局部變量
為什么以及怎么設(shè)置frame?
我們知道,當(dāng)進(jìn)程陷入內(nèi)核態(tài)的時(shí)候,會(huì)在堆棧中保存中斷現(xiàn)場(chǎng)。因?yàn)橛脩?hù)態(tài)和內(nèi)核態(tài)是兩個(gè)運(yùn)行級(jí)別,所以要使用兩個(gè)不同的棧。當(dāng)用戶(hù)進(jìn)程通過(guò)系統(tǒng)調(diào)用剛進(jìn)入內(nèi)核的時(shí)候,CPU會(huì)自動(dòng)在該進(jìn)程的內(nèi)核棧上壓入下圖所示的內(nèi)容:(圖來(lái)自《Linux內(nèi)核完全注釋》)
在處理完系統(tǒng)調(diào)用以后,就要調(diào)用do_signal()函數(shù)進(jìn)行設(shè)置frame等工作。這時(shí)內(nèi)核堆棧的狀態(tài)應(yīng)該跟下圖左半部分類(lèi)似(系統(tǒng)調(diào)用將一些信息壓入棧了):
在找到了信號(hào)處理函數(shù)之后,do_signal函數(shù)首先把內(nèi)核堆棧中存放返回執(zhí)行點(diǎn)的eip保存為old_eip,然后將eip替換為信號(hào)處理函數(shù)的地址,然后將內(nèi)核中保存的“原ESP”(即用戶(hù)態(tài)棧地址)減去一定的值,目的是擴(kuò)大用戶(hù)態(tài)的棧,然后將內(nèi)核棧上的內(nèi)容保存到用戶(hù)棧上,這個(gè)過(guò)程就是設(shè)置frame.值得注意的是下面兩點(diǎn):
之所以把EIP的值設(shè)置成信號(hào)處理函數(shù)的地址,是因?yàn)橐坏┻M(jìn)程返回用戶(hù)態(tài),就要去執(zhí)行信號(hào)處理程序,所以EIP要指向信號(hào)處理程序而不是原來(lái)應(yīng)該執(zhí)行的地址。
之所以要把frame從內(nèi)核棧拷貝到用戶(hù)棧,是因?yàn)檫M(jìn)程從內(nèi)核態(tài)返回用戶(hù)態(tài)會(huì)清理這次調(diào)用所用到的內(nèi)核棧(類(lèi)似函數(shù)調(diào)用),內(nèi)核棧又太小,不能單純的在棧上保存另一個(gè)frame(想象一下嵌套信號(hào)處理),而我們需要EAX(系統(tǒng)調(diào)用返回值)、EIP這些信息以便執(zhí)行完信號(hào)處理函數(shù)后能繼續(xù)執(zhí)行程序,所以把它們拷貝到用戶(hù)態(tài)棧以保存起來(lái)。
以上這些搞清楚之后,下面的事情就順利多了。這時(shí)進(jìn)程返回用戶(hù)空間,就會(huì)根據(jù)內(nèi)核棧中的EIP值執(zhí)行信號(hào)處理函數(shù)。那么,信號(hào)處理程序執(zhí)行完后,怎么返回程序繼續(xù)執(zhí)行呢?
信號(hào)處理函數(shù)執(zhí)行完后怎么辦?
信號(hào)處理程序執(zhí)行完畢之后,進(jìn)程會(huì)主動(dòng)調(diào)用sigreturn()系統(tǒng)調(diào)用再次回到內(nèi)核,查看有沒(méi)有其他信號(hào)需要處理,如果沒(méi)有,這時(shí)內(nèi)核就會(huì)做一些善后工作,將之前保存的frame恢復(fù)到內(nèi)核棧,恢復(fù)eip的值為old_eip,然后返回用戶(hù)空間,程序就能夠繼續(xù)執(zhí)行。至此,內(nèi)核遍完成了一次(或幾次)信號(hào)處理工作。
-
Linux
+關(guān)注
關(guān)注
87文章
11459瀏覽量
212789 -
數(shù)字信號(hào)處理
+關(guān)注
關(guān)注
16文章
567瀏覽量
46540 -
IP內(nèi)核
+關(guān)注
關(guān)注
0文章
13瀏覽量
12958
發(fā)布評(píng)論請(qǐng)先 登錄
如何配置和驗(yàn)證Linux內(nèi)核參數(shù)
樹(shù)莓派4 性能大比拼:標(biāo)準(zhǔn)Linux與實(shí)時(shí)Linux 4.19內(nèi)核的延遲測(cè)試

差分輸入的AD轉(zhuǎn)換芯片如何處理單端輸入的信號(hào)?
騰訊云內(nèi)核團(tuán)隊(duì)修復(fù)Linux關(guān)鍵Bug
AFE5809EVM GUI如何處理低頻信號(hào)?
如何處理溫度傳感器的信號(hào)干擾
LMX2572LP如果輸入是單端信號(hào),OSC_INM不用,應(yīng)該如何處理?
deepin社區(qū)亮相第19屆中國(guó)Linux內(nèi)核開(kāi)發(fā)者大會(huì)
詳解linux內(nèi)核的uevent機(jī)制
數(shù)字地和模擬地如何處理
linux驅(qū)動(dòng)程序如何加載進(jìn)內(nèi)核
Linux內(nèi)核測(cè)試技術(shù)

請(qǐng)問(wèn)比較器不用的引腳如何處理比較好?
Linux內(nèi)核中的頁(yè)面分配機(jī)制

評(píng)論