D-Bus 基本工具
busctl:systemd提供的 D-Bus 的raw command,功能強大, 是我最常使用。dbus-monitor:專門用來監看 Bus 上所有訊息的即時狀態, 曾經想用他看bus上的signal有沒有正確收送。(但我自己的經驗是, 在多services運行的系統中, 而且每個service都非常忙碌的狀態下, 整體觀察下來獲取的信息太多, 結果找不到重點想看的部分。至今我也沒找到他更好的用法…如果大家有玩出什麼好方法, 歡迎再跟我分享。)dbus-send:傳統的 D-Bus 命令列工具, 單一行為觸發測試的時候會用到。
實用指令
因為我目前沒有server可以跑, 所以example的部分的大家參考就好, 有些可能是以前抓的, 有些可能是ubuntu的, 有些可能是網路上看到覺得這個指令也不錯的。建議大家使用指令這件事情就是親自找個virtual machine或者有真實系統的, 就在系統上跑跑看。 busctl的版本不一樣, 你可能會看到不一樣的結果。那…要怎麼知道我現在手上的busctl有哪些subcommand可以執行呢?我看了一下systemd source code src/busctl/busctl.c 不要太舊的版本都有 help() 所以理論上是可以執行busctl --help
那接下來, 就是可以跟著來下幾個指令玩玩:
- 列出所有服務 (Bus Names):
busctl --system list範例輸出:NAME PID PROCESS CONNECTION
:1.0 512 systemd-logind ...
org.freedesktop.DBus - - -
org.freedesktop.NetworkManager 890 NetworkManager ...org.freedesktop.DBus服務。它的 PID 和 PROCESS 欄位是空的,因為它就是 D-Bus Daemon 本身,是整個D-Bus的管理者。 - 查詢某service的object tree:
busctl --system tree org.freedesktop.NetworkManager - 即時監聽廣播 (Signals):
dbus-monitor --system "type='signal'"
Note:當你插拔網路線或改變 Wi-Fi 狀態,會看到signal訊息跳出來。試著解析訊息中的sender(發送者)、interface(介面名稱) 和member(訊號名稱)。
- 查詢某service的object tree:
https://www.freedesktop.org/software/systemd/man/latest/busctl.html 官方文件。
核心功能:Method, Property, Signal
- Method Call:就像「一封信 + 回信」。它是主動的請求,通常會得到一個回覆(無論是成功或失敗)。這邊針對系統的設計, 有一件重要的事情:Method Call 可以是同步的(程式停下來等待回覆);也可以是非同步的(程式繼續執行,稍後再處理回覆)。
- Property:像「隨時可查的公告」。它代表一個物件的狀態。你可以讀取它(
get),也可以在有權限的情況下修改它(set)。 - Signal:像「郵局廣播」。它是事件驅動的,通常沒有回覆。服務發出一個 Signal,所有感興趣的客戶端都能收到。
- Interface:像是部門的「契約」或「規格書」,定義了這個部門(物件)能提供哪些
Method、Property和Signal。
D-Bus Signature
在 D-Bus 裡,程式之間要互相傳遞資料。問題來了, 傳的東西是字串?整數?布林?還是陣列? 收的人要怎麼知道怎麼解讀?
Signature 就是「型別說明書」。它用簡單的字母縮寫,告訴你「這個參數是什麼型別」。
常用的Signature:

