RFC-0266:可對應記憶體的核心時脈

RFC-0266:可對應記憶體的核心時脈
狀態已接受
區域
  • 核心
說明

提供一種方法,讓使用者模式程序在不進入 Zircon 核心的情況下讀取核心時鐘狀態。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2024-11-21
審查日期 (年-月-日)2025-02-14

問題陳述

Starnix 需要一種方法,為其用戶端程式提供行為符合 Linux 定義的 CLOCK_MONOTONIC 時鐘。這個時鐘必須代表單調的時間軸,與系統全域的 UTC 時間軸有相同的速率調整,但當由可透過外部方式建立觀察順序的不同執行緒觀察時,這個時鐘也必須一律是單調和連續的。換句話說,這些屬性必須在多位觀察者進行的觀察序列中保持一致。

請注意,Zircon/Fuchsia 的 CLOCK_MONOTONIC 定義目前不符合所有這些要求,也沒有任何計畫讓其符合這些要求。雖然 Zircon 的版本一貫是單調且連續的,但它一律會以系統底層參考時鐘硬體的速率執行,且不會嘗試將速率比對任何外部參考。

Starnix 客戶使用 Linux CLOCK_MONOTONIC 時鐘的頻率預期會很高,因此我們必須盡力讓任何解決方案的查詢成本盡可能低。更複雜的是,Starnix 的用戶端必須在受限模式下運作,也就是說,這些用戶端不得直接發出 Zircon 系統呼叫。在最佳情況下,Starnix 用戶端可以退出受限模式,並進入 Starnix 核心,在該核心中,Zircon 系統呼叫是可行的。請注意,這是 Starnix 目前用來提供系統層級世界標準時間時間軸存取權的策略,但這項做法的效能成本已經相當高。理想的解決方案應該能夠為 Starnix 用戶端提供 Linux CLOCK_MONOTONIC 存取權,而不需要要求用戶端退出受限制模式,進入 Zircon 核心,因為這類額外負擔已經變得難以承受。

摘要

Zircon Kernel Clocks Objects 已可滿足 CLOCK_MONOTONIC Linux 版本的大部分需求。這類方法可用於建構使用者模式定義的時間軸,並由核心保證在多個觀察器觀察時,時間軸一律是單調遞增且連續的,還可委派給權威機構 (在本例中為 Timekeeper) 進行維護

本 RFC 提出一種方法,可讓 Zircon 核心時鐘以不必進入 Zircon 核心的方式讀取,讓讀取作業的額外負擔降到最低,前提是時鐘物件的參考時鐘也能以不必進入 Zircon 核心的方式讀取。此外,這種讀取時鐘物件的方式會提供路徑,讓 Starnix 用戶無須退出限制模式,即可讀取時鐘,進而讓 Starnix 能夠將 Linux CLOCK_MONOTONIC 和世界標準時間同時發布給用戶,而無須退出限制模式。

利害關係人

  • Starnix 實作者
  • 計時員維護人員
  • 核心維護人員
  • 媒體維護者

協助人員:

  • jamesr@google.com

審查者:

  • rashaeqbal@google.com
  • rudymathu@google.com
  • jamesr@google.com
  • adanis@google.com
  • mpuryear@google.com

諮詢:

  • maniscalco@google.com
  • mcgrathr@google.com
  • fmil@google.com

社會化:

這份 RFC 是在內部設計文件中提出,並與利害關係人討論,直到認為已準備好開始 RPC 程序為止。

需求條件

這項提案的主要目的之一,是為 Starnix 提供路徑,讓其為受限制模式程序提供代表 Linux 版本的 CLOCK_MONOTONIC 時鐘。雖然能夠將時鐘對應至記憶體有其他好處,但這裡列出的要求主要是為了滿足 Starnix 和 Linux 定義的必要條件。

記憶體對應時脈需要能夠執行以下操作:

  • 符合 CLOCK_MONOTONIC 的 Linux 需求
  • 會受到與系統全域 UTC 時鐘相同的速率調整。
  • 在多個觀察者所做的有序觀察序列中,維持單調性和連續性。
  • 以「可負擔的執行階段成本」從 Starnix 受限模式程序進行查詢。

CLOCK_MONOTONIC 的 Linux 需求

根據 Linux clock_gettime man 頁面CLOCK_MONOTONIC 的必要條件如下。

A nonsettable system-wide clock that represents monotonic time sinceas
described by POSIX"some unspecified point in the past".  On Linux, that point
corresponds to the number of seconds that the system has been running since it
was booted.

The CLOCK_MONOTONIC clock is not affected by discontinuous jumps in the system
time (e.g., if the system administrator manually changes the clock), but is
affected by the incremental adjustments performed by adjtime(3) and NTP.  This
clock does not count time that the system is suspended.  All CLOCK_MONOTONIC
variants guarantee  that  the  time returned by consecutive calls will not go
backwards, but successive calls maydepending on the architecturereturn
identical (not-increased) time values.

可維護性

CLOCK_MONOTONIC 的 Linux 定義要求,為了維持系統層級的世界標準時間定義,也必須對 CLOCK_MONOTONIC 時間軸進行相同的速率調整。目前,代表系統世界標準時間時間軸的計時器是由元件管理服務建立,並透過程序啟動通訊協定,以重複的句柄 (具備適當權限) 分發至程序。名為 Timekeeper 的程序會收到此時鐘的句柄,並具有寫入權限。它負責使用可用的外部參考資料維護世界標準時間時鐘,持續修正世界標準時間的本地表示法。

根據上述 Linux 需求,系統中必須將 Timekeeper 對系統全域 UTC 時鐘所做的調整率,套用至 Starnix 將提供給客戶的 Linux CLOCK_MONOTONIC 版本。

一致性

