直接渲染管理器

直接渲染管理器（Direct Rendering Manager，通常縮寫為DRM ) 是Linux 內核的一個子系統，負責與現代顯卡的GPU接口。DRM 向使用者空間公開了API，程序可以使用該 API 向 GPU 發送命令和數據並執行配置顯示器模式設置等操作。DRM 最初是作為X Server直接渲染基礎設施的內核空間組件開發的，但從那時起，它已被其他圖形堆棧替代方案（例如Wayland）以及獨立應用程式和庫（例如SDL2和Kodi）使用。

使用者空間程序可以使用DRM API來命令GPU進行硬體加速的3D渲染和影片解碼，以及GPGPU計算。

## 概述

Linux 內核已經有一個名為fbdev的API，用於管理圖形適配器的幀緩衝區，但它無法滿足現代 GPU 的 3D 加速需求。此類設備通常需要在專用的顯存中設置和管理命令隊列，並將命令分派到 GPU，並且還需要管理該記憶體中的緩衝區和可用空間。最初，使用者空間程序（例如X Server）直接管理這些資源，但它們通常假定它們是唯一訪問這些資源的程序。當兩個或多個程序試圖同時控制同一硬體並以自己的方式配置其資源時，大多數情況下都會導致災難性的後果。

DRM 旨在允許多個程序協作使用影片硬體資源。DRM 獲得對 GPU 的獨占訪問權限，並負責初始化和維護命令隊列、記憶體和任何其他硬體資源。希望使用 GPU 的程序向 DRM 發送請求，DRM 充當仲裁者並避免衝突。

多年來，DRM 的範圍不斷擴大，涵蓋了以前由使用者空間程序處理的更多功能，例如幀緩衝區管理和模式設置、記憶體共享對象和記憶體同步。其中一些擴展被賦予了特定的名稱，例如圖形執行管理器（GEM）或內核模式設置（KMS），並且當它們提供的功能被特別提及時，術語將占主導地位。但它們實際上是整個內核 DRM 子系統的一部分。

在電腦中包含雙 GPU（一個獨立 GPU 和一個集成 GPU）的趨勢導致了GPU 切換等新問題，這些問題也需要在 DRM 層解決。為了匹配Nvidia Optimus技術，DRM提供了GPU卸載能力，稱為PRIME。

## 開發管理

DRM在Linux內核內部開發，其原始碼位於 Linux 源碼的 `/drivers/gpu/drm` 目錄下。該子系統的維護者是 Dave Airlie，另有其他維護者負責管理特定的驅動程序。

遵循 Linux 內核開發的慣例，DRM 子系統的共同維護者和貢獻者會將包含新功能和錯誤修復的補丁發送給 DRM 主維護者，由主維護者將其集成到自己的 Linux 代碼倉庫中。每當新版本的 Linux 即將發布時，DRM 主維護者會將所有已準備好被合入主線（mainline）的補丁提交給 Linus Torvalds。作為整個內核的最高維護者，Torvalds 對某個補丁是否適合包含在內核中擁有最終決定權。

由於歷史原因，libdrm 庫的原始碼是在 Mesa 項目的框架下進行維護的。

## 發展歷史

1999 年，在為 XFree86 開發 DRI 的過程中，Precision Insight 公司為 3dfx 顯卡創建了第一個版本的 DRM，當時是以 Linux 內核補丁的形式包含在 Mesa 源碼中。同年晚些時候，DRM 代碼被合入 Linux 內核 2.3.18 的主線，位於 `/drivers/char/drm/` 目錄下（用於字符設備）。在接下來的幾年裡，所支持的顯卡數量不斷增長。到 2001 年 1 月 Linux 2.4.0 發布時，除了 3dfx Voodoo3 顯卡之外，已經支持了 Creative Labs GMX 2000、Intel i810、Matrox G200/G400 和 ATI Rage 128，而在 2.4.x 系列期間，這一列表進一步擴大，新增了 ATI Radeon 顯卡、部分 SiS 顯卡以及 Intel 830M 及後續的集成 GPU。

2004 年下半年，DRM 被拆分為兩個組件——DRM 核心和 DRM 驅動，這一拆分被稱為 DRM 核心/個性化拆分，並於內核版本 2.6.11 中合入。此次拆分允許多個設備的多個 DRM 驅動同時工作，為多 GPU 支持鋪平了道路。

