RFC-0126:驅動程式執行階段

RFC-0126:驅動程式執行階段
狀態已接受
區域
  • 裝置
說明

指定可在內運作的驅動程式新執行階段。

變更
  • 564883
作者
審查人員
提交日期 (年/月)2021-08-04
審查日期 (年/月)2021-09-21

摘要

這個 RFC 會制定設計中,同一程序內的驅動程式會如何彼此通訊的設計。驅動程式會透過 zircon 核心模型的處理中執行階段進行通訊。執行階段會提供與 zircon 管道和通訊埠類似的基本功能,並且會在這個執行階段之上建構新的 FIDL 傳輸方式,因此效能優於透過 zircon 管道和通訊埠所提供的效能。這個新的執行階段和 FIDL 傳輸機制將取代目前駕駛人使用的現有 banjo 執行階段。

未在同一個程序中共置的驅動程式之間通訊時,將繼續使用以 Zircon 管道為基礎的 FIDL。為驅動程式庫進行基礎傳輸提供公開透明的說明,不在本提案範圍內,我們會在日後的提案中介紹。

RFC 也會為執行緒模型驅動程式建立一組規則,以便有效率地共用執行緒。無論是來自處理中的驅動程式,以及來自相同執行緒程序外的驅動程式,都將有機會提供。

提振精神

現有的驅動程式庫執行階段已可解決建立時遇到的問題。我們現在還沒有 FIDL,在 C 中幾乎人都編寫驅動程式,而 zircon 核心尚未經過妥善最佳化。因此,用於相互通訊的介面驅動程式,一開始是具有相關類型清除內容指標的函式資料表。在 C++ 驅動程式更常編寫的當下,FIDL 已建立完成,但由於繫結實作作業承受大量的負擔,因此不屬於跨驅動程式庫程式的通訊工作中。為了改善 C++ 驅動程式人體工學,我們正針對 C 通訊協定定義手動產生包裝函式。為減少需要維護 C 通訊協定定義和 C++ 包裝函式的手動作業,我們建立新的 IDL (其語法與當時的 FIDL 大致相同),建立這個 IDL 可以從常見的可靠來源自動產生 C 和 C++ 程式碼。後來被稱為斑鳩琴雖然 banjo 已做了一些初步投資,但與維護模式相比,這種模式快速進入維護模式,而近幾年的改善幅度與 FIDL 相近而得。Banjo 已不再需要滿足目前的要求,因此必須重新構思驅動程式庫執行階段。

此設計以幾個不同的問題為動力,並希望一次解決這些問題。

穩定 ABI

驅動程式庫程式架構正在解決的最重大問題,就是啟用穩定的驅動程式庫 SDK。這是平台層級目標,這將有助於 Fuchsia 達成廣泛的硬體支援,並確保我們符合更新 OS 各個部分的願景,而不影響各項功能。Banjo 是目前的跨驅動程式庫通訊解決方案,在建構時並未考慮 API 和 ABI 的進化。而會透過最佳化的方式,簡化作業,降低負擔。因此,在不中斷所有依附的用戶端和伺服器的情況下,目前沒有任何理想的機制可以修改 banjo 程式庫。這會使我們難以實現我們的平台發展,同時達成目標。

執行緒安全

如今,在編寫驅動程式庫時,要實作斑點通訊協定非常困難,且無需在驅動程式中產生專用執行緒來處理要求。這是因為在通訊協定中叫用方法時,採用這些通訊協定的驅動程式沒有任何規則。處理來電時,您必須確保邏輯:

  • 用戶端可能會同時從多個執行緒呼叫,因此處理同步問題。
  • 處理重複作業。

處理第一個問題的方法顯而易見,就是啟用鎖定。然而,單純取得鎖定很容易導致死結,因為驅動程式可能會回呼到另一個驅動程式庫,而在同一個堆疊框架中,系統可能會再次呼叫來嘗試重新取得相同鎖定。目前驅動程式中使用的所有鎖定實作都不安全,而且我們不希望開始使用遞迴鎖定,但原因為替代項目一節。

因此,如要正確處理這個問題,目前唯一的方法是將工作推送到佇列,然後稍後再服務該佇列。由於目前的驅動程式庫執行階段沒有任何機制可安排稍後執行工作,因此驅動程式庫必須建立自己的執行緒以提供這個佇列。

這會造成問題,因為這項設定會破壞在相同程序內配置驅動程式的多數預期效能優勢。此外,在導入驅動程式庫程式的複雜度方面,其稅金也十分龐大。系統中的許多驅動程式都不會選擇將工作推送到佇列,因此很可能會避免使用一個新的用戶端,以避免以無可避免的方式將系統鎖死。我們有歷史上發生的這些錯誤不清楚這些驅動程式,而且只有在極少發生錯誤狀況時才會重複出現,因此會以不穩定的方式顯示。

樣板序列化

導致執行新執行階段的另一個問題是,缺乏與非驅動程式的通訊缺乏完整性。驅動程式庫實作 FIDL 服務通常是對非驅動程式庫程式元件公開,並且透過將要求 (通常只有有限的翻譯) 轉送給其他驅動程式庫以處理要求。這項程序與樣板程式碼相差無幾,雖然類型 (結構、列舉等) 的定義,甚至可在 zircon 管道傳輸 FIDL 與班約傳輸 FIDL 之間共用通訊協定,因此產生的類型完全不一致。因此,驅動程式庫作者必須實作邏輯,在一組產生的類型之間進行序列化。

這個特定問題經常被引用為現今編寫驅動程式庫時最大的人體工學問題之一。以往,問題在於最近在不同 FIDL 傳輸之間共用類型,而斑鳩琴本身也最近變成 FIDL 傳輸方式,而這個問題更糟。先前,這些類型的定義很容易無法相互對應,進而造成細微錯誤。如果手動序列化邏輯沒有在定義變更時正確更新,則可能仍會發生這些錯誤。

相關人員

講師:abarth@google.com

審查人員:abarth@google.com (FEC 成員)、abdulla@google.com (FDF)、yifeit@google.com (FIDL)、eieio@google.com (scheduling/Zircon)

顧問:駕駛團隊、網路團隊、儲存空間團隊、核心團隊、FIDL 團隊和效能團隊的成員。