如 Linux 規定所述,CLOCK_MONOTONIC 必須是單調且連續的。因此,觀察時鐘的執行緒絕對不會看到時鐘倒轉或跳轉。換句話說,對時鐘的觀察結果必須與這些單調/連續屬性一致。

雖然 Linux 說明頁面並未明確指出這項規定,但我們也將這項規定解讀為,時鐘不但必須從單一觀察者的角度保持一致,也必須符合「多觀察者」的一致性。換句話說,如果有多個執行緒產生有序的觀察序列 (O1、O2、O3 ... On),觀察序列必須與單調和連續屬性一致。

只有在觀察者之間有互動並建立此順序時,多位觀察者所做的一連串觀察才會有明確的順序。舉例來說,如果兩個執行緒 (T1 和 T2) 做出兩個觀察 (O1 和 O2),並將這些觀察結果傳送至第三個執行緒,則沒有明確的順序。T1 和 T2 並未互動,因此無法判斷哪個觀察值「發生在先」,而且序列並未有明確的順序。

以下程式碼可產生有序的時間觀察序列,舉例來說,假設有一個受互斥鎖保護的觀察全域清單 (可能是內部偵錯記錄),許多不同的執行緒會定期鎖定互斥鎖、進行觀察、將觀察結果附加到全域清單,最後解鎖互斥鎖。清單現在包含多位觀察者所做的觀察結果有序序列,而排序是由互斥鎖的排他性所建立。這個有序的觀測序列必須與時鐘的單調和連續屬性一致。

可透過 Starnix 用戶端以「實惠的執行階段成本」讀取。

這不是 Linux 的必要條件,但仍是必要條件。Starnix 嚴格篩選模式程序必須能夠以「非常便宜」的方式觀察 CLOCK_MONOTONIC 時鐘。請注意,Starnix 用戶端目前使用 Zircon 核心時鐘物件,為其用戶端提供 UTC。這些系統原本會退出受限制模式,並進入 Starnix 核心,然後在該核心中建立進入 Zircon 核心的 Zircon 系統呼叫。

需要同時退出限制模式和進入 Zircon 核心,最終會產生無法接受的額外負擔,因此系統已進行最佳化,讓 Starnix 嘗試保留時鐘物件轉換作業的快取副本,讓系統能夠查詢時鐘,而無需每次查詢都進入 Zircon 核心。雖然這項功能有所改善,但 Starnix 用戶端仍必須退出限制模式,且在維持時鐘轉換的快取副本時,會產生其他複雜問題,並在與非 Starnix 程式的觀察結果相比時,產生一致的結果。

設計

大致來說,這項設計是使用 Zircon Kernel Clock Objects 來管理 Linux CLOCK_MONOTONIC 時間軸的 Starnix 全局概念,並擴充物件 API,以便以極低成本觀察時鐘,並以不會讓 Starnix 用戶端退出限制模式的方式完成。

除了「永不離開受限模式」的效能目標之外,Zircon Kernel 時鐘已符合上述規定。讓我們快速檢視每個選項。

可維護性

核心時鐘是核心物件,透過具備權限的句柄進行管理,因此本質上可委派。這會提供多個選項供 Starnix 使用,以便維護代表 Linux CLOCK_MONOTONIC 定義的時鐘。

1) 時鐘可由元件管理服務建立,並分發給 Timekeeper 進行維護,以及給 Starnix 使用。2) 時鐘可由 Timekeeper 建立及維護,並透過 FIDL 介面從 Starnix 的 Timekeeper 擷取使用。3) Starnix 可以建立時鐘,並透過 FIDL 介面將其傳送至 Timekeeper 進行維護。4) 其他問題。

這項做法的一大優勢在於,由於它是核心物件,因此可以將時鐘的維護工作委派給目前也負責維護世界標準時間時鐘的 Timekeeper,讓您可以輕鬆地將相同的速率調整套用至 Linux CLOCK_MONOTONIC 時鐘,而這項調整也適用於 Fuchsia 世界標準時間時鐘。

一致性

Zircon 核心時鐘已確保多觀察器一致性,如上所述。核心會禁止任何可能產生不一致觀察值 (根據建立時鐘時所使用的屬性定義) 的更新作業。因此,無論維護者有多嚴重的錯誤或惡意行為,都無法讓觀察者看到不一致的序列。

同樣地,在核心中用於觀察時鐘的通訊協定可確保一致的觀察結果。只要 API 的任何新增項目都遵循相同的通訊協定,就能確保一致性。

可接受的觀察成本

這就是設計中最關鍵的部分,也就是以可接受的效能代價,觀察核心時鐘物件的能力。這裡的主要目標是設計一種讀取時鐘的方法,不必進入 Zircon 核心 (可消除這項額外負擔),而且還能以不必讓嚴格篩選模式程序退出嚴格篩選模式的方式實作 (必須這樣做才能建立 Zircon 系統呼叫,無論該系統呼叫是否進入 Zircon 核心)。

可對應的狀態

瞭解核心時鐘物件如何執行其功能可能會有所幫助,詳細資訊請參閱這裡的公開說明文件。

不過,您必須瞭解最重要的一點,即核心時鐘的狀態其實只是一個仿射函式,可將所選參照時鐘 (Zircon 單調或啟動) 轉換為時鐘所代表合成時間軸的目前值。如要持續觀察時鐘物件,就必須執行成功的「讀取交易」,在其中記錄轉換的目前狀態,以及參考時鐘的目前值,並確保在交易期間,合成時鐘的轉換狀態不會有所變更。

目前,時鐘的狀態會儲存在核心記憶體中,無法從使用者模式 (直接) 存取。使用者模式可能會使用 zx_clock_get_details 系統呼叫擷取最新版本的時鐘狀態,但這不僅需要系統呼叫,而且在系統呼叫傳回時,資訊可能已經過時,因此可能會導致觀察序列不一致。

