《D-Bus 进程间通信原理详解:总线、寻址与消息模型》(三部曲 01 · 理论)
2026/9/19 23:35:50 网站建设 项目流程

D-Bus 进程间通信原理详解:总线、寻址与消息模型(三部曲 01 · 理论)

📌 本文是《GDBus 三部曲》第 01 篇(理论篇):只讲协议与概念,不写业务代码。
读者假设:零D-Bus基础、有 C/Linux 基础。
读完本文 → 02《GDBus C 语言开发实战:从一份 XML 到一个能跑的服务》

1. GDBus、D-Bus 和 gdbus 的关系

名称说明
D-BusLinux/Unix 常用的进程间通信协议
dbus-daemonD-Bus 总线守护进程,负责消息转发、连接管理、权限控制
GDBusGLib/GIO 提供的 D-Bus 编程 API
gdbus命令行观察、调试工具
gdbus-codegen根据 XML 接口描述生成 C 代码

GDBusConnection、GDBusProxy、GDBusInterfaceSkeleton 等具体 API 类是 02 的主角,这里先不展开。

整体结构

D-Bus message

D-Bus message

C/C++ Client
GDBus

dbus-daemon
Session/System Bus

C/C++ / Python /
Rust / systemd ...
D-Bus Service

2. D-Bus 的主要用途

D-Bus 主要用于进程之间通信,例如:

  • 桌面应用之间通信
  • 用户态后台服务通信
  • 系统服务通信:systemd、NetworkManager、BlueZ、UDisks、login1 等
  • 应用间事件通知

适合以下场景

  • 客户端请求服务执行操作
  • 服务端返回结果
  • 服务端广播状态变化
  • 客户端监听系统事件
  • 多个客户端共享同一个系统服务

不太适合

  • 高频大数据传输
  • 传输大文件
  • 极低延迟的实时通信
  • 大量图像、音视频数据传输

D-Bus 更适合发送控制命令、状态、事件和元数据

3. D-Bus 的核心对象模型

D-Bus 中最重要的一组概念是:

  • Bus- 总线
  • Connection- 连接
  • Name- 名称
  • Object Path- 对象路径
  • Interface- 接口
  • Method- 方法
  • Signal- 信号
  • Property- 属性

一次方法调用通常由以下信息共同决定

信息示例
BusSession Bus
Destinationcom.example.Calculator
Object Path/com/example/Calculator
Interfacecom.example.Calculator
MethodAdd
Parameters(2, 3)

4. Bus:总线

4.1 Session Bus

Session Bus 是用户会话级总线。

适合:

  • 桌面程序
  • GUI 应用
  • 用户级后台服务
  • 同一用户下的多个进程
  • 用户级通知

查看地址:

echo"$DBUS_SESSION_BUS_ADDRESS"# 典型输出:# unix:path=/run/user/1000/bus

使用 gdbus 观察:

gdbus introspect\--session\--destorg.example.Service\--object-path /org/example/Object

4.2 System Bus

System Bus 是系统级总线。

适合:

  • 系统服务
  • 网络管理
  • 蓝牙管理
  • 电源管理
  • 硬件管理
  • systemd
  • 多用户共享的系统功能

使用 gdbus 观察:

gdbus introspect\--system\--destorg.freedesktop.NetworkManager\--object-path /org/freedesktop/NetworkManager

4.3 Session Bus 和 System Bus 的区别

对比项Session BusSystem Bus
生命周期用户登录会话系统运行期间
典型用户桌面应用系统服务
权限一般较宽松通常受到严格限制
典型服务桌面组件systemd、NetworkManager
守护进程运行身份普通用户dbus 专用用户(服务多为 root 或专用用户)
是否需要 policy通常较少通常需要

5. Connection:连接