社交化:這個 RFC 的草稿已傳送至 FEC 討論郵寄清單,以便建立註解。本設計中,先前採用的概念是圍繞驅動程式庫架構、FIDL 和 Zircon 團隊。

設計

具體而言,設計將會透過一組新的原始物件實現,以及運用這些基元的新 FIDL 傳輸方式。在深入探討設計之前,我們首先會探討在進行此設計時所需的其他部分要求。

相關規定

效能

新驅動程式庫執行階段的高階目標之一,就是利用驅動程式庫執行階段達到更高的效能。驅動程式的效能需求差異很大,但大致來說,我們的目標是針對以下指標進行最佳化:

  • 總處理量高
  • 每秒高 I/O 作業數 (IOPS)
  • 低延遲
  • 低 CPU 使用率

舉例來說,我們希望盡可能提高 NVMe SSD 的處理量,或是確保發生輸入事件與收到事件的軟體之間的延遲時間降至最低。儘管在某種程度上,這些工作需要的系統除了提供驅動程式庫架構以外,還需要更多配合才能達到所需效能等級,但我們想確保驅動程式庫執行階段不會成為瓶頸。

大部分設計著重於最佳化,讓我們能透過以 Zircon 管道為基礎的 FIDL 能達成的駕駛人溝通,改善其跨驅動程式通訊的效能。如果我們無法在上述所有指標上超越以 Zircon 管道為基礎的 FIDL 效能,這個指標可能就不適合取代目前的斑鳩琴執行階段。想要超越 Zircon 頻道 FIDL 的需求,我們會在後續章節中討論。

駕駛員人體工學

新驅動程式庫執行階段的另一個高階目標,是確保驅動程式仍能輕鬆編寫。如果未考慮駕駛人受到設計決策的影響,盡可能提高成效。為實現目標,作者提供選項,讓他們在人體工學或零負擔之間自由選擇。此外,想要將驅動程式庫從簡單的實作轉換為更多效能,而無需重新編寫,也是理想的選擇。

安全性與彈性

同一程序中的驅動程式會有相同的安全界線。這是因為儘管所有嘗試隔離驅動程式,我們還是無法實際上拒絕其存取共用程序中其他驅動程式的記憶體區域。這麼做是刻意設計,因為共用位址空間是主機代管的其中一項關鍵優勢,我們可以利用這些空間來改善效能,而這個空間無法帶來其他管道提供的效能。因此,我們必須假設任何具備合理能力的惡意實體,只要取得驅動程式庫程式主機中其中一個驅動程式庫的控制權,就能存取同一程序中所有驅動程式的所有功能。因此,為了提供額外的安全性優勢,我們不需要考慮在驅動程式庫執行階段層中實作安全性檢查。

然而,這並不代表我們不該採用許多通常用於提供安全保證的機制,因為這些機制可能會提高個別驅動程式的彈性。舉例來說,我們不會盲目假設指標中的指標指向緩衝區內的資料,但我們還是能驗證是否如此。這有助於減少因錯誤而產生的損壞情形,進而幫助問題根源。換句話說,防範錯誤仍很實用。設計的其餘部分應秉持這一點。

零複製選項

即使是在最佳情況下,以管道為基礎的 fidl 也會產生至少 2 個副本。一個從使用者空間複製到核心的一個副本 (透過 zx_channel_write),再從核心複製到使用者空間 (透過 zx_channel_read)。一般來說,額外的副本會發生在用戶端執行線性步驟,並選擇在伺服器上以非同步的方式處理要求,或選擇在伺服器內以慣用語言處理要求總數,或將產生的資料全部移至慣用語言 4 類型中。由於使用新驅動程式庫執行階段的驅動程式會屬於同一程序,因此會處於位址空間,因此,最佳案例可能會需要 0 個複本,而人體工學案例也只能產生 1 個副本。

在驅動程式之間傳遞的大多數資料,與以管道為基礎的 FIDL 相似,都應控管資料結構,而資料平面資料則儲存在 VMO 中。這有助於我們減少成本最高的複本。不過,控制資料結構可能會逐漸變大,在特定情況下達到數 KB 數 (例如網路封包批次要求)。此外,在數個驅動程式庫堆疊中,資料通常未更動,且會經過多個層。我們可以捨棄這些副本事實上,在目前以 banjo 為基礎的執行階段中,我們努力達成零副本目標,但要達到這個目標,將會造成嚴重問題,也容易出現錯誤。這是導致我們無法採納 Rust 驅動程式庫程式的許多原因之一。

值得注意的是,FIDL 中的副本通常不會被視為效能瓶頸,因為系統是系統呼叫的負荷量主。也就是說,在驅動程式庫執行階段中,我們會避免系統呼叫,因此複製作業預計將成為在驅動程式之間傳送訊息的時間相當可觀。

從效能的角度來看,這類副本不一定會導致系統現在出現有意義的瓶頸。不過,這些執行個體可能會對 CPU 使用率造成可評估,但因此,提供觸及零副本的功能就很實用。

推送與提取

Zircon 頻道採用提取式機制。Userspace 會在頻道上註冊信號,以便瞭解何時需要讀取資料;收到通知後,就會從管道讀取到該通道提供的緩衝區。FIDL 繫結通常會反轉這個機制來改為推送。註冊回呼,且會在準備就緒時觸發回呼。

我們擁有大量的彈性,可讓您改用完全推送式機制,或是繼續模擬 zircon 管道機制。對一般使用者而言,推送內容可更加符合人體工學,而且可能更具效能,因為提取模型收到訊號後,需要重新進入執行階段。在選擇更高效能的其他系統中 (例如 Windows IOCPLinux io_uring),為了提升效能,系統通常會預先註冊緩衝區,方法是將此多餘的項目移除至核心並刪除副本。以處理中的執行階段來說,進入執行階段的價格很低,實際上不需要預先註冊緩衝區達成零複製。因此,與 zircon 管道和 zircon 管道的差異在 API 中推出差異不一定是成功。此外,由於語言極度仰賴提取式機制,如果我們選擇從傳輸層級轉出,就可能對其模型造成阻礙。

基本

如先前所述,新版設計將需要使用新的原始物件,才能建構新的 FIDL 傳輸。基元本身主要是在先由 zircon 核心提供的原始元件建立模型。