不過,這個狀態並沒有什麼特別之處。擁有時鐘讀取權的使用者,已可擷取時鐘的有效轉換詳細資料。我們應該可以避免讓程式進入 Zircon 核心,以便觀察時鐘的詳細資料,我們只需要擴充時鐘物件實作,讓時鐘的狀態儲存在可對應至使用者模式程序的 RAM 頁面中 (唯讀)。透過這種方法,使用者模式可將時鐘狀態對應至其程序,並且在遵循觀察通訊協定的情況下,如果可以直接存取參考時鐘,而無須進入 Zircon 核心,就能進行一致的觀察,而不會產生額外的進入核心作業負荷。

在使用者模式下觀察時鐘

在使用者模式中觀察記憶體對應時鐘時,必須遵循非常特定的通訊協定,且該通訊協定「必須」隨時與核心的行為相符。此外,要遵循本通訊協定,您必須瞭解對應頁面中時鐘狀態資料的特定版面配置。請務必讓 Zircon 保留修改觀察通訊協定或共用記憶體版面配置的功能,而無須與用戶端程式協調,也不必經過完整的正式淘汰週期。

因此,使用者必須將對應時鐘狀態的指標視為不透明 blob 的指標,並將其傳遞至 Zircon vDSO 中實作的 Zircon 系統呼叫。請注意,這些系統呼叫「幾乎」不會需要實際進入 Zircon 核心,才能讀取時鐘。唯一需要進入核心的情況,是當時鐘物件的參考時鐘只能透過進入核心來讀取時 (目前這種情況非常罕見)。將共用資料格式保持不透明,並將讀取實作項目放入 Zircon vDSO 中實作的 Zircon 系統呼叫,可讓使用者不必進入 Zircon 核心,就能享有效能優勢,同時保留變更結構和讀取通訊協定的詳細資料,而無須變更 ABI 合約。

這表示 (如先前所述),Starnix 用戶端會在受限模式下執行,因此無法建立 Zircon 系統呼叫,即使是不會進入 Zircon 核心的系統呼叫也一樣。請注意,對於想要存取現有 Zircon 系統呼叫 (通常不會進入 Zircon 核心) 的受限模式用戶端而言,這已經是個問題,最明顯的例子就是 zx_clock_get_monotonic()

目前,Starnix 會針對這些情況使用特殊的因應措施。建立了通用程式庫 (libfasttime),其中包含用於實作 get_monotonic 的邏輯。當擷取單調性參照資料不需要進入核心時,Zircon vDSO 會使用這個程式庫實作 zx_clock_get_monotonic(),否則會改用完整核心項目。

Starnix 會對其受限制模式的用戶端執行類似操作,讓這些用戶端可以存取 Zircon 單調性參照,並使用 libfasttime 中的程式碼,在受限制模式下實作實際操作,而無須退出受限制模式即可實作 zx_clock_get_monotonic() 的功能。這裡的計畫是透過將記憶體對應的讀取時鐘作業放入 libfasttime,然後在所有地方使用此實作方式,簡單地擴充這項技巧。

據瞭解,這表示 Starnix 與 Zircon 核心之間存在依附性。具體來說,每次 libfasttime 中的某項內容變更時,都必須重新建構並部署 Starnix 和 Zircon。我們正致力於消除這項依附性,而新的時鐘讀取作業會在最終實現時利用這些努力。擴充現有技術不會使情況變得更糟,因此可視為可接受的做法。

時鐘讀取通訊協定詳細資料。

以下說明目前如何以可確保一致性的方式觀察核心時鐘,並將在 libfasttime 中初步實作。這項功能的目的在於提供一些直覺性的資訊,讓您瞭解這一切是如何在「幕後」運作,但不應視為核心的 ABI 合約的一部分。

在內部,核心時鐘物件會使用名為 SeqLock 的特殊鎖定機制來保護其狀態。這些鎖定機制在實作不需系統呼叫的時間觀察方式時,具有一些實用的屬性。特別注意:

1) SeqLock 允許並行讀取,但讀取交易永遠無法阻止寫入交易。因此,使用者模式程式無法防止其他使用者模式程式讀取時鐘,也無法暫停核心中的程式碼,因為該程式碼會更新時鐘。2) SeqLock 讀取交易不需要變更狀態。因此,如果鎖定狀態是對應時鐘狀態的一部分,即使狀態已對應為唯讀,仍可在使用者模式中使用鎖定。3) SeqLock 讀取交易不會阻斷讀取器執行緒,也就是說,在使用者模式中不需要 futex 系統呼叫。此外,它們無法防止讀取或寫入交易同時發生,因此無需停用中斷 (這項操作無法在使用者模式下執行)。

從高層面來說,對時鐘進行適當觀察很簡單。順序如下:

1) 使用 SeqLock 保護對應時鐘狀態,開始讀取交易。2) 將時鐘的狀態複製到本機堆疊分配的緩衝區。3) 讀取參考時脈值。4) 結束讀取交易,如果遭到中斷,請重試,直到成功為止。5) 使用儲存的參考時鐘值和時鐘狀態,計算合成時間值。

不過,深入探討後,這項作業並沒有想像中簡單。SeqLock 交易需要為鎖定狀態和受保護酬載的狀態使用原子載入作業,並可能需要明確的圍籬指示,具體取決於酬載的處理方式 (AcqRel 原子與 Relaxed 原子)。另外,硬體計數器註冊是基準時鐘的基礎,但並非記憶體,且在排序和管線執行時不會遵循 C++ 記憶體模型。您必須使用特殊架構和計時器專屬指令,才能避免讀取此暫存器時「滑過」交易。最後,用於將參考時鐘值轉換為適當的綜合時鐘值的方法,必須在所有使用者之間保持一致,否則四捨五入誤差可能會導致觀察者使用不同的轉換技術,產生一連串看似不一致的觀察結果。