一个进程要参与 D-Bus 通信,首先要连接到总线,这条连接就是 Connection。

  • 连接建立时,进程向 dbus-daemon 发送Hello(),总线分配一个唯一名称(如:1.42
  • 通常一个进程一条连接,每条连接都有独立的唯一名称
  • 连接是后续一切通信的载体:方法调用、信号收发、属性访问都通过它进行

如何在代码里建立连接(g_bus_get_sync等 API)属于动手内容,见 02。

6. Name:D-Bus 名称

D-Bus 中存在两种名称。

6.1 Well-known name

稳定、可读的服务名称。

示例:

  • com.example.Calculator
  • org.freedesktop.NetworkManager
  • org.freedesktop.systemd1

客户端通常通过 well-known name 找服务。

6.2 Unique name

每个连接被总线分配的唯一名称。

示例:

  • :1.42
  • :1.103

特点:

  • 每次连接可能不同
  • 进程重启后会变化
  • 可以唯一标识当前连接
  • 适合识别某个具体客户端

可以类比为:

  • well-known name:服务的逻辑名称
  • unique name:某次实际连接的临时标识

查看当前总线上的名字:

busctl--userlist busctl list

7. Object Path:对象路径

对象路径类似文件系统路径,用于标识服务中的对象。

示例:

  • /com/example/Calculator
  • /com/example/Device/0
  • /org/freedesktop/NetworkManager

规则:

  • 必须以/开头
  • 由多个路径段组成
  • 每个路径段只能包含字母、数字和下划线
  • 不能使用空格和连字符
  • 路径区分大小写

不合法:

  • com/example/Object
  • /com/example/my-object

合法:

  • /com/example/Object
  • /com/example/MyObject
  • /com/example/Device_0

一个服务可以导出多个对象:

服务名:com.example.DeviceManager 对象: /com/example/DeviceManager /com/example/Device/0 /com/example/Device/1 /com/example/Device/2

8. Interface:接口

接口是方法、信号和属性的集合。

一个对象可以实现多个接口:

/com/example/Calculator ├── com.example.Calculator ├── org.freedesktop.DBus.Introspectable ├── org.freedesktop.DBus.Properties └── org.freedesktop.DBus.Peer

常见标准接口:

接口用途
org.freedesktop.DBus.Introspectable查看对象接口信息(Introspect)
org.freedesktop.DBus.Properties访问属性(Get/Set/GetAll/PropertiesChanged)
org.freedesktop.DBus.ObjectManager管理多个对象(GetManagedObjects 等)
org.freedesktop.DBus.PeerPing(探活)、GetMachineId(获取机器标识)

接口名通常使用反向域名:

  • com.company.product.Manager
  • com.company.product.Device
  • org.example.Application

9. Method:方法

方法是请求-响应模型:

Client Service | | | -------- Method Call ------> | | | | <------ Method Return ------- | | |

方法可以有:

  • 输入参数
  • 输出参数
  • D-Bus 错误
  • 超时
  • 取消操作

10. Signal:信号

Signal 是事件通知模型。信号同样经过 dbus-daemon 分发:客户端先向总线注册 match rule(匹配规则,说明想收谁的什么信号),之后 daemon 按规则转发。

Service
发送 Signal

dbus-daemon
按 match rule 匹配

Client A
已订阅,收到

Client B
已订阅,收到

Client C
未订阅,收不到

特点:

  • 通常没有返回值(发后即忘)
  • 一个服务发送,多个已订阅的客户端都能收到
  • 客户端需要先订阅(match rule)
  • 适合状态变化、事件通知

示例:

  • TemperatureChanged(temperature)
  • DeviceAdded(object_path)
  • StateChanged(old_state, new_state)

11. Property:属性

属性用于表示对象状态。

标准接口:

  • org.freedesktop.DBus.Properties

包含:

  • Get()
  • Set()
  • GetAll()
  • PropertiesChanged()

例如:

  • Version: string
  • Enabled: boolean
  • State: string
  • Temperature: double

属性访问实际上也是 D-Bus 方法调用:

  • org.freedesktop.DBus.Properties.Get
  • org.freedesktop.DBus.Properties.Set
  • org.freedesktop.DBus.Properties.GetAll

12. Introspection:接口自描述

D-Bus 支持通过 XML 描述对象接口。

使用:

gdbus introspect\--session\--destcom.example.Calculator\--object-path /com/example/Calculator

执行后得到:

<!DOCTYPEnodePUBLIC"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN""http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"><nodename="/com/example/Calculator"><interfacename="com.example.Calculator"><methodname="Add"><argname="a"type="i"direction="in"/><argname="b"type="i"direction="in"/><argname="result"type="i"direction="out"/></method><signalname="ResultChanged"><argname="result"type="i"/></signal><propertyname="Version"type="s"access="read"/></interface></node>

解读:

  • 接口:com.example.Calculator
  • 方法Add:两个i(int32)入参,一个i出参
  • 信号ResultChanged:一个i参数
  • 属性Version:类型s(string),只读

type字段就是 D-Bus 的签名字母(见 §15)。Introspection 是调试 D-Bus 的第一步:先看接口长什么样,再决定怎么调。

13. 一条 Method Call 的旅程

把前面的概念串起来,看一次Add(2, 3)的完整旅程:

  1. Client 连接总线,获得唯一名称(如:1.42
  2. Client 组装消息:destination=com.example.Calculator、path=/com/example/Calculator、interface=com.example.Calculator、method=Add、参数(2, 3)
  3. 消息经 unix socket 发给dbus-daemon
  4. daemon 按 destination 查路由:well-known name → 某条连接,转发过去
  5. Service 收到,按 path → interface → method 分发到具体函数,算出 5
  6. Service 回Method Return(通过 reply 序号与请求配对),原路经 daemon 回 Client
  7. Client 收到返回值(5,)

出错的三种情况:

  • Service 返回Error消息(方法内部失败)
  • 目标名字不存在 → daemon 直接回错(ServiceUnknown等)
  • 等不到回复 →超时(D-Bus 默认 25 秒)

Signal 的旅程不同:没有 destination、没有回复,消息发到总线后由 daemon 按 match rule 扇出给所有订阅者——一对多、发后即忘。

14. 常用观察工具

理论篇只介绍只读观察类工具;写代码、发信号等动手操作见 02。

常用命令:

  • gdbus introspect—— 查看对象的接口
  • gdbus call—— 调用一次方法
  • gdbus monitor—— 监听某个服务收发的消息
  • busctl list—— 查看总线上有哪些名字
  • dbus-monitor—— 更底层的总线抓包

14.1 gdbus introspect

gdbus introspect\--system\--destorg.freedesktop.systemd1\--object-path /org/freedesktop/systemd1

查看根节点:

gdbus introspect\--system\--destorg.freedesktop.systemd1\--object-path /

14.2 gdbus call

基本格式:

gdbus call\--session\--dest<服务名>\--object-path<对象路径>\--method<接口名.方法名>\<参数1>\<参数2>

示例:

gdbus call\--session\--destcom.example.Calculator\--object-path /com/example/Calculator\--methodcom.example.Calculator.Add\2\3

返回:

(5,)

⚠️引号坑gdbus call的参数按 GVariant 语法解析。字符串必须写成"'hello'"——直接裸敲hello会报解析错误。这是新手最常撞的第一个坑。

调用 systemd 方法:

gdbus call\--system\--destorg.freedesktop.systemd1\--object-path /org/freedesktop/systemd1\--methodorg.freedesktop.systemd1.Manager.GetUnit\ssh.service

返回:

(objectpath '/org/freedesktop/systemd1/unit/ssh_2eservice',)

注意.被转义成了_2e——这是路径转义,不是乱码。

14.3 gdbus monitor / dbus-monitor

监听指定服务:

gdbus monitor\--session\--destcom.example.Calculator

监听系统服务:

gdbus monitor\--system\--destorg.freedesktop.NetworkManager

低层监听:

dbus-monitor--sessiondbus-monitor--system

15. D-Bus 类型系统和 Signature

D-Bus 使用 Signature(签名)描述数据类型。

15.1 基本类型

Signature类型C/GLib 类型
yuint8guchar
bbooleangboolean
nint16gint16
quint16guint16
iint32gint32
uuint32guint32
xint64gint64
tuint64guint64
ddoublegdouble
sstringgchar*
oobject pathgchar*(对象路径)
gsignaturegchar*(签名)
hunix fdGUnixFDList*(文件描述符传递,进阶,见 03)
vvariantGVariant*

15.2 容器类型

Signature含义
aT类型为 T 的数组
(T1T2…)结构体
{KV}字典项(K 必须是基本类型)
a{sv}string → variant 字典

示例:

  • s—— 一个字符串
  • (ii)—— 两个 int32 组成的结构体
  • (is)—— 一个 int32 和一个字符串组成的结构体
  • as—— 字符串数组
  • a{sv}—— string → variant 字典
  • a{ss}—— string → string 字典

⚠️拼接签名 vs 单一类型:方法有两个参数(int32 和 string)时,消息体的线上签名写作is(拼接)。但在 GDBus/GVariant 的 C API 里,参数永远打包成一个 tuple,写作(is)。GVariant 的类型串必须是单一完整类型,裸写is是非法的——直接传给g_variant_new会断言崩溃。

15.3 方法参数是一个 tuple

D-Bus 方法参数整体打包为一个 tuple,所以Add(2, 3)的参数签名是(ii)

GVariant*parameters=g_variant_new("(ii)",2,3);

GVariant 的创建与解析(数组、字典、嵌套结构)属于动手内容,见 02;深度操作见 03。

16. 系列导航

📌《GDBus 三部曲》

  • 01 理论(本文):协议、概念、类型系统、观察工具
  • 02 实战:《GDBus C 语言开发实战:从一份 XML 到一个能跑的服务》——环境安装、XML 定义接口、gdbus-codegen、服务端/客户端完整代码、编译运行、权限配置
  • 03 疑难手册:断线重连、GVariant 深度操作、ObjectManager 等——按问题查询,不通读

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询