原始的 API/ABI 將以 C 為基礎,類似於現有的 libdriver API。我們將按語言包裝函式,提供更多慣用用途。雖然透過 FIDL 定義 API 可能可行,就像在 FIDL 中定義 zircon syscall 介面的方式一樣,由於整體 API 應該維持相當精簡,因此不太值得費力。

競技場

第一個原始版本是競技場為減少副本數量,我們需要一種方法,確保與要求相關聯的資料生命週期均至少維持在要求待處理狀態。提供給傳輸的資料必需由緩衝區支援。在以管道為基礎的 FIDL 繫結中,傳入訊息資料通常會從堆疊上啟動,而且如果要求必須以非同步方式回覆,則可視需要移至堆積分配範圍。FIDL 繫結可能會直接讀取至堆積記憶體,但在寫入時不會發生。相反地,如果 Google 是由執行階段提供的執行階段資料傳回資料,則可將該要求的生命週期綁定至競技場。我們也可以針對競技場啟用更多分配配額,並確保只要要求就能繼續保留這些配額。處理要求時,最好將要求轉送至其他驅動程式庫,或向下游驅動程式庫發出要求;在這種情況下,可以重複利用相同的領域,並與新的要求一起傳遞。也許您可以透過這類配置,讓許多驅動程式負責導覽,而不需要全域分配。在區塊堆疊等驅動程式庫堆疊中,系統通常會在同一個驅動程式庫主機中導覽 6 個以上的驅動程式,且通常會將相同的要求轉送到較低層級的驅動程式,並對資料進行最少的修改。

執行階段會提供 API,用於建立競技場、降低對競賽的參照、執行配置,以及檢查運動場所備份的指定記憶體區域是否適用。最後一個 API 是不尋常的,但這是為了保持 FIDL 傳輸實作中的完善程度。之後可以新增釋出個別分配的 API,但初始實作會略過這個配置。只有在刪除競技場本身時,才會釋出配置作業,移除競技場的所有參照都會發生。

有了執行階段,就能在必要時對這個競技場進行最佳化調整。舉例來說,基本實作項目可能會選擇將所有配置要求直接轉送至全球 Malloc,並等到物件刪除後再針對產生的每個分配作業免費提供。或者,您也可以做為單一分配的上升空間導入,其大小上限為因應每個要求分配的數量上限。我們可能需要進一步擴充競技介面,讓用戶端可以提示基礎競技場應採用的策略,因為相同的競技策略不一定能在所有驅動程式庫堆疊中達到最佳效果。

版本

第二個原始版本是管道原始版本。5 個 Zircon 頻道提供的介面幾乎相同。最大的差異包括:

  • 寫入 API 會包含競技物件,並增加其參考計數。
  • 寫入 API 需要所有傳入至此的緩衝區 (資料 + 帳號代碼資料表),皆須由該場館支援。在聯合發布作業中,執行階段會取得這些緩衝區 (以及競技遊戲支援的所有其他緩衝區) 的擁有權。
  • 讀取 API 會提供可讓呼叫端取得該參照擁有權的工作。
  • 讀取 API 會提供資料和處理常式資料表的緩衝區,而不是希望呼叫端提供複製到其中的緩衝區。

需要這些差異,才能允許以建構基礎的 FIDL 繫結達到零複製目標。

Zircon 頻道的相似之處包括:

  • 頻道是成對組合。
  • 管道可能無法重複,因為管道為雙向單一生產端單一消費者佇列。

調度工具

最後一個原始檔案類似於 zircon 連接埠物件。與其提供封鎖目前的執行緒的機制,而是提供透過回呼指出目前可讀取或關閉的註冊管道的機制。我們也將提供註冊驅動程式庫執行階段管道的機制。

驅動程式會建立多個調度工具,類似於建立多個通訊埠的功能。調度工具是實作下一節提及的執行緒模型的主要代理程式。建立調度工具時,必須指定調度工具應在哪一種執行緒模式下作業。

此外,您還能透過一個 API,從驅動程式庫執行階段調派程式物件接收 async_dispatcher_t*,以等待 Zircon 物件以及張貼工作。針對這個調度工具註冊的回呼,都會符合相同的執行緒保證,提供執行階段管道所需的連線。

範例

雖然我們不必為原始檔案指定確切的介面,但在評估設計時,建議您先查看吸管範例,瞭解可能的情況:

https://fuchsia-review.googlesource.com/c/fuchsia/+/549562

執行緒模型

除了共用相同的位址空間之外,主機配置驅動程式的第二大優勢是能夠做出比核心所給的更好的排程決策。我們的目標是利用驅動程式共用相同執行緒的功能,而不是為每個驅動程式庫提供自己的執行緒。當多個獨立程式碼共用同一個執行緒時,必須遵循某種合約以確保正確性。

執行緒模型會設定駕駛者可利用哪些規則和預期,確保其正確性。如果正確執行,執行緒模型可能會大幅簡化開發體驗。不想處理並行、同步和重新登錄的驅動程式開發人員應具備可忽略這些問題的機制,同時仍能編寫功能性程式碼。另一方面,執行緒模型必須提供足夠的自由度,讓駕駛人能夠達成本文前述的成效目標。

因此,我們提議的執行緒模型,提供下列兩種高階模式,讓駕駛人在以下環境中運作:

  • 並行進行同步。
  • 目前未同步。

不妨先釐清所用的字詞:

  • 並行:多項作業可能會彼此交錯。
  • 已同步:在任何時間點,驅動程式庫中都不會同時有兩個硬體執行緒。所有呼叫會依次排序。
  • 未同步處理:在任何特定時間內,多個硬體執行緒可能同時位於驅動程式庫中。我們無法保證來電的發生順序。

同步處理不代表工作只會固定至單一 Zircon 執行緒。只要執行階段能夠保證完成同步,即可將驅動程式庫免費遷移至其管理的任何執行緒。因此,在同步模式下使用執行緒本機儲存空間 (TLS) 並不安全。而是驅動程式庫執行階段可能會提供替代 API。建議您將同步模式檢視為「虛擬」執行緒或光纖。

不同步的模式可讓駕駛人享有最佳彈性。產生額外工作時,驅動程式庫可能會使用更精細的鎖定配置,進而提高效能。選擇採用這個模式的驅動程式較有可能會死結程序,因此在日後選擇使用這個模式的驅動程式時,我們可能會設下限制。