將所有的影片模式設置代碼集中放在內核中的一個地方這一想法，多年來已得到廣泛認可，但顯卡製造商一直主張，進行模式設置的唯一方法是使用他們自己提供的、包含在每塊顯卡的影片 BIOS 中的例程。這些代碼必須使用 x86 實模式執行，這阻止了在保護模式下運行的內核調用它們。當 Luc Verhaegen 和其他開發者找到了一種基於原生實現而非 BIOS 調用來完成模式設置的方法時，情況發生了變化，這證明使用普通內核代碼進行模式設置是可行的，並為後來成為內核模式設置（KMS）的技術奠定了基礎。2007 年 5 月，Jesse Barnes（英特爾）發布了第一個 drm-modesetting API 提案，以及 i915 DRM 驅動中為英特爾 GPU 實現的原生模式設置的工作實現。2007 年 12 月，Jerome Glisse 開始為 ATI 顯卡向 radeon DRM 驅動中添加原生模式設置代碼。2008 年期間，API 和驅動的開發工作持續進行，但由於內核空間中也必須有一個記憶體管理器來處理幀緩衝區，開發工作被推遲了。

2008 年 10 月，Linux 內核 2.6.27 在若干重大變更到來之前，進行了一次重大的原始碼重組。DRM 原始碼樹被移動到了自己的源碼目錄 `/drivers/gpu/drm/`，各個驅動被移入了各自的子目錄。頭文件也被移到了一個新的 `/include/drm` 目錄下。

顯存管理日益增長的複雜性導致了解決該問題的若干種方法。第一次嘗試是由 Thomas Hellstrom（Tungsten Graphics）與 Emma Anholt（英特爾）和 Dave Airlie（Red Hat）合作開發的轉換表映射（TTM）記憶體管理器。TTM 於 2007 年 11 月被提議合入主線內核 2.6.25，並於 2008 年 5 月再次提議，但最終被放棄，轉而採用一種稱為圖形執行管理器（GEM）的新方法。GEM 最初由英特爾的 Keith Packard 和 Emma Anholt 開發，作為其 i915 驅動的一種更簡單的記憶體管理解決方案。GEM 受到廣泛歡迎，並被合入 2008 年 12 月發布的 Linux 內核版本 2.6.28。與此同時，TTM 不得不等到 2009 年 9 月，才作為新款 Radeon KMS DRM 驅動的需求，最終被合入 Linux 2.6.31。

隨著記憶體管理就位以處理緩衝對象，DRM 開發者終於可以將在內核中已經完成開發的 API 和代碼添加進去，以實現模式設置。這個擴展後的 API 被稱為內核模式設置（KMS），而實現它的驅動通常被稱為 KMS 驅動。2009 年 3 月，KMS 與 i915 驅動的 KMS 支持一同被合入 Linux 內核版本 2.6.29。自 libdrm 2.4.3 版本起，KMS API 已暴露給使用者空間程序。針對英特爾顯卡的使用者空間 X.Org DDX 驅動也是首個使用新 GEM 和 KMS API 的驅動。對 radeon DRM 驅動的 KMS 支持被添加到 2009 年 9 月發布的 Linux 2.6.31 中。新款 radeon KMS 驅動使用了 TTM 記憶體管理器，但對外暴露的是與 GEM 兼容的接口和 ioctl，而非 TTM 的原生接口。

自 2006 年起，nouveau 項目一直在官方 Linux 內核之外為 NVIDIA GPU 開發自由軟體 DRM 驅動。2010 年，nouveau 源碼作為實驗性驅動被合入 Linux 2.6.33。在合併時，該驅動已經完成了向 KMS 的轉換，並且在 GEM API 背後使用 TTM 作為其記憶體管理器。

新的 KMS API——包括 GEM API——是 DRM 發展過程中的一個重要里程碑，但這並未阻止 API 在後續幾年中持續增強。KMS 在 Linux 2.6.33 中增加了對頁面翻轉配合異步 VBLANK 通知的支持，但當時僅限 i915 驅動；radeon 和 nouveau 後來在 Linux 2.6.38 發布期間添加了該支持。新的頁面翻轉接口被添加到 libdrm 2.4.17 中。2011 年初，在 Linux 2.6.39 發布周期期間，所謂的「啞緩衝」（dumb buffers）被添加到 KMS API 中。這是一種與硬體無關的非加速方式，用於處理適合用作幀緩衝區的簡單緩衝區。其目標是降低諸如 Plymouth 這類應用程式的複雜性，它們不需要使用由驅動特定 ioctl 提供的特殊加速操作。該特性自 libdrm 2.4.25 版本起被暴露出來。同年晚些時候，KMS API 還增加了一種新的主要對象類型，稱為平面（plane）。平面是為表示掃描輸出引擎支持的硬體疊加層而開發的。對平面的支持被合入 Linux 3.3 和 libdrm 2.4.30。另一個添加到 API 中的概念——在 Linux 3.5 和 libdrm 2.4.36發布期間——是通用對象屬性（generic object properties），這是一種向任何 KMS 對象添加通用值的方法。屬性對於為 CRTC 和平面等對象設置特殊行為或功能尤為有用。

