首頁 >系統教程 >Linux >linux異步訊號handle淺析

linux異步訊號handle淺析

WBOY
WBOY轉載
2024-02-13 22:03:13397瀏覽

Linux系統是一種支援多任務並發執行的作業系統,它可以同時運行多個進程,從而提高系統的使用率和效率。但是,如果這些進程之間需要進行資料交換和協作,就需要使用一些進程間通訊(IPC)的方式,例如訊息佇列、共享記憶體、信號量等。其中,訊號是一種比較簡單而靈活的IPC方式,它可以讓一個行程向另一個行程發送簡短的訊息,通知它發生了某種事件或異常。 Linux系統中的訊號有兩種類型,分別是同步訊號和非同步訊號。本文將淺析linux非同步訊號handle的方法,包括非同步訊號的意義、產生、發送、接收、處理和忽略等面向。

linux異步訊號handle淺析

#在初學linux程式設計的時候,一直覺得非同步訊號handle是個很神奇的東西,使用者程式可以使用singal之類的系統呼叫為某某訊號註冊一個訊號處理函數(handle函數)。
程式的二進位程式碼在記憶體中都有確定的執行流程,為什麼收到非同步訊號以後,程式會被“中斷”,然後跳到這個handle函數裡面去運作呢?核心怎麼有能力讓程式做這樣的跳轉呢,總不可能臨時修改程式的可執行程式碼吧?

後來學習了一些核心知識,才知道原來行程收到訊號以後,並不是立即就被「中斷」的,而是先在行程的控制結構(task_struct)中記錄下收到了某某訊號,然後等到行程即將從內核態返回用戶態的時候,流程才被“中斷”,handle函數才會被呼叫。
使用者行程什麼時候會從核心態返回使用者態呢?一般主要是三種情況:系統呼叫(使用者程序主動進入核心)、中斷(使用者程序被動進入核心)、被調度執行(使用者程序從等待執行變成正在執行)。
行程從收到訊號到它從核心態傳回用戶態的過程,是需要一定時間的。但是這個時間一般會很短,至少時鐘中斷會以較大的頻率(例如1毫秒一次)將使用者程序帶入核心(當然,只針對正在執行的進程)。

在進程即將從內核態傳回用戶態時,如果有訊號需要處理,對應的handle函數將被呼叫(當然,可能沒有註冊handle,這時內核對訊號進行預設的處理)。注意,現在的行程還在內核態,內核是怎麼呼叫用戶態的handle函數?
直接調用可以嗎?當然不行。核心程式碼運行在高CPU特權等級下,如果直接呼叫handle函數,則handle函數也會在相同的CPU特權下執行。那麼用戶將可以在handle函數裡面為所欲為。
所以,呼叫handle必須先回傳用戶態。但返回用戶態後,程式流程又不受核心控制了,難不成核心還真的把用戶進程的可執行程式碼暫時改掉?

內核實際的做法還是比較巧妙。使用者進程進入核心以後,都會在其對應的核心堆疊上留下返回位址,以便流程返回。核心呼叫handle函數的方法就是暫時改掉堆疊上的回傳位址,然後再按原有的回傳用戶態的流程去回傳。結果這一返回,就到了handle函數去了。 (當然,需要修改的不只回傳位址,而是一整個呼叫堆疊。)
雖然現在臨時把回傳地址改了,但使用者進程最終還是要回到原來那個回傳地址去的。那麼,原先的返回位址及其呼叫堆疊應該保存在哪裡呢?進程的內核堆疊空間有限,而且還需要應付handle函數中可能發生的系統調用,所以內核把這些資訊放在內核棧上是不現實的,只能壓到了用戶棧上去。

當handle函數執行完畢,執行流程要回到核心去。同樣,由於CPU特權等級不同,從handle函數返回核心時不能單純利用RET指令去返回的。需要執行一次系統呼叫。

在handle執行完後,為什麼要回到內核,再從內核回到原始回傳位址?如果直接回到原始的回傳地址那自然是很便捷。而且要這麼做也不難,原始回傳位址及其呼叫棧已經被壓到了用戶棧上,核心只需要在handle函數的呼叫棧上稍做手腳就行了。
1.返回到原始返回地址並不是回到那個地址就行了,需要把整個現場都恢復(主要是寄存器什麼的)。當然,核心也可以在使用者堆疊上面壓一些程式碼,來完成這些事情;
2、現在可能不只一個訊號要處理,最好讓使用者行程返回內核,繼續處理其他訊號;

為了傳回內核,首先,內核在回到handle函數之前,先將某個回傳位址壓到使用者堆疊上,以便從handle回傳時能夠回到指定的位址上。這個指定的位址其實也在行程的使用者堆疊上,核心又在這個位址上放了幾條指令(在堆疊上放置可執行程式碼),讓行程去呼叫一個名叫sigreturn的系統呼叫。

回到handle函數前的使用者堆疊大致如下:
原有資料 -> 呼叫sigreturn的指令(設其位址為a) -> 原始回傳位址及其呼叫堆疊 -> 傳回位址(值為a) -> handle的堆疊變數

核心在handle函數的呼叫堆疊上放置sigreturn指令,這是在linux 2.4時的做法。每次呼叫使用者的handle函數都需要向使用者堆疊拷貝這麼幾條指令,這並不太好。
linux 2.6有一個叫vsyscall page的頁面,上麵包含了核心為使用者程式準備的一些指令,其中就包含呼叫sigreturn指令。這個vsyscall頁被對應到每個行程的虛擬位址空間靠近末端的部分,被所有使用者行程共享,對於使用者行程是唯讀的。這樣,handle函數的呼叫堆疊上就不需要再塞入sigreturn指令了,直接將handle函數的回傳位址設為vsyscall頁中對應的程式碼即可。

為了讓handle執行完以後自動呼叫sigreturn返回內核,內核做了很多事情。那可不可以約定好,讓用戶自己去呼叫sigreturn呢?
當然,這是可以的。只是為了讓訊號處理機製成為一套完整的機制,核心並沒有這麼做。否則用戶在handle函數裡面忘記呼叫sigreturn的話,可能莫名其妙地進程就崩潰了。而編譯器也很難找出這樣的錯誤。

進程呼叫sigreturn系統呼叫重新進入核心後,壓在使用者堆疊上的原始返回位址及其呼叫堆疊被取得。最終核心又會修改棧,讓行程返回用戶空間時回到這個原始回傳位址。

本文淺析了linux非同步訊號handle的方法,包括非同步訊號的意義、產生、發送、接收、處理和忽略等面向。透過了解和掌握這些知識,我們可以掌握Linux訊號處理的核心知識,從而提高系統的穩定性和效率。當然,linux非同步訊號handle還有很多其他的特性和用法,需要我們不斷地學習和研究。希望本文能帶給你一些啟發和幫助。

以上是linux異步訊號handle淺析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除