系統會在執行階段定義這些模式,並與先前所述的驅動程式庫執行階段分派器物件建立關聯。由於可能有多個調度工具,因此也可以在單一驅動程式庫中混用及比對這些模式,藉此管理並行需求。例如,您可以建立多個序列的同步調度工具來處理兩個無須彼此保持同步的硬體。在實際運作上,驅動程式預期會使用單一調度工具,並選擇利用特定語言結構,管理其他並行和同步處理需求。這是因為比起驅動程式庫程式執行階段,由於系統能更精細地追蹤依附元件,因此只要根據語言專屬結構 (例如 C++ 中的 fpromise::promisestd::future 的 R) 制定更明智的排程決策,以 C 編寫的驅動程式由於缺少任何一種標準並行管理基本功能或程式庫,因此最有可能使用驅動程式庫執行階段基元管理並行。

與驅動程式庫之間的溝通有何影響

由於上述基本功能並未提供驅動程式庫可以呼叫其他驅動程式庫的任何機制,因此所有呼叫都必須由執行階段中介。將驅動程式庫執行階段管道寫入時,我們可以改為在同一個堆疊頁框中,選擇呼叫其他驅動程式庫。執行階段會檢查管道的另一端已設定為使用哪個執行緒模式,判斷是否要呼叫頻道另一端的驅動程式是否擁有頻道另一端。如果未同步處理,則系統一律會在同一個堆疊頁框中呼叫其他驅動程式庫。如果調度工具會同步及並行,執行階段即可嘗試並取得調度工具的鎖定,如果成功,則在同一個堆疊頁框中呼叫驅動程式庫。如果失敗,可以將工作排入佇列,以便稍後處理。

這看似簡單的最佳化作業,但初步基準測試顯示,如果不需要返回非同步迴圈,就能大幅提升延遲和 CPU 使用率。

出租

依據上述所有模式,駕駛人都不得有重複作業。執行階段能夠協調驅動程式之間的所有互動,也讓我們無法判斷目前呼叫堆疊中的是否為輸入的驅動程式庫。如果已安裝應用程式,而不是直接呼叫驅動程式庫,執行階段會改為將工作排入佇列中,而該佇列在返回原始執行階段迴圈後,將由其他 Zircon 執行緒或相同執行緒提供服務。

區塊化

在之前討論的所有模式中,都有隱性要求:參與共用執行緒的駕駛者不會封鎖。可能因為某些正當原因而想要封鎖執行緒。為了允許這些用途,在建立調度工具時,除了設定調度工具要使用的執行緒模式之外,驅動程式庫還會指定是否需要在呼叫時封鎖。這可讓執行階段判斷必須建立的執行緒數量下限,以避免發生死結的風險。舉例來說,如果驅動程式庫建立 N 個調度工具 (全部可能會封鎖),執行階段就必須分配至少 N+1 執行緒來為各個調度工具提供服務。

系統一開始不支援在調度器中指定封鎖功能的功能,但系統並不會將其同步處理。不過,只要使用這項功能,我們可以重新評估這項決定。上述的簡易 N+1 執行緒策略可能因為執行緒數量不明,進入該驅動程式庫,因而無法執行所有封鎖。

工作優先順序 / 執行緒設定檔

某些作業的優先順序高於其他作業。雖然能夠在工作層級指派優先順序及繼承優先順序,但這項工作並不容易。我們會以調度工具層級的概念,而非嘗試在 zircon 核心以外之處進行創新。建立調度工具時可以指定設定檔。雖然執行階段無法保證所有回呼都會在設定透過該設定檔執行的 Zircon 執行緒上發生,但針對每個提供的 Zircon 執行緒設定檔,至少會產生一個 Zircon 執行緒。

這裡有很多需要瞭解的執行階段如何以最佳方式處理執行緒設定檔,而且可能需要與 zircon 排程器團隊和驅動程式庫程式作者進行某種程度的合作,才能提供符合用途執行緒設定檔所需的解決方案。

FIDL 繫結

與其完全重新構思 FIDL,繫結目標為鎖定驅動程式庫執行階段傳輸的目標,能夠針對驅動程式庫執行階段提供的好處,盡可能減少使用者端變更的必要項目組合。預期的情況是,目前指定 Zircon 管道的每個語言繫結都會建立分支,而寫入驅動程式也支援這些管道,以提供同樣以驅動程式庫執行階段傳輸為目標的變化版本。初次實作時,我們將使用 LLCPP 繫結,因為 C++ 是編寫驅動程式的主要語言,日後可能會保持最完善支援的語言。本節的其餘部分會在做出 FIDL 繫結的特定評論時,假設 LLCPP 繫結。

非通訊協定類型應完整使用,不必修改。這點很重要,因為能夠在多個傳輸方式之間共用產生的類型,是重要的屬性驅動者需求。

針對通訊協定產生的類別和結構 (也稱為訊息層),系統一律會重新產生。我們或許可以將這些範本範本化,以有條件使用驅動程式庫執行階段 API,但我們需要針對每個通訊協定產生管道和驅動程式庫執行階段傳輸的支援。另一個建議選項是建立最低類別,針對基礎傳輸建立抽像管道讀取和寫入 API。可惜的是,這項設計並不適合我們的設計,因為這類領域在以管道為基礎的 FIDL 中會是尷尬的,而讀取和寫入 API 的緩衝區擁有權關係是這兩種傳輸方式之間的一致性。在維護不同繫結的團隊方面,有一定程度的合作應決定程式碼重複使用、差異及繫結的適當等級。

個別要求的領域

先前章節所述,驅動程式庫執行階段會提供區域物件,該物件會與透過管道傳送的訊息一起傳遞。FIDL 繫結應利用這個競技場達成零複製作業。

使用者可選擇不要利用這個競技場建立要傳送給其他駕駛人的結構。這沒問題,繫結可確保類型將類型複製到執行階段提供競技器支援的緩衝區。如果您未指定競技場,提高人體工學的效率便一定會產生副本。我們選用的唯一設計方法,是明確指出系統正在執行複製作業,確保複製作業不會發生忽略狀況。