2010 年，Dave Airlie 開發了一個在 DRM 驅動之間提供 GPU 卸載功能的早期概念驗證。由於 Airlie 試圖模仿 NVIDIA Optimus 技術，他決定將其命名為「PRIME」。Airlie 於 2011 年底恢復了 PRIME 的開發工作，但這次基於 Linux 內核 3.3 引入的新 DMA-BUF 緩衝區共享機制。基本的 DMA-BUF PRIME 架構於 2012 年 3 月完成，並被合入 Linux 3.4 版本，同時也被合入 libdrm 2.4.34。隨後在 Linux 3.5 發布期間，多個 DRM 驅動實現了 PRIME 支持，包括適用於英特爾顯卡的 i915、適用於 AMD 顯卡的 radeon 以及適用於 NVIDIA 顯卡的 nouveau。

近年來，DRM API 通過新增和改進的特性逐步擴展。2013 年，作為 GSoC 的一部分，David Herrmann 開發了多渲染節點特性。他的代碼作為實驗性特性被添加到 Linux 內核版本 3.12 中，得到了 i915、radeon和nouveau驅動的支持，並自 Linux 3.17 起默認啟用。2014 年，Matt Roper（英特爾）開發了通用平面（或稱統一平面）概念，根據這一概念，幀緩衝區（主平面）、疊加層（次級平面）和光標（光標平面）都被視為單一類型的對象，使用統一的 API 進行管理。通用平面支持提供了更一致的 DRM API，具有更少、更通用的 ioctl。為了保持 API 的向後兼容性，該特性由 DRM 核心作為一個附加能力暴露出來，供 DRM 驅動選擇提供。通用平面支持首次亮相於 Linux 3.15 和 libdrm 2.4.55。已有多個驅動（如 Intel i915）實現了該特性。

最新的 DRM API 增強是原子模式設置 API，它為 DRM 設備上的模式設置和頁面翻轉操作帶來了原子性。原子模式設置 API 的想法最早於 2012 年初提出。Ville Syrjälä（英特爾）接手了設計和實現該原子 API 的任務。基於他的工作，Rob Clark（德州儀器）採用了類似的方法，目標是實現原子頁面翻轉。後來在 2013 年，這兩個提議的特性被合併為一個，使用單個 ioctl 來完成兩項任務。由於這是一個需求特性，它不得不等待通用平面的支持在 2014 年年中被合入。2014 年下半年，Daniel Vetter（英特爾）和其他 DRM 開發者對原子代碼進行了大幅增強，以方便現有的 KMS 驅動向新的原子框架過渡。所有這些工作最終被合入 Linux 3.19 和 Linux 4.0 版本，並自 Linux 4.2 起默認啟用。libdrm 自 2.4.62 版本起暴露了新的原子 API。已有多個驅動完成了向新原子 API 的轉換。到 2018 年，已有十個基於這種新原子模型的新 DRM 驅動被添加到 Linux 內核中。

## 軟體架構

DRM運行在內核空間，因此使用者空間程序必須使用內核系統調用來請求其服務。然而，DRM並未定義其自身的定製系統調用。相反，它遵循 Unix 的「一切皆文件」原則，通過文件系統命名空間暴露 GPU，在 `/dev` 層次結構下創建設備文件。DRM 檢測到的每個 GPU 稱為一個 DRM 設備，並會創建一個設備文件 `/dev/dri/cardX` (其中 X 是一個順序編號) 用於與之交互。希望與 GPU 對話的使用者空間程序必須打開此文件並使用 ioctl 調用與 DRM 進行通信。不同的 ioctl 調用對應於 DRM API 的不同功能。

為了方便使用者空間程序與 DRM 子系統進行交互，創建了一個名為 libdrm 的庫。該庫僅僅是一個包裝器，為 DRM API 的每個 ioctl 調用提供一個編寫在 C 中的函數，以及常量、結構體和其他輔助元素。使用 libdrm 不僅避免了直接向應用程式暴露內核接口，還提供了接口復用和共享代碼的常見優勢。

DRM 由兩部分組成：一個通用的「DRM 核心」和一個針對每種受支持硬體的特定部分（「DRM 驅動」）。DRM 核心提供基本框架，不同 DRM 驅動可以在其中註冊，並向使用者空間提供一組基本的 ioctl 調用，這些調用具有通用且與硬體無關的功能。另一方面，DRM 驅動實現硬體相關的 API 的特定部分，對應於其支持的 GPU 類型；它應提供未被 DRM 核心涵蓋的其餘 ioctl 調用，但也可以擴展 API，提供僅在該硬體上可用的額外 ioctl 調用。當特定的 DRM 驅動提供增強的 API 時，使用者空間 libdrm 也會被擴展為一個額外的庫 libdrm-driver，使用者空間可以使用它來與額外的 ioctl 調用進行交互。

### API