為了讓所有內容保持一致,這裡的設計會要求將程式碼重新分割,如下所示:

1) zx_clock_readzx_clock_get_details 運算的核心會從核心程式碼移至 libfasttime,核心程式碼會重構以使用 libfasttime 實作。2) 針對已對應至本機位址空間的時鐘狀態運作的 zx_clock_readzx_clock_get_details 版本,將會新增為 Zircon 系統呼叫,並加入至 Zircon vDSO。這些 vDSO 呼叫的實作方式會以 libfasttime 實作為依據,確保每個人都使用正確對應的實作方式。

API

支援可對應時鐘的使用者模式 API 包含三個新的系統呼叫,其中兩個通常不會進入 Zircon 核心。此外,我們還會推出新的選項,在建立時鐘時需要傳遞,並推出新的 zx_object_get_info() 主題,讓使用者瞭解時鐘執行個體的對應大小。

建立時鐘

建立可對應的鬧鐘時,使用者必須將新的 ZX_CLOCK_OPT_MAPPABLE 標記傳遞至 zx_clock_create 的選項欄位。當建立可對應時鐘的呼叫成功時,時鐘物件會在內部配置 VMO,並使用這個 VMO 儲存其狀態,而非內部核心記憶體。此外,除了預設時鐘權利外,從這個成功呼叫傳回的句柄也會包含 ZX_RIGHT_MAP。未使用這個選項建立的鬧鐘初始句柄不會包含權利。

判斷對應時鐘所需的空間

在本 RFC 草稿撰寫期間,核心時鐘狀態的目前大小 (包括對齊用的填充) 為 160 位元組。這麼做就能輕鬆將資料塞入單一 4 KB 頁面,並為日後的資料成長預留充裕空間。

不過,基於多項原因,單純假設已對應時鐘的大小現在和將來都會精確為一個 4 KB 頁面,並不明智。

1) 系統的基礎頁面大小可能會有所變更,例如從 4 KB 變成 16 KB,甚至 64 KB。2) 時鐘物件的狀態可能需要大幅增加,甚至可能超過單一頁面 (可提供理論示例)。3) 開發人員可能需要暫時延長對應時鐘表示法的大小,以便進行需要維護更多狀態的偵錯作業。即使較大的時鐘呈現方式從未傳送給任何使用者,在實驗中增加時鐘大小也不會破壞現有程式碼。

因此,對應時鐘的大小不保證為常數,而使用者如要將時鐘狀態對應至其位址空間,就必須知道時鐘狀態的大小,才能妥善管理位址空間。

為解決這個問題,我們將新增新的資訊主題 (ZX_INFO_CLOCK_MAPPED_SIZE)。在已成功使用 ZX_CLOCK_OPT_MAPPABLE 旗標建立的時鐘上,與 zx_clock_get_info() 搭配使用時,會以位元組為單位,在單一 uint64_t 中傳回對應時鐘狀態的大小。

如果嘗試為無法對應的鬧鐘物件擷取對應的鬧鐘大小,系統會將此視為錯誤,並傳回 ZX_ERR_INVALID_ARGS;如果嘗試擷取非鬧鐘類型的物件對應的鬧鐘大小,系統會傳回 ZX_ERR_WRONG_TYPE

對應及取消對應時鐘

我們會新增一個新的系統呼叫 (zx_vmar_map_clock),讓使用者實際將內部 VMO 對應至其位址空間,以便用於 readget_details 作業。

zx_status_t zx_vmar_map_clock(zx_handle_t handle,
                              zx_vm_option_t options,
                              uint64_t vmar_offset,
                              zx_handle_t clock_handle,
                              uint64_t len,
                              user_out_ptr<zx_vaddr_t> mapped_addr);

這項作業幾乎zx_vmar_map()zx_vmar_map_iob() 相同,但有幾個重要的差異。

1) 允許使用 zx_vmar_map() 的選項大多數,但並非全部。特別注意,使用者不得指定 ZX_VM_PERM_WRITEZX_VM_PERM_EXECUTEZX_VM_PERM_READ_IF_XOM_UNSUPPORTED。使用者模式用戶端不得變更時鐘的狀態 (因此沒有 WRITE),而且絕對不是程式碼 (因此沒有 EXECUTE)。嘗試使用任何這些選項都會導致 ZX_ERR_INVALID_ARGS 錯誤。系統支援所有其他選項,特別是與 VMAR 中對應位置相關的選項。2) len 是必要的 (與其他 VMAR 對應作業相同),但對應的數量並非任意或受使用者模式控制。指定時鐘執行個體的對應長度「必須」與使用新推出的 ZX_INFO_CLOCK_MAPPED_SIZE 主題擷取資訊時所回報的大小相符。任何其他傳遞給 len 的值都會視為錯誤。3) 未提供 vmo_offset 參數。使用者必須一律對應所有時鐘狀態。不允許從 VMO 偏移開始的部分時鐘狀態對應,因此 vmo_offset 會變得毫無用處。4) 不提供 region_index 參數,因為 zx_vmar_map_iob() 就是使用該參數。IO 環形緩衝區物件可能會有多個區域,且每個區域都可以獨立對應,但時鐘物件則不會,這表示索引參數不會發揮作用。

將時鐘的內部 VMO 保留在核心內,表示我們無須在將時鐘的 VMO 傳遞至任何其他 VMO 系統呼叫時,指定或測試時鐘的 VMO 行為。

取消對時鐘狀態的對應,與取消使用者模式建立的任何其他對應相同。使用者只要呼叫 zx_vmar_unmap(),傳遞對應作業傳回的虛擬位址,以及原始對應中使用的 len

