陳小蘭, 楊 斌
(西南交通大學信息科學與技術學院,四川成都 610031)
順應對稱多處理器(Symmetric Multi-Processor,SMP)及多核的出現,Linux操作系統經過不斷地完善和發展,早就針對SMP體系結構作了很大的改動,實現了對多處理器的支持,但目前針對多處理及多核平臺的系統移植及多核平臺的應用程序多核化還存大很大的空白,因此有必要深入理解內核級的多處理器支持。位圖是內核中進行操作系統資源管理分配的重要手段之一,文中分析的多處理器平臺下Linux 2.6啟動過程中的位圖,就是希望能為多處理及多核平臺的系統移植及多核平臺的應用程序開發提供一些指導。
分析基于Intel X86 SMP體系結構,Linux操作系統內核為2.6.10。
位圖是Linux內核中一個32位的全局變量,其中的每一位對應一種狀態,在單處理器結構下,位圖主要用于調度、內存管理等,以更快地索引當前應投入運行的任務或是查找空閑塊等。
在多處理器結構下也有上述單處理器結構中的位圖,但卻增加了與多處理器啟動過程相關的位圖,這些位圖是Linux內核中32位的全局CPU變量,其中每一位都與某一個特定的CPU相對應,分別對應0和1兩種狀態,但所起的作用在多處理器啟動過程中的不同階段卻各不相同。隨著Linux操作系統對SMP支持的不斷完善和發展,2.6版本的內核中還增加了新的和SMP啟動相關的位圖,所有這些位圖都將在下面進行詳細分析。
位圖是在系統啟動過程中依次建立起來的,因此,要分析位圖的作用首先要弄清系統啟動的整個過程。
系統啟動過程主要包括以下幾個步驟:
(1)系統加電后,BIOS初始化,自檢(屏蔽AP);
(2)BIOS將MBR里面的引導程序(Grub,Lilo等)調入內存,并由該引導程序將Linux內核調入;
(3)Linux操作系統啟動部分運行setup.S和head.S,將內核解壓到內存,并進行實模式下及保護模式下的初始化[1];
(4)進入執行內核中的start-kernel()函數,對系統的各種資源進行初始化,創建BSP的空閑進程;(5)init()函數的執行,首先完成對SMP系統的初始化,再進行上層應用的初始化。
在SMP體系結構下,SMP的初始化包括主CPU(BSP)和次CPU(AP)的初始化,相關位圖就是在這兩個過程中依次建立起來的。
從BIOS初始化到start-kernel函數的執行,系統都還沒有對AP做任何處理,一直是BSP在運行,BSP在start-kernel()中進行自身的初始化工作,并設置好自己相應的cpu-online-map位圖和 cpu-callout-map位圖[2]。cpu-online-map表示當前聯機的CPU,僅在CPU已經可以用于內核調度或接收設備中斷時設置。而cpu-callout-map是每次CPU啟動后向主CPU發出通知時,由主CPU負責設置,而主CPU自身所對應的這個位圖的相應位則由主CPU自己在此進行設置。BSP在start-kernel()中調用smp-prepare-boot-cpu()就是將cpu-online-map位圖和cpu-callout-map位圖中與主CPU相對應的那一位設置為1,表示BSP已經初始化。
在start-kernel函數中,主要處理例如cache、內存等初始化工作,最后在進入BSP的空閑進程前要調用kernel-thread()創建init內核線程[3],在init()函數里,具體實現SMP系統各CPU的初始處理機制。分析相關的SMP初始化函數,源代碼在linux/init/main.c中:

在函數smp-prepare-cpus中,調用smp-boot-cpus函數啟動并初始化各AP,源代碼在linux/arch/i386/kernel/Smpboot.c中,這個函數的重點是對每個AP依次調用do-boot-cpu函數,下面來看在do-boot-cpu中做了什么工作,關鍵代碼在linux/arch/i386/kernel/smpboot.c中。這一函數首先創建自己的idle進程(即0號進程),然后BSP將AP在一開始被喚醒后需要執行的代碼(trampoline.S)的首地址寫入熱啟動向量。這樣,當BSP對AP發送IPI時,AP受到啟動后響應中斷,自動跳入這個trampoline.S代碼部分繼續執行。為了AP有足夠的時間響應中斷,BSP在發送中斷請求后要延遲一段時間。在這期間,BSP是怎樣得知AP是否初始化完了呢?AP又是怎樣知道自已可以繼續往下執行直到進入空閑進程呢?這就得依靠兩個全局位圖cpu-callout-map和cpucallin-map了,具體實現流程如圖1所示。
引入cpu-callin-map和cpu-callout-map兩個位圖后,主CPU和次CPU之間的應答變得非常容易,否則,主CPU和相應的次CPU都無法繼續往下執行,而需要靠處理器間中斷(IPI)來完成。
AP在跳入 trampoline.S代碼后,將載入符號表(gdt)和局部符號表(ldt),然后進入保護模式并跳至head.S的入口處,以下是進入保護模式并跳至head.S的入口處的代碼片斷:源代碼在linux/arch/i386/kernel/trampoline.S中:


