運“芯”帷幄 —CMP的作業系統技術
2005-05-19
■ 清華大學電腦科學與技術系 董淵 林昊翔
■ 清華大學資訊技術研究院 汪東升 李鵬
單晶片多處理器(CMP),特別是在一個晶片上整合了多個相同通用處理器的單晶片對稱式多處理器(同構CMP)的發展,是上世紀90年代以來整合電路製造工藝的進步與微處理器體繫結構的發展所帶來的必然發展方向。
和目前在伺服器領域廣泛採用的對稱式多處理器(Symmetric Multi-Processor, SMP)結構類似,在CMP系統中,位於同一個晶片內部所有處理器核心以平等的身份參與任務調度和中斷處理,共用記憶體和外部裝置,而且也可以共用片內的 (部分或全部)快取。
CMP的結構相對簡單,可以直接使用現有的處理器核心,因此開發週期與成本相對較低,結構簡單帶來的另一個好處是更易獲得高的主頻。由於多個處理器整合在 一塊晶片上,且共用cache,微處理器之間的通訊延遲會明顯降低,有利於提高系統的整體效能。因此,CMP具有良好的發展前景和廣泛的應用空間,眾多著 名大學、科研機構和商業公司都展開了廣泛而積極的研究。
而要想真正發揮CMP的優勢,軟體,特別是作業系統和編譯工具等系統軟體的支援至關重要,沒有這些軟體,CMP將處於“空轉”狀態。因此,每一個CMP系統都需要為其量身打造的系統軟體。
CMP對作業系統提出的挑戰
系統軟體對於CMP廣泛、深入的應用有重要的意義,這裡我們討論作業系統。作業系統是電腦系統的基本系統軟體,在整個電腦系統中處於核心地位,負責控 制、管理電腦的所有軟體、硬體資源,是惟一直接和硬體系統打交道的軟體,是整個軟體系統的基礎部分,同時還為電腦使用者提供良好的介面。
對於普通使用者而言,作業系統是一個資源管理者,通過它提供的系統命令和介面操作等工具,以某種易於理解的方式完成系統管理功能,有效地控制各種硬體資源,組織自己的資料,完成自己的工作並和其他人共用資源。
對於程式員來講,作業系統提供了一個與電腦硬體等價的擴充或虛擬計算平台。作業系統提供給程式員的工具除了系統命令、介面操作之外,還有系統調用,系 統調用抽象了許多硬體細節,程式可以以某種統一的方式進行資料處理,程式員可以避開許多具體的硬體細節,提高程式開發效率,改善程式移植特性。
並行是電腦科學與技術的重要分支之一,其核心思想是通過任務的合理劃分和分配,使得多個處理器可以同時執行一個或多個任務,以達到系統整體計算能力的大 幅度提升。CMP的意義在於能夠提供任務並存執行的一個新思路,支援在一個晶片內的多個處理器核心之間任務的劃分和分配(也就是調度),而任務的調度則需 要作業系統來完成。
CMP的發展對作業系統提出了新的挑戰。首先,如何合理組織、調度任務才能最大程度地發揮CMP結構的效能?其次,如何保持作業系統的外部介面的相對穩 定?對於一般使用者而言,大家希望的是平滑的過渡,一方面介面最好和以前的作業系統完全相同,另一方面以前能用的應用程式最好還能夠不做任何修改就直接在 CMP的機器上直接運行,也就是說CMP對於使用者來講最好是透明的,這需要作業系統在使用者介面和編程介面方面都保持不變。
如何更好地組織和調度任務以便將CMP結構的效能發揮到極致是核心的問題,這是人們對CMP最大的期望。要解決這個問題,需要軟硬體共同協作,從任務調 度、中斷分配、資源共用等幾個方面入手,硬體方面則要求CMP系統提供全新的同步與互斥、中斷分配以及CPU核心之間的中斷等機制。
支援CMP作業系統的關鍵技術
目前國際上對於CMP的研究還處於探索階段,相關作業系統也處在積極研究時期。研究、分析和借鑒支援CMP作業系統的關鍵技術對於我們認識、理解和設計用 於CMP的作業系統有著非常重要的意義,這裡,我們簡單介紹支援CMP作業系統的引導和初始化、調度,中斷處理和同步、互斥技術。
系統引導和初始化
作業系統的引導和初始化是指從系統加電到能夠在多個處理器核心之間平等地進行任務調度的過程,這一過程是建立平等調度實施的基礎,對於整個系統的運行具有 重要意義。雖然說對稱式多處理器系統中,各處理器可以平等地並行工作,但這是建立在系統有多個可並存執行任務的基礎之上,而在引導和初始化過程中,由於很多 工作只能串列地執行,所以在這個階段處理器核心是不平等的,是有主次之分的。系統加電之後,受到硬體控制,只啟動其中一個處理器,稱為主CPU或者引導處 理器(Booting Processor, BP),而其他處理器,稱為次CPU或者應用處理器(Application Processor, AP),則處於停機等待狀態。
加電啟動之後,主CPU跳轉到特定的記憶體位址,通常映射到唯讀記憶體,這裡儲存著整個電腦的引導程式bootloader,其任務是進行簡單的硬體檢 測、初始化環境參數、將作業系統核心裝載到記憶體中,跳轉到作業系統的起始地址並開始執行。這段引導過程完全由BP完成。
進入作業系統核心之後,BP需要進行最初的草創性工作,完成運行環境準備、各種初識狀態設定、基本讀寫資料區段清零、bootloader傳遞過來的各種環 境參數儲存、記憶體棧的開闢以及棧指標、全域指標設定。前面這部分工作全部由底層彙編程式碼完成,之後,BP跳轉到由進階語言編寫的函數,開始第二個階段 CPU本身的初始化。
在CPU初始化過程中,BP首先開始自檢,收集CPU相關的指令集、儲存管理、快取以及副處理器等基本資料。接著為次CPU準備運行環境,同時為AP 準備一個鎖,之後喚醒AP,AP轉入主CPU設定好的地址,開始鎖測試而進入等待狀態。喚醒AP之後,BP輸出自身資訊之後,繼續進行記憶體等各種資源的初 始化。
接下來的工作主要有BP進行開發板以及外部裝置初始化,之後準備用於所有CPU的空閑進程,這是一個不參與調度的進程,當某個CPU沒有需要執行的任務, 就轉入這個進程。準備好空閑進程之後,由BP解除對AP的鎖,各AP逐個啟動,進行各種關於各自CPU的初始化,將自身的狀態填寫到適當的資料結構,最後 相繼進入空閑狀態。
所有的AP都完成初始化並進入空閑狀態後,由BP來完成整個系統最後階段的初始化,並執行系統的第一個進程,之後真正步入對稱式多處理器環境,所有的CPU進入正常、平等的調度。
調度
調度系統對於作業系統乃至整個電腦系統的整體效能有非常重要的影響,嵌入式、案頭和高端伺服器系統對於調度器的要求是很不一樣的。在CMP結構中調度機 制的重點在於更好地滿足多處理機並行性上,核心思想是通過降低CPU間調度競爭和選擇下一個運行進程的開銷,以及提高系統整體Server Load Balancer的能力,從而大幅度 提高多處理機系統的執行效率。其特性如下:
1.調度演算法
在傳統單CPU結構的調度系統中,所有就緒進程(狀態為TASK_RUNNING)被組織到同一個雙向鏈表之中,稱為全域任務隊列,調度過程中將遍曆此鏈 表中的所有進程,調用計算每一個進程的權值,從中選擇權值最大的進程投入運行。由於調度器要遍曆所有就緒進程,因此選擇下一個運行進程的時間複雜度是O (n)(n為就緒進程的個數)。同時,因為就緒隊列是全域性的,對單CPU系統來講只可能有一個CPU訪問這個隊列,而在多處理器結構中,必須通過一個全 局的自旋鎖保證同一時刻只有一個CPU進行訪問,這樣導致系統中其他CPU的等待。如果就緒進程個數比較多,那麼就緒隊列就會成為一個明顯的瓶頸。
在支援CMP的作業系統中,每個CPU維護一個自己的就緒進程隊列,稱為局部任務隊列,這樣大大降低了CPU間的競爭。就緒進程按時間片是否用完分為 active和expired兩大類,active類包括那些時間片沒用完、當前可被調度的就緒進程,expired類包括那些時間片已用完的就緒進程。 同時,每類中的進程按照其優先順序的不同處於不同的優先順序鏈表中。
調度時,active隊列中非空的最高優先順序鏈表的第一項被作為候選進程,使得選擇下一個運行進程的操作可以在固定時間內完成。同時核心建立了一個位映射 數組來對應每一個優先順序鏈表,使用標誌位的方式極大降低尋找非空的鏈表所需要的時間。當一個進程耗盡其時間片後,核心重新計算它的優先順序,並把它放置在 expired隊列的相應優先順序鏈表中。進程優先順序的計算過程還可以分散到進程的執行過程中進行。這種將集中計算過程分散的做法,保證了調度器啟動並執行時間 上限,降低了不必要的開銷;同時在記憶體中保留更加豐富的資訊的做法也加速了候選進程的定位過程。當active隊列中沒有可調度進程時,核心簡單地對調 active和 expired隊列,將原來的expired隊列作為新的active隊列後即可進行新一輪調度。
此調度演算法選擇下一個運行進程的時間複雜度是O(1),與就緒進程的個數無關,調度效率大大提高。
2.系統Server Load Balancer
支援CMP作業系統核心的調度系統需要很好地解決進程與CPU之間的“親和”問題。如果不考慮親和,一個進程可能在CPU之間比較頻繁地遷移,互動式進程 (或高優先順序的進程)可能還會在CPU之間不斷“跳躍”,這樣,每一次遷移之後,都可能造成頻繁的記憶體訪問,導致整體效能下降。
支援CMP作業系統核心的調度系統盡量使得每個進程一直在固定的CPU上執行,這樣可以提高Cache的命中率;但是如果某個CPU的就緒隊列過長,不斷 的進程切換反而造成Cache命中率的下降,而且還造成其他CPU不能充分發揮效能。無論當前CPU是繁忙或空閑,時鐘中斷每隔一段時間都會啟動一次以平 衡負載。當然,一旦當前CPU發現自己的就緒隊列為空白時,也會主動進行Server Load Balancer。
為了進行有效Server Load Balancer,作業系統核心根據系統結構的特點,引入調度域(struct sched_domain)的概念將全體CPU一層一層地劃分成不同的地區,每個調度域中的CPU分成若干個CPU組,且滿足任一CPU惟一存在於一個組 中。每個CPU屬於一個基本的調度域(該域至少包括本CPU),但是CPU同時還屬於一個或多個更大的調度域。CPU的多個調度域通過構成一個單向鏈表, 且必須滿足:1.父調度域是子調度域的超集;2.每個CPU的最高層調度域必須包括系統中的全部處理器。例如在一個支援超執行緒的CMP系統中,每個邏輯 CPU的基本調度域包含所在傳統物理CPU上的全部的邏輯CPU,基本域的每個CPU分組包含一個邏輯CPU;基本調度域的父調度域是這個系統的最高層調 度域,它包含系統中所有的邏輯CPU,該域的每個CPU分組包含一個物理CPU上的全部邏輯CPU。
對於CMP系統,每個晶片上的多個核天然地構成一層調度域。一個單晶片CMP系統和一個普通SMP系統的基本調度域的差別僅在於CMP基本調度域的CPU 組包含的對象是一個“CPU核”,而SMP基本調度域的CPU組包含的是一個傳統的物理CPU。對於像支援超執行緒和多核SMP這樣複雜的CMP系統,必須 增加一個基於晶片層面的新調度域。
進行Server Load Balancer時,從當前CPU的基本調度域出發,遍曆所有的調度域。如果某個域有一段時間沒有進行過Server Load Balancer,先尋找域中負載最大的CPU組(CPU組的 負載等於組中所有CPU的負載之和),然後尋找組中最繁忙的CPU(該CPU的負載超過本CPU至少25%),更新雙方的負載記錄,確定需要遷移的進程數 為源CPU負載與本CPU負載之差的一半(經過更新後的值),然後按照從 expired 隊列到 active 隊列、從低優先順序進程到高優先順序進程的順序進行遷移,但實際上真正執行遷移的進程往往少於計劃遷移的數目。
中斷處理
傳統的單一處理器通常都採用一個外部中斷控制器來解決外部裝置到CPU的通訊,對於多處理器系統,處理器之間也需要通過中斷方式進行通訊,對於CMP而言, 多個處理器之間的中斷控制器(本地中斷控制器,簡稱本地控制器)也需要和處理器一起封裝到晶片內部。此外,還需要一個全域的中斷控制器(簡稱全域控制器) 負責各個處理器核心之間的中斷分配,也要設計在晶片內部。
1.中斷分配
全域中斷控制器擔負著把來自外部裝置的插斷要求提交和分配給片內各CPU的任務。對於每一個中斷向量,可以採用靜態或動態兩種不同模式之一。如果某個中斷 向量是靜態分配的,則控制器把這種插斷要求提交給預設的一個或多個CPU;動態模式則可以發送給所有CPU,或者隨機地發送給某個處理器。
本地控制器處理本處理器內部產生的插斷要求、來自外部中斷控制器的插斷要求以及其他處理器發送過來的插斷要求。
2.處理器間中斷
在CMP系統中,晶片內部一個處理器常常要有目標地向系統中的其他處理器發出插斷要求,這種中斷被稱為處理器間中斷(IPI, Inter Processor Interrupt)。IPI至少應該包含以下兩種:
“重新調度”中斷。當前CPU可以發送該中斷來指示目標CPU可能需要一次進程調度,至於目標CPU在處理完該中斷以後是否進行進程調度,那得要看事先或者在處理中斷的過程中是否把當前進程設定為需要調度。
“請求執行”中斷。這個中斷被用來請求目標CPU執行一個指定的函數。之所以要通過IPI請其他CPU執行,是因為某個函數必須由目標CPU才能完成,而 不能由別的CPU代替。比如某個處理器改變了記憶體中某個頁面映射目錄或頁面映射表的內容,從而可能引起其他處理器的TLB與其不一致時,就向系統中正在使 用這個映射表的處理器發送這個中斷,請它們自己執行代碼,廢棄各自TLB的內容。
3.時鐘中斷
在所有插斷要求中,時鐘中斷扮演著特殊的重要角色,每一個處理器都有自身的時鐘發生器。在系統初始化階段,系統先設定一個外部時鐘中斷源供所有CPU共 享,並且以此基準測算各個CPU的運算速度,並校準自身的時鐘中斷髮生間隔。由於各處理器設計完全一致,所有CPU都有基本相同的時鐘脈衝周期,為了不讓 所有處理器都在同一時刻發生時鐘中斷,作業系統應該使各個CPU的時鐘中斷在相位上相互錯開,把這些中斷均勻地分布在時鐘中斷的周期中。
同步與互斥技術
在多任務系統中,不同的任務可能需要同時訪問某些共用變數和共用資源,形成了競爭的關係。這就需要系統提供同步與互斥機制,使得這些共用變數和共用資源同 一時刻只能被一個任務以獨佔的方式訪問。傳統單一處理器系統中在宏觀上是並行,微觀上,由於只有一個處理器,同一時刻只能執行一個任務,同步和互斥的問題相 對比較容易解決,而CMP的作業系統在運行時,同一時刻有可能有多個任務執行,傳統的方法有時不能滿足這種特定的情況,需要引入新的機制。
同步和互斥機制需要底層硬體提供“讀-修改-寫”類型的訪存原子操作。這樣的操作能夠讓CPU從主儲存空間中讀取一個值,修改之,再將修改過的值儲存到儲存 器的相同位置中。整個過程是一次完整的匯流排交易,不能被其他CPU核心的訪存操作所打斷。“讀-修改-寫”類型的原子操作有多種實現方式,最常見的包括 test_and_set,swap,load-linked/store-conditional等方式。
Test_and_set指令從主儲存空間中讀取一個值(通常是一個位元組或一個字),將它和0比較並根據比較的結果設定條件碼,最後將1無條件地存放到儲存 器的相應單元。一旦一條test_and_set指令開始了它的匯流排周期,那麼任何其他CPU都不能訪問主儲存空間。
在Intel的處理器中,CPU晶片有一個LOCK引腳,如果組合語言的程式中在一條指令前加上首碼“LOCK”,經過彙編厚的機器代碼就使CPU在執行 這條指令時將LOCK引腳拉低,將匯流排鎖上,這時匯流排上的其他CPU暫時不能進行匯流排操作,從而保證了操作的原子性。X86系列的CPU還提供一條 xchg指令,提供了原子的交換操作,這條指令不論前面是否加LOCK首碼,都是一個原子操作。
在MIPS等RISC處理器中,對原子操作進行了簡化,他們提供了一對指令(load-linked/store-conditional),一起使用這 一對指令就能執行一次原子的“讀-修改-寫”操作。Load-linked指令執行原子“讀-修改-寫”操作的前半部分,它從儲存空間中讀取一個值,在硬體 中設定一個標誌,表示進行中原子操作,並設定讀取的地址。然後使用store-conditional指令完成修改操作。這樣對於相應的儲存空間位置來 說,從load-linked開始到store-conditional結束的整個指令序列都是以原子的方式執行的。
作業系統可以使用根據上述原理編寫的自旋鎖(spin-lock)。它的作用是取得一個變數(稱之為鎖)的存取權限,如果成功,則進行下一步操作,如對共 享變數或共用資源的訪問,如果沒有成功,則一直查詢該鎖,直到成功取得該鎖的存取權限為止。自旋鎖是保證同步和互斥操作的重要操作。
利用硬體提供的“讀-修改-寫”原子,作業系統可以完成各種同步和互斥操作,正確地解決資源的共用問題。