談論進程上下文 、中斷上下文 、 原子上下文以前,有必要討論下兩個概念:程序員
a -- 上下文併發
上下文是從英文context翻譯過來,指的是一種環境。相對於進程而言,就是進程執行時的環境;異步
具體來講就是各個變量和數據,包括全部的寄存器變量、進程打開的文件、內存信息等。函數
b -- 原子性能
原子(atom)本意是「不能被進一步分割的最小粒子」,而原子操做(atomic operation)意爲"不可被中斷的一個或一系列操做" ;atom
1、爲何會有上下文這種概念spa
內核空間和用戶空間是現代操做系統的兩種工做模式,內核模塊運行在內核空間,而用戶態應用程序運行在用戶空間。它們表明不一樣的級別,而對系統資源具備不一樣的訪問權限。內核模塊運行在最高級別(內核態),這個級下全部的操做都受系統信任,而應用程序運行在較低級別(用戶態)。在這個級別,處理器控制着對硬件的直接訪問以及對內存的非受權訪問。內核態和用戶態有本身的內存映射,即本身的地址空間。操作系統
其中處理器總處於如下狀態中的一種:.net
內核態,運行於進程上下文,內核表明進程運行於內核空間;線程
內核態,運行於中斷上下文,內核表明硬件運行於內核空間;
用戶態,運行於用戶空間。
上下文的切換,用戶空間和內核空間具備不一樣的 地址映射,通用或專用的寄存器組,而用戶空間的進程要傳遞不少變量、參數給內核,內核也要保存用戶進程的一些寄存器、變量等,以便系統調用結束後回到用戶 空間繼續執行,
2、進程上下文
所謂的進程上下文,就是一個進程在執行的時候,CPU的全部寄存器中的值、進程的狀態以及堆棧上的內容,當內核須要切換到另外一個進程時,它 須要保存當前進程的全部狀態,即保存當前進程的進程上下文,以便再次執行該進程時,可以恢復切換時的狀態,繼續執行。
一個進程的上下文能夠分爲三個部分:用戶級上下文、寄存器上下文以及系統級上下文。
用戶級上下文: 正文、數據、用戶堆棧以及共享存儲區;
寄存器上下文: 通用寄存器、程序寄存器(IP)、處理器狀態寄存器(EFLAGS)、棧指針(ESP);
系統級上下文: 進程控制塊task_struct、內存管理信息(mm_struct、vm_area_struct、pgd、pte)、內核棧。
當發生進程調度時,進行進程切換就是上下文切換(context switch)。
操做系統必須對上面提到的所有信息進行切換,新調度的進程才能運行。而系統調用進行的是模式切換(mode switch)。模式切換與進程切換比較起來,容易不少,並且節省時間,由於模式切換最主要的任務只是切換進程寄存器上下文的切換。
進程上下文主要是異常處理程序和內核線程。內核之因此進入進程上下文是由於進程自身的一些工做須要在內核中作。例如,系統調用是爲當前進程服務的,異常一般是處理進程致使的錯誤狀態等。因此在進程上下文中引用current是有意義的。
3、中斷上下文
硬件經過觸發信號,向CPU發送中斷信號,致使內核調用中斷處理程序,進入內核空間。這個過程當中,硬件的一些變量和參數也要傳遞給內核, 內核經過這些參數進行中斷處理。
因此,「中斷上下文」就能夠理解爲硬件傳遞過來的這些參數和內核須要保存的一些環境,主要是被中斷的進程的環境。
內核進入中斷上下文是由於中斷信號而致使的中斷處理或軟中斷。而中斷信號的發生是隨機的,中斷處理程序及軟中斷並不能事先預測發生中斷時當前運行的是哪一個進程,因此在中斷上下文中引用current是能夠的,但沒有意義。
事實上,對於A進程但願等待的中斷信號,可能在B進程執行期間發生。例如,A進程啓動寫磁盤操做,A進程睡眠後B進程在運行,當磁盤寫完後磁盤中斷信號打斷的是B進程,在中斷處理時會喚醒A進程。
4、進程上下文 VS 中斷上下文
內核能夠處於兩種上下文:進程上下文和中斷上下文。
在系統調用以後,用戶應用程序進入內核空間,此後內核空間針對用戶空間相應進程的表明就運行於進程上下文。
異步發生的中斷會引起中斷處理程序被調用,中斷處理程序就運行於中斷上下文。
內核會限制中斷上下文的工做,不容許其執行以下操做:
a -- 進入睡眠狀態或主動放棄CPU
因爲中斷上下文不屬於任何進程,它與current沒有任何關係(儘管此時current指向被中斷的進程),因此中斷上下文一旦睡眠或者放棄CPU,將沒法被喚醒。因此也叫原子上下文(atomic context)。
b -- 佔用互斥體
爲了保護中斷句柄臨界區資源,不能使用mutexes。若是得到不到信號量,代碼就會睡眠,會產生和上面相同的狀況,若是必須使用鎖,則使用spinlock。
c -- 執行耗時的任務
中斷處理應該儘量快,由於內核要響應大量服務和請求,中斷上下文佔用CPU時間太長會嚴重影響系統功能。在中斷處理例程中執行耗時任務時,應該交由中斷處理例程底半部來處理。
d -- 訪問用戶空間虛擬內存
由於中斷上下文是和特定進程無關的,它是內核表明硬件運行在內核空間,因此在中斷上下文沒法訪問用戶空間的虛擬地址
e -- 中斷處理例程不該該設置成reentrant(可被並行或遞歸調用的例程)
由於中斷髮生時,preempt和irq都被disable,直到中斷返回。因此中斷上下文和進程上下文不同,中斷處理例程的不一樣實例,是不容許在SMP上併發運行的。
f -- 中斷處理例程能夠被更高級別的IRQ中斷
若是想禁止這種中斷,能夠將中斷處理例程定義成快速處理例程,至關於告訴CPU,該例程運行時,禁止本地CPU上全部中斷請求。這直接致使的結果是,因爲其餘中斷被延遲響應,系統性能降低。
5、原子上下文
內核的一個基本原則就是:在中斷或者說原子上下文中,內核不能訪問用戶空間,並且內核是不能睡眠的。也就是說在這種狀況下,內核是不能調用有可能引發睡眠的任何函數。
這四個宏所訪問的count都是thread_info->preempt_count。這個變量實際上是一個位掩碼。最低8位表示搶佔計數,一般由spin_lock/spin_unlock修改,或程序員強制修改,同時代表內核允許的最大搶佔深度是256。
8-15位是軟中斷計數,一般由local_bh_disable/local_bh_enable修改,同時代表內核允許的最大軟中斷深度是256。
16-27位是硬中斷計數,一般由enter_irq/exit_irq修改,同時代表內核允許的最大硬中斷深度是4096。
第28位是PREEMPT_ACTIVE標誌。用代碼表示就是:
PREEMPT_MASK: 0x000000ff
SOFTIRQ_MASK: 0x0000ff00
HARDIRQ_MASK: 0x0fff0000
凡是上面4個宏返回1獲得地方都是原子上下文,是不允許內核訪問用戶空間,不允許內核睡眠的,不允許調用任何可能引發睡眠的函數。並且表明thread_info->preempt_count不是0,這就告訴內核,在這裏面搶佔被禁用。
但 是,對於in_atomic()來講,在啓用搶佔的狀況下,它工做的很好,能夠告訴內核目前是否持有自旋鎖,是否禁用搶佔等。可是,在沒有啓用搶佔的狀況 下,spin_lock根本不修改preempt_count,因此即便內核調用了spin_lock,持有了自旋鎖,in_atomic()仍然會返回 0,錯誤的告訴內核目前在非原子上下文中。因此凡是依賴in_atomic()來判斷是否在原子上下文的代碼,在禁搶佔的狀況下都是有問題的。