圖1 位圖cpu-callout-map和cpu-callin-map的設置
這段代碼通過將ax(第0位已置1)所指示的控制字裝入機器狀態字(即CR0的低5位)使處理器進入保護模式運行,然后通過一個長跳轉,到0x10:0x00100000,即內核被解壓后的起始地址,也就是head.S的startup-32()函數。
由此,各AP轉入head.S繼續執行,在這一步中要依次為各個AP設置其相應的0號進程的task-struct和內核堆棧,在支持SMP的Linux中,每個進程相應的task-struct結構中新增加了一個cpus-allowed位圖,通過這個位圖,可以設置進程在所希望的CPU上運行。進程遷移就是利用這個位圖進行的。默認情況下cpus-allowed的所有位都被設置為1,進程可以在系統中所有可用的處理器上執行[4]。當然,這個位圖也可以在系統啟動完成后由用戶通過相應的系統調用設置一個不同的由一個或幾個位組合的位掩碼。當這一處理器綁定關系改變時,內核就會采用遷移線程把任務推到合法的處理器上。在head.S中,AP執行的代碼與BSP所執行的并不完全一致,源代碼在linux/arch/i386/kernel/head.S中:

AP執行head.S時,當執行到上述代碼的時候,由于ready的值被改變,不再等于1,所以就繼續向前執行,調用initialize-secondary函數,而不是象BSP一樣執行標號1處的代碼(調用start-kernel函數)[5]。initializesecondary函數里面的代碼很簡單,源代碼在linux/arch/i386/kernel/smpboot.c中:

這是一段內嵌匯編程序,將程序跳轉至current->thread.eip(即前面的idle->thread.eip)處。CPU執行start-secondary函數,對相應的AP進行初始化。初始化完成后,則向主CPU一樣對cpu-online-map中與初始化目標CPU相對應的那一位進行設置,表示該AP已經初始化。最終,這個AP將進入自己的空閑狀態。
這樣,一個AP的啟動過程就完成了。每個AP的啟動就是依次經歷上述過程,并在上述過程中依次對相應位圖進行設置。
AP啟動完成后,從函數調用返回到smp-boot-cpus(),即回到主CPU的運行中,這時,所有次CPU都已啟動,接下來就是構造cpu-sibling-map[]位圖數組,數組中的每一位保存同屬CPU的邏輯CPU號,當CPU支持超線程技術時就會表現出該位圖的優勢,利用它可以很快地找到當前CPU核的兄弟CPU。當然,如果一個物理CPU中只有一個邏輯核,則該位圖數組各元素的值就是自身的邏輯CPU號。圖2是以一個物理CPU中含有兩個邏輯核的體系結構為例來說明。

圖2 cpu-sibling-map位圖示例
smp-boot-cpus()執行完后,將調用下一函數fixup-cpu-present-map(),該函數的作用是設置cpu-presentmap位圖。在Linux內核不支持熱插撥時,cpu-present-map可以用cpu-possible-map來代替,cpu-presentmap表示某一時刻計算機中使用的CPU位圖,而cpu-possible-map位圖是從開機系統啟動到某一時刻所有曾經使用過的CPU位圖,這兩者在Linux內核不支持熱插撥時代表相同的含義,但從2.6支持熱插撥以后,以前在smp-init()中要激活所有CPU的,現在則只需根據cpu-present-map來激活相應CPU即可。因此當一個熱插撥CPU被插入時,就要更新cpu-present-map的相應位。
多處理器結構下,位圖是很重要的。它在整個Linux內核的引導過程中,起著重要的作用。
啟動過程中位圖的使用使得CPU之間協調變得更加容易,不必處處都采用處理間中斷,從系統使用者的角度來講,則可以通過查看位圖來了解CPU的相關信息,使操作更加容易。通過剖析BSP和AP各自的啟動過程,對其中所用到的位圖作了詳細分析,使整個BSP和AP的啟動過程變得清晰明了,在此基礎上進行多處理器結構下不同平臺間的Linux系統移植也將變得更加容易。
[1] 賈秋亭,侯瑞蓮.嵌入式Linux的開發[J].山東輕工業學院學報(自然科學版),2006,(4).
[2] 何志宏,何為民.嵌入式Linux在開發板gec2410上的完全啟動過程[J].科技廣場,2008,(8).
[3] (美)BOVET&CESATI.陳莉君,馮銳,牛欣源譯.深入理解LINUX內核[M].北京:中國電子出版社,2001:127.
[4] (美)Robert Love.陳莉君,康華,張波譯.Linux內核設計與實現[M].北京:機械工業出版社,2006:47.
[5] 毛德操,胡希明.Linux內核源代碼情景分析[M].杭州:浙江大學出版社,2009:1511.