使用者可在對時鐘建立對應後自由關閉時鐘句柄,但對應關係仍會保留,且仍可用於讀取時鐘的狀態。如果已關閉對對應時鐘的所有句柄,且時鐘物件已遭到銷毀,內部 VMO 和任何剩餘的對應項目都會保留。不過,由於物件已遭到銷毀,且目前沒有任何方式可更新轉換,因此任何程式都無法繼續「維護」時鐘的轉換。從概念上來說,時鐘會繼續存在,並且永遠會持續偏移。請注意,這與時鐘維護者關閉其最後一個時鐘的句柄,而時鐘的使用者保留其句柄,這兩者沒有什麼不同。

觀察時鐘。

zx_status_t zx_clock_read_mapped(const void* clock_addr, zx_time_t* now);

zx_status_t zx_clock_get_details_mapped(const void* clock_addr,
                                        uint64_t options,
                                        void* details);

為了觀察時鐘,使用者可以呼叫適當的新系統呼叫,例如 zx_clock_read_mappedzx_clock_get_details_mapped

這些系統呼叫會在 Zircon vDSO 中實作,且只有在存取基礎參考時鐘時,才會進入 Zircon 核心。

這些函式本身的運作方式與未對應的計數器函式 (zx_clock_readzx_clock_get_details) 相同,但有以下例外狀況。

1) 使用者必須傳遞時鐘狀態在目前位址空間中對應的位址,而非將時鐘句柄做為第一個引數傳遞,且該狀態仍處於對應狀態。傳遞任何其他值,或將未對應 (完全或部分) 的地址傳遞至時鐘狀態,都會導致未定義的行為。2) 呼叫任一日常安排時,系統不會進行權利檢查。由於系統已成功將時脈狀態對應至使用者的位址空間,因此使用者自然有權讀取已對應的時脈。

如先前所述,Starnix 用戶端會在受限模式下執行,因此無法直接使用這些系統呼叫。為避免需要退出限制模式,Starnix 可能會以類似於目前使用方式的方式使用 libfasttime,以便實作 zx_clock_get_monotonic() 的等效功能,直到開發出可避免 Starnix 依賴 libfasttime 的更佳技術為止。

zx_info_maps_t 結構體中的時鐘對應表示

Zircon 目前提供幾種不同的方式,讓開發人員擷取目前有效對應項目的相關資訊,以利診斷。您可以使用 zx_object_get_info() 主題存取這兩種主題:

++ ZX_INFO_VMAR_MAPS ++ ZX_INFO_PROCESS_MAPS

第一個主題會列舉特定 VMAR 中的有效對應項目集,第二個主題則會列舉程序物件的有效對應項目集。無論是哪種情況,結果都會以 zx_info_maps_t 結構體陣列的形式傳回。由於時鐘物件本身可以建立對應項目,因此我們需要能夠回答一些問題,瞭解在已對應時鐘的列舉記錄中,會顯示哪些資訊。

1) 名稱欄位要填入什麼?目前,核心時鐘沒有可指派的名稱,因此當列舉時鐘對應項目時,名稱會設為常數字串「kernel-clock」,方便人類查看診斷資料。如果 (某天) 核心時鐘擴充功能可讓使用者設定名稱,下一個明顯的步驟就是回報使用者指派的名稱,而非常數名稱。2) 記錄中的聯集「type」是什麼?依照對應的 IOB 區域範例,類型會是 ZX_INFO_MAPS_TYPE_MAPPING,並填入 u.mapping 的成員。3) u.mapping.vmo_koid 欄位會回報哪些值?雖然核心時鐘在內部使用 VMO,藉此取得存取權,以便透過對應與程序共用時鐘狀態所需的頁面,但此 VMO 並未包裝在 VmObjectDispatcher 中,因此沒有指派 KOID。因此,系統會改為回報時鐘物件本身的 KOID。

實作

這項功能的實作內容會以一系列 Gerrit CL 的形式發布,主要目的是讓程式碼審查的規模保持在合理且可控的範圍內。要降落的 CL 如下所示。

1) 針對上述 3 個呼叫,API 虛設常式會新增至 Zircon vDSO。這些輔助程式的初始實作會直接傳回 ZX_ERR_NOT_SUPPORTED。這些功能一開始會加入 @next API 下方。2) readget_details 實作項目會移至 libfasttime,並變更核心程式碼,以便使用這些實作項目,而非使用自己的實作項目。3) 更新 zx_clock_create 的行為、實際實作 zx_clock_get_vmo,以及支援 ZX_INFO_CLOCK_MAPPED_SIZE 主題,都會加入核心。4) zx_clock_read_mappedzx_clock_get_details_mapped 的實際實作項目將新增至 Zircon vDSO。5) 將 C++ 專屬包裝函式新增至 libzx。6) 系統會將整個功能的測試加入現有的 Zircon 核心時鐘測試。我們會在現有的公開說明文件中加入這項功能和新系統呼叫的說明文件。

此時,實作已完成。其他語言專屬繫結現在可加入 Rust,而 Starnix 也能使用 libfasttime 中的功能和程式碼,為受限制模式的用戶端提供對 zircon 時鐘的有效存取權。

經過一段時間的使用和穩定化 (在此期間,API 可能會視需要進行變更) 後,API 就會從 @next 中移除,並升級為目前 Zircon Kernel API 的成員。

成效

從 Zircon 程序存取記憶體對應核心時鐘的成本,預計會比透過系統呼叫存取的成本略低,因為進入 Zircon 核心和執行句柄驗證的所有成本都會消除。這麼做也會減少對處理程序句柄表的壓力,因為在句柄驗證期間,系統不再需要鎖定該表格以便讀取。

這項功能的代價是記憶體用量會略微增加,因為我們現在需要一個頁面的記憶體來追蹤時鐘狀態,而非目前用於追蹤時鐘狀態的約 160 位元組核心堆積記憶體。此外,在 VMO 物件本身的核心表示法中,需要少量核心記錄,以及在選擇對應的程序中對應時鐘所需的頁面表 overhead。

