本系列博文主要介紹linux內核幾個重要概念和技術原理,部分來自於網上總結、部分來自於自己對《linux內核設計與實現》和《深入理解linux內核》的總結。目的是讓一些剛接觸linux內核的人對linux內核的一些實現技術有一個大概了解。
2.6新的可搶占式內核是指內核搶占,即當進程位於內核空間時,有一個更高優先級的任務出現時,如果當前內核允許搶占,則可以將當前任務掛起,執行優先級更高的進程。
在2.5.4版本之前,Linux內核是不可搶占的,高優先級的進程不能中止正在內核中運行的低優先級的進程而搶占CPU運行。進程一旦處於核心態(例如用戶進程執行系統調用),則除非進程自願放棄CPU,否則該進程將一直運行下去,直至完成或退出內核。與此相反,一個可搶占的Linux內核可以讓Linux內核如同用戶空間一樣允許被搶占。當一個高優先級的進程到達時,不管當前進程處於用戶態還是核心態,如果當前允許搶占,可搶占內核的Linux都會調度高優先級的進程運行。
內核即將返回用戶空間的時候,如果need resched標志被設置,會導致schedule()被調用,此時就會發生用戶搶占。在內核返回用戶空間的時候,它知道自己是安全的。所以,內核無論是在從中斷處理程序還是在系統調用後返回,都會檢查need resched標志。如果它被設置了,那麼,內核會選擇一個其他(更合適的)進程投入運行。
簡而言之,用戶搶占在以下情況時產生:
l 從系統調返回用戶空間。
l 從中斷處理程序返回用戶空間。
在不支持內核搶占的內核中,內核代碼可以一直執行,到它完成為止。也就是說,調度程序沒有辦法在一個內核級的任務正在執行的時候重新調度—內核中的各任務是協作方式調度的,不具備搶占性。當然,運行於內核態 的進程可以主動放棄CPU,比如,在系統調用服務例程中,由於內核代碼由於等待資源而放棄CPU,這種情況叫做計劃性進程切換(planned process switch)。內核代碼一直要執行到完成(返回用戶空間)或明顯的阻塞為止,
在單CPU情況下,這樣的設定大大簡化了內核的同步和保護機制。可以分兩步對此加以分析:
首先,不考慮進程在內核中自願放棄CPU的情況(也即在內核中不發生進程的切換)。一個進程一旦進入內核就將一直運行下去,直到完成或退出內核。在其沒有完成或退出內核之前,不會有另外一個進程進入內核,即進程在內核中的執行是串行的,不可能有多個進程同時在內核中運行,這樣內核代碼設計時就不用考慮多個進程同時執行所帶來的並發問題。Linux的內核開發人員就不用考慮復雜的進程並發執行互斥訪問臨界資源的問題。當進程在訪問、修改內核的數據結構時就不需要加鎖來防止多個進程同時進入臨界區。這時只需再考慮一下中斷的情況,若有中斷處理例程也有可能訪問進程正在訪問的數據結構,那麼進程只要在進入臨界區前先進行關中斷操作,退出臨界區時進行開中斷操作就可以了。
再考慮一下進程自願放棄CPU的情況。因為對CPU的放棄是自願的、主動的,也就意味著進程在內核中的切換是預先知道的,不會出現在不知道的情況下發生進程的切換。這樣就只需在發生進程切換的地方考慮一下多個進程同時執行所可能帶來的並發問題,而不必在整個內核范圍內都要考慮進程並發執行問題。
實現內核的可搶占對Linux具有重要意義。首先,這是將Linux應用於實時系統所必需的。實時系統對響應時間有嚴格的限定,當一個實時進程被實時設備的硬件中斷喚醒後,它應在限定的時間內被調度執行。而Linux不能滿足這一要求,因為Linux的內核是不可搶占的,不能確定系統在內核中的停留時間。事實上當內核執行長的系統調用時,實時進程要等到內核中運行的進程退出內核才能被調度,由此產生的響應延遲,在如今的硬件條件下,會長達100ms級。
這對於那些要求高實時響應的系統是不能接受的。而可搶占的內核不僅對Linux的實時應用至關重要,而且能解決Linux對多媒體(video, audio)等要求低延遲的應用支持不夠好的缺陷。
由於可搶占內核的重要性,在Linux2.5.4版本發布時,可搶占被並入內核,同SMP一樣作為內核的一項標准可選配置。
有幾種情況Linux內核不應該被搶占,除此之外Linux內核在任意一點都可被搶占。這幾種情況是:
(1) 內核正進行中斷處理。在Linux內核中進程不能搶占中斷(中斷只能被其他中斷中止、搶占,進程不能中止、搶占中斷),在中斷例程中不允許進行進程調度。進程調度函數schedule()會對此作出判斷,如果是在中斷中調用,會打印出錯信息。
(2) 內核正在進行中斷上下文的Bottom Half(中斷的下半部)處理。硬件中斷返回前會執行軟中斷,此時仍然處於中斷上下文中。
(3) 內核的代碼段正持有spinlock自旋鎖、writelock/readlock讀寫鎖等鎖,處干這些鎖的保護狀態中。內核中的這些鎖是為了在SMP系統中短時間內保證不同CPU上運行的進程並發執行的正確性。當持有這些鎖時,內核不應該被搶占,否則由於搶占將導致其他CPU長期不能獲得鎖而死等。
(4) 內核正在執行調度程序Scheduler。搶占的原因就是為了進行新的調度,沒有理由將調度程序搶占掉再運行調度程序。
(5) 內核正在對每個CPU“私有”的數據結構操作(Per-CPU date structures)。在SMP中,對於per-CPU數據結構未用spinlocks保護,因為這些數據結構隱含地被保護了(不同的CPU有不一樣的per-CPU數據,其他CPU上運行的進程不會用到另一個CPU的per-CPU數據)。但是如果允許搶占,但一個進程被搶占後重新調度,有可能調度到其他的CPU上去,這時定義的Per-CPU變量就會有問題,這時應禁搶占。
為保證Linux內核在以上情況下不會被搶占,搶占式內核使用了一個變量preempt_ count,稱為內核搶占鎖。這一變量被設置在進程的PCB結構task_struct中。每當內核要進入以上幾種狀態時,變量preempt_ count就加1,指示內核不允許搶占。每當內核從以上幾種狀態退出時,變量preempt_ count就減1,同時進行可搶占的判斷與調度。
從中斷返回內核空間的時候,內核會檢查need_resched和preempt_count的值。如果need_ resched被設置,並且preempt count為0的話,這說明可能有一個更為重要的任務需要執行並且可以安全地搶占,此時,調度程序就會被調用。如果preempt-count不為0,則說明內核現在處干不可搶占狀態,不能進行重新調度。這時,就會像通常那樣直接從中斷返回當前執行進程。如果當前進程持有的所有的鎖都被釋放了,那麼preempt_ count就會重新為0。此時,釋放鎖的代碼會檢查need_ resched是否被設置。如果是的話,就會調用調度程序。
在2.6版的內核中,內核引入了搶占能力;現在,只要重新調度是安全的,那麼內核就可以在任何時間搶占正在執行的任務。
那麼,什麼時候重新調度才是安全的呢?只要premptcount為0,內核就可以進行搶占。通常鎖和中斷是非搶占區域的標志。由於內核是支持SMP的,所以,如果沒有持有鎖,那麼正在執行的代碼就是可重新導人的,也就是可以搶占的。
如果內核中的進程被阻塞了,或它顯式地調用了schedule(),內核搶占也會顯式地發生。這種形式的內核搶占從來都是受支持的(實際上是主動讓出CPU),因為根本無需額外的邏輯來保證內核可以安全地被搶占。如果代碼顯式的調用了schedule(),那麼它應該清楚自己是可以安全地被搶占的。
內核搶占可能發生在:
l 當從中斷處理程序正在執行,且返回內核空間之前。
l 當內核代碼再一次具有可搶占性的時候,如解鎖及使能軟中斷(local_bh_enable)等。
l 如果內核中的任務顯式的調用schedule()
l 如果內核中的任務阻塞(這同樣也會導致調用schedule())