使用者可能偏好在堆疊上分配所有物件,希望它們會完全在與呼叫本身相同的堆疊框架內使用。視執行緒限製而定,在某些情況下,可能會發生這種情形。事實上,這也是現今駕駛會用班鳩琴而當做的事。雖然這是我們可能支援的類型,但當我們判斷必須將類型移至堆積內時,自行將類型移至堆積中所需的類型 (透過與接收端中的 ToAsync() 呼叫類似的情形) 即可,但請避免支援此功能。而是應使用相同邏輯,將訊息複製到支援備份緩衝區中 (如上所述)。日後如果有強勁的使用案例,我們可能可以重新審視這個提案。

訊息驗證

目前,當驅動程式互相通訊時,我們會執行動作 (例如列舉驗證及 zircon 處理驗證等動作),驗證訊息是否符合其所用介面所指定的必要合約。在新的執行階段中,我們可能會開始執行部分驗證,但需要依據個別驗證功能來決定要執行哪個驗證。舉例來說,列舉驗證可能成本低廉,且不太可能導致任何可評估的效能損失。另一方面,如果需要透過核心來回往返,zircon 管道驗證的成本可能相當高昂,因此也會考慮如何移除。此外,我們會選擇避免移除權限管道,因為同樣需要來回瀏覽核心。我們做出這些決定的程序需要進行基準測試,以決定我們選擇執行的驗證作業是否仍為實作項目。

我們能做這些選擇的原因是,同一個驅動程式庫主機中的驅動程式會共用相同的安全邊界。這些驗證步驟只用來增強我們的韌性。

要求轉送

駕駛人接收要求、進行最少的修改,並轉送至較低層級驅動程式庫的常見作業。以符合人體工學的方式能夠壓低成本 是我們的目標此外,若將訊息轉寄到驅動程式庫管道的兩端管道,很可能是有用的活動,因為驅動程式庫可能必須在其他位置管理並行和公園資料。雖然這可能會將其推送到佇列,但使用驅動程式庫執行階段管道做為佇列,可能會簡化以較少程式碼達成相同效果的方法。

取消傳輸層級

當駕駛因新條件或規定而必須取消部分未完成的工作時,在目前都沒有在孟買和以 FIDL 為基礎的 FIDL 中,駕駛必須以統一的方式取消工作。在以管道為基礎的 FIDL 中,許多繫結會讓您忽略最後的回覆。視用途而定,這或許足夠,因為這種方法可以處理與要求相關聯的狀態,而這可能是唯一的目標。不過,有時必須傳播取消作業,讓下游驅動程式庫瞭解取消作業。發生這種情況時,您可以在通訊協定層建構支援,或直接關閉管道組合的用戶端端。第二種解決方案只有在需要取消「所有」未完成的交易,且不需要與伺服器同步時 (因為您無法取得確認),才適用第二種解決方案。

驅動程式執行階段管道可提供與 zircon 通道傳輸所提供的相同等級的支援。不過,在傳輸層支援交易層級取消傳播作業是很好的想法,但由於 FIDL 和傳輸作業之間需要權衡,因此相當困難。傳輸作業將不知道交易 ID,因為交易 ID 是以管道原始為基礎建構的 FIDL 概念。此外,對 zircon 管道的運作方式可能也不太值得,因為這麼做可能會導致開發人員混淆,而必須處理這兩種傳輸。也讓層在實作多個傳輸時更加困難。

實作

這項設計的實作主要分為三個階段:

  1. 實作驅動程式代管程序執行階段 API
  2. 實作以驅動程式代管程序執行階段 API 為基礎建構的 FIDL 繫結
  3. 將客戶從 Banjo 遷移

驅動程式主機執行階段 API

系統會在新的驅動程式代管程序中實作執行階段 API,用於做為元件執行的驅動程式。並非授予所有驅動程式使用 API 的能力,而是受到新的元件資訊清單欄位 (名為 driver_runtime) 和 colocate 欄位一併指定的限制。這個屬性可讓驅動程式庫執行元件瞭解是否應向驅動程式庫提供 API。屬於相同 Driver_host 的所有驅動程式都必須具備相同的屬性值。

除了上述設計中提及的基本功能之外,還需要新增支援功能,建立機制,讓新的驅動程式庫傳輸管道可在繫結機制上的驅動程式之間轉移。此外,還得實作一個輔助規則,用來描述繫結規則和節點屬性,讓驅動程式繫結至實作驅動程式庫傳輸 FIDL 服務的裝置。

將在隔離的 devmgr 中新增支援功能,方便我們產生在新執行元件中執行的驅動程式。這個環境將用於實作基本功能、編寫測試及執行效能基準。

執行階段 API 的初始實作著重於 MVP 加以統合,以便我們開始處理 FIDL 繫結。準備就緒後,您可以平行處理 FIDL 繫結作業,而執行階段 API 的實作可以進行最佳化。

FIDL 繫結

FIDL 繫結會寫入現有的 fidlgen_llcpp 程式碼集內。輸出驅動程式庫執行階段標頭時,系統會在標記後方設下限制。實作策略會採用與上述類似的策略,著重於先進行少部分變更來讓繫結生效,並透過新的驅動程式庫執行階段。作業開始後,我們會建立 Microbenchmark 來瞭解效能。然後,我們可以疊代繫結並進行最佳化,例如移除不必要的驗證步驟。

傳輸類型定義最終會新增至共用標頭中,但在工作完成後,會將這些標頭從現有 FIDL 標頭分割成指定標頭,我們將直接重新發出類型。這會導致所有嘗試使用 zircon 管道和驅動程式庫執行階段傳輸的驅動程式發生問題,但我們希望在該時間點將傳輸定義分割為共用的標頭了。

遷移

具體來說,遷移作業會相當困難。儘管如此,大多數驅動程式都位於 fuchsia.git 存放區中。比起嘗試一次將整個世界從 Banjo 移至新的執行階段,一次遷移單一驅動程式代管程序會比不容易。這項策略需要採取下列步驟:

  1. 移植驅動程式庫使用的 Banjo 通訊協定需要複製及修改,才能建立以驅動程式庫傳輸為目標的版本。
  2. 要移植的驅動程式將針對班機和驅動程式庫傳輸 FIDL 服務實作支援。
  3. 系統會為驅動程式建立新的元件資訊清單、繫結程式和建構目標,指出其指定新的執行階段。
  4. 針對驅動程式庫居住的每個電路板,在驅動程式代管程序中的所有驅動程式均轉移至其後,內含驅動程式庫的 Jamboard gni 檔案將更新為使用新版驅動程式庫,而非以班上為目標的舊驅動程式。
  5. 您可以刪除用於 Banjo 的驅動程式建構變數,如同驅動程式庫中使用或提供 Banjo 通訊協定的任何程式碼。
  6. 任何驅動程式不再使用 banjo 通訊協定後,系統可能會刪除該通訊協定。

