linux系統下程式所處的環境:
a-- 執行於使用者空間,執行使用者程序。
b-- 執行於核心空間,處於程序上下文中,代表某個特定的程序執行。
c-- 執行於核心空間,處於中斷上下文中,與任何程序無關,代表硬體執行於核心空間,處理某個特定的中斷。
通過系統呼叫,使用者空間的應用程式就會進入核心空間,由核心代表該程序執行於核心空間,這就涉及到
上下文的切換
,使用者空間和核心空間具有不同的 位址對映,通用或專用的暫存器組,而使用者空間的程序要傳遞很多變數、引數給核心,核心也要儲存使用者程序的一些暫存器、變數等,以便系統呼叫結束後回到使用者 空間繼續執行。
二、程序上下文
所謂的程序上下文,就是乙個程序在執行的時候,cpu的所有暫存器中的值、程序的狀態以及堆疊上的內容,當核心需要切換到另乙個程序時,它 需要儲存當前程序的所有狀態,即儲存當前程序的程序上下文,以便再次執行該程序時,能夠恢復切換時的狀態,繼續執行。
乙個程序的上下文可以分為三個部分:使用者級上下文、暫存器上下文以及系統級上下文。
使用者級上下文: 正文、資料、使用者堆疊以及共享儲存區;
暫存器上下文: 通用暫存器、程式暫存器(ip)、處理器狀態暫存器(eflags)、棧指標(esp);
系統級上下文: 程序控制塊task_struct、記憶體管理資訊(mm_struct、vm_area_struct、pgd、pte)、核心棧。
當發生程序排程時,進行程序切換就是上下文切換(context switch)。
作業系統必須對上面提到的全部資訊進行切換,新排程的程序才能執行。而系統呼叫進行的是模式切換(mode switch)。模式切換與程序切換比較起來,容易很多,而且節省時間,因為模式切換最主要的任務只是切換程序暫存器上下文的切換。
程序上下文主要是異常處理程式和核心執行緒。核心之所以進入程序上下文是因為程序自身的一些工作需要在核心中做。例如,系統呼叫是為當前程序服務的,異常通常是處理程序導致的錯誤狀態等。所以在程序上下文中引用current是有意義的。
三、中斷上下文
硬體通過觸發訊號,向cpu傳送中斷訊號,導致核心呼叫中斷處理程式,進入核心空間。這個過程中,硬體的一些變數和引數也要傳遞給核心, 核心通過這些引數進行中斷處理。
所以,「中斷上下文」就可以理解為硬體傳遞過來的這些引數和核心需要儲存的一些環境,主要是被中斷的程序的環境。
核心進入中斷上下文是因為中斷訊號而導致的中斷處理或軟中斷。而中斷訊號的發生是隨機的,中斷處理程式及軟中斷並不能事先**發生中斷時當前執行的是哪個程序,所以在中斷上下文中引用current是可以的,但沒有意義。
事實上,對於a程序希望等待的中斷訊號,可能在b程序執行期間發生。例如,a程序啟動寫磁碟操作,a程序睡眠後b程序在執行,當磁碟寫完後磁碟中斷訊號打斷的是b程序,在中斷處理時會喚醒a程序。
四、程序上下文 vs 中斷上下文
核心可以處於兩種上下文:程序上下文和中斷上下文。
在系統呼叫之後,使用者應用程式進入核心空間,此後核心空間針對使用者空間相應程序的代表就執行於程序上下文。
非同步發生的中斷會引發中斷處理程式被呼叫,中斷處理程式就執行於中斷上下文。
中斷上下文和程序上下文不可能同時發生。
執行於程序上下文的核心**是可搶占的,但中斷上下文則會一直執行至結束,不會被搶占。因此,核心會限制中斷上下文的工作,不允許其執行如下操作:
a -- 進入睡眠狀態或主動放棄cpu
由於中斷上下文不屬於任何程序,它與current沒有任何關係(儘管此時current指向被中斷的程序),所以中斷上下文一旦睡眠或者放棄cpu,將無法被喚醒。所以也叫原子上下文(atomic context)。
b -- 占用互斥體
為了保護中斷控制代碼臨界區資源,不能使用mutexes。如果獲得不到訊號量,**就會睡眠,會產生和上面相同的情況,如果必須使用鎖,則使用spinlock。
c -- 執行耗時的任務
中斷處理應該盡可能快,因為核心要響應大量服務和請求,中斷上下文占用cpu時間太長會嚴重影響系統功能。在中斷處理例程中執行耗時任務時,應該交由中斷處理例程底半部來處理。
d -- 訪問使用者空間虛擬記憶體
因為中斷上下文是和特定程序無關的,它是核心代表硬體執行在核心空間,所以在中斷上下文無法訪問使用者空間的虛擬位址
e -- 中斷處理例程不應該設定成reentrant(可被並行或遞迴呼叫的例程)
因為中斷發生時,preempt和irq都被disable,直到中斷返回。所以中斷上下文和程序上下文不一樣,中斷處理例程的不同例項,是不允許在smp上併發執行的。
f -- 中斷處理例程可以被更高階別的irq中斷
如果想禁止這種中斷,可以將中斷處理例程定義成快速處理例程,相當於告訴cpu,該例程執行時,禁止本地cpu上所有中斷請求。這直接導致的結果是,由於其他中斷被延遲響應,系統效能下降。
五、原子上下文
核心的乙個基本原則就是:在中斷或者說原子上下文中,核心不能訪問使用者空間,而且核心是不能睡眠的。也就是說在這種情況下,核心是不能呼叫有可能引起睡眠的任何函式。一般來講原子上下文指的是在中斷或軟中斷中,以及在持有自旋鎖的時候。核心提供 了四個巨集來判斷是否處於這幾種情況裡:
[cpp]view plain
copy
#define in_irq() (hardirq_count()) //在處理硬中斷中
#define in_softirq() (softirq_count()) //在處理軟中斷中
#define in_interrupt() (irq_count()) //在處理硬中斷或軟中斷中
#define in_atomic() ((preempt_count() & ~preempt_active) != 0) //包含以上所有情況
這四個巨集所訪問的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()來判斷是否在原子上下文的**,在禁搶占的情況下都是有問題的。
Linux 程序狀態剖析
先看看一張笨叔叔 奔跑吧linux核心 扣的圖,從這張圖中我們可以看到乙個程序的所有狀態 描述建立態 建立了新的程序 新程序 就緒態程序獲得了可以執行的所有資源和準備條件 task running 執行態程序正在cpu中執行 task running 阻塞態程序因為等待某項資源而被暫時移出了cpu ...
詳細剖析Linux程序排程時機
linux在眾多程序中是怎麼進行排程的,這個牽涉到linux程序排程時機的概念,由linux核心中schedule 的函式來決定是否要進行程序的切換,如果要切換的話,切換到哪個程序等等。linux程序排程時機主要有 1 程序狀態轉換的時刻 程序終止 程序睡眠 2 當前程序的時間片用完時 curren...
Linux (程序間通訊)共享記憶體剖析
在linux中,每個程序都有屬於自己的程序控制塊 pcb 和位址空間 addr space 並且都有乙個與之對應的頁表,負責將程序的虛擬位址與實體地址進行對映,通過記憶體管理單元 mmu 進行管理。兩個不同的虛擬位址通過頁表對映到物理空間的同一區域,它們所指向的這塊區域即共享記憶體。key 共享識別...