DRM 核心向使用者空間應用程式導出多個接口，通常通過相應的 libdrm 包裝函數使用。此外，驅動程序還會通過 ioctl 調用和 sysfs 文件導出設備特定的接口，供使用者空間驅動程序和設備感知應用程式使用。

外部接口包括：

- 記憶體映射: 允許用戶空間程序直接訪問 GPU 記憶體。
- 上下文管理: 管理 GPU 的狀態和配置。
- DMA 操作: 允許直接記憶體訪問，無需 CPU 干預。
- AGP 管理: 管理顯存的地址空間。
- VBlank 控制: 控制垂直同步，防止畫面撕裂。
- 柵欄管理: 用於同步不同操作之間的執行順序，確保數據一致性。
- 記憶體管理: 管理 GPU 記憶體的分配和釋放。
- 輸出管理: 管理顯示器的輸出設置。

### DRM-Master 和 DRM-Auth

DRM API 中存在一些操作（ioctl 調用），出於安全或並發問題，必須限制每個 DRM 設備僅允許單個使用者空間進程使用。為了實現此限制，DRM 將這些受限的 ioctl 調用限制為僅由 DRM 設備「主」進程調用。通常，所有打開了設備節點 `/dev/dri/cardX` 的進程中，第一個調用 `SET_MASTER` ioctl 的進程的文件句柄會被標記為主。任何沒有作為 DRM-Master 調用這些受限 ioctl 的嘗試都會返回錯誤。一個進程也可以通過調用 `DROP_MASTER` ioctl 放棄其主角色，並允許其他進程獲取它。

X Server（或任何其他顯示伺服器）通常是管理每個 DRM 設備時獲取 DRM-Master 狀態的進程，並在啟動時打開相應的設備節點，並在圖形會話結束或崩潰之前保留這些權限。

對於剩餘的使用者空間進程，有一種其他方法可以獲得在 DRM 設備上執行某些受限操作的權限，稱為 DRM-Auth。它本質上是一種針對 DRM 設備的身份驗證方法，用於證明該進程已獲得 DRM-Master 的批准，以獲取這些權限。該過程包括：

- 客戶端通過 `GET_MAGIC` ioctl 從 DRM 設備獲取一個唯一的令牌（一個 32 位整數），並通過某種方式（通常是某種形式的 IPC；例如，在 DRI2 中，存在一個 DRI2Authenticate 請求，任何 X 客戶端都可以發送到 X Server）將其傳遞給 DRM-Master 進程。
- DRM-Master 進程隨後通過調用 `AUTH_MAGIC` ioctl 將令牌發送回 DRM 設備。
- 設備授予與 DRM-Master 收到的令牌匹配的進程文件句柄特殊的權限。

### Graphics Execution Manager (GEM)

隨著現代GPU顯存容量不斷增大，以及OpenGL等圖形 API 日益複雜，若在每個上下文切換時都重新初始化顯卡狀態，其性能開銷已變得過高。此外，現代 Linux 桌面需要一種優化共享螢幕外緩衝區與合成管理器的方式。這些需求導致了在內核中管理圖形緩衝區的新的方法的開發。Graphics Execution Manager (GEM) 就是這些方法之一。

GEM 提供了一個帶有顯式記憶體管理原語的 API。通過 GEM，使用者空間程序可以創建、處理和銷毀存放在顯存的記憶體對象。這些對象，稱為「GEM 對象」，從使用者空間的觀點來看是持久的，並且不必每次程序重新獲得 GPU 控制時都重新加載。當使用者空間程序需要一塊影片記憶體（用於存儲幀緩衝、紋理或 GPU 所需的任何其他數據）時，它會請求通過 GEM API 將其分配給 DRM 驅動程序。DRM 驅動程序跟蹤使用的顯存，如果可用空間有空閒，則可以滿足請求，並將「句柄」返回給使用者空間，以便進一步引用分配的記憶體進行後續操作。GEM API 還提供用於填充緩衝區和不再需要時釋放它的操作。在使用者空間進程關閉 DRM 設備文件描述符時（有意或由於終止），從未釋放的 GEM 句柄的記憶體將被回收。

GEM 還允許兩個或多個使用同一 DRM 設備（因此，相同的 DRM 驅動程序）的使用者空間進程共享一個 GEM 對象。GEM 句柄是進程本地的 32 位整數，在其他進程中不具備相同的含義，因此無法直接用於跨進程共享。為此需要全局命名空間，而 GEM 通過引入全局句柄（即 GEM 名稱）來滿足這一需求。GEM 名稱是一個唯一的 32 位整數，用於在相同 DRM 設備內部標識由同一驅動程序創建的單個 GEM 對象。GEM 提供了一個 `flink` 操作來從 GEM 句柄獲取一個 GEM 名稱。然後進程可以將此 GEM 名稱（32 位整數）傳遞給另一個進程，可以使用任何可用的 IPC 機制。接收進程可以使用此 GEM 名稱來獲取指向原始 GEM 對象的本地 GEM 句柄。