對於包含在多個驅動程式庫主機中的驅動程式,但並非所有驅動程式庫均已攜碼轉移,您可能需要同時將兩個版本納入主面板。繫結規則和元件執行元件欄位可確保在適當的驅動程式庫主機中載入正確的驅動程式庫版本。此外,駕駛人也能輕鬆偵測自身是否繫結至公開了班機服務或驅動程式庫運輸 FIDL 服務的驅動程式庫。

許多團隊都會參與遷移作業,因為單一團隊無法自行遷移超過 300 個行李驅動程式。這是一份詳細的遷移文件,可在最少的協助下執行大多數遷移作業。此外,在預計執行遷移作業的團隊之前,我們需要提前提供充分的通知,以便他們做好規劃。

我們也需要決定相關條件,以整理通訊埠的驅動程式清單。舉例來說,我們可能希望盡可能減少產品版本中的重複驅動程式數量,並同時平行處理盡可能移植的驅動程式數量。系統會建立並主動管理程序,確保在整個過程中避免發生迴歸錯誤,並及時完成遷移作業。

評估主機位置

執行遷移作業的團隊,也應該重新評估其驅動程式是否需要與其他驅動程式一同上線。做為另一個成果,驅動程式庫程式架構團隊將合作出一份文件,協助駕駛者進行評估。團隊可能需要執行一些基準測試,以協助制定決策流程,並且建立支援來確保這類基準化效能同樣實用。

效能

許多先前在 RFC 中指定的設計點,都可能需要先進行基準測試和評估,我們才會將重點放在最高收益。雖然我們已經完成初步基準測試,以協助判斷想要進入的驅動程式庫執行階段方向,但仍需更嚴謹,確保所有最佳化項目都能發揮實用價值。

系統將建立 Microbenchmark,確保在傳輸層和 FIDL 繫結層級的效能都優於 Zircon 管道對應的效能。

此外,我們也必須建立更 e2e 導向的基準,確保更全面的程度,不會影響任何用來最佳化的核心指標:

  • 總處理量高
  • 每秒高 I/O 作業數 (IOPS)
  • 低延遲
  • 低 CPU 使用率

我們會利用最重要的用途判斷要將哪些驅動程式移往新執行階段,以便開始基準化結果。部分驅動程式庫堆疊範例可能包括:

  • NVMe SSD
  • 乙太網路 NIC
  • 螢幕觸控輸入
  • USB 音訊

確保我們能取得種類繁多的驅動程式庫用途,有助於我們建立信心,不會過度最佳化任何單一用途。

這些基準測試將在驅動程式庫介面層執行,而不是在較高層級的介面執行,且涵蓋所有層,通常包含相關裝置的技術堆疊。這是因為驅動程式通常在效能方面通常並非瓶頸,因此使用端對端基準要充分瞭解驅動程式庫效能,因此很難充分瞭解這些駕駛人的效能。我們正努力解決已知瓶頸,但必須確保駕駛人不會成為新的瓶頸。

初步基準測試結果

我們執行了廣泛的基準測試,以協助我們決定設計時的方向。我們採用現有的堆疊 (區塊和網路),並測試了下列情境:

  • 在所有 Banjo 呼叫中插入管道寫入及讀取呼叫 (未傳遞任何資料,無執行緒躍點)。
  • 延後所有 banjo 呼叫以非同步工作的形式在共用調度工具迴圈中執行 (未插入執行緒躍點)。
  • 延遲所有 banjo 呼叫,以非同步工作的形式為每個驅動程式庫非同步調度工具迴圈執行。

工作負載會針對這些經過修改的驅動程式堆疊執行,其佇列長度、作業大小、工作負載總大小以及讀取和寫入方式都不相同。

我們的基準測試是在採用 x64 減少的個別變體板的 NUC 執行。所有基準可預期,所有基準都會降低整體處理量、增加尾延遲及 CPU 使用率。

  • 由於 1 和 2 的佇列長度偏低,差異特別明顯。
  • 每個驅動程式庫執行緒的整體結果較差。
  • 在斑鳩琴呼叫中插入管道讀取和寫入並不會大幅影響處理量,但對尾延遲的影響最為明顯。
  • 視所有試用實驗的參數而定,CPU 使用率相對增加 50% 到 150%。絕對 CPU 使用率總是相當簡單 (10% 至 150%),因此相對增加也讓絕對 CPU 使用率大幅提升。

如要查看完整結果,請按這裡這裡

人體工學

先前在需求方面的章節所述,人體工學對於整體設計來說非常重要。我們想避免除了原本就必要的概念,更要說明什麼概念。驅動程式作者有必要瞭解 FIDL,因為以管道為基礎的 FIDL 是與非驅動程式庫元件互動的方式。因此,若能減少駕駛人與非驅動程式庫程式元件的互動所產生的跨驅動程式庫通訊差異量,將有助於減少新概念。Banjo 採用的技術完全不同,但以較複雜的方式取代這個技術時,看起來可能會像是減少人體工學,但以管道為基礎的 FIDL 應該能有效提高整體人體工學。儘管我們可以分享各種類型,長久以來的開發人員痛點也對這樣的期望更有信心。

此外,引進執行緒模型可望大幅簡化撰寫正確驅動程式的便利性,再次改善整體人體工學。

人體工學是最大的困擾之一,就是支援 C 驅動程式。由於 Fuchsia 平台不再有樹狀結構中的 C 驅動程式,所以我們不清楚這個提案對 C 驅動程式的影響。事實上,初始實作甚至不支援 C 驅動程式,因為 FIDL 目前會因 zircon 管道傳輸,而缺少適當的 C 繫結。這項設計使用的許多設計選擇都以主要用於 C 的系統為基礎,因此只要寫入的 C FIDL 繫結符合人體工學就能使用,那麼 C 驅動程式將不受影響。有一段時間我們未能針對這個前端提供足夠的回饋,這就表示一個風險。

