驅動程式中的通訊協定

什麼是通訊協定?

通訊協定是嚴格的介面定義。

乙太網路驅動程式庫發布的介面符合 ZX_PROTOCOL_ETHERNET_IMPL。 也就是說,它必須提供資料結構 (在本例中為 ethernet_impl_protocol_ops_t) 中定義的一組函式。

實作通訊協定的所有裝置都會使用這些函式,例如所有乙太網路裝置都必須提供函式,用於查詢介面的 MAC 位址。

當然,其他通訊協定必須提供的函式會有不同的需求。舉例來說,區塊裝置會提供 fuchsia.hardware.block.volume.Service FIDL 服務,其 volume 通訊協定 (fuchsia.storage.block.Block) 包含可傳回裝置大小 (以區塊為單位) 的方法,以及用於 I/O 的開放式工作階段。

在許多情況下,通訊協定會利用介面的通用實作項目,讓驅動程式更簡單。舉例來說,「ethernet」驅動程式庫會實作乙太網路介面,並繫結至實作 Ethermac 通訊協定的裝置。部分通訊協定會使用共用記憶體和非 RPC 信號,以提高效率、縮短延遲時間,並提升處理量。

類別代表裝置實作介面或通訊協定的承諾。 裝置位於裝置檔案系統的拓撲路徑下,例如 /sys/platform/pci/00:02:00/e1000。如果是特定類別,也會以別名形式顯示在 /dev/class/CLASSNAME/... 下方。e1000 驅動程式庫會實作 Ethermac 介面,因此也會顯示在 /dev/class/ethermac/000。類別目錄中的名稱不重複但沒有意義,且是視需要指派。

通訊協定範例:

  • PCI 根通訊協定 (ZX_PROTOCOL_PCIROOT),
  • PCI 裝置通訊協定 (ZX_PROTOCOL_PCI),以及
  • 乙太網路實作通訊協定 (ZX_PROTOCOL_ETHERNET_IMPL)。

方括號中的名稱是與通訊協定對應的 C 語言常數,可供參考。

依附平台與獨立平台

如上所述,ZX_PROTOCOL_ETHERNET_IMPL「接近」用戶端使用的函式,但少了一個步驟。這是因為在用戶端和驅動程式庫之間,還有一個通訊協定 ZX_PROTOCOL_ETHERNET。這項額外通訊協定可處理所有乙太網路驅動程式的常見功能,避免程式碼重複。這類功能包括緩衝區管理、狀態報告和管理功能。

這實際上是「平台相關」與「平台無關」的解除耦合;通用程式碼存在於平台無關部分 (一次),而驅動程式庫專屬程式碼則實作於平台相關部分。

這種架構會在多個位置重複出現,例如顯示控制器、I2C 匯流排和序列驅動程式。

程序 / 通訊協定對應

為求簡單,我們並未討論與驅動程式相關的程序分離。如要瞭解這些問題,請看看其他作業系統如何處理, 並與 Fuchsia 的做法比較。

在 Linux 等單一核心中,許多驅動程式都是在核心內實作。也就是說,這些執行緒共用相同的位址空間,且實際上位於同一個「程序」中。

這種做法的主要問題是故障隔離 / 利用。不良的驅動程式庫可能會導致整個核心故障,因為這類驅動程式與核心位於相同的位址空間,因此具有所有核心記憶體和資源的存取權。遭駭的驅動程式庫也會造成安全威脅,原因相同。

另一種極端做法是將每個驅動程式庫服務都放入自己的程序中,某些微核心作業系統會採用這種做法。主要缺點是,如果一個驅動程式庫依賴另一個驅動程式庫的服務,核心必須在兩個驅動程式庫程式程序之間執行至少一個內容切換作業 (如果不是資料轉移作業)。微核心作業系統通常會設計成快速執行這類作業,但頻率過高並不可取。

Fuchsia 採用的方法是以驅動程式代管程序的概念為基礎。驅動程式代管程序是包含通訊協定堆疊的程序,也就是一或多個共同運作的通訊協定。驅動程式代管程序會從 ELF 共用程式庫 (稱為動態共用物件或 DSO) 載入驅動程式。

通訊協定堆疊可有效為裝置建立完整的「驅動程式庫」,其中包含平台相關和平台無關的元件,並位於獨立的程序容器中。