不幸的是，使用 GEM 名稱共享緩衝區並不安全。惡意第三方進程可以嘗試猜測兩個其他進程共享的緩衝區的 GEM 名稱，只需探測 32 位整數即可。一旦找到 GEM 名稱，其內容就可以被訪問和修改，從而違反了緩衝區資訊的機密性和完整性。這個缺點後來通過在 DRM 中引入 DMA-BUF 支持來克服，因為 DMA-BUF 將使用者空間中的緩衝區表示為文件描述符，這些緩衝區可以安全地共享。

除了管理影片記憶體空間之外，另一個重要的任務是處理 GPU 與 CPU 之間的記憶體同步。當前的記憶體架構非常複雜，通常涉及系統記憶體和有時影片記憶體的多個級別緩存。因此，影片記憶體管理器還應處理緩存一致性，以確保 CPU 和 GPU 之間共享的數據的一致性。這意味著通常影片記憶體管理內部依賴於 GPU 和記憶體架構的硬體細節，因此是驅動程序特定的。

GEM 最初由英特爾工程師開發，旨在為其 i915 驅動提供顯存管理功能。英特爾 GMA 9xx 系列是採用統一記憶體架構（UMA）的集成 GPU，其 GPU 與 CPU 共享物理記憶體，沒有獨立的顯存（VRAM）。GEM 定義了用於記憶體同步的「記憶體域」，儘管這些記憶體域是與 GPU 無關的抽象概念，但它們的設計初衷和最佳適配場景都是 UMA 架構，因此不太適合其他記憶體架構（例如配備獨立顯存的方案）。正因如此，其他 DRM 驅動雖然向使用者態程序暴露了 GEM API，但在內部卻實現了各自更適合其特定硬體和記憶體架構的記憶體管理器。

GEM API 也包含用於控制執行流（即命令緩衝區）的 ioctl，但它們屬於英特爾私有接口，只能配合 Intel i915 及更新的 GPU 使用。事實上，除了記憶體管理相關的 ioctl 之外，沒有任何其他 DRM 驅動曾嘗試實現 GEM API 的其他功能。

#### TTM（轉換表映射）

TTM（Translation Table Maps）是 GEM 之前開發的通用 GPU 記憶體管理器。它專門設計用於管理 GPU 可能訪問的多種記憶體類型，包括獨立的顯存（通常安裝於顯卡上）以及通過名為 GART（圖形地址重映射表）的 IOMMU 可訪問的系統記憶體。TTM 還需要處理 CPU 無法直接尋址的那部分顯存，並在此基礎上實現儘可能高的性能——因為使用者態圖形應用程式通常處理大量影片數據。另一個重要問題是維護所涉及的多種記憶體與緩存之間的一致性。

TTM 的核心概念是「緩衝對象」（buffer objects），即 GPU 在某一時刻必須能夠尋址的顯存區域。當使用者態圖形應用程式需要訪問某個緩衝對象（通常是向其填充內容）時，TTM 可能要求將其重定位到 CPU 可尋址的記憶體類型上。當 GPU 需要訪問某個尚未進入其地址空間的緩衝對象時，可能還會發生進一步的重定位（即 GART 映射操作）。上述每一次重定位操作都必須處理相關的數據及緩存一致性問題。

TTM 的另一個重要概念是柵欄（fences）。柵欄本質上是一種管理 CPU 與 GPU 之間並發性的機制。柵欄用於追蹤緩衝對象是否已不再被 GPU 使用，通常是為了通知所有有權訪問該對象的使用者態進程。

TTM 試圖以一種普適的方式管理所有類型的記憶體架構（無論是否配備獨立顯存），並試圖提供一個涵蓋所有可設想功能的、適用於任意硬體類型的記憶體管理器，這最終導致其解決方案過於複雜，API 也遠大於實際所需。一些 DRM 開發者認為它將難以與任何特定驅動（尤其是其 API）良好適配。當 GEM 作為更簡單的記憶體管理器出現時，它的 API 相比 TTM 的 API 更受青睞。然而，部分驅動開發者認為 TTM 所採用的方法更適合配備獨立顯存和 IOMMU 的獨立顯卡，因此他們決定在內部使用 TTM，同時將其緩衝對象作為 GEM 對象暴露出來，從而支持 GEM API。當前使用 TTM 作為內部記憶體管理器但提供 GEM API 的驅動實例包括：用於 AMD 顯卡的 radeon 驅動，以及用於 NVIDIA 顯卡的 nouveau 驅動。

#### DMA 緩衝區共享與 PRIME