對 Starnix 嚴格篩選模式程序的影響則更為顯著。用戶端不再需要退出受限模式,並進入 Starnix 核心,才能存取時鐘物件。此外,Starnix 核心不再需要監控系統層級 UTC 的變更,並更新其維護的內部快取版本,以免執行系統呼叫來讀取時鐘。相反地,限制模式的用戶端可以直接讀取時鐘,無須退出目前的 Starnix 或 Zircon 核心內容。

Ergonomics

整體 API 人因工程應與現有 API 幾乎完全相同。只要進行少量設定 (擷取及對應 VMO),使用者現在就能以與現有方式相同的方式讀取 Zircon 核心時鐘,只要將時鐘狀態指標替換為會傳遞至原始讀取例程的時鐘句柄即可。

以下提供一個簡短範例,說明 API 的差異。

// An object which creates a non-mappable kernel clock and allows users to read it.
class BasicClock {
 public:
  BasicClock() {
    const zx_status_t status = zx::clock::create(ZX_CLOCK_OPT_AUTO_START,
                                                 nullptr,
                                                 &clock_);
    ZX_ASSERT(status == ZX_OK);
  }

  zx_time_t Read() const {
    zx_time_t val{0};
    const zx_status_t status = clock_.read(&val);
    ZX_ASSERT(status == ZX_OK);
    return val;
  }

 private:
  zx::clock clock_;
};

// And now the same thing, but with a mappable clock.  There are a few extra
// setup and teardown steps, but the read operation is virtually identical.
// An object which creates a non-mappable kernel clock and allows users to read it.
class MappedClock {
 public:
  MappedClock() {
    // Make the clock
    zx_status_t status = zx::clock::create(ZX_CLOCK_OPT_AUTO_START | ZX_CLOCK_OPT_MAPPABLE,
                                           nullptr,
                                           &clock_);
    ZX_ASSERT(status == ZX_OK);

    // Get the mapped size.
    status = clock_.get_info(ZX_INFO_CLOCK_MAPPED_SIZE, &mapped_clock_size_,
                             sizeof(mapped_clock_size_), nullptr, nullptr);
    ZX_ASSERT(status == ZX_OK);

    // Map it.  We can close the clock now if we want to, just need to remember to un-map our
    // region when we destruct.
    constexpr zx_vm_option_t opts = ZX_VM_PERM_READ | ZX_VM_MAP_RANGE;
    status = zx::vmar::root_self()->map(opts, 0, clock_vmo, 0, mapped_clock_size_, &clock_addr_);
    ZX_ASSERT(status == ZX_OK);
  }

  ~MappedClock() {
    // Un-map our clock.
    zx::vmar::root_self()->unmap(clock_addr_, mapped_clock_size_);
  }

  // The read is basically the same, just pass the mapped clock address instead of using the handle.
  zx_time_t Read() const {
    zx_time_t val{0};
    const zx_status_t status = zx::clock::read_mapped(reinterpret_cast<const void*>(clock_addr_),
                                                      &val);
    ZX_ASSERT(status == ZX_OK);
    return val;
  }

 private:
  zx::clock clock_;
  zx_vaddr_t clock_addr_{0};
  size_t mapped_clock_size_{0};
};

回溯相容性

對於可直接發出 Zircon 系統呼叫的程式,則無需考量任何回溯相容性問題。我們不會對現有的時鐘功能進行任何變更,而 Zircon vDSO 的特性可讓我們不必擔心要對狀態結構的版面配置或必要的時鐘觀察通訊協定做出任何長期承諾。

不過,Starnix 會依賴核心 (更精確地說,是 libfasttime)。對結構體版面配置或觀察通訊協定所做的任何變更都會在 libfasttime 程式庫中更新,而 Starnix 則需要重新編譯。目前這項問題並未被視為重大疑慮,原因有二:

1) 由於系統會為在限制模式下執行的 Starnix 用戶端提供對 Zircon 單調和啟動時間表的無系統呼叫存取權,因此這已經是現況。換句話說,這並非新的依附元件,只是延續先前已完成的作業。2) 從長期來看,我們正在考慮如何消除先前存在的依附元件。當這些想法實現時,這個新 API 將可從改善中受惠,就像 zx_clock_get_monotonic 一樣。

安全性考量

允許使用者將時鐘狀態 (只讀) 對應至其位址空間,並不會造成任何已知的安全性影響。用於觀察時鐘的通訊協定不允許使用者模式獨占時鐘 (這會使我們面臨各種 DoS 攻擊),或修改時鐘狀態 (可能會使核心保證的時鐘屬性失效)。使用者模式中已可透過呼叫 zx_clock_get_details() 存取時鐘狀態中的所有資訊,這項新功能只是針對我們取得資訊的方式進行最佳化。

新增直接存取核心時鐘狀態的功能後,不會帶來額外的高解析度計時器存取能力。時鐘只是現有參考資料的轉換,因此未來可能對可用於程序的基礎參考時鐘解析度設下任何限制,也會 (必然) 影響從這些參考資料衍生的合成時鐘。

隱私權注意事項

這項提案並未涉及任何已知的隱私權問題。這裡沒有任何使用者資料。

測試

您可以透過兩種方式擴充 Zircon 時鐘單位測試,以便測試核心功能。

1) 我們會新增測試,確保強制執行上述規則 (涉及建立、對應及取消對應可對應時鐘)。2) zx_clock_readzx_clock_get_details 的現有測試將擴展,以便測試這些呼叫的 mapped 版本。行為應幾乎相同,因此測試結果也應相同。

說明文件

1) 頂層公開文件將更新,加入功能的一般說明,並附上新加入的 API 方法連結。此外,您應至少提供一個建立、對應、讀取及取消對應時鐘的範例。2) zx_clock_create 會擴充來說明新的 ZX_CLOCK_OPT_MAPPABLE 標記。3) 我們會在時鐘參考文件中新增頁面,說明 zx_clock_get_vmozx_clock_read_mappedzx_clock_get_details_mapped

