linux非同步訊號handle淺析

來源:互聯網
上載者:User

在初學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是個很神奇的東西,使用者程式可以使用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系統調用重新進入核心後,壓在使用者棧上的原始返回地址及其調用棧被擷取。最終核心又會修改棧,讓進程返回使用者空間時返回到這個原始返回地址上。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.