DMA 緩衝區共享 API（通常縮寫為 DMA-BUF）是 Linux 內核內部的一個 API，旨在提供一種通用機制，用於在多個設備（可能由不同類型的設備驅動管理）之間共享 DMA 緩衝區。例如，一個 Video4Linux 設備和一個圖形適配器設備可以通過 DMA-BUF 共享緩衝區，從而實現影片流數據的零拷貝：前者產生數據，後者消費數據。任何 Linux 設備驅動都可以作為導出方（exporter）、使用方（consumer）或同時扮演兩種角色來實現該 API。

該功能首次在 DRM 中得到了利用，用於實現 PRIME——一種 GPU 卸載方案，它使用 DMA-BUF 在獨立顯卡和集成顯卡的 DRM 驅動之間共享渲染後的幀緩衝區。DMA-BUF 的一個重要特性是：共享緩衝區以文件描述符的形式呈現給使用者空間。為實現 PRIME，DRM API 新增了兩個 ioctl，一個用於將本地 GEM 句柄轉換為 DMA-BUF 文件描述符，另一個用於執行相反的操作。

後來，這兩個新增的 ioctl 被重新用於解決 GEM 緩衝區共享固有的不安全問題。與 GEM 名稱不同，文件描述符無法被猜測（它們不屬於全局命名空間），並且 Unix 作業系統提供了一種安全的方式來通過 Unix 域套接字傳遞它們，即使用 `SCM_RIGHTS` 語義。當某個進程希望與另一個進程共享一個 GEM 對象時，它可以將自己的本地 GEM 句柄轉換為 DMA-BUF 文件描述符，然後將其傳遞給接收方，接收方再根據收到的文件描述符獲取自己的 GEM 句柄。DRI3 和 Wayland 都使用此方法在客戶端與 X Server（或 Wayland 合成器）之間共享緩衝區。

## 內核模式設置（Kernel Mode Setting）

使用者空間中必須存在一個"DRM 主控"程序，該程序對 KMS 擁有獨占訪問權限。

為了正常工作，顯卡或圖形適配器必須設置一種模式——即螢幕解析度、色彩深度和刷新率的組合——該模式需在自身及所連接顯示器支持的數值範圍之內。此操作稱為模式設置（mode-setting），通常需要原始訪問圖形硬體的能力——即能夠寫入顯卡顯示控制器的特定寄存器。在開始使用幀緩衝區之前，以及當應用程式或使用者要求更改模式時，都必須執行模式設置操作。

早期，想要使用圖形幀緩衝區的使用者空間程序也負責提供模式設置操作。因此，它們需要以特權訪問權限運行才能操作影片硬體。在 Unix 類作業系統中，X Server 是最典型的例子。其模式設置實現位於針對每種特定顯卡類型的 DDX 驅動中。這種後來被稱為使用者空間模式設置（User space Mode-Setting，簡稱 UMS）的方法帶來了若干問題。它不僅破壞了作業系統本應在程序與硬體之間提供的隔離性，引發了穩定性和安全性方面的擔憂，而且如果兩個或更多用戶空間程序同時嘗試進行模式設置，還可能使圖形硬體陷入不一致的狀態。為避免這些衝突，X Server 實際上成為了唯一執行模式設置操作的使用者空間程序；其餘使用者空間程序則依賴 X Server 來設置合適的模式並處理任何涉及模式設置的其他操作。最初，模式設置僅在 X Server 啟動過程中執行，但後來 X Server 獲得了在運行時執行模式設置的能力。XFree86 3.1.2 版本引入了 XFree86-VidModeExtension 擴展，允許任何 X 客戶端向 X Server 請求更改顯示模式（解析度）。VidMode 擴展後來被更通用的 XRandR 擴展所取代。

然而，在 Linux 系統中執行模式設置的並非只有這一處代碼。在系統啟動過程中，Linux 內核必須為虛擬控制台設置一個最小文本模式（基於 VESA BIOS 擴展定義的標準模式）。此外，Linux 內核幀緩衝區驅動也包含用於配置幀緩衝區設備的模式設置代碼。為避免模式設置衝突，XFree86 Server——以及後來的 X.Org Server——通過在處理使用者從圖形環境切換到文本虛擬控制台時保存其模式設置狀態，並在使用者切換回 X 時恢復該狀態，來應對這種情況。這一過程在切換時會產生令人煩惱的閃爍，並且也可能失敗，導致輸出顯示損壞或無法使用。

使用者空間模式設置方法還引發了其他問題：

- 掛起/恢復過程必須依賴使用者空間工具來恢復先前的模式。這些程序中任何一個發生故障或崩潰，都可能因模式設置錯誤而導致系統無法正常顯示，從而使系統不可用。
- 當螢幕處於圖形模式時（例如 X 正在運行時），內核無法顯示錯誤或調試消息，因為內核唯一知道的模式只有 VESA BIOS 標準文本模式。
- 一個更緊迫的問題是，繞過 X Server 的圖形應用程式大量湧現，以及 X 之外的其他圖形棧替代方案的出現，進一步加劇了模式設置代碼在整個系統中的重複。