在遷移程序中,我們會進行使用者研究,瞭解預期結果是否反映現實。

回溯相容性

我們會先實作這些變更,再透過 fuchsia SDK 匯出任何斑鳩琴介面。因此,我們沒有設下任何限制來確保回溯相容性。不過,由於我們需要進行個別遷移,因此我們可能需要確保某種程度的回溯相容性,因為同一驅動程式庫同時支援 Banjo 和驅動程式庫傳輸 FIDL。詳細內容請參閱遷移一節

安全性考量

本 RFC 中建議的設計不應變更系統的安全性架構。目前存在於相同程序的驅動程式將繼續採用此設定,因此安全性界線不應改變。不過,如同遷移至新驅動程式庫執行階段的預期結果,用戶端應該針對各介面「重新評估」,不論驅動程式是否與父項都位於相同的程序內。我們將撰寫說明文件,協助開發人員進行評估。

隱私權注意事項

這項設計預計不會對隱私權造成任何影響。

測試

並會透過不同機制測試設計的各個部分。驅動程式執行階段將透過對相等 zircon 基元的類似測試,進行概略的單元測試來測試。此外,也將利用隔離的 devmgr 和 CFv2 測試架構編寫整合測試。

系統會透過 GIDL 和整合測試來測試驅動程式傳輸的 FIDL 繫結,以確保正確性。整合測試一定需要針對每個繫結進行編寫,而將針對該繫結的 zircon 管道傳輸變化版本現有的整合測試,是最可能採取的路徑。這些整合測試可能也需要利用隔離的 devmgr 和 CFv2 測試架構。

遷移至新驅動程式庫執行階段的驅動程式可能需要為每個驅動程式進行測試計畫。隔離開發人員型測試應支援新的傳輸機制,而無需採取任何特殊行動。對於單元測試,可能需要編寫測試程式庫來提供驅動程式庫執行階段 API 的實作,而無需在驅動程式代管程序中執行測試。也可類似目前在單元測試中模擬 libdriver API 的方法。測試程式庫和 API 驅動程式代管程序實作之間的程式碼共用,將視為減少功能偏移。

說明文件

但需要新指南來說明如何編寫會使用驅動程式庫程式傳輸 FIDL 的驅動程式庫程式,以及如何建立用來提供服務的驅動程式庫。查詢必須依每個繫結而編寫,但由於我們一開始僅指定 llcpp,因此只需要編寫一組。在理想情況下,我們可以調整現有的 llcpp 指南,以減少製作指南所需的整體工作量。

此外,驅動程式的參考區段也也需要更新,加入執行階段 API 的相關資訊。此外,還要瞭解執行緒模型和如何編寫安全執行緒程式碼的相關資訊。

必須有一份最佳做法文件,協助駕駛人判斷是否要使用 FIDL 的驅動程式庫傳輸或 zircon 管道傳輸版本。

最後,如遷移一節所述,針對如何從 Banjo 遷移至驅動程式庫傳輸 FIDL 的說明文件。

缺點、替代方案和未知

線性

在實現零複製目標之前,我們可以避免 FIDL 今天執行的線性處理步驟。最重要的是,這麼做會違反結構配置方面違反現有 FIDL 合約的規定。避免線性化,這也表示我們不會執行解碼格式的轉換作業。

FIDL 之所以運作方式,其中一項原因就是允許解碼更加簡單。尤其要確保在一次傳遞中,所有位元組都會納入考量,且邊界會檢查所有參照位於訊息緩衝區內的資料。由於我們在安全性與彈性一節中討論了安全性疑慮,因此我們不會考慮到先前的疑慮。至於後者,我們需要確保所有資料緩衝區都指向競技場分配的記憶體。驅動程式執行階段會提供 API,讓我們可以執行這項檢查。

線性處理的其中一個好處,就是能夠壓縮緩衝區。由於訊息已由某個領域支援,因此轉移訊息的擁有權就和轉移遊戲的擁有權一樣簡單。且無法複製該訊息,不一定對顯而易見的影響。

線性的另一個優點是能簡化解碼。實際上,解碼訊息所需的額外複雜度,可能會比不執行資料副本就超過利息。這個時候必須謹慎衡量,確保略過線性處理絕對效果明顯。

最後,線性方式可藉由取得更近的本地性,改善快取效能。如果領域順利實作 (做為撞擊配置器),在公共區域仍應保持良好的區域,而在同一個堆疊框架內呼叫其他驅動程式,在時態性地位改善,應該在此處發生任何損失的機率不太可能造成問題。

雖然這個 RFC 並非建議略過線性的規劃,但實作需要的功能集,以便我們根據後續工作評估實作功能是否值得實作。

外洩的握柄

我們跳過線性的其中一個問題,是當 FIDL 訊息傳送者在接收方的聯集或資料表中使用不明的欄位 (可能會使用新版 FIDL 程式庫),可能會導致漏水問題。在 zircon 管道傳輸和建議的驅動程式庫程式執行階段傳輸中,接收器可以安全地忽略新欄位,並以其他方式瞭解訊息的其餘內容。不過,在 Zircon 管道變化版本中,帳號代碼會與資料分開儲存。也就是說,如果控點位於不明欄位,接收器仍會注意控點是否存在,並能關閉未使用的控點。如果完全略過編碼步驟,無論瞭解完整的訊息結構為何,我們都無法瞭解所有控制代碼。

解決這個問題的方法為部分編碼與解碼,而不會進行線性處理。更具體而言,系統會在編碼期間將控點截斷,並替換為偏移量資料表,而在接收時,帳號代碼會在解碼時移回物件中。與 Zircon 管道類似,可在解碼期間追蹤和關閉未使用的資料表項目。採用這種半途方式,我們應該就能利用略過線性步驟,創造最大效能優勢,同時避免潛在的陷阱。

電匯格式遷移

另一個問題是,在 FIDL 傳輸格式遷移期間,兩個對等互連驅動程式可能會使用不相容版面配置的物件。舉例來說,我們目前正中止 FIDL 信封的大小。就目前 IPC 案例而言,這個問題解決了,只要在兩種編碼線格式之間轉換轉換器之間加入轉換器即可。雖然我們可能會為非線性解碼格式編寫額外的轉換器,但這麼做會增加維護的負擔。

