級別: 中級 |
內核黑客,Linux on Cell 的內核維護者, IBM Deutschland Entwicklung GmbH
2005 年 6 月 25 日
對于 Linux on the Cell 的基本平臺支持早已搭建好了,目前正努力加入主流的 Linux 內核樹。閱讀本文可以了解 Cell 這種獨一無二的體系結構,以及可以運行 Linux 的 SPU 文件系統接口。
本文改編自 LinuxTag 2005 上發表的 The Cell processor programming model 一文;要獲得更多詳細信息,請參閱 參考資料 一節。
Cell 處理器是由 Sony、Toshiba 和 IBM® 共同設計的,它是今年 CPU 市場上最值得期待的新品。據說它采用了一種全新的體系結構,在消費和工作站市場上具有前所未有的性能。它采用了一個 64 位的 PowerPC® 核心,將多個獨立的向量處理器(稱為協處理部件,Synergistic Processing Unit,SPU)組合成單個微處理器。
與現有的 SMP 系統或其他多核心的處理器實現不同,在 Cell 中,只有通用的 PowerPC 核心才可以在一個通用的操作系統上運行,而 SPU 則是專門用來運行一些計算任務的。將 Linux™ 移植到 Cell 的 PowerPC 核心上是一個相當簡單的任務,因為它與現有的一些平臺非常類似,例如 IBM pSeries® 或 Apple Power Macintosh,但是僅僅這樣并不能使用 SPU 的那些功能強大的計算能力。
只有內核才可以直接與 SPU 進行通信,因此需要將硬件接口抽象為系統調用或設備驅動程序。用戶接口中最重要的一些功能包括將一個程序的二進制文件加載到 SPU 中,在 SPU 程序和 Linux 用戶空間的應用程序中傳輸內存的內容,并對程序的執行情況進行同步。其他挑戰還有 SPU 程序執行與現有工具(例如 GDB 和 OProfile)的集成。
Sony、IBM 和 Toshiba 在德克薩斯州奧斯汀成立了一個聯合小組,他們負責實現 Linux 內核移植的一些基礎工作。當前的內核補丁基于最新的 2.6.xx 內核,它是由位于德國 Bblingen 的 IBM LTC(Linux Technology Center)小組進行維護的。他們希望將這些補丁大部分都集成到 2.6.13 版本的內核中,這樣就可以成為將來各個發行版本的一部分。
Cell 處理器
PowerPC Processing Element
Cell 處理器有一個 PowerPC 處理部件(PowerPC Processing Element,PPE),它采用的是 64 位的 PowerPC AS 體系結構,這與 PowerPC 970 CPU(也稱為 G5)和所有最新的 IBM POWER™ 處理器使用的體系結構都完全相同。與 970 類似,它可以使用 VMX(AltiVec)向量指令并行執行算術運算。
而且,Cell 處理器可以使用與 IBM POWER5™ 處理器或 Intel® 的 Pentium 4 處理器中超線程技術類似的一種同步多線程(simultaneous multithreading,SMT)技術。
IBM LTC 有一個在 PPE 上運行的標準 Linux 發行版,它只需要添加少量的內核補丁來增加對與現有目標平臺有所區別的硬件特性的支持即可。具體來說,Cell 處理器包括一個中斷控制器和一個 IOMMU 的實現,它們與早期的內核版本的支持都是不兼容的。
我們在 LTC 運行的硬件是一個基于 Cell 處理器的刀片服務器樣機,它具有兩個 Cell 處理器,用作一個對稱多處理(SMP)系統,目前配備了 512MB 內存。它設計用于一個 IBM BladeCenter™ 機架中。
在下一個內核發行版中集成對 PPE 的支持之后,將可以為現有的所有 64 位 PowerPC 機器使用一個內核二進制文件,包括 Cell、Apple Power Mac 和 IBM pSeries。
盡管還沒有計劃要在 Cell 上支持 32 位的 Linux 內核,但是我們可以在 Cell 平臺上使用 PowerPC 64 的內核,并且借助 ELF32 二進制格式的支持來運行 32 位和 64 位的發行版本。注意,所有的 32 位 PowerPC 應用程序應該不加任何修改都可以在這種平臺上運行。
Synergistic Processing Element
Synergistic Processing Element(SPE)是 Cell 處理器中最關鍵的一個特性,也正是它無與倫比的處理能力的源泉。一個芯片中封裝了 8 個 SPE,每個 SPE 中具有一個 SPU、一個內存流控制器(Memory Flow Controller,MFC) 以及 256KB SRAM 用作本地內存。
SPU 本身使用向量操作,每個時鐘周期可以執行多達 8 條浮點指令。
總線接口
Cell 處理器具有 3 個高速總線接口,一個用于內存之間的連接,另外兩個用于 I/O 或 SMP 的連接。內存接口連接 XDRAM 芯片,它是目前速度最快的一種內存技術,速度遠比目前的 DDR 和 DDR2 接口更快。
與內存接口類似,其他兩個接口也是基于 Rambus 技術的。這兩個接口中有一個專門用來連接 I/O 設備,通常是為 FlexIO 協議采用一個南橋或北橋芯片。另外一個接口也可以用來連接 I/O 設備,或者用來連接多個 Cell 處理器,形成一個 SMP 系統。
基本的 SPU 設計
SPU 就像是簡單的 CPU 設計與數字信號處理器之間的交叉。它使用相同的指令來實現 32 位或 128 位的向量處理。它具有一個 18 位的地址空間,可以訪問 256KB 的本地存儲器,后者是芯片本身的一個部分。這既不需要使用內存管理單元,也不需要使用指令或數據緩存。相反,SPU 可以以 L1 緩存的速度來訪問本地存儲器中的任何 128 位的字。
內存流控制器
MFC 是本地存儲內存與系統內存之間的主要通信工具。正如前面介紹的一樣,在每個 SPE 中都有一個 MFC。它具有一個集成的內存管理單元,通常使用與 PPE 類似的頁表查詢機制來提供對某個進程地址空間的訪問。
DMA 請求通常會涉及數據在 SPE 本地存儲與 PPE 端虛擬地址空間之間進行移動。DMA 請求的類型包括對齊讀寫操作,就像是可以使用單字的原子更新操作一樣,例如實現一個可以在 SPE 和用戶進程之間進行共享的自旋鎖。
SPE 和 PPE 都可以發起 DMA 傳輸。PPE 可以通過在內核模式中使用內存映射寄存器來發起 DMA 傳輸,而SPE 則可以使用在 SPU 上運行的代碼來寫入 DMA 通道。
MFC 可以具有對同一個地址空間的多個并發的 DMA 請求,這些請求可以來自于 PPE 和 SPU。每個 MFC 都可以訪問一個單獨的地址空間。
指令集
在 SPU 內部運行的程序需要非常簡單,而且是自包含的,因此在 SPU 中并不需要復雜的訪問保護或不同的優先級模式。結果是指令集中包含大部分算術操作和轉移操作,但是并不包含 PPE 中的那種內核模式指令。
而且,執行代碼產生的異常結果也不會向 SPU 進行匯報。如果發生了一個非常嚴重的錯誤,例如一個無效的操作碼,那么 SPU 就會停止,并向 PPE 發送一個中斷。有些常見的異常源在 SPU 上根本就不可能出現。例如,根本就不存在尋址異常,因為所有的指針都是對齊的,并且在視圖訪問某處內存時都根據本地存儲的大小進行了截斷。
算法向量操作與 PPE 的 VMX 操作非常類似,您可以使用這種操作進行高度優化的視頻、圖像處理或科學應用。
SPU 與 Cell 處理器的其他部件之間的主要通信方法由許多“通道”來定義。每個通道都是一個預定義的函數,它可能是一個讀通道,也可能是一個寫通道。
例如,郵箱機制就是 SPE 和 PPE 之間常用的一種基本通信方法。SPU 有一個讀通道來從郵箱接收單個數據字,有兩個寫通道來發送數據字(更詳細的介紹請參看下文)。這兩個寫通道中有一個定義用來在數據可用時對 CPU 產生外部中斷,另外一個則不具有通知機制。
當 SPU 視圖從一個空的郵箱中讀取信息時,它會停止執行,直到有值寫入自己的內存映射寄存器中為止。
當 PPE 希望訪問郵箱時,它需要能夠訪問內存映射的寄存器空間,后者通常只對于內核空間來說是可用的。每個 SPU 具有三個郵箱寄存器,每個都可以訪問這三個 SPU 郵箱通道。
內存映射寄存器是由 PPE 用來控制對 SPE 的特定屬性進行控制的,但是 SPU 代碼本身則不能對它進行訪問。例如,一個 PPE 端的郵箱寄存器會作為一個只寫的物理內存位置出現。當 PPE 將一個數據字寫入這個地址時,SPU 就可以從對應的郵箱讀通道中讀取這個數據字的內容。
其他通道用來訪問與 PPE 上的用戶上下文關聯在一起的虛擬內存。通過向 DMA 通道寫入數據,SPE 可以發起一個內存傳輸操作,這可以與 SPU 代碼和 PPE 控制流并行執行。例如,只有在由于所訪問的頁面已經被交換到磁盤上而發生頁面失效的情況時,PPE 才會接收到一個中斷。
可能的編程模型
字符設備
在 Linux 程序中使用 SPU 需要使用一部分內核代碼,因為控制寄存器只能從 PPE 中使用特權模式進行訪問。讓用戶空間的程序訪問硬件資源最簡單的方法是通過一個字符設備驅動程序,它可以通過 read、write 和 ioctl 系統調用進行訪問。
這對于很多簡單設備來說都是適用的,在有些地方可以用來對處理器的功能進行測試,但是這種方法具有很多問題。最為重要的是,如果每個 SPU 都是由一個字符設備來表示的,那么程序就很難發現一個還沒有被其他程序使用的 SPU。還有,這個接口不允許按照健全的方式對一個多用戶系統中的 SPU 進行可視化。
系統調用
另外一種使用 SPU 的方法是用來定義一組系統調用。這使得在從 SPU 上運行的進程進行抽象的底層單元時,替換物理上的 SPU 成為可能。SPU 處理器可以由內核進行調度,所有的用戶不用彼此進行交互就可以直接創建它們。從底層來說,這也意味著要復制一些內核的基礎設施,同時解決可能出現的大量新系統調用,從而提供所有必要的功能。
例如,如果現有的 Linux 進程 ID 之后是一個新的線程 ID 空間,那么就需要對這個 PID 真正修改所有的系統調用(kill
、getpriority
、ptrace
等等),或者提供新版本的系統調用。而這兩種方法都不是跨平臺觀點所倡導的。
SPU 文件系統
虛擬文件系統
LTC 小組最終采用的解決方案是創建一個虛擬文件系統來使 SPU 具體化?,F在存在很多類似的文件系統,例如 procfs、sysfs 或 mqueue。與具有設備基礎的文件系統不同,這幾種文件系統并不需要使用一個分區來存儲數據,而是會將所有的資源都保存在內存中,同時可以使用一些常見的系統調用,例如 open
、read
和 getdents
,從而在用戶空間和內核空間之間進行通信。
我們將這種文件系統稱為“spufs”,通常將其掛載到 /spu 上,不過要是掛載到其他地方上也是可以的。
硬件資源的映射
spufs 中的每個目錄都是指一個邏輯的 SPU 上下文。這個 SPU 上下文會被當成一個類似的物理 SPU 對待,當前的實現可以在它們之間強制進行直接映射。將來,我們計劃要對此進行修改,使其可以保存邏輯上下文而不只是物理 SPU 上下文,并且可以采用一些內核開關來切換它們。
當文件系統掛載到系統中之后,它最初是空的,在其根目錄中唯一有效的操作就是使用 mkdir
系統調用創建一個新的目錄。
每個上下文目錄中都包含一組固定的文件,它們是在建立這個上下文時自動創建的。最重要的有:
使用 SPU 上下文
要在一個進程中使用 SPU,用戶需要具有對 spufs 的掛載點具有寫權限,并要為這個新的 SPU 上下文選擇一個尚未使用的名字。mkdir
系統調用用來創建這個上下文,用戶進程然后可以打開這個目錄中相關的文件。
|
程序的文本和數據段現在需要寫入到 mem 文件中,這可以使用 write
系統調用,或者通過將該文件映射到該進程的地址空間中實現。通常,不需要對內存重新進行分配,因為 SPU 程序是靜態鏈接的。
由于每個執行 SPU_RUN ioctl 的線程在進行 ioctl 系統調用時都會阻塞,因此不能同時與其他任何系統資源進行交互,包括其他的 SPU 上下文或屬于執行上下文的文件。一個進程可以使用多個 SPU 上下文,但是要在每個給定的時間點上在多個 SPU 上運行,這個進程需要包含至少一個每個正在運行的 SPU 上下文所使用的線程。
同理,如果這個程序與使用郵箱訪問的 SPU 代碼進行通信,它就需要創建一個新的線程,例如通過調用 fork
或 pthread_create
。其中一個線程然后會對運行文件調用 SPU_RUN ioctl 系統調用,而其他線程則可以對郵箱文件和其他可能的文件描述符執行一個事件循環。
在運行時不需要與 SPU 代碼進行通信的程序可以只有單個執行線程,它可以是用戶空間或 SPU 上運行的一個進程。
當使用 SPU 上下文的程序執行完之后,就必須關閉所有在這個上下文目錄中打開的文件描述符,然后使用 rmdir
系統調用刪除這個目錄。
mkdir
創建一個完整的目錄,而 rmdir
則刪除這個目錄及其中包含的所有文件。
信號處理
我們可能需要將信號發送給正在執行 SPU 代碼的線程。通常這是由 SPU 代碼本身調用的。在發生這種情況時,SPU 就會停止,ioctl 調用也會被中斷。
如果這導致在用戶空間中調用一個信號處理程序,那么就會創建一個新的堆棧幀,在其中會更新這個線程和 ioctl 的參數,從而在執行信號處理程序之前刷新當前指令的指針。通常,信號處理程序將會返回,并進入上次離開時 ioctl 的位置處,這樣 SPU 程序就可以繼續執行了。
SPU 庫抽象
庫接口
我們已經在底層編程模型的基礎上構建了一個可移植的庫接口。這個庫接口并不依賴于文件系統的實現細節,但是也可以在其他內核接口稍有不同的操作系統上使用。
這個接口并不是提供一個邏輯 SPU 的抽象,而是面向線程的,它的工作方式與 pthread 庫類似。在創建一個 SPU 線程時,這個庫就創建一個新線程,它負責管理與主線程異步的 SPU 上下文。
從 SPU 中使用庫調用
當 SPU 需要執行任何標準的庫調用時,例如 printf
或 exit
,它都需要回調主線程。它是通過執行一個具有標準參數值的特殊停止和信號匯編指令實現的。這個值是從 ioctl 調用中返回的,用戶線程必須對此進行響應。這通常意味著從 SPE 的本地存儲中拷貝參數,在用戶線程中運行各自的庫函數,并通過再次調用 ioctl 繼續執行。
從 SPU 中直接進行系統調用
我們正在考慮為 spufs 增加一個直接的系統調用,在這種情況中,停止并產生信號的指令不能跟蹤到用戶空間中,而是讓內核從本地存儲中讀取系統調用參數,并直接進入系統調用。
由于這發生在一個用戶進程的 ioctl 系統調用之內,因此 SPU 系統調用的任何指針參數都假設是指向該進程的地址空間,這個 SPU 程序需要使用 DMA 來訪問它們。
工具鏈支持
編譯器,binutils
由于 Cell 的 PPE 使用了與 PowerPC 970 CPU 相同的指令集,因此不需要對編譯器和 binutils 進行任何修改。然而,如果使用專門為 CPU 的流水線結構進行優化的編譯器時,編譯后的代碼可以更有效地運行。您可以找到 GCC 的一個補丁,它用來添加 PPE 的流水線定義,這樣就可以創建優化的代碼。
由于 SPU 指令集并不與現有的 CPU 架構直接相關,因此我們為 GCC 和 binutils 編寫了一個新的后端程序。SPU 代碼與 PPC 代碼分開進行編譯,并在運行時進行加載。
GCC 對 PPE 優化的修改和 CPU 后端都希望可以作為 Linux 發行版本的一部分發布。
編譯器引入了一些新的特性來實現 DMA 傳輸和其他郵箱訪問,這是因為這些并不是 C 語言標準的部分。還有,這些新特性以及一個新的數據類型使用了向量指令進行并行化的計算。這與 VMX/AltiVec 或 SSE 向量指令的功能類似。
未來的 GCC 版本應該可以使用自動向量化技術,自動創建向量代碼,但是現在還不具有這種功能。這就像是顯式的向量指令通常都比編譯器所生成的代碼更加有效一樣。
調試器
調試 SPU 程序會出現一些新的問題。雖然為 SPU 自己創建一個新的 GDB 目標非常簡單,但是大部分用戶都需要對 PPE 和 SPE 之間的交互進行調試。我們此處討論的方法是要啟用 GDB 在一個二進制文件中支持對多個目標程序的支持,并修改 PowerPC 目標程序,使其知曉 spufs 的存在。當 PowerPC 調試器發現一個程序運行 SPU_RUN ioctl 調用時,它就切換到 SPU 后端代碼上,并使用 SPU 上下文,而不是主程序的上下文。
分析器
雖然現在還沒有用于 spufs 程序的分析器,但是我們計劃對 OProfile 進行擴展,使其可以包含 PPE/SPE 程序。這需要修改 OProfile 的內核代碼來周期性地對 SPU 指令指針進行采樣。
在用戶空間的 OProfile 中,從 spufs 文件到需要加載的實際 ELF 文件需要一個額外的間接級別。
結束語
在目前的 Linux on Cell 中,您可以編寫專用的程序在這種原型系統上運行,同時使用這種芯片的全部性能。雖然大部分程序都不能立即在 Cell 平臺上更好地運行,但是很有可能將一些性能關鍵的程序移植到使用在 SPU 上運行的庫代碼的程序,以實現更好的性能。
基本的平臺支持目前正在設法進入主流的 Linux 內核中,SPU 文件系統接口也正在逐步穩定地通往包含到主發行商的內核版本的征途上。