為了解決這些問題，模式設置代碼被移到了內核中的一個單獨位置，具體來說是現有的 DRM 模塊中。此後，每個進程（包括 X Server）都應該能夠命令內核執行模式設置操作，而內核將確保並發操作不會導致不一致的狀態。添加到 DRM 模塊中以執行這些模式設置操作的新內核 API 和代碼被稱為內核模式設置（Kernel Mode-Setting，簡稱 KMS）。

內核模式設置帶來了諸多好處。最直接的好處當然是消除了來自內核（Linux 控制台、fbdev）和使用者空間（X Server DDX 驅動）兩方面的重複模式設置代碼。KMS 還使得編寫替代圖形系統變得更加容易，因為現在這些系統不再需要實現自己的模式設置代碼。通過提供集中化的模式管理，KMS 解決了在控制台與 X 之間以及不同 X 實例之間切換（快速使用者切換）時的閃爍問題。由於 KMS 在內核中可用，它也可以在啟動過程的早期階段使用，從而避免了這些早期階段因模式切換而產生的閃爍。

KMS 作為內核的一部分，使其能夠使用僅在內核空間可用的資源，例如中斷。例如，掛起/恢復過程後的模式恢復由內核自身管理，大大簡化了流程，並且順便提高了安全性（不再需要需要 root 權限的使用者空間工具）。內核還允許輕鬆熱插拔新的顯示設備，解決了一個長期存在的問題。模式設置也與記憶體管理密切相關——因為幀緩衝區本質上就是記憶體緩衝區——因此強烈建議與圖形記憶體管理器緊密集成。這正是內核模式設置代碼被納入 DRM 而不是作為一個獨立子系統的核心理由。

為了避免破壞 DRM API 的向後兼容性，內核模式設置作為某些 DRM 驅動的附加驅動特性提供。任何 DRM 驅動在向 DRM 核心註冊時都可以選擇提供 `DRIVER_MODESET` 標誌，以表明其支持 KMS API。那些實現了內核模式設置的驅動通常被稱為 KMS 驅動，以區別於傳統的（無 KMS）DRM 驅動。

KMS 的普及程度如此之高，以至於某些缺乏 3D 加速能力（或者硬體廠商不願暴露或實現 3D 加速）的驅動，也實現了 KMS API（即使沒有實現 DRM API 的其他部分），從而使得顯示伺服器（如 Wayland）能夠輕鬆運行。

### KMS 設備模型

KMS 將輸出設備建模並管理為一系列抽象硬體模塊，這些模塊通常存在於顯示控制器的顯示輸出流水線中。這些模塊包括：

- CRTC：每個 CRTC（CRT Controller 的縮寫）代表顯示控制器的一個掃描輸出引擎，指向一個掃描輸出緩衝區（幀緩衝區）。CRTC 的作用是讀取掃描輸出緩衝區中的當前像素數據，並藉助 PLL 電路從中生成影片模式時序信號。可用的 CRTC 數量決定了硬體能夠同時處理的獨立輸出設備數量，因此要使用多頭（multi-head）配置，每個顯示設備至少需要一個 CRTC。兩個（或更多）CRTC 也可以工作在克隆模式下，此時它們從同一個幀緩衝區掃描輸出，將相同的圖像發送到多個輸出設備。
- 連接器（Connector）：連接器代表顯示控制器將掃描輸出操作產生的影片信號發送到何處進行顯示。通常，KMS 中的連接器概念對應於硬體中的物理連接器（VGA、DVI、FPD-Link、HDMI、DisplayPort、S-Video 等），輸出設備（顯示器、筆記本面板等）永久或臨時地連接到該連接器上。與當前物理連接的輸出設備相關的資訊——例如連接狀態、EDID 數據、DPMS 狀態或支持的影片模式——也存儲在連接器內部。
- 編碼器（Encoder）：顯示控制器必須使用適合目標連接器的格式，對來自 CRTC 的影片模式時序信號進行編碼。編碼器代表能夠執行其中一種編碼的硬體模塊。數字輸出的編碼示例包括 TMDS 和 LVDS；對於 VGA 和 TV out 等模擬輸出，通常使用特定的 DAC 模塊。一個連接器一次只能接收來自一個編碼器的信號，且每種類型的連接器僅支持某些編碼方式。此外，還可能存在額外的物理限制，使得並非每個 CRTC 都連接到每個可用的編碼器，從而限制了 CRTC-編碼器-連接器的可能組合。
- 平面（Plane）：平面並非硬體模塊，而是一個記憶體對象，包含一個緩衝區，供掃描輸出引擎（即 CRTC）從中讀取數據。存放幀緩衝區的平面稱為主平面（primary plane），每個 CRTC 都必須有一個關聯的主平面，因為它是 CRTC 確定影片模式——顯示解析度（寬度和高度）、像素大小、像素格式、刷新率等——的數據源。如果顯示控制器支持硬體光標疊加層，CRTC 還可能關聯光標平面（cursor plane）；如果能夠從額外的硬體疊加層掃描輸出並「實時」合成或混合最終發送到輸出設備的圖像，則還可能關聯次級平面（secondary plane）。