非 LLCPP 繫結

在本文撰寫時,只有一個 FIDL 繫結實作項目 LLCPP,可利用略過線性化步驟所產生的最佳化程序,以及處理可能從非線性化狀態進行的最佳化,以及處理可能從非線性化狀態進行解碼,因為這是唯一能自然理解傳輸格式方式的繫結。至於其他繫結,在接收端可以先執行將緩衝區線性調整的額外步驟,再繼續進行其他一般繫結特定解碼。此外,在傳送端,系統仍可以執行線性處理,但繫結只需小心避免將指標轉換成偏移。

透明傳輸

本文件所述的設計可讓他們選擇是否完全在駕駛者手中,針對特定通訊協定,使用 Zircon 管道傳輸 FIDL 的驅動程式庫程式傳輸版本。這項設計的初始版本選擇隱藏驅動程式庫的基礎傳輸功能,目標是改善整體驅動程式庫人體工學、減少學習的概念數量,並讓平台進一步控管驅動程式庫是否置於同一位置。經過多次辯論後,這項構想會大幅提高設計,並介紹許多需要解決的新問題,因此決定借助這個想法。舉例來說,需將訊息線性處理已成為動態決策,而不是在編譯期間已知的訊息。

如果我們決定嘗試向驅動程式庫公開基礎傳輸,便需要編寫後續的 RFC。

Operations

如果在傳輸層導入一個概念來追蹤邏輯運算,以便在堆疊中的各個驅動程式中瀏覽,我們或許能夠提供更完善的診斷資訊,並有機會做出更好的排程決策。如同本設計所述,與 zircon 管道式 FIDL 類似,轉換是目前的通訊協定層概念。本提案的早期版本考慮將配置器做為單一擁有權,讓我們可以使用分配器做為追蹤駕駛之間的作業,但利用可能欠缺的分配器和目前設計,改為提出拒絕配置器設計。重新審視傳輸中的第一類運算概念是未來可考慮的行為。

單件複製傳輸

如果我們判斷不應達到零副本,我們有許多選項可以嘗試替代設計。我們可以選擇移除區域,並改為要求預先註冊完成的緩衝區或環形,例如 Windows IOCPio_uring,甚至是 NVMe 設計。要求完成後,我們會將結果寫入預先註冊緩衝區/環,也可能會做為線性步驟的一部分。這類設計非常適合內建背壓。另一個選擇是保留競技場 但要一律執行線性處理如果這個 RFC 的審查人員認為有用,可以考慮進一步評估這些方法的優缺點。

運輸背壓

Zircon 管道有一個已知的問題,就是他們沒有任何背壓佈建。本文撰寫時有全域頻道訊息限制。如果達到上限,擁有管道的程序就會終止接收端。

由於我們要設計新的管道基本功能,因此在這裡可以選擇改進方向。由於此提案涉及很大,因此我們認為 我們應該棄用此提案。不過,這可能值得一探究竟,因為這個問題正由 FIDL 團隊積極調查。證明在驅動程式庫執行階段中嘗試新的做法,也許是瞭解所採取的方法是否值得在 zircon 系統呼叫介面中實作,其中須耗費略高一點的時間。

雙向頻道和活動

已建議 zircon 頻道的雙向特性是誤用。我們可以選擇不將驅動程式庫執行階段管道設為雙向,而是需要兩個聲道組合 (4 結束) 才能進行雙向通訊,與內建 golang 管道類似。以聯結方式來說,我們不支援在驅動程式庫執行階段傳輸期間的 Fidl 事件。相較於驅動程式庫程式執行階段傳輸支援的 FIDL 功能,系統可能盡可能更符合功能組合。除了減少使用者混淆之外,如果我們決定朝這個方向前進,將能更輕鬆地達成之後的不透明傳輸。

遞迴鎖定

除了不支援會自動排入佇列的工作還將重新進入驅動程式庫的存取權之外,我們可以選擇只在駕駛人選用的情況下允許該工作。為了正確處理這種情況,駕駛必須採取下列任一做法:

  1. 將工作本身排入佇列,並排定稍後處理佇列的狀態。
  2. 使用遞迴鎖定。

第一個選項看來 RFC 的設計設計似乎沒有任何好處。第二個選項需要使用我們最初選擇的鎖定,避免在我們的平台中實作支援功能。這種遞迴鎖定的原因是無法確保獲取鎖定的順序正確性。如果透過多項不同的訂單取得鎖定,程式碼中可能會發生死結。因此,最簡單的方法,就是確保我們絕不會重新進入驅動程式庫,而不是危及這個問題。

Rust 驅動程式支援

我們不打算在本提案中支援 Rust 驅動程式,但我們預期不久後可能會啟用這項功能。許多駕駛人希望在紫紅色範圍內寫出駕駛員,而斑鳩琴也使驅動程式庫程式架構團隊難以完全支援生鏽功能。設計時,雖然我們不一定是動力因素,但能否輕鬆啟用以 Rust 編寫的驅動程式。

如先前所述,針對驅動程式庫傳輸,Rust FIDL 繫結通常可以達成。目前這類繫結的特定設計和實作仍會保持開啟,很可能是未來的 RFC 的主旨。

工作優先順序

日後,最好探索如何沿用工作或訊息層級的優先順序,而不是從調度工具層級沿用。雖然 zircon 核心團隊中有很大的計畫要探索這個概念,但驅動程式庫執行階段具有很大的彈性,因此可以在這裡進行實驗,並推出比核心功能更多的創新構想。當執行階段的初始實作作業完成,並做出可立即的果實最佳化作業後,這可能就是重點所在。

電源管理頻道

在其他驅動程式庫架構中,如果較低層級的驅動程式處於暫停狀態,是很實用的作業,會自動停止作業。這項 RFC 中提供的設計並無任何益處,但為了改善驅動程式庫人體工學改良,這是探索的領域。在電源管理方面,目前有許多關於驅動程式庫架構的開放式問題。

先前的圖片和參考資料

處理中的執行緒模型概念都不是新想法,也不是如何有效率地處理並行。採取的方法是從以下來源 (以及更多) 汲取靈感: