這一篇的題目就是這一篇的重點。ObjectMapper負責了兩件非常重要的事情,他負責回答系統上有哪些D-bus物件,這些物件「在哪一條 object path、由誰哪一個service提供、擁有哪些 interface...etc」。大部分的人在聽到OpenBMC,都會想要趕緊表達自己也知道systemd, dbus...,因為Dbus的使用確實是整個系統的核心IPC的方式,龐大的管理系統中如果沒有好的IPC方式,常常就會出現很多問題。但是像Dbus這麼分散的管理方式,有好也有不好。當我想要去知道哪個服務擁有或提供怎樣的interface的時候,該怎麼知道?這時候就該提到ObjectMapper。如果你也正在更深入的了解OpenBMC,而且對Dbus已經有一定程度的認識,那麼你一定要認識它「ObjectMapper」。
我以前也以為Entity Manager是一個很聰明的service,它會掃描整台機器,知道有幾顆 PSU、幾顆 NIC,也能自動找出哪顆 FRU 是 Motherboard、哪一顆又是 DC-SCM。後來看了看程式碼之後,有個雀躍的小發現,entity-manager 其實一開始什麼都不知道。它不知道硬體拓撲,不知道執行緒之間誰會先啟動,甚至不知道哪些服務會在 D-Bus 上、以什麼路徑提供什麼介面。它就像一個在黑暗房間裡的人,手上拿著一份「可能會看到什麼」的清單(configurations/*.json),但完全不知道這些東西現在在哪裡???
ObjectMapper 是為了讓整個 OpenBMC 能「彼此看見」
在傳統 Linux 的 D-Bus 世界裡,每一個 daemon 都知道自己 export 的 object path,但它並不知道別人 export 了什麼物件。如果你想靠介面名稱找到對應的 object,你只能「已知路徑 → 查內容」,而不能「給我所有擁有某介面的物件」。D-Bus 本身沒有提供這種「反向搜尋」的能力。而在BMC 上的硬體是會動態發生變化的。像是如果我們做了一次cycle/reset或者某些可以支援熱插拔的元件被插上(insert) 或拔出(remove),FRU 順序就可能和上一輪不一樣; PSU 插拔會造成 D-Bus 物件新增或消失; MCTP enumeration 的順序也會有可能發生變動; 不同 daemon 啟動的速度不同,有些service可能晚個幾秒才會在 bus 上註冊好物件。這樣的系統,如果沒有一個「全域的、可搜尋的 D-Bus 物件索引」,任何想要偵測硬體的 daemon 都會寸步難行。
因此 OpenBMC 多做了一件一般 Linux 不會做的事——它寫了一個中央登錄站,名字叫 ObjectMapper。這個服務在整個系統啟動後會一直監控 D-Bus,只要有任何 daemon 在 bus 上 export object、移除 object、註冊 interface,它都會即時記錄下來,並替其他程式維護一張完整的地圖(用地圖不知道好不好...就是一個索引好了)。
entity-manager 如何使用 ObjectMapper?
一個比較簡單的例子,假如你問它某種 interface,它就能告訴你現在系統裡有哪些物件擁有這個 interface、在哪個路徑、由哪個 service 實現。
GetSubTree("/", 0, ["xyz.openbmc_project.FruDevice"])
意思是:
- 從 root
/開始 - depth = 0 → 找出所有層級(不限深度)
- 篩選出「至少擁有 FruDevice 介面」的 object
回傳的是一個 dictionary(以下是示意):
{
<object_path> : [
{
<service_name> : [ <interface1>, <interface2>, ... ]
}
]
}
也就是:
- 每個 object path 對應一個 array(通常只有一筆)
- array 裡的元素是「service → 它提供的 interfaces list」
好,所以這個呼叫會由 ObjectMapper 回傳一份列表,裡面是所有與 Probe 規則相關的 D-Bus 物件。接著 entity-manager 會對每一個物件進行 GetAll,把該介面的屬性讀回來,再交給 PerformProbe 逐一比對。因此,整個 Probe → 比對 → FoundDevice → systemConfiguration 的流程,都建立在 ObjectMapper 提供的物件清單之上。要是沒有 mapper,entity-manager 甚至連「第一個要比對的物件在哪裡」都不知道。
ObjectMapper 的細節,比想像的更「中心化」
雖然 D-Bus 是去中心化的架構,因為D-Bus 的設計哲學:每個 service 自己 export 自己的 object。複習一下D-Bus 的 object model 分成三種角色:
- bus daemon(例如 system bus、session bus):只負責 轉傳訊息
- service:自己註冊名字、自己 export 物件
- client:需要自己知道 object path 與 interface
bus daemon 不會記錄所有服務提供了哪些 object path、哪些 interface、有哪些屬性。但 ObjectMapper 很有趣地扮演了某種「中央索引」的角色。它不是管理者,也不是硬體抽象層,不干涉任何物件,只是旁聽。但因為它掌握了所有資訊,其他服務自然會形成一個依賴關係。
ObjectMapper使用不當可能會產生的問題
ObjectMapper 本質上並不是慢或低效的元件。真正的問題是出現在許多服務反覆呼叫它、且呼叫方式太粗暴。最經典的例子,就是大量的 GetSubTree("/") 呼叫。這個介面看起來方便:「給我整棵樹下所有符合 interface 的物件」。 但它也意味著 ObjectMapper 必須:
- 掃整棵 D-Bus 物件樹
- 檢查每個節點是否有對應介面
- 編組整個回傳資料
- 再把結果傳回呼叫者
這在充滿大量devices的平台(例如: 滿DIMM的系統)會瞬間變得昂貴。例如:開機時,Entity-Manager、fru-device、sensors… 這些 daemon 都會開始在 D-Bus 上註冊物件。而早期我們只知道ObjectMapper很好用,所以就會想用 GetSubTree("/") 掃全世界的方式,只要系統一有新物件出現,就重新跑一次完整掃描。這導致:
- 使用期間 ObjectMapper 被轟炸
- 某些平台出現 sensors 掛載延遲
- 有些 daemon 因 timeout 被 systemd 重啟
這不是 ObjectMapper 的問題──它只是被迫一直回答你問他的問題...很忙! 囧。
後續的改善方式包括:
- 降低 polling
- 去除不必要的頻繁 GetSubTree
- 改成 event 驅動,而不是暴力全掃描
我想這也是 Entity-Manager 在現行版本中引入 debounce timer的原因吧 !如果有大量的device一直在出現,你不需要出現十個問十次,短暫時間內(例如 500ms)大量device都出現在之後再一次詢問完整的狀態即可。(我暫時沒有找到當時的討論串,只是我自己在看這段設計的時候的想法)
另一個我印象中可能出現的慘況,在特定情況下 GetSubTree 回傳的資料量太大,造成 client blocking 或 timeout。目前的系統sensor個數都非常多,GetSubTree 回傳結果可能有上千筆,部分 daemon 在 parsing JSON 時 CPU 會升高,如果 client 端沒設定 async call,就可能造成系統阻塞。可以做的是盡量經準查詢(例如:指定 interface)或者說限制 depth,避免一次性什麼都要。你不能因為他好用就狂用啊!
2025.12.14 寫於三峽北大校區「paper shoot cafe」













