Dbus 进程间通信(一)

Posted by feizaipp on March 24, 2019

我的博客

DBus 是针对桌面环境优化的 IPC(interprocess communication) 机制,用于进程间通信或进程。我现在用的 CentOS 7 系统,大量的使用 DBus 通信机制。它是三层架构的 IPC 系统,包括:

  • libdbus 函数库,用于两个应用程序互相联系和交互消息
  • 基于 libdbus 构造的消息总线守护进程,可同时与多个应用程序相连,并能把来自一个应用程序的消息路由到0或者多个其他程序
  • 基于特定应用程序框架的封装库或捆绑,例如, libdbus-glib 和 libdbus-qt ,还有绑定在其他语言,例如 Python

DBus 具有低延迟、低开销、设计简洁而高效等优点。同时,协议是二进制的,而不是文本的,这样就排除了费时的序列化过程。从开发者的角度来看, D-BUS 是易于使用的。有线协议容易理解,客户机程序库以直观的方式对其进行包装。 DBus 的主要目的是提供如下的一些更高层的功能:

  • 结构化的名字空间
  • 独立于架构的数据格式
  • 支持消息中的大部分通用数据元素
  • 带有异常处理的通用远程调用接口
  • 支持广播类型的通信

1. 总线

       在 DBus 中, bus 是核心的概念,代表一个通道,不同的程序之间可以通过这个通道传递消息,比如方法调用、发送信号和监听特定的信号。在一台机器上总线守护有多个实例(instance)。这些总线之间都是相互独立的。在系统中包括两种总线,一种是系统总线;另一种是会话总线。可以使用 D-Feet 工具查看系统支持哪些 DBus 服务,D-feet

1.1 系统总线(system bus)

系统总线在系统引导时启动。这个总线由操作系统和后台进程使用,安全性好,使得任意的应用程序不能欺骗系统事件。它是桌面会话和操作系统的通信,这里操作系统一般而言包括内核和系统守护进程。这种通道的最常用的方面就是发送系统消息,比如:插入一个新的存储设备;有新的网络连接等,相对应的系统服务是 udisks 和 networkmanager。

1.2 会话总线(session bus)

会话总线当用户登录后启动,属于用户私有。它是用户的应用程序用来通信的一个会话总线,比如一个桌面会话中两个桌面应用程序的通信,典型的例子是 Gnome 的 ScreenSave ,这是给用户提供锁屏/亮屏操作的会话总线。

2. DBus 结构介绍

2.1 dbus-daemon 系统服务

如果你了解 Android 的话,对 Binder 一定不会陌生, Binder 是 Android 系统的 IPC 机制, Binder 机制中有一个 servicemanager 系统服务,他负责管理服务的注册,查找。 dbus-daemon也有类似的功能,进程间通信的服务端向 dbus-daemon 注册总线,客户端向 dbus-daemon 获得总线,并将消息传递到指定的进程。两者在底层上, Binder 使用的 binder 驱动负责通信, binder 通信中使用了共享内存技术;而 dbus-daemon 使用 socket 负责通信。所以如果你打算在不同的进程之间传递大量的数据, DBus 的性能是不如 Binder 的,但是 dbus 支持广播,据我所知 Binder 是不支持广播的。

2.2 Dbus Name

在使用 Dbus 系统进行 IPC 通信的过程中,我们首先得知道当前进程要跟谁通信, Dbus Name 就是标识 Dbus 服务的地址,类似 Andorid Binder 系统的服务名。 Dbus Name 的命名有两种方式,一种是 "Unique Connection Name",是以冒号开头的,是全局唯一但不友好的命名,另一种是 "Well-know Name",友好的命名方式。 Dbus Name 的命名规则如下:

  • Dbus Name 就像网址一样,由 “.” 号分割的多个子字符串组成,每个子字符串都必须至少有一个以上的字符
  • 每个子字符串都只能由 “[A-Z][a-z][0-9]_-“ 这些 ASCII 字符组成,只有 Unique Name 的子串可以以数字开头
  • 每个 Dbus Name 至少要有一个 “.”,和两个子字符串,不能以“.”开头
  • Dbus Name 不能超过 255 个字符

2.3 Object Path

所有使用 DBus 的应用程序都包含一些对象, 当经由一个 DBus 连接收到一条消息时,该消息是被发往一个对象而不是整个应用程序。在开发中程序框架定义着这样的对象,例如 JAVA,GObject,QObject 等等,在 DBus 中成为 native object 。对于底层的 DBus 协议,即libdbus API,并不理会这些 native object ,它们使用的是一个叫做 object path 的概念。通过 object path ,高层编程可以为对象实例进行命名,并允许远程应用引用它们。这些名字看起来像是文件系统路径,例如一个对象可能叫做 "/org/gnome/ScreenSaver"。易读的路径名是受鼓励的做法,但也允许使用诸如 "/org/mydbus/c5yo817y0c1y1c5b" 等,只要它可以为你的应用程序所用。简单地说,一个应用创建对象实例进行 DBus 的通信,这些对象实例都有一个名字,命名方式类似于路径,例如/com/mycompany,这个名字在全局(session或者system)是唯一的,用于消息的路由。

2.4 Interface Name

每一个对象支持一个或者多个接口,接口是一组方法和信号,接口定义一个对象实体的类型。D-Bus对接口的命名方式,类似 org.freedesktop.Introspectable 。开发人员通常将使用编程语言类的的名字作为接口名字。在调用 Dbus 服务的方法时要指定调用哪个 Interface 里的方法,这个概念比较抽象,我下面两篇文章介绍 Dbus 使用时在介绍 Interface 具体时怎么工作的。

2.5 方法和信号

每一个对象有两类成员,方法和信号。方法是一段函数代码,带有输入和输出。信号是广播给所有兴趣的其他实体,信号可以带有数据 。在 DBus 中有四种类型的消息:方法调用(method calls)、方法返回(method returns)、信号(signals)和错误(errors)。要执行 DBus 对象的方法,需要向对象发送一个方法调用消息。对象受到方法调用消息后,执行方法实体,并将结果返回给调用者,返回消息或者错误消息。信号与方法的不同之处在于,信号不需要被动的调用,当事件触发时,信号实体向总线上所有关心该信号的信号使用者发送广播消息。

2.6 地址

地址指服务端与客户端进行通信的地址。如果使用 dbus-daemon 的话,应用程序就时客户端, dbus-daemon 是服务端,一个 DBus 的地址是指 server 用于监听, client 用于连接的地方,如 unix:path=/var/run/dbus/system_bus_socket , 表示 server 将在路径/var/run/dbus/system_bus_socket 的 UNIX domain socket 监听。地址可以是指定的 TCP/IP socket 或者其他在在 DBus 协议中定义的传输方式。

3. DBus 结构总结

上面介绍完了 Dbus 所有的结构,那么各个结构之间的关系是怎样的呢?看下面的图就能明白了,这张图是我在 D-feet 程序里截取出来的。 D-feet 是一个 DBus 调试用的工具,他能显示出当前系统所有 Dbus 服务,包括 system 总线和 session 总线,并且还能调用各个服务接口里的方法。

DBus 总线结构