即將發布的新的穩定的內核支持更多類型的處理器,並且可靠性和可擴展性得到了提高 ,因而將推動 Linux 得到更廣泛的應用。在這裡我們將重點介紹一些不同程度的變化,並給出部分代碼樣例。
Linux 內核的開發已經經歷了一個漫長的過程,最初是 Linus Torvalds 於1991年發布的原始的0.1版本,這個版本中包括一個基本的調度器、IPC(進程間通信)和內存管理算法。而現在它已經是一個以往操作系統的實用的替代品,在市場上表現出了強大的競爭力。越來越多的政府機構和IT巨頭的注意力正在轉向 Linux。從最小的嵌入式設備到 S/390,從手表到大型企業服務器,Linux 現在幾乎可以用於所有的地方。
Linux 2.6 是 Linux 開發周期中的下一個主要版本,它包括了一些強有力的特性,這些特性旨在改進高端企業服務器的性能和支持越來越多的嵌入式設備(要了解更詳細的關於 Linux 2.6 對大型的、小型的以及多處理器系統支持問題的分析,請參閱參考資料一節中到 Joseph Pranevich 的“Linux 的精彩世界”的鏈接)。
本文為關注 Linux 的用戶分析了 Linux 2.6 的一些重要特性,並且討論了驅動程序開發人員可能會感興趣的多方面的變化。
Linux 2.6亮點
無論是對於企業服務器還是對於嵌入式系統,Linux 2.6 都是一個巨大的進步。對高端的機器來說,新特性針對的是性能改進、可擴展性、吞吐率,以及對 SMP 機器 NUMA 的支持。對於嵌入式領域,添加了新的體系結構和處理器類型——包括對那些沒有硬件控制的內存管理方案的 MMU-less 系統的支持。並且,和往常一樣,為了滿足桌面用戶群的需要,添加了一整套新的音頻和多媒體驅動程序。
在本文中,我們分析了 Linux 2.6的一些最引人關注的特性,但是仍有很多值得關注的變化,包括增強的內核核心轉儲、快速互斥支持、改進的I/O子系統,等等,在這裡我們不能全部討論。在側欄中總結了其中一些,其余的我們在參考資料一節中給出了鏈接。
新的調度器
2.6版本的 Linux 內核使用了由 Ingo Molnar 開發的新的調度器算法,稱為O(1)算法,它在高負載的情況下執行得極其出色,並且當有很多處理器時也可以很好地擴展。
在2.4版本的調度器中,時間片重算算法要求在所有的進程都用盡它們的時間片以後,它們的新時間片才會被重新計算。這樣的話在一個有很多處理器的系統中,當進程用完它們的時間片以後得等待重算(以得到新的時間片),從而導致大部分的處理器處於空閒狀態;這將影響SMP的效率。除此之外,當空閒的處理器開始執行那些時間片尚未用盡的處於等待狀態的進程(如果它們自己的處理器忙),會導致進程開始在處理器之間“跳躍”。當一個高優先級進程或者交互式進程發生跳躍時,整個系統的性能就會受到影響。
新的調度器解決上述問題的方法是,基於每個 CPU 來分布時間片,並且取消了全局同步和重算循環。調度器使用了兩個優先級數組,即活動數組和過期數組,可以通過指針來訪問它們。活動數組中包含了所有映射到某個CPU而且時間片尚未用盡的任務。過期數組中包含了一個時間片已經用盡的所有任務的有序列表。如果所有活動任務的時間片都已用盡,那麼指向這兩個數組的指針互換,過期數組(包含了准備運行的任務)成為活動數組,而空的活動數組成為包含過期任務的新數組。數組的索引存儲在一個64位的位圖中,找到最高優先級的任務是很容易的。
新的調度器現在不再有大的 runqueue_lock。它維持每個處理器的運行隊列/鎖機制,以使得兩個不同處理器上的兩個進程可以完全並行地休眠、喚醒和上下文切換。重算循環(為進程重新計算時間片)和 goodness 循環已經被取消,O(1)算法用於 wakeup() 和 schedulee()。
新調度器的主要好處包括:
SMP效率:如果有工作需要完成,那麼所有處理器都會工作。
等待進程:沒有進程需要長時間地等待處理器;同時,沒有進程會無端地占用大量的CPU時間。
SMP進程映射:進程只映射到一個CPU而且不會在CPU之間跳躍。
優先級:不重要的任務的優先級低(反之亦然)。
負載平衡:調度器會降低那些超出處理器負載能力的進程的優先級。
交互性能:使用新的調度器,即便是在非常高負載的情況下,系統花費很長時間來響應鼠標點擊或者鍵盤輸入的情況將不會再發生。
內核搶占
內核搶占補丁在2.5系列中就已經被打上,接下來在2.6中也會打。這將顯著地降低用戶交互式應用程序、多媒體應用程序等類似應用程序的延遲。這一特性對實時系統和嵌入式系統來說特別有用。
2.5的內核搶占模塊的工作由 Robert Love 完成。在先前的內核版本中(包括2.4內核),不允許搶占以內核模式運行的任務(包括通過系統調用進入內核模式的用戶任務),直到它們自己主動釋放 CPU。
在內核2.6中,內核是可搶占的。一個內核任務可以被搶占,為的是讓重要的用戶應用程序可以繼續運行。這樣做最主要的優勢在於,可以極大地增強系統的用戶交互性,用戶將會覺得鼠標點擊和擊鍵的事件得到了更快速的響應。
當然,不是所有的內核代碼段都可以被搶占。可以鎖定內核代碼的關鍵部分,不允許搶占。鎖定可以確保每個 CPU 的數據結構和狀態始終受到保護而不被搶占。
以下的代碼片斷顯示了每個 CPU 的數據結構問題(在SMP系統中):
清單 1. 存在內核搶占問題的代碼
int arr[NR_CPUS]; arr[smp_processor_id()] = i; /* kernel preemption could happen here */ j = arr[smp_processor_id()] /* i and j are not equal as smp_processor_id() may not be the same */
在這種情形下,如果在特定點發生了內核搶占,任務將會由於重新調度而被分配到其他處理器——smp_processor_id() 將返回一個不同的值。
這種情形應該通過鎖定來進行保護。
FPU 模式是另外一種CPU應該被保護起來不被搶占的情形。當內核在執行浮點指令時,FPU 狀態不被保存。如果這時發生了搶占,由於重新調度,FPU 狀態就會與搶占前完全不同。所以 FPU 代碼必須始終被鎖定,以防止內核搶占。
鎖定可以這樣來實現,在關鍵部分禁止搶占,在之後再激活搶占。以下是在2.6內核中禁止和激活搶占的定義:
preempt_enable() -- 搶占計數器減1
preempt_disable() -- 搶占計數器加1
get_cpu() -- 先後調用 preempt_disable() 和 smp_processor_id()
put_cpu() -- 重新激活preemption()
使用這些定義,清單 1可以重寫成這樣:
清單 2. 使用防搶占鎖的代碼
int cpu, arr[NR_CPUS]; arr[get_cpu()] = i; /* disable preemption */ j = arr[smp_processor_id()]; /* do some critical stuff here */ put_cpu() /* re-enable preemption */
注意 preempt_disable()/enable()調用是可以嵌套的。也就是說,preempt_disable() 可以被調用 n 次,只有當第 n 次 preempt_enable() 被調用後,搶占才被重新激活。
當使用自旋鎖時,搶占是被隱式地禁止的。例如,一個 spin_lock_irqsave() 調用會隱式地通過調用 preempt_disable() 來防止搶占;spin_unlock_irqrestroe() 通過調用 preempt_enable() 來重新激活搶占。
改進的線程模型以及對 NPTL 的支持
在2.5內核中已經做了很多的改進線程性能的工作。在2.6中改進的線程模型仍然是由 Ingo Molnar 來完成的。它基於一個1:1的線程模型(一個內核線程對應一個用戶線程),包括內核內在的對新的 NPTL(Native Posix Threading Library)的支持,這個新的 NPTL 是由 Molnar 和 Ulrich Drepper 合作開發的。
2.6內核中其他引人注目的變化
文件系統
對 ext2/ext3 文件系統做了改進,包括對擴展屬性和POSIX訪問控制列表的支持。NTFS的驅動程序也已經重寫,可以支持(reentrant safe)SMP,大於4KB的簇,等等。同時2.6也支持 IBM 的 JFS(journaling file system) 和 SGI 的 XFS。
音頻
對桌面用戶而言,一個更令人期待的特性是稱為 ALSA(Advanced Linux Sound Architecture) 的新的 Linux 音頻體系結構,它取代了缺陷很多的舊的 OSS (Open Sound System)。新的聲音體系結構支持USB音頻和MIDI設備,全雙工重放,等等。在桌面上播放 MP3 和其他音頻文件再也不會像以前那樣了!
總線
SCSI/IDE子系統經過大幅度的重寫,一些驅動程序仍然處於測試階段或者收尾階段。
電源管理
支持 ACPI(高級電源配置管理界面,Advanced Configuration and Power Interface),用於調整 CPU(可以在不同的負載下使CPU工作於不同的時鐘頻率以節電)和軟件掛起(這一特性仍在測試中)。
聯網和IPSec
內核中加入了對 IPSec (IP安全)的支持,因此也支持 IP 有效負載壓縮等各種 RFC。刪除了原來內核內置的HTTP服務器 kttpd。IPSec 特性使用了內核提供的新的加密 API。這個加密API中包含了各種流行的算法,比如 MD4,MD5 DES,等等。加入了對新的 NFSv4 (網絡文件系統)客戶機/服務器的支持。
用戶界面層
2.6內核重寫了幀緩沖/控制台層。這將意味著需要更新各種用戶空間幀緩沖工具,如 fbset 和 fbdesl。人機界面層還加入了對近乎所有可接入設備的支持,從觸摸屏到盲人用的設備,到各種各樣的鼠標。
線程操作可以提高速度;2.6內核現在可以處理任意數目的線程,PID最大可以到20億(IA32上)。
另外一個變化是引入了 TLS(Thread Local Storage)系統調用,這個調用允許分配一個或多個 GDT(Global Descriptor Table)條目,作為線程注冊表。每個 CPU 有一個 GDT,每個條目對應一個線程。這樣就可以實現一個不受創建的線程數限制的1:1線程模型(因為每一個新的內核線程都是為一個用戶線程而創建)。2.4內核中每個處理器最多只能支持8,192個線程。
系統調用 clone 被擴展,以優化線程的創建。如果 CLONE_PARENT_SETID 標志被設置,內核會把線程ID存儲在一個給定的內存位置,如果當線程結束時 CLONE_CLEARID 標志被設置,內核就會把那個內存位置清空。這有助於用戶級的內存管理去識別沒有使用的內存塊。同樣,對線程注冊表的信號安全加載的支持也已經融入到這個體系中。當 pthread_join 發生時由內核根據線程ID來完成 Futex(fast user space mutex)。(要了解futex的更多信息,請參閱參考資料).
POSIX信號處理在內核空間中完成。一個信號會傳遞給進程中一個可用的線程;銷毀信號會終止整個進程。停止和繼續信號也會影響整個進程,這樣就可以實現對多線程進程的工作控制。
引入了退出系統調用的一個變種,叫做 exit_group(),這個系統調用終止整個進程和它的線程。此外,退出處理通過引入O(1)算法得到了改進,從而可以在兩秒內終止一個具有成千上萬個線程的進程(而在2.4內核中完成同樣的事情需要15分鐘)。
修改了 proc 文件系統,不再報告所有的線程而只是報告原始的線程。這樣就避免了 /proc 報告速度的下降。內核保證原始的線程在所有其他線程終止之前不會終止。