要理解Linux中內核如何對信號進行捕捉,我們需要很多前置知識的理解:
- 內核態和用戶態的區別
- CPU指令集權限
- 內核態和用戶態之間的切換
由于文章的側重點不同,上面這些知識我會在這篇文章盡量詳細提及,更詳細內容還得請大家查看這篇文章:
內核態與用戶態詳解
一、內核態和用戶態的區別
內核態和用戶態本質上是對CPU狀態的描述。內核態的權限最大,能夠執行一切“特權代碼”。而用戶態則受限制,必須切換到內核態才能執行“特權代碼”。
這就意味著內核態能夠調度全部硬件資源,本質上就是對權限進行保護。由于硬件資源非常復雜,稍有不慎就會出現很大的問題,所以不是人人都有權限去調度。這樣做就是為了安全性和穩定性。
二、CPU指令集權限
具體來說每一條匯編指令都對應一條CPU指令,非常多的CPU指令組成了CPU指令集。指令集則通過CPU實現軟件對硬件的調度。
同時CPU指令有分級權限。CPU指令能夠操作硬件資源,硬件資源是非常復雜的,很容易出問題。所以操作系統直接屏蔽掉用戶對CPU指令集的操作。
CPU指令分級總共有四級,從高到低依次為:
- ring0
- ring1
- ring2
- ring3
Linux系統僅采用ring0和ring3這2個權限。用戶態的程序工作在3,內核態的程序處于0:
- ring0權限最高,可以使用所有CPU指令集,有對硬件的所有操作權限
- ring3權限最低,僅能使用常規CPU指令集,不能使用操作硬件資源的CPU指令集。代碼沒有對硬件的直接控制權限,也不能直接訪問地址的內存,程序是通過調用系統接口(System Call APIs)來達到訪問硬件和內存
三、內核態和用戶態之間的切換
1.如何理解進程切換?
- 在當前進程的進程地址空間中的內核空間,找到操作系統的代碼和數據。
- 執行操作系統的代碼,將當前進程的代碼和數據剝離下來,并換上另一個進程的代碼和數據。
注意: 當你訪問用戶空間時你必須處于用戶態,當你訪問內核空間時你必須處于內核態。
從用戶態切換為內核態通常有如下幾種情況:
- 需要進行系統調用時。
- 當前進程的時間片到了,導致進程切換。
- 產生異常、中斷、陷阱等。
與之相對應,從內核態切換為用戶態有如下幾種情況:
- 系統調用返回時。
- 進程切換完畢。
- 異常、中斷、陷阱等處理完畢。
其中,由用戶態切換為內核態我們稱之為陷入內核。每當我們需要陷入內核的時,本質上是因為我們需要執行操作系統的代碼,比如系統調用函數是由操作系統實現的,我們要進行系統調用就必須先由用戶態切換為內核態。
2.系統調用
用戶態要主動切換到內核態要有統一的入口,它們就是內核提供的系統調用接口,下面是Linux整體架構圖:
我們可以看出來通過系統調用將Linux整個體系分為內核態和用戶態,而內核提供了一組通用的訪問接口,它們使應用程序能訪問到內核的資源,如CPU、內存、I/O,這些接口就叫系統調用。
四、內核如何實現信號的捕捉?
當我們在執行主控制流程的時候,可能因為某些情況而陷入內核,當內核處理完畢準備返回用戶態時,就需要進行信號pending的檢查。(此時仍處于內核態,有權力查看當前進程的pending位圖)
在查看pending位圖時,如果發現有未決信號,并且該信號沒有被阻塞,那么此時就需要該信號進行處理。
1.待處理信號是默認或忽略
如果待處理信號的處理動作是默認或者忽略,則執行該信號的處理動作后清除對應的pending標志位,如果沒有新的信號要遞達,就直接返回用戶態,從主控制流程中上次被中斷的地方繼續向下執行即可。
2.待處理信號是自定義捕捉的
但如果待處理信號是自定義捕捉的,即該信號的處理動作是由用戶提供的,那么處理該信號時就需要先返回用戶態執行對應的自定義處理動作,執行完后再通過特殊的系統調用sigreturn再次陷入內核并清除對應的pending標志位,如果沒有新的信號要遞達,就直接返回用戶態,繼續執行主控制流程的代碼。
注意: sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關系,是兩個獨立的控制流程。
3.巧記
當待處理信號是自定義捕捉時的情況比較復雜,可以借助下圖進行記憶:
其中,該圖形與直線有4個交點就代表在這期間有4次狀態切換,而箭頭的方向就代表著此次狀態切換的方向,圖形中間的圓點就代表著檢查pending表。
當識別到信號的處理動作是自定義時,能直接在內核態執行用戶空間的代碼嗎?不能!!!
- 理論上來說是可以的,因為內核態是一種權限非常高的狀態,但是絕對不能這樣設計。
- 如果允許在內核態直接執行用戶空間的代碼,那么用戶就可以在代碼中設計一些非法操作,比如清空數據庫等,雖然在用戶態時沒有足夠的權限做到清空數據庫,但是如果是在內核態時執行了這種非法代碼,那么數據庫就真的被清空了,因為內核態是有足夠權限清空數據庫的。
- 也就是說,不能讓操作系統直接去執行用戶的代碼,因為操作系統無法保證用戶的代碼是合法代碼,即操作系統不信任任何用戶。
特別鳴謝:
Linux進程信號
內核態與用戶態詳解