如要進一步瞭解,請參閱 Fuchsia 指令列提供的 driver dump 指令。這個工具會顯示裝置樹狀結構,以及程序 ID、DSO 名稱和其他實用資訊。

以下是經過大幅編輯的版本,只顯示 PCI 乙太網路驅動程式庫部分:

1. [root]
2.    [sys]
3.       <sys> pid=1416 /boot/driver/bus-acpi.so
4.          [acpi] pid=1416 /boot/driver/bus-acpi.so
5.          [pci] pid=1416 /boot/driver/bus-acpi.so
            ...
6.             [00:02:00] pid=1416 /boot/driver/bus-pci.so
7.                <00:02:00> pid=2052 /boot/driver/bus-pci.proxy.so
8.                   [e1000] pid=2052 /boot/driver/e1000.so
9.                      [ethernet] pid=2052 /boot/driver/ethernet.so

從上述內容可以看出,程序 ID 1416 (第 3 到 6 行) 是進階設定和電源介面 (ACPI) 驅動程式庫,由 DSO bus-acpi.so 實作。

在主要列舉期間,ACPI DSO 偵測到 PCI 匯流排。 這導致發布了含有 ZX_PROTOCOL_PCI_ROOT 的父項 (第 5 行,導致出現 [pci] 項目),然後導致驅動程式代管程序載入 bus-pci.so DSO 並繫結至該 DSO。這個 DSO 就是我們在上述討論中提到的「基本 PCI 驅動程式庫」。

在繫結期間,基礎 PCI 驅動程式庫列舉了 PCI 匯流排,並找到乙太網路卡 (第 6 行偵測到匯流排 0、裝置 2、函式 0,顯示為 [00:02:00])。(當然,系統也找到許多其他裝置,但為了簡化,我們已將這些裝置從上述清單中移除)。

偵測到這個裝置後,基礎 PCI 驅動程式庫會發布新的父項 ZX_PROTOCOL_PCI,以及裝置的 VID 和 DID。此外,系統也建立新的驅動程式代管程序 (程序 ID 2052),並載入 bus-pci.proxy.so DSO (第 7 行)。這個 Proxy 可做為新驅動程式代管程序 (PID 2052) 與基本 PCI 驅動程式庫 (PID 1416) 之間的介面。

因此決定將裝置驅動程式庫「切斷」到自己的程序中,新的驅動程式代管程序和基本 PCI 驅動程式庫現在位於兩個不同的程序中。

新的驅動程式代管程序 2052 接著會尋找相符的子項 (第 8 行的 e1000.so DSO;由於該子項具有 ZX_PROTOCOL_PCI 和正確的 VID 和 DID,因此視為相符)。該 DSO 會發布 ZX_PROTOCOL_ETHERNET_IMPL,並繫結至相符的子項 (第 9 行的 ethernet.so DSO;由於具有 ZX_PROTOCOL_ETHERNET_IMPL 通訊協定,因此視為相符)。

這個鏈結未顯示的是,最終 DSO (ethernet.so) 會發布 ZX_PROTOCOL_ETHERNET,這是用戶端可使用的部分,因此當然不會再涉及「裝置」繫結。

驅動程式架構第 2 版 (DFv2)

如果啟用驅動程式庫架構第 2 版,driver dump 會顯示略有不同的樹狀結構。

$ driver dump
[root] pid=4766 fuchsia-boot:///#meta/platform-bus.cm
   [sys] pid=4766
      [platform] pid=4766
         [pt] pid=4766 fuchsia-boot:///#meta/platform-bus-x86.cm
            [acpi] pid=4766
               [acpi-pwrbtn] pid=4766 fuchsia-boot:///#meta/hid.cm
               ...
            [PCI0] pid=4766 fuchsia-boot:///#meta/bus-pci.cm
               [bus] pid=4766
                 ...
                 [00_04_0] pid=4766 fuchsia-boot:///#meta/virtio_ethernet.cm
                    [virtio-net] pid=4766 fuchsia-boot:///#meta/netdevice-migration.cm
                       [netdevice-migration] pid=4766 fuchsia-boot:///#meta/network-device.cm
                          [network-device] pid=4766
        ...

請務必注意,節點 (在 DFv2 中,裝置稱為節點) 不會與 .so 檔案建立關聯。而是附加至特定節點的驅動程式庫元件資訊清單網址。