| 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 目前就是使用這項策略提供全系統 UTC 時間軸的存取權,但這種做法的效能成本已經相當高。理想的解決方案應能讓 Starnix 用戶端存取 Linux CLOCK_MONOTONIC,且不必讓用戶端退出受限模式或進入 Zircon 核心,因為這類負擔實際上已過於沉重。
摘要
Zircon 核心時鐘物件已能滿足 Linux 版 CLOCK_MONOTONIC 的大部分需求。這類時間軸可讓您建構使用者模式定義的時間軸,並保證 (由核心) 在多個觀察者觀察時,一律為單調遞增且連續,也可以委派給授權單位 (在本例中為 Timekeeper) 進行維護。
本 RFC 建議採用某種方式,允許讀取 Zircon 核心時鐘,不必進入 Zircon 核心,因此讀取作業的負擔非常小,前提是時鐘物件的參考時鐘也能讀取,不必進入 Zircon 核心。此外,這種讀取時鐘物件的方法,可讓 Starnix 用戶讀取時鐘,甚至不必離開受限模式,讓 Starnix 能夠向用戶發布 Linux CLOCK_MONOTONIC 和 UTC,而不必離開受限模式。
利害關係人
- Starnix 實作人員
- Timekeeper 維護人員
- 核心維護人員
- 媒體維護人員
協助人員:
- 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 since—as
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 may—depending on the architecture—return
identical (not-increased) time values.
可維護性
Linux 對 CLOCK_MONOTONIC 的定義要求,為維持系統全域的 UTC 定義,對 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 核心時鐘物件,向用戶端提供世界標準時間。這些程序原本會退出受限模式並進入 Starnix 核心,接著發出進入 Zircon 核心的 Zircon 系統呼叫。
需要同時退出受限模式並進入 Zircon 核心,最終導致無法接受的負擔量,因此系統經過最佳化,讓 Starnix 嘗試保留時鐘物件轉換的快取副本,允許查詢時鐘,而不必為每個查詢進入 Zircon 核心。雖然這是一項進步,但仍需要 Starnix 用戶端退出受限模式,而且在維護時鐘轉換的快取副本,以及與非 Starnix 程式的觀察結果相比時,產生一致的結果時,也會產生其他複雜情況。
設計
從高層次來看,這裡的設計是使用 Zircon 核心時鐘物件,管理 Starnix 全域的 Linux CLOCK_MONOTONIC 時間軸概念,並擴充物件 API,以便以非常便宜的方式觀察時鐘,讓 Starnix 用戶端甚至不必退出受限模式。
除了永不離開受限模式的效能目標外,Zircon 核心時鐘已符合上述要求。我們快速回顧一下每種格式。
可維護性
核心時鐘是核心物件,由帶有權限的控制代碼管理,因此本質上可委派。這為 Starnix 提供多個選項,可維護代表 Linux CLOCK_MONOTONIC 定義的時鐘。
1) 時鐘可由元件管理服務建立,並分配給 Timekeeper 維護,以及分配給 Starnix 使用。2) 時鐘可由 Timekeeper 建立及維護,並透過 FIDL 介面從 Timekeeper 擷取,供 Starnix 使用。3) 時鐘可由 Starnix 建立,並透過 FIDL 介面傳送至 Timekeeper 維護。4) 完全不同的其他情況。
主要優點是,由於這是核心物件,因此時鐘的維護作業可以委派給目前也維護世界標準時間時鐘的 Timekeeper,這樣一來,要將套用至 Fuchsia 世界標準時間時鐘的相同速率調整項目,套用至 Linux CLOCK_MONOTONIC 時鐘,就會相對簡單。
一致性
如上所述,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_read 和 zx_clock_get_details 作業的核心會從核心程式碼移至 libfasttime,而核心程式碼會重構為使用 libfasttime 實作項目。2) 運作於時鐘狀態的 zx_clock_read 和 zx_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 個位元組。這會輕鬆納入單一 4KB 頁面,並為日後成長留下充足空間。
不過,基於多項原因,我們不宜直接假設對應時鐘的大小現在和未來都會剛好是 4KB 頁面。
1) 系統的基礎頁面大小可能在某天變更,例如從 4 KB 變更為 16 KB,甚至是 64 KB。 2) 時鐘物件的狀態可能需要大幅成長,甚至可能超過單一頁面 (如有需要,可提供理論範例)。3) 開發人員可能需要暫時擴大對應時鐘表示法的大小,以利進行偵錯工作,這需要維護更多狀態。即使較大的時鐘表示法從未發布給任何終端使用者,但理想情況下,在實驗中增加時鐘大小不會破壞現有程式碼。
因此,對應時鐘的大小不保證會保持不變,且希望將時鐘狀態對應至位址空間的使用者,必須瞭解時鐘狀態的大小,才能妥善管理位址空間。
為解決這個問題,我們將新增資訊主題 (ZX_INFO_CLOCK_MAPPED_SIZE)。如果搭配 zx_clock_get_info() 使用,且時鐘已透過 ZX_CLOCK_OPT_MAPPABLE 旗標成功建立,則對應的時鐘狀態大小 (以位元組為單位) 會在單一 uint64_t 中傳回。
如果嘗試擷取非可對應時鐘物件的時鐘對應大小,系統會視為錯誤並傳回 ZX_ERR_INVALID_ARGS;如果嘗試擷取非時鐘型別物件的對應時鐘大小,系統則會傳回 ZX_ERR_WRONG_TYPE。
對應及取消對應時鐘
系統會新增一個新的系統呼叫 (zx_vmar_map_clock),讓使用者將內部 VMO 對應至自己的位址空間,以便用於 read 和 get_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_WRITE、ZX_VM_PERM_EXECUTE 或 ZX_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 Ring Buffer 物件可能有多個區域,每個區域都可以獨立對應,但時鐘物件沒有,因此索引參數沒有任何用途。
將時鐘的內部 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_mapped 或 zx_clock_get_details_mapped。
這些系統呼叫會在 Zircon vDSO 中實作,且只有在存取基礎參考時鐘需要時,才會進入 Zircon 核心。
這些函式本身的運作方式與非對應的計數器部分 (zx_clock_read 和 zx_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) 記錄中聯集「類型」為何?請參閱對應的 IOB 區域範例,類型會是 ZX_INFO_MAPS_TYPE_MAPPING,而 u.mapping 的成員會填入。3) u.mapping.vmo_koid 欄位中回報的值為何?雖然核心時鐘會在內部使用 VMO,取得與程序共用時鐘狀態所需的頁面存取權 (透過對應),但這個 VMO 並未包裝在 VmObjectDispatcher 中,因此沒有指派給它的 KOID。因此,系統會改為回報時鐘物件本身的 KOID。
實作
這項功能的實作作業會以一連串的 Gerrit CL 形式完成,主要是為了確保程式碼審查規模合理且易於管理。要併入的 CL 如下。
1) API 存根會新增至 Zircon vDSO,用於上述 3 個呼叫。
這些虛設常式的初始實作只會傳回 ZX_ERR_NOT_SUPPORTED。這些項目一開始會新增至 @next API 下方。
2) read 和 get_details 實作項目會移至 libfasttime,核心程式碼會改用這些實作項目,而非使用自己的實作項目。3) zx_clock_create 的更新行為、zx_clock_get_vmo 的實際實作,以及對 ZX_INFO_CLOCK_MAPPED_SIZE 主題的支援功能,將新增至核心。4) zx_clock_read_mapped 和 zx_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 物件本身的 Kernel 代表項目也需要少量 Kernel 記帳,以及在選擇這樣做的程序中對應時脈所需的頁面表額外負荷。
預期 Starnix 限制模式程序受到的影響會更顯著。用戶端不必再退出受限模式並輸入 Starnix 核心,即可存取時鐘物件。此外,Starnix 核心也不再需要監控系統範圍的 UTC 變更,並更新其維護的內部快取版本,以免發出系統呼叫來讀取時鐘。受限模式用戶端可以直接讀取時鐘,不必為了 Starnix 或 Zircon 核心而離開目前的環境。
人體工學
整體 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) 鑑於系統會以無系統呼叫存取權的形式,將 Zircon 單調和啟動時間軸提供給以受限模式執行的 Starnix 用戶端,因此目前就是這種情況。換句話說,這不是新的依附元件,只是延續現有的做法。2) 就長期而言,我們正在考慮如何消除先前存在的依附元件。如果這些想法付諸實現,這個新 API 就會像 zx_clock_get_monotonic 一樣,從這些改良項目中受益。
安全性考量
允許使用者將時鐘狀態 (唯讀) 對應至位址空間,目前沒有已知的安全性影響。用於觀察時鐘的通訊協定不允許使用者模式專屬鎖定時鐘 (這會使我們公開於各種 DoS 攻擊),也不允許修改時鐘狀態 (可能導致核心保證的時鐘屬性失效)。使用 zx_clock_get_details() 的呼叫,即可從使用者模式存取時鐘狀態中包含的所有資訊,這項新功能只是我們取得資訊的方式最佳化。
新增直接存取核心時鐘狀態的功能,不會帶來存取高解析度計時器的額外能力。時鐘只是現有參照的轉換,因此程序可用的基礎參照時鐘解析度,未來可能出現的任何限制,也會 (必然) 影響從這些參照衍生而來的合成時鐘。
隱私權注意事項
這項提案目前沒有已知的隱私權影響。這裡完全不會涉及任何使用者資料。
測試
如要測試核心功能,請透過兩種方式擴充 Zircon 時鐘單元測試。
1) 系統會新增測試,確保強制執行涉及建立、對應及取消對應可對應時鐘的規則 (如上所述)。2) zx_clock_read 和 zx_clock_get_details 的現有測試將會擴充,一併測試這些呼叫的 mapped 版本。兩者的行為應幾乎完全相同,因此測試結果也應如此。
說明文件
1) 頂層公開文件將會更新,加入這項功能的一般說明,並提供新加入 API 方法的連結。此外,還應至少包含建立、對應、讀取及取消對應時鐘的範例。2) zx_clock_create 將擴充為說明新的 ZX_CLOCK_OPT_MAPPABLE 標記。3) 時鐘參考文件會新增頁面,說明 zx_clock_get_vmo、zx_clock_read_mapped 和 zx_clock_get_details_mapped。
缺點、替代方案和未知事項
建立可對應時鐘的替代方案
除了明確禁止上述所有不允許的行為 (並為該程式碼編寫測試),實作成本相對較低。進行小幅重構,將觀察常式移至通用位置,並稍微重組核心中的 ClockDispatcher 程式碼,以便除了內部儲存空間外,還能使用 VMO 儲存資料。然後進行功能測試和撰寫說明文件。
雖然有替代方案,但需要投入大量心力,而且最終產生的並非通用工具,而是非常具體的解決方案。
舉例來說,Starnix 可以建立及管理自己的 Linux 概念,然後透過類似的記憶體對應技術,或強制受限模式用戶端退出受限模式並進入 Starnix 核心,將這個概念公開給受限模式程序。CLOCK_MONOTONIC但仍有許多問題需要解決。
1) 鎖定高讀取並行。SeqLock 非常酷,但在使用者模式中,雖然很容易將其用於讀取交易,但將其鎖定為使用者模式中的專屬寫入作業,卻是非常冒險的提議,因為無法停用中斷或防止非自願搶占,這可能會導致更新作業在搶占後,阻止讀取作業繼續進行。2) 取得時鐘校正資訊。Starnix 想建立的時鐘應與系統全域 UTC 參考時鐘一樣,受到相同的速率調整,但這些調整是由 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()」的替代方案
在這項提案中,核心時鐘物件將具備「可對應物件」的能力,加入 VMO 和 IOB 區域的行列。與 IOB 區域一樣,這些區域會指派自己的特殊系統呼叫,用於對應基礎 VMO,但 VMO 本身絕不會直接提供給使用者模式。
除了這個方法,您也可以略過專門的對應呼叫,直接讓使用者存取基礎 VMO。因此,請移除 zx_vmar_map_clock() 系統呼叫,並改為新增 zx_clock_get_vmo() 呼叫。
優點
假設相同方法可套用至 IOB 區域 (核心中其他可對應的物件,不嚴格來說是 VMO 物件),這表示系統不必再將 VMO 以外的任何物件視為可對應的物件。這項一致性有助於使用者瞭解及推斷系統行為。
++ "What objects are 'mappable' in Zircon? 非常簡單!VMO,僅此而已」。 ++ "What can I do with a 'clock vmo'? 非常簡單!您帳號獲得的權限允許執行的任何操作。
「我應該在診斷對應查詢中回報哪個 KOID?」這類問題就變得無關緊要。其中一個只會回報 VMO 本身的 KOID。現在,它正式以核心 Dispatcher 物件的形式存在 (「幕後」),我們只要使用指派給 Dispatcher 的 KOID 即可。同樣地,VMO 是可命名的物件,因此要回報的名稱現在也很明顯。
此外,開發人員可能也會更容易遵守先前「您必須準確對應我們告知的大小」的規定。這項規定不會消失,但 zx_clock_get_vmo() 系統呼叫可指定傳回所需對應大小,以及 VMO 的控制代碼。這樣一來,使用者就必須知道大小才能取得 VMO (這是對應時鐘狀態的必要條件),因此很容易遵守規定。
最後,直接公開 VMO 可為系統上層提供其他時脈共用方式。他們可以複製並共用時鐘本身的控制代碼 (具備適當權限),但現在也可以複製基礎 VMO 的控制代碼 (同樣具備適當權限),然後改為共用該控制代碼。
缺點
不過,直接公開 VMO 並非完全沒有缺點。允許對應時鐘狀態的用途非常明確。從使用者的角度來看,對應的 VMO 內容甚至未定義。使用者唯一需要做的,就是將對應的位址當做符記傳遞至專用系統呼叫,這類呼叫的負擔比傳統對應項目更低。
不過,公開 VMO 本身會公開大量額外的核心 API 介面區域。使用者可直接存取基礎 VMO,但許多操作在功能上毫無意義,現在卻可以執行。
++ Can a user create a CoW clone of a clock state VMO, or any child for that
matter? 這代表什麼意義?
++ Can a user control the cache policy of a clock state VMO?
++ What happens if a user attempts to set the size of a clock state VMO? 「串流」大小如何?++ What about the various op_range operations a user can request? 這些內容是否完全沒有價值?是否會造成任何安全威脅?++ Who controls the VMO object's properties? 任何人都可以設定 VMO 名稱嗎?還是應該設下限制?如果應該限制,該怎麼做?
現在使用者可以執行的部分作業,同時也是無意義的良性作業,例如 zx_vmo_read()。使用者絕不需要執行這項操作,因為系統甚至不會為使用者定義要讀取的內容 (這是設計使然)。不過,允許他們這麼做會有任何危險嗎?從技術上來說,如果允許讀取資料,但未遵循序列鎖定通訊協定,可能會導致正式的資料競爭,因為時鐘會同時更新,但實際上不會。他們可能會從讀取作業中取得垃圾資料,但系統不會停止運作並發生問題。
不過,對於許多其他作業,答案並非如此明確。CoW 分身幾乎肯定不允許,但物件名稱呢?
必須逐一評估這些潛在作業,確保允許作業不會造成安全性與隱私權問題。預期 (謹慎起見,如果沒有其他原因) 大部分這類作業都會遭到禁止。如果這些作業沒有實際意義,預設立場應為禁止。
這表示現在必須強制執行這些限制。部分作業可透過適當的控制代碼權限完成,但其他作業可能需要自訂強制執行機制。無論採用哪種機制,核心中都會有程式碼 (部分是新程式碼),需要強制執行這些限制。您需要編寫測試,確保限制已到位,且系統會正確執行限制。此外,測試和強制執行程式碼也必須永久維護,不僅適用於現有 API,也適用於日後新增至系統的任何 VMO API。
絕大多數使用者對這些功能都不感興趣。 他們只是想以最佳方式存取時鐘物件,並不想設定對應的快取政策,也不想建立時鐘狀態的子項副本 (無論這代表什麼)。使用者可能想設定對應的名稱,但只要為時鐘物件本身命名,就能輕鬆完成這項操作,與為 VMO 命名一樣簡單。
很難忽略這個想法:讓使用者存取這個 API 介面區域,對地圖時鐘的合法使用者沒有任何好處,但可能對試圖在公開 API 介面區域中尋找漏洞的惡意行為者有利。分析這些潛在攻擊、減輕影響,以及在系統日後演進時強制執行這些緩解措施的成本,目前還無法完全掌握,但可以肯定的是,這些成本幾乎一定不容小覷。
結論
目前的決定是遵循 IOB 區域的先例,讓時鐘物件本身「可對應」,大幅縮減公開的 API 介面,並希望降低這項功能及其維護作業的整體複雜度。
未來我們可能會重新評估這項決策,或許會決定統一時鐘和 IOB 區域,並直接公開基礎 VMO,而非「可對應 VMO 的特殊項目」,但目前系統只會提供極為有限的能力,控制共用狀態的使用方式。
既有技術和參考資料
Current API
序列鎖