#### 原子顯示

近年來，人們一直在努力為 KMS API 的某些常規操作（特別是模式設置和頁面翻轉操作）引入原子性。這一增強後的 KMS API 被稱為原子顯示（Atomic Display），曾用名包括原子模式設置（atomic mode-setting）和原子頁面翻轉（atomic或nuclear pageflip）。

原子模式設置的目的是：在存在多種限制的複雜配置中，通過避免那些可能導致影片狀態不一致或無效的中間步驟，來確保模式的正確變更；同時，當某個失敗的模式設置過程需要回滾（rollback）時，它也能避免系統進入危險的影片狀態。原子模式設置通過提供模式測試能力，允許用戶提前判斷某特定模式配置是否合適。當一個原子模式經過測試並確認有效後，就可以通過一個不可分割的（原子）提交操作來應用它。測試和提交操作均由同一個新增的 ioctl 通過不同的標誌位提供。

另一方面，原子頁面翻轉允許在同一個輸出上更新多個平面（例如主平面、光標平面，以及可能的某些疊加層或次級平面），並使所有這些更新在同一個 VBLANK 間隔內保持同步，從而確保顯示正常、無撕裂。這一需求對於傾向於使用多個平面/疊加層來節省電量的移動和嵌入式顯示控制器尤為重要。

新的原子 API 構建在舊的 KMS API 之上。它使用相同的模型和對象（CRTC、編碼器、連接器、平面等），但可修改的對象屬性數量不斷增加。原子操作流程基於修改相關屬性，以構建我們想要測試或提交的狀態。需要修改哪些屬性取決於我們是要進行模式設置（主要涉及 CRTC、編碼器和連接器的屬性）還是頁面翻轉（通常涉及平面的屬性）。兩種場景使用同一個 ioctl，區別在於傳遞給它的屬性列表不同。

### 渲染節點

在原始的 DRM API 中，DRM 設備 `/dev/dri/cardX` 同時用於特權操作（模式設置、其他顯示控制）和非特權操作（渲染、GPGPU 計算）。出於安全原因，打開關聯的 DRM 設備文件需要「等同於 root 權限」的特殊特權。這導致了一種架構：只有某些可靠的使用者空間程序（如 X Server、圖形合成器等）擁有 DRM API 的完整訪問權限，包括模式設置 API 等特權部分。其他想要進行渲染或執行 GPGPU 計算的使用者空間應用程式，必須通過使用特殊的認證接口，由 DRM 設備的擁有者（即「DRM 主控」）授予權限。隨後，被認證的應用程式可以使用受限版本的 DRM API 進行渲染或計算，但無權執行特權操作。這種設計帶來了一個嚴重的限制：系統中必須始終有一個正在運行的圖形伺服器（X Server、Wayland 合成器等）作為 DRM 設備的 DRM 主控，以便其他使用者空間程序能夠被授予使用該設備的權限——即使在完全不涉及圖形顯示的場景（如純 GPGPU 計算）中也是如此。

「渲染節點」的概念試圖通過將 DRM 使用者空間 API 拆分為兩個接口——一個特權接口和一個非特權接口——並為每個接口使用獨立的設備文件（或稱「節點」）來解決上述場景。對於找到的每一個 GPU，其對應的 DRM 驅動——如果支持渲染節點特性——除了創建主節點 `/dev/dri/cardX` 之外，還會創建一個設備文件 `/dev/dri/renderDX`，稱為渲染節點。使用直接渲染模型的客戶端，以及希望利用 GPU 計算能力的應用程式，只需打開任意現有的渲染節點，並使用這些節點所支持的 DRM API 受限子集來調度 GPU 操作，即可無需額外特權完成工作——前提是它們擁有打開該設備文件的文件系統權限。顯示伺服器、合成器以及任何其他需要模式設置 API 或任何特權操作的程序，則必須打開標準的、授予完整 DRM API 訪問權限的主節點，並照常使用它。渲染節點明確禁止使用 GEM flink 操作，以防止通過不安全的 GEM 全局名稱進行緩衝區共享；只能使用 PRIME（DMA-BUF）文件描述符與另一個客戶端（包括圖形伺服器）共享緩衝區。

## 外部連結

- DRM 主頁
- Linux GPU 驅動程序開發人員指南 （以前稱為Linux DRM 開發人員指南）
- 嵌入式 Linux 會議 2013 - Ja8fM7rTae4