缺點、替代方案和未知因素

建立可對應時鐘的替代方案

除了明確禁止上述所有禁止行為 (並為該程式碼編寫測試) 之外,實作成本相對較低。進行小幅重構,將觀察例程移至常用位置,並稍微重構核心中的 ClockDispatcher 程式碼,讓核心除了使用內部儲存空間外,也能使用 VMO 儲存空間。接著,只需進行功能測試和撰寫說明文件即可。

雖然有其他替代方案,但需要耗費更多心力,而且最後產生的解決方案會非常專門,而非一般工具。

舉例來說,Starnix 可以建立及管理自己的 Linux CLOCK_MONOTONIC 概念,然後將其公開給受限制模式程序,方法是使用類似的記憶體對應技術,或是強制其受限制模式用戶端退出 Starnix 核心的受限制模式。但仍有許多問題需要解決。

1) 為高讀取並行性進行鎖定。SeqLock 非常酷,但在使用者模式中,使用它們進行讀取交易很容易,但在使用者模式中將其鎖定以進行專屬寫入作業則非常危險,因為無法停用中斷或以其他方式防止非自願的預取動作,這可能導致更新作業在遭到預取時,阻止讀取作業的後續進度。2) 取得時鐘校正資訊。Starnix 要建立的時鐘應採用與系統全域世界標準時間參考值相同的速率調整,但這些調整是由 Timekeeper 而非 Starnix 執行。複製 Timerkeeper 的運作 (除了是重複的工作) 會耗費大量心力,因此 Timerkeeper 可能需要將修正資訊轉送至 Starnix 維護的時鐘,而 Starnix 可能需要向 Timekeeper 提供回饋資訊,才能關閉時鐘復原迴圈。3) 確保正確的記憶體順序語意。視 #1 使用的解決方案而定,您必須小心確保時鐘狀態酬載可在沒有任何正式資料競爭的情況下觀察,同時保留讀取交易中包含的時鐘註冊觀察資料。這可能會是棘手的情況,而且要正確執行及維護也相當困難。

因此,Starnix 可以視需要執行這類作業,但這會導致重複作業,因為核心時鐘目前已可執行這些作業。日後,這也意味著需要進行額外測試,以及額外的長期維護需求。

libfasttime 抽象化的替代方案

為了讓 Starnix 限制模式用戶端享有預期的效能優勢,程式碼必須以某種方式存在於限制模式用戶端程式中,才能同時瞭解時鐘狀態的版面配置,並遵循觀察時鐘狀態所需的通訊協定。

本提案會以 libfasttime 的形式提供程式碼,以滿足這項需求,讓用戶端在其程式中嵌入程式碼,以便觀察其記憶體對應時鐘。這項做法的明顯 (且重大) 缺點,是會在無法使用 Zircon 系統呼叫,但希望使用記憶體對應時鐘功能的用戶端之間建立依附性。每當核心以 libfasttime 變更的方式變更時,用戶端必須針對新程式庫重新建構,否則可能會發生不相容的情形。雖然這是先前存在的問題 (zx_clock_get_monotonic() 的等同實作中已出現),但這並不會改變這個事實。

另一種做法是將 Zircon 公開的 ABI 正式化,並嚴格記錄時鐘狀態的記憶體內版面配置,以及用於持續觀察時鐘的必要通訊協定,讓用戶端能夠實作及維護自己的「可對應時鐘讀取」例程。

不過,這會導致以下結果:

1) 變更時鐘狀態的版面配置或用於觀察時鐘狀態的通訊協定,難度會大幅增加。基本上,對這項功能的 Zircon 端進行變更會變得更加困難。2) 客戶的責任負擔大幅增加。除了使用 (經過測試和維護的) 程式庫程式碼,他們現在還必須負責閱讀並充分瞭解假設的新 API 規格。此外,他們還必須實作、測試及維護自己的觀察例行程序版本。

因此,雖然這是另一種方法,但由於剛才提到的兩個重大缺點,目前並未認真考慮這項做法。我們預期,最終會推出其他方法,為類似 Starnix 限制模式用戶端的用戶端解決這個問題,讓核心可靈活運作,而不會對用戶端造成過多的實作和維護負擔。

ZX_CLOCK_OPT_MAPPABLE 的替代方案

與其要求時鐘建立者決定特定時鐘是否可繪製,不如在需要時直接建立時鐘狀態 VMO。舉例來說,如果使用者呼叫 zx_vmar_map_clock(),但尚未配置 VMO,只要即時配置 VMO,並將其換成核心堆積狀態即可。

請注意,這是單向轉移。一旦時鐘可供對應,我們就無法合理地將時鐘改回不可對應的狀態,因為我們無法有效控制目前是否有對應的用戶端,也沒有任何合理的方式強制用戶端改回。

當然,您可以編寫程式碼來執行這項操作,但需要解決競合條件。您必須在 zx_vmar_map_clock 附近引入鎖定 (或其他序列化機制),以免需要 VMO 分配的並行要求彼此競爭。在 VMO 儲存空間中交換,取代核心內的狀態,實際上會是時鐘更新,需要在狀態從一個位置移至另一個位置時,進行序列鎖定寫入交易。

雖然可行,但會增加程式碼、複雜度和測試次數。此外,嚴謹的測試會表示測試同時處理並行要求所產生的競爭狀態,這類情況無法透過單元測試等方式確定。

整體而言,要求使用者在建立時鐘時選擇「可對應性」目前被視為合理且不太繁重的規定,可避免需要處理延遲繫結要求的複雜性,以便成為可對應的項目。

zx_vmar_map_clock() 的替代方案

