十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

《D-Bus 进程间通信原理详解:总线、寻址与消息模型》(三部曲 01 · 理论) 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 编程 APIgdbus命令行观察、调试工具gdbus-codegen根据 XML 接口描述生成 C 代码GDBusConnection、GDBusProxy、GDBusInterfaceSkeleton 等具体 API 类是 02 的主角这里先不展开。整体结构D-Bus messageD-Bus messageC/C ClientGDBusdbus-daemonSession/System BusC/C / Python /Rust / systemd ...D-Bus Service2. 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 BusDestinationcom.example.CalculatorObject Path/com/example/CalculatorInterfacecom.example.CalculatorMethodAddParameters(2, 3)4. Bus总线4.1 Session BusSession Bus 是用户会话级总线。适合桌面程序GUI 应用用户级后台服务同一用户下的多个进程用户级通知查看地址echo$DBUS_SESSION_BUS_ADDRESS# 典型输出# unix:path/run/user/1000/bus使用 gdbus 观察gdbus introspect\--session\--destorg.example.Service\--object-path /org/example/Object4.2 System BusSystem Bus 是系统级总线。适合系统服务网络管理蓝牙管理电源管理硬件管理systemd多用户共享的系统功能使用 gdbus 观察gdbus introspect\--system\--destorg.freedesktop.NetworkManager\--object-path /org/freedesktop/NetworkManager4.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. NameD-Bus 名称D-Bus 中存在两种名称。6.1 Well-known name稳定、可读的服务名称。示例com.example.Calculatororg.freedesktop.NetworkManagerorg.freedesktop.systemd1客户端通常通过 well-known name 找服务。6.2 Unique name每个连接被总线分配的唯一名称。示例:1.42:1.103特点每次连接可能不同进程重启后会变化可以唯一标识当前连接适合识别某个具体客户端可以类比为well-known name服务的逻辑名称unique name某次实际连接的临时标识查看当前总线上的名字busctl--userlist busctl list7. 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/28. Interface接口接口是方法、信号和属性的集合。一个对象可以实现多个接口/com/example/Calculator ├── com.example.Calculator ├── org.freedesktop.DBus.Introspectable ├── org.freedesktop.DBus.Properties └── org.freedesktop.DBus.Peer常见标准接口接口用途org.freedesktop.DBus.Introspectable查看对象接口信息Introspectorg.freedesktop.DBus.Properties访问属性Get/Set/GetAll/PropertiesChangedorg.freedesktop.DBus.ObjectManager管理多个对象GetManagedObjects 等org.freedesktop.DBus.PeerPing探活、GetMachineId获取机器标识接口名通常使用反向域名com.company.product.Managercom.company.product.Deviceorg.example.Application9. Method方法方法是请求-响应模型Client Service | | | -------- Method Call ------ | | | | ------ Method Return ------- | | |方法可以有输入参数输出参数D-Bus 错误超时取消操作10. Signal信号Signal 是事件通知模型。信号同样经过 dbus-daemon 分发客户端先向总线注册 match rule匹配规则说明想收谁的什么信号之后 daemon 按规则转发。Service发送 Signaldbus-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: stringEnabled: booleanState: stringTemperature: double属性访问实际上也是 D-Bus 方法调用org.freedesktop.DBus.Properties.Getorg.freedesktop.DBus.Properties.Setorg.freedesktop.DBus.Properties.GetAll12. Introspection接口自描述D-Bus 支持通过 XML 描述对象接口。使用gdbus introspect\--session\--destcom.example.Calculator\--object-path /com/example/Calculator执行后得到!DOCTYPEnodePUBLIC-//freedesktop//DTD D-BUS Object Introspection 1.0//ENhttp://www.freedesktop.org/standards/dbus/1.0/introspect.dtdnodename/com/example/Calculatorinterfacenamecom.example.CalculatormethodnameAddargnameatypeidirectionin/argnamebtypeidirectionin/argnameresulttypeidirectionout//methodsignalnameResultChangedargnameresulttypei//signalpropertynameVersiontypesaccessread//interface/node解读接口com.example.Calculator方法Add两个iint32入参一个i出参信号ResultChanged一个i参数属性Version类型sstring只读type字段就是 D-Bus 的签名字母见 §15。Introspection 是调试 D-Bus 的第一步先看接口长什么样再决定怎么调。13. 一条 Method Call 的旅程把前面的概念串起来看一次Add(2, 3)的完整旅程Client 连接总线获得唯一名称如:1.42Client 组装消息destinationcom.example.Calculator、path/com/example/Calculator、interfacecom.example.Calculator、methodAdd、参数(2, 3)消息经 unix socket 发给dbus-daemondaemon 按 destination 查路由well-known name → 某条连接转发过去Service 收到按 path → interface → method 分发到具体函数算出 5Service 回Method Return通过 reply 序号与请求配对原路经 daemon 回 ClientClient 收到返回值(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 introspectgdbus 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--system15. D-Bus 类型系统和 SignatureD-Bus 使用 Signature签名描述数据类型。15.1 基本类型Signature类型C/GLib 类型yuint8gucharbbooleangbooleannint16gint16quint16guint16iint32gint32uuint32guint32xint64gint64tuint64guint64ddoublegdoublesstringgchar*oobject pathgchar*对象路径gsignaturegchar*签名hunix fdGUnixFDList*文件描述符传递进阶见 03vvariantGVariant*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 方法参数是一个 tupleD-Bus 方法参数整体打包为一个 tuple所以Add(2, 3)的参数签名是(ii)GVariant*parametersg_variant_new((ii),2,3);GVariant 的创建与解析数组、字典、嵌套结构属于动手内容见 02深度操作见 03。16. 系列导航《GDBus 三部曲》01 理论本文协议、概念、类型系统、观察工具02 实战《GDBus C 语言开发实战从一份 XML 到一个能跑的服务》——环境安装、XML 定义接口、gdbus-codegen、服务端/客户端完整代码、编译运行、权限配置03 疑难手册断线重连、GVariant 深度操作、ObjectManager 等——按问题查询不通读
返回列表