進階容器型別:
aT→ 陣列(array of T)- 例如
as= 字串陣列 =["Taipei","Oxford","Florence"]
- 例如
(…)→ 結構(多個值打包)- 例如
(si)= 一個字串(假設代表姓名) + 一個整數(假設代表幸運數字)busctl --user call org.citybonnie.Hello /org/citybonnie/Hello \\
org.citybonnie.Hello RegisterUser '(si)' "Bonnie" 87
- 例如
a{KT}→ 字典(map)- 例如
a{ss}= 字串對字串的對照表 - 以下的指令是表示有兩組, 每組都是兩個字串組成
busctl --user call org.citybonnie.Hello /org/citybonnie/Hello \\
org.citybonnie.Hello UpdateConfig a{ss} 2 \\
"city" "Florence" \\
"mood" "curious"
- 例如
v→ variant (裡面包一個型別 + 值)- 例如你希望包的是一個整數, 那就要在v後面接i然後一個整數值, 那指令可能是:
busctl --user call org.citybonnie.Hello /org/citybonnie/Hello \\
org.citybonnie.Hello EchoVariant v i 123 - 例如你希望包的是一個字串, 那你的指令可能是:
- 例如你希望包的是一個整數, 那就要在v後面接i然後一個整數值, 那指令可能是:
練習時間
下面有一個daemon, 也不知到能不能work, 也不知道執行起來長怎樣?
如果你的環境還沒有安裝相關套件的話可以安排一下:
$ sudo apt-get update $ sudo apt-get install -y python3-dbus python3-gi gir1.2-glib-2.0
接著, 把這個daemon執行起來之後, 你就可以用busctl來了解一下這個daemon究竟實作了怎樣的method, property and signal?
PS. 這裡用的是session bus, 所以要用busctl --user / dbus-monitor --user 別忘了帶 --user,不然會連到 system bus 看不到東西。
歡迎大家跟我分享你下的command和結果呦!
#!/usr/bin/env python3
import sys
import random
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
BUS_NAME = "org.citybonnie.Hello"
OBJ_PATH = "/org/citybonnie/Hello"
IFACE = "org.citybonnie.Hello"
CITIES = [
"Taipei", "Florence", "London", "Oxford", "Cambridge",
"Brisbane", "Sydney", "Tuscany", "Verona", "Milan"
]
class HelloService(dbus.service.Object):
def __init__(self, bus):
self._bus = bus
self._name = dbus.service.BusName(BUS_NAME, bus)
super().__init__(bus, OBJ_PATH)
# properties
self._mood = "curious"
self._counter = dbus.Int32(0)
# 每 5 秒自動送一張 Postcard(signal)
GLib.timeout_add_seconds(5, self._auto_postcard)
# -----------------------
# Methods
# -----------------------
@dbus.service.method(dbus_interface=IFACE,
in_signature="s", out_signature="s")
def SayHello(self, name):
"""印出 hello, 你的名字 並回傳同一句話"""
msg = f"hello, {name}"
print(msg, flush=True)
self._counter = dbus.Int32(int(self._counter) + 1)
return msg
@dbus.service.method(dbus_interface=IFACE,
in_signature="ss", out_signature="")
def SendPostcard(self, city, message):
"""立即送出一張明信片(Signal)"""
self.Postcard(city, message)
# -----------------------
# Signals
# -----------------------
@dbus.service.signal(dbus_interface=IFACE, signature="ss")
def Postcard(self, city, message):
"""廣播:城市 + 訊息"""
pass
def _auto_postcard(self):
"""每 5 秒自動送一張 Postcard,並遞增 Counter。"""
city = random.choice(CITIES)
self._counter = dbus.Int32(int(self._counter) + 1)
message = f"Counter={int(self._counter)} | Mood={self._mood}"
self.Postcard(city, message)
return True # True 表示持續排程
# -----------------------
# Properties(透過 org.freedesktop.DBus.Properties)
# -----------------------
@dbus.service.property(dbus_interface=IFACE, signature="s")
def Mood(self):
return self._mood
@Mood.setter
def Mood(self, value):
# 允許從 busctl set-property 寫入
self._mood = str(value)
@dbus.service.property(dbus_interface=IFACE, signature="i")
def Counter(self):
return self._counter
@Counter.setter
def Counter(self, value):
self._counter = dbus.Int32(int(value))
def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
# 用 user/session bus,避免 root 權限
bus = dbus.SessionBus()
service = HelloService(bus)
print(f"{BUS_NAME} running on session bus. Object={OBJ_PATH}", flush=True)
GLib.MainLoop().run()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(0)














