DBusMessage 概述和数据结构
1. 概述
DBusMessage 是 D-Bus 通信的基本单元,用于在应用程序之间传递数据。每个消息由两部分组成:
- 消息头(Header):包含路由信息、消息类型、标志等元数据
- 消息体(Body):包含实际的数据参数
1.1 消息在 D-Bus 中的作用
D-Bus 是一个进程间通信(IPC)系统,DBusMessage 是通信的基本载体:
-
方法调用(Method Call):客户端调用服务端的方法
- 包含目标服务、对象路径、接口、方法名
- 包含输入参数
- 期望收到方法返回或错误消息
-
方法返回(Method Return):服务端返回方法调用的结果
- 包含回复序列号(用于匹配原始方法调用)
- 包含输出参数或返回值
-
信号(Signal):应用程序广播事件通知
- 不期望回复
- 可以被多个接收者订阅
- 用于事件通知和状态变化
-
错误(Error):方法调用失败时返回错误信息
- 包含错误名和错误消息
- 包含回复序列号(用于匹配原始方法调用)
1.2 消息的生命周期
消息从创建到销毁的完整生命周期:
创建 → 填充 → 锁定 → 序列化 → 传输 → 反序列化 → 使用 → 释放
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
new set lock marshal send demarshal get unref
详细步骤:
-
创建(Creation)
- 使用
dbus_message_new_*函数创建 - 分配内存并初始化结构
- 引用计数初始化为 1
- 使用
-
填充(Population)
- 设置头字段(路径、接口、成员等)
- 添加参数到消息体
- 可以多次修改
-
锁定(Locking)
- 调用
dbus_message_lock()锁定消息 - 锁定后不允许修改
- 确保网络数据的一致性
- 调用
-
序列化(Marshaling)
- 转换为网络字节流格式
- 处理字节序对齐
- 准备传输
-
传输(Transport)
- 通过
DBusConnection发送 - 使用底层传输机制(Unix socket、TCP 等)
- 通过
-
反序列化(Demarshaling)
- 接收端解析字节流
- 重建
DBusMessage结构 - 验证消息完整性
-
使用(Usage)
- 应用程序读取消息内容
- 处理消息参数
- 执行相应操作
-
释放(Destruction)
- 调用
dbus_message_unref()减少引用计数 - 引用计数为 0 时自动释放
- 清理所有关联资源
- 调用
2. DBusMessage 数据结构
2.1 公开接口
DBusMessage 是一个不透明类型(opaque type),应用程序只能通过 API 函数访问:
c
typedef struct DBusMessage DBusMessage;
设计原因:
- 封装:隐藏内部实现细节
- 稳定性:允许内部结构变化而不影响 API
- 安全性:防止应用程序直接修改内部状态
2.2 内部结构
实际的 DBusMessage 结构定义在 dbus-message-private.h 中:
c
struct DBusMessage
{
DBusAtomic refcount; /**< 引用计数 */
DBusHeader header; /**< 消息头(网络数据和缓存) */
DBusString body; /**< 消息体(网络数据) */
unsigned int locked : 1; /**< 消息已锁定,不允许修改 */
#ifndef DBUS_DISABLE_CHECKS
unsigned int in_cache : 1; /**< 已在缓存中(调试特性) */
#endif
DBusList *counters; /**< 0-N 个 DBusCounter,用于跟踪消息大小/unix fds */
long size_counter_delta; /**< 增加的大小计数器的增量 */
dbus_uint32_t changed_stamp : CHANGED_STAMP_BITS; /**< 迭代器失效时递增 */
DBusDataSlotList slot_list; /**< 通过整数 ID 存储的数据 */
#ifndef DBUS_DISABLE_CHECKS
int generation; /**< 消息创建时的 _dbus_current_generation */
#endif
#ifdef HAVE_UNIX_FD_PASSING
int *unix_fds; /**< 与此消息关联的 Unix 文件描述符 */
unsigned n_unix_fds; /**< 数组中有效 fd 的数量 */
unsigned n_unix_fds_allocated; /**< 数组的分配大小 */
long unix_fd_counter_delta; /**< unix fd 计数器的增量 */
#endif
};
2.3 字段详解
2.3.1 refcount(引用计数)
c
DBusAtomic refcount;
类型 :DBusAtomic(原子整数)
作用:管理消息的生命周期,支持多个引用共享同一消息
操作:
dbus_message_ref():增加引用计数dbus_message_unref():减少引用计数- 引用计数为 0 时自动释放消息
使用场景:
- 消息需要被多个地方使用
- 消息需要延迟释放
- 消息需要传递给回调函数
示例:
c
DBusMessage *msg = dbus_message_new_method_call(...);
dbus_message_ref(msg); // refcount = 2
// ... 使用消息
dbus_message_unref(msg); // refcount = 1
dbus_message_unref(msg); // refcount = 0, 消息被释放
2.3.2 header(消息头)
c
DBusHeader header;
类型 :DBusHeader 结构
作用:存储消息的路由信息和元数据
内容:
- 消息类型(METHOD_CALL、METHOD_RETURN、ERROR、SIGNAL)
- 消息标志(如 NO_REPLY_EXPECTED)
- 路由信息(路径、接口、成员、目标、发送者)
- 序列号和回复序列号
- 消息体类型签名
存储方式:
- 使用
DBusString存储网络字节流格式的数据 - 包含字段位置缓存(
DBusHeaderField数组) - 支持延迟解析和缓存优化
2.3.3 body(消息体)
c
DBusString body;
类型 :DBusString(D-Bus 的字节缓冲区)
作用:存储消息的实际参数数据
内容:
- 按照 D-Bus 类型系统序列化的参数
- 支持基本类型(整数、字符串等)
- 支持复杂类型(数组、结构体、字典等)
格式:
- 使用 D-Bus 的编组(marshaling)格式
- 8 字节对齐
- 支持字节序转换
2.3.4 locked(锁定标志)
c
unsigned int locked : 1;
类型:位域(1 位布尔值)
作用:标记消息是否已锁定,锁定后不允许修改
锁定时机:
- 调用
dbus_message_lock()显式锁定 - 调用
dbus_connection_send()发送时自动锁定 - 调用
dbus_message_marshal()序列化时自动锁定
锁定后的限制:
- 不允许修改头字段
- 不允许添加或修改参数
- 不允许删除字段
设计原因:
- 确保网络数据的一致性
- 防止在发送过程中修改消息
- 支持消息缓存和重用
2.3.5 in_cache(缓存标志)
c
#ifndef DBUS_DISABLE_CHECKS
unsigned int in_cache : 1;
#endif
类型:位域(1 位布尔值,仅在调试模式下存在)
作用:标记消息是否在缓存中(调试特性)
使用场景:
- 检测消息是否被意外释放
- 调试内存管理问题
- 验证消息生命周期
注意 :仅在 DBUS_DISABLE_CHECKS 未定义时编译
2.3.6 counters(计数器列表)
c
DBusList *counters;
long size_counter_delta;
类型:
counters:DBusList*(链表,每个节点包含一个DBusCounter*)size_counter_delta:long(消息大小增量)
作用:跟踪消息的大小和 Unix 文件描述符数量
DBusCounter 的作用:
- 跟踪所有活跃消息的总大小
- 跟踪所有活跃消息的 Unix fd 总数
- 用于资源限制和监控
工作原理:
- 消息添加到计数器时,增加计数器的值
- 消息释放时,减少计数器的值
- 计数器可以设置阈值,超过时触发通知
使用场景:
- 限制连接的消息大小
- 监控资源使用
- 防止内存耗尽
示例代码:
c
// 添加计数器
_dbus_message_add_counter(message, counter);
// 消息大小变化时,计数器自动更新
// size_counter_delta = header.size + body.size
// 消息释放时,自动减少计数器
2.3.7 changed_stamp(变更戳)
c
dbus_uint32_t changed_stamp : CHANGED_STAMP_BITS; // 21 位
类型:位域(21 位无符号整数)
作用:检测迭代器是否失效
CHANGED_STAMP_BITS:定义为 21,意味着可以表示 2^21 = 2,097,152 个不同的状态
工作原理:
- 消息创建时,
changed_stamp初始化为某个值 - 消息修改时(添加参数、修改字段等),
changed_stamp递增 - 迭代器创建时,保存当前的
changed_stamp - 使用迭代器时,检查
changed_stamp是否匹配 - 如果不匹配,说明消息已被修改,迭代器失效
失效场景:
- 消息被锁定后修改
- 消息被复制
- 消息被序列化/反序列化
设计原因:
- 防止使用无效的迭代器
- 检测并发修改
- 提供调试信息
2.3.8 slot_list(数据槽列表)
c
DBusDataSlotList slot_list;
类型 :DBusDataSlotList(数据槽列表)
作用:通过整数 ID 存储任意数据
数据槽机制:
- 每个消息可以分配多个数据槽
- 每个数据槽有一个唯一的整数 ID
- 可以存储任意指针数据
- 支持设置释放函数
使用场景:
- 存储消息相关的上下文信息
- 实现消息过滤和路由
- 缓存解析结果
- 关联用户数据
API:
c
// 分配数据槽
dbus_message_allocate_data_slot(&slot_id);
// 设置数据
dbus_message_set_data(message, slot_id, data, free_func);
// 获取数据
void *data = dbus_message_get_data(message, slot_id);
// 释放数据槽
dbus_message_free_data_slot(&slot_id);
2.3.9 generation(生成号)
c
#ifndef DBUS_DISABLE_CHECKS
int generation;
#endif
类型 :int(仅在调试模式下存在)
作用:标记消息创建时的全局生成号
使用场景:
- 检测消息是否来自旧的生命周期
- 调试内存管理问题
- 验证消息有效性
注意 :仅在 DBUS_DISABLE_CHECKS 未定义时编译
2.3.10 unix_fds(Unix 文件描述符)
c
#ifdef HAVE_UNIX_FD_PASSING
int *unix_fds;
unsigned n_unix_fds;
unsigned n_unix_fds_allocated;
long unix_fd_counter_delta;
#endif
类型:
unix_fds:int*(文件描述符数组)n_unix_fds:unsigned(有效 fd 数量)n_unix_fds_allocated:unsigned(数组分配大小)unix_fd_counter_delta:long(fd 计数器增量)
作用:支持通过消息传递 Unix 文件描述符
生命周期:
- 添加 fd 时,需要
dup()文件描述符 - 消息销毁时,自动关闭所有关联的 fd
- 传递 fd 时,使用
SCM_RIGHTS控制消息
使用场景:
- 传递文件句柄
- 传递 socket
- 传递管道
限制:
- 仅在支持 Unix fd 传递的系统上可用
- 需要特殊的传输机制(Unix socket)
- 有最大数量限制
2.4 内存布局
DBusMessage 结构的内存布局(简化):
+-------------------+
| refcount (4/8) | <- 引用计数(原子整数)
+-------------------+
| header | <- DBusHeader 结构
| - data | - DBusString(网络数据)
| - fields[11] | - DBusHeaderField 数组(缓存)
| - padding | - 填充和字节序
| - byte_order |
+-------------------+
| body | <- DBusString(参数数据)
+-------------------+
| locked (1 bit) | <- 锁定标志
| in_cache (1 bit) | <- 缓存标志(调试)
+-------------------+
| counters | <- DBusList*(计数器链表)
| size_counter_delta| <- long(大小增量)
+-------------------+
| changed_stamp | <- 21 位(变更戳)
+-------------------+
| slot_list | <- DBusDataSlotList(数据槽)
+-------------------+
| generation | <- int(生成号,调试)
+-------------------+
| unix_fds | <- int*(文件描述符数组)
| n_unix_fds | <- unsigned(有效数量)
| n_unix_fds_alloc | <- unsigned(分配大小)
| unix_fd_counter | <- long(fd 计数器增量)
+-------------------+
大小估算(64 位系统):
- 基本结构:约 100-200 字节
- header.data:可变(取决于字段数量)
- body:可变(取决于参数大小)
- unix_fds:可变(取决于 fd 数量)
2.5 字段之间的关系
2.5.1 header 和 body
- 分离存储 :header 和 body 使用独立的
DBusString - 独立管理:可以独立重新分配内存
- 网络格式:直接使用网络字节流格式,无需转换
2.5.2 locked 和修改操作
- 锁定前:可以自由修改 header 和 body
- 锁定后:所有修改操作都会失败或触发断言
2.5.3 counters 和资源管理
- 添加消息:增加计数器的值
- 释放消息:减少计数器的值
- 大小跟踪 :
size_counter_delta存储消息大小
2.5.4 changed_stamp 和迭代器
- 创建迭代器 :保存当前的
changed_stamp - 修改消息 :递增
changed_stamp - 使用迭代器 :检查
changed_stamp是否匹配
3. 设计原则
3.1 不透明类型
- 封装:隐藏内部实现细节
- 稳定性:允许内部结构变化
- 安全性:防止直接修改
3.2 引用计数
- 共享:多个引用可以共享同一消息
- 自动管理:引用计数为 0 时自动释放
- 线程安全:使用原子操作
3.3 网络格式存储
- 零拷贝:直接使用网络字节流格式
- 高效:无需格式转换
- 对齐:支持 8 字节对齐
3.4 延迟解析
- 缓存:字段位置缓存
- 按需解析:只在需要时解析字段
- 性能优化:避免不必要的解析