在這個提案中,核心時鐘物件可成為「可對應的物件」,加入 VMOs 和 IOB 區域的行列。與 IOB 區域一樣,這些區域會指派專屬的特殊系統呼叫,用於對應基礎 VMO,但 VMO 本身永遠不會直接提供給使用者模式。

您可以改用其他方法,略過專屬對應呼叫,並讓使用者直接存取基礎 VMO。因此,請移除 zx_vmar_map_clock() 系統呼叫,並改為新增 zx_clock_get_vmo() 呼叫。

優點

假設相同方法可套用至 IOB 區域 (核心中其他可對應的非嚴格 VMO 物件),這表示系統將不再將 VMOs 以外的任何物件視為可對應的物件。在使用者瞭解及推論系統時,這種一致性可能會帶來好處。

++「Zircon 中哪些物件可進行對應?方法很簡單!VMOs,其他皆無」++「我可以使用『時鐘 vmo』做什麼?方法很簡單!您可以執行任何授予該代管帳戶的權限允許的動作。

像是「在診斷對應查詢中,我要回報哪個 KOID?」這類問題,就變得毫無意義。其中一個只會回報 VMO 本身的 KOID。由於它現在已正式存在 (「在幕後」) 做為核心調度器物件,我們可以直接使用指派給調度器的 KOID。同樣地,VMOs 也是可命名的物件,這表示要回報的名稱現在也已明確。

另一個好處是,開發人員可以更輕鬆地遵守先前提到的「您必須準確對應我們要求的大小」規定。這項要求並未消失,但可以指定 zx_clock_get_vmo() 系統呼叫,以便除了 VMO 的句柄外,還能傳回所需的對應大小。這樣一來,使用者必須知道大小才能取得 VMO (這是一項必須先對應時鐘狀態的硬性規定),因此很容易遵循這項規定。

最後,直接公開 VMO 可讓系統上層以其他方式共用時鐘。他們可以複製並分享時鐘本身的句柄 (具有適當權限),但現在也能直接複製基礎 VMO 的句柄 (同樣具有適當權限),並改為分享該句柄。

缺點

不過,直接公開 VMO 並非完全沒有缺點。允許對時鐘狀態進行對應的目的非常明確。從使用者的角度來看,系統連內容對應的 VMO 都沒有定義。使用者唯一需要做的,就是將對應的地址用作類似符記,傳遞至專屬的系統呼叫,這些系統呼叫的額外負擔比傳統系統呼叫更低。

不過,公開 VMO 本身會公開大量額外的核心 API 途徑。在直接存取底層 VMO 的情況下,使用者嘗試執行的許多操作在功能上毫無意義,但現在可以執行這些操作。

++ 使用者是否可以為時鐘狀態 VMO 或任何子項建立 CoW 複本?代表什麼意思? ++ 使用者可以控管時鐘狀態 VMO 的快取政策嗎?++ 如果使用者嘗試設定時鐘狀態 VMO 的大小,會發生什麼事?那麼「串流」大小呢?++ 使用者可以要求的各種 op_range 作業為何?是否提供任何值?是否會造成任何安全威脅?++ 誰控管 VMO 物件的屬性?是否任何人都可以設定 VMOs 名稱,還是應該限制這項權限?如果應限制使用,該如何進行?

目前開放使用者的部分運算同時具備無用和良性兩種特性,例如 zx_vmo_read()。使用者「永遠」不需要執行這項操作,因為系統不會為使用者定義要讀取的內容 (這是設計上的考量)。不過,允許他們這樣做是否有任何危險?從技術層面來說,是的,因為如果允許這些物件在不遵循序列鎖定通訊協定下讀取資料,可能會導致時鐘同時更新,進而導致正式資料競爭,但實際上並不會。這些物件可能會從讀取作業中取得垃圾,但系統不會因此停止運作並發生錯誤。

不過,對於許多其他作業而言,答案並非如此明確。應該幾乎肯定禁止 CoW 複本,但物件名稱呢?

每個潛在的作業都必須個別評估,確保從安全性和隱私權的角度來看,允許作業是安全的。我們預期 (至少在謹慎考量下) 會禁止大部分的這些作業。如果這些操作沒有實質用途,則預設立場應為禁止操作。

這表示我們現在必須強制執行這些限制。其中部分功能可以使用適當的處理程序權限執行,但其他功能可能需要自訂的強制機制。無論機制為何,核心中都會存在需要強制執行這些限制的程式碼 (部分為新程式碼)。您必須編寫測試,確保限制已生效,且已正確強制執行。測試和執行程式碼也必須永久維護,不僅適用於現有的 API,也適用於系統中新增的任何未來 VMO API。

絕大多數使用者都不在乎這些。他們只想針對時鐘物件提供最佳化存取權,不想設定對應項目的快取政策,也不想建立時鐘狀態的子項複本 (無論其意義為何)。有人可能想設定對應項目的名稱,但只要命名時鐘物件本身,就能輕鬆完成,這與命名 VMO 一樣簡單。

很難忽略這樣的想法:讓使用者存取這個 API 途徑區域對已對應時鐘的使用者沒有好處,但可能對試圖在公開的 API 途徑區域中尋找漏洞的惡意人士有益。我們尚未完全瞭解分析這些潛在攻擊、緩解攻擊,以及在系統未來發展時強制執行這些緩解措施的成本,但可以肯定的是,這些成本肯定不低。

結語

目前的決定是遵循 IOB 區域的先例,並讓時鐘物件本身「可對應」,大幅減少公開的 API 介面,並希望能降低功能和維護作業的整體複雜度。

日後,我們可能會重新評估這項決定,或許會決定使用模型來統一時鐘和 IOB 區域,在該模型中,底層 VMOs 會直接公開,而非「除了 VMOs 以外,可對應的特殊項目」,但目前系統僅提供極為有限的能力,用於控制共用狀態的使用方式。

既有技術與參考資料

目前的 API

序列鎖定