D-Bus

D-Bus 的英文全称是 Desktop Bus,从名字可以看出它最早是被设计用于桌面操作系统的通信总线,即桌面应用程序和操作系统内核间的通信。D-Bus 提供了一种进程间通信及远程过程调用机制,从而实现多个不同的应用程序在同一台电脑上同时进行通信。

D-Bus 作为 freedesktop.org 项目的一部分,其设计目的是使 Linux 桌面环境提供的服务标准化。同时,freedesktop.org 项目也开发了一个称为 libdbus 的自由及开放原始码软件库,作为规范的参考实现。因此,libdbus 库常与 D-Bus 本身混淆。当然,也存在着其他的 D-Bus 实现,例如 GDBus(GNOME),QtDBus(Qt),dbus-java 以及 sd-bus(systemd 的一部分)。

概述

D-Bus 可用于同一个桌面会话中不同桌面应用软件间的通信,能集成桌面会话,也解决了进程的生命周期的问题。它也允许桌面会话与操作系统间的通信,这通常包括了内核与任何的系统守护进程或一般进程间的通信。

当大量的程序进行通信时,由于程序跟程序之间必须创建起一对一的通信,则整体的通信网络将会非常复杂,而通信效率也会因此低下不可靠。D-Bus 提供了一个软件汇流抽象层,能够汇整所有的消息到一个虚拟的通信频道。连接到 D-Bus 的程序无法也无需知道 D-Bus 是如何操作,只需遵从 D-Bus 的标准,就能保证所有连接到 D-Bus 的程序能进行彼此间的交流。换句话说,D-Bus 是一个消息总线系统,即进程间通信的介质,让应用程序间可以通信并交换消息。

Linux 桌面环境通过实例化多个总线来利用 D-Bus 设施,特别是:

  • 一个单一的系统总线,可供系统的所有用户和进程使用,它提供对系统服务(即操作系统和任何系统守护程序提供的服务)的访问;
  • 每个用户登录会话的会话总线,为同一桌面会话中的用户应用程序提供桌面服务,并允许将桌面会话集成为一个整体。

一个进程可以连接到任意数量的 D-Bus 总线上,前提是它已被授予访问这些总线的权限。实际上,这意味着任何用户进程都可以连接到系统总线及其当前会话总线,但不能连接到另一个用户的会话总线,甚至不能连接到同一用户拥有的不同会话总线。如果将所有用户会话组合到单个用户总线中,则后一种限制可能会在未来发生变化。

利用 D-Bus 可以很方便地为应用程序增加新的功能,简化已有功能,包含信息分享、模块化及权限分离。例如当使用蓝牙接收到通话时,通知所有正在运行中的音乐播放器,使其静音或暂停播放,直到通话结束。

D-Bus 也可以用作框架来集成用户应用程序的不同组件。例如,办公套件可以通过会话总线进行通信,以在文字处理器和电子表格之间共享数据。

设计规范

总线模型

在 D-Bus 的上下文中,每个到总线的连接都通过所谓的总线名称来标识。总线名称由两个或两个以上点分隔的字母、数字、破折号和下划线组成。有效总线名称的一个例子是 org.freedesktop.NetworkManager

当进程建立到总线的连接时,总线会为连接分配一个特殊的总线名称,称为唯一连接名称(unique connection name)。这种类型的总线名称是不可变的 —— 只要连接存在就保证它们不会改变,更重要的是,它们不能在总线生命周期内重复使用。这意味着与该总线的任何其他连接都不会分配这样唯一的连接名称,即使同一进程关闭与总线的连接并创建一个新连接也是如此。唯一的连接名称很容易识别,因为它们以冒号字符开头 —— 否则禁止使用。唯一连接名称的示例是 :1.1553(冒号后面的字符没有特殊含义)。

一个进程可以为其连接请求额外的总线名称,前提是任何请求的名称尚未被另一个总线连接使用。在 D-Bus 的说法中,当一个总线名称被分配给一个连接时,就说该连接拥有该总线名称。从这个意义上说,一个总线名称不能同时由两个连接拥有。但是,与唯一的连接名称不同,如果它们可用,这些名称可以被重用 —— 一个进程可能会回收另一个进程释放的总线名称,无论该进程是有意还是无意释放。

这些额外的总线名称(通常称为众所周知的名称)背后的想法是提供一种使用预先安排的总线名称来引用服务的方法。例如,报告系统总线中当前时间和日期的服务位于其连接拥有 org.freedesktop.timedate1 总线名称的进程中,无论它是哪个进程。

总线名称可以作为实现单实例应用程序的一种简单方法(第二个实例检测总线名称已被使用)。它还可以用来跟踪服务进程的生命周期,因为当由于进程终止而释放总线名称时,总线会发送一个通知。

对象模型

由于 D-Bus 最初的概念是作为几个面向组件的通信系统的替代品,因此 D-Bus 与其前身共享一个对象模型,在该对象模型中表达客户端和服务之间通信的语义。D-Bus 对象模型中使用的术语模仿了一些面向对象编程语言使用的术语。但这并不意味着 D-Bus 在某种程度上仅限于 OOP 语言 —— 事实上,最常用的实现(libdbus)是用 C 编写的,这是一种过程编程语言。

在 D-Bus 中,进程通过暴露对象来提供服务。这些对象具有可以调用的方法,以及对象可以发出的信号。方法和信号统称为对象的成员。任何连接到总线的客户端都可以通过使用对象的方法、发出请求或命令对象执行操作来与对象交互。例如,表示时间服务的对象可以由客户端使用返回当前日期和时间的方法来查询。客户端还可以侦听对象在其状态由于某些事件而发生变化时发出的信号,这些事件通常与底层服务相关。例如,当管理硬件设备(例如 USB 或网络驱动程序)的服务发出“添加新硬件设备”事件的信号时。客户应该指示总线他们有兴趣从特定对象接收某些信号,因为 D-Bus 总线只将信号传递给那些对它们感兴趣的进程。

连接到 D-Bus 总线的进程可以请求它导出任意数量的 D-Bus 对象。每个对象由一个对象路径标识,由一串数字、字母和下划线分隔并以斜杠字符为前缀,因为它们与 Unix 文件系统路径相似,所以称其为对象路径。对象路径由请求进程选择,并且在该总线连接的上下文中必须是唯一的。有效对象路径的示例是 /org/kde/kspread/sheets/3/cells/4/5。然而,在对象路径中形成层次结构并没有被强制(但也不被劝阻)。服务对象的特定命名约定完全取决于此类服务的开发人员,但许多开发人员选择使用项目保留的域名来命名它们的前缀(例如 /org/kde)。

每个对象都不可避免地与导出它的特定总线连接相关联,并且从 D-Bus 的角度来看,它只存在于这种连接的上下文中。因此,为了能够使用某个服务,客户端不仅必须指明提供所需服务的对象路径,还必须指明服务进程连接到总线的总线名称。这反过来又允许连接到总线的几个进程可以毫不含糊地导出具有相同对象路径的不同对象。

接口指定可以与对象一起使用的成员(方法和信号)。它是一组方法声明(包括其传递和返回参数)和信号(包括其参数),这些声明由类似 Java 语言接口符号的点分隔名称标识。一个有效接口名称的例子是 org.freedesktop.Introspectable。尽管接口名称和总线名称相似,但注意不要弄错了。一个 D-Bus 对象可以实现多个接口,但至少必须实现一个,为它定义的每个方法和信号提供支持。 一个对象实现的所有接口的组合称为对象类型。

使用对象时,客户端进程最好在成员名称之外提供成员的接口名称,但仅当对象实现的不同接口中可用的成员名称重复而导致歧义时才强制执行 —— 否则,选定的成员未定义或错误。另一方面,发出的信号必须始终指示它属于哪个接口。

D-Bus 规范还定义了对象可能想要实现的几个标准接口,除了它自己的接口。尽管在技术上是可选的,但大多数 D-Bus 服务开发人员选择在他们的导出对象中支持它们,因为它们为 D-Bus 总线客户端,例如 introspection。这些标准接口是:

  • org.freedesktop.DBus.Peer:提供了一种测试 D-Bus 连接是否有效的方法。
  • org.freedesktop.DBus.Introspectable:提供了一种自省机制,通过该机制,客户端进程可以在运行时获取对象实现的接口、方法和信号的描述(以 XML 格式)。
  • org.freedesktop.DBus.Properties:允许 D-Bus 对象公开底层的本机对象属性或属性,或者在不存在时模拟它们。
  • org.freedesktop.DBus.ObjectManager:当 D-Bus 服务分层排列其对象时,此接口提供了一种方法来查询对象关于其路径下的所有子对象,以及它们的接口和属性,使用单个方法调用。

D-Bus 规范定义了许多管理总线操作(称为“总线服务”) ,这些操作将使用驻留在 org.freedesktop.DBus 总线名称中的 /org/freedesktop/DBus 对象来执行。每条总线都为自己保留这个特殊的总线名称,并管理专门针对这个总线名称和对象路径组合的任何请求。总线提供的管理操作是由对象的接口 org.freedesktop.DBus 定义的。例如,这些操作用于提供有关总线状态的信息,或管理其他知名总线名称的请求和释放。

通信模型

D-Bus 被认为是一个通用的、高级的进程间通信系统。为了实现这些目标,D-Bus 通信基于进程之间的消息交换而不是“原始字节”(raw bytes)。D-Bus 消息是一个进程可以通过总线发送到另一个连接的进程的高级离散项。消息具有明确定义的结构(甚至定义了其有效负载中携带的数据类型),允许总线验证它们并拒绝任何格式不正确的消息。在这方面,D-Bus 更接近于 RPC 机制,而不是经典的 IPC 机制,具有自己的类型定义系统和自己的封送处理。

总线支持在客户端和服务进程之间交换消息的两种模式:

  • 一对一的请求-响应:这是客户端调用对象方法的方式。客户端向导出对象的服务进程发送一条消息,服务反过来用一条消息回复客户端进程。客户端发送的消息必须包含对象路径、调用方法的名称(以及可选的接口名称)以及由对象选择的接口定义的输入参数的值(如果有的话)。回复消息携带请求的结果,包括对象的方法调用返回的输出参数的值,或者如果出现错误则异常信息。
  • 发布/订阅:这是对象向相关方宣布信号发生的方式。对象的服务进程广播一条消息,总线只传递给订阅对象信号的连接客户端。消息携带对象路径、信号名称、信号所属的接口以及信号参数的值(如果有)。通信是单向的:没有来自任何客户端进程的原始消息的响应消息,因为发送者既不知道身份也不知道接收者的数量。

每个 D-Bus 消息都由一个标头和一个主体组成。标头由几个字段组成,这些字段标识消息的类型、发送者以及将消息传递给其接收者所需的信息(目标总线名称、对象路径、 方法或信号名称、接口名称等)。正文包含接收器进程解释的数据有效负载 —— 例如输入或输出参数。所有数据都以众所周知的二进制格式编码,称为有线格式(wire format),它支持各种类型的序列化,例如整数和浮点数、字符串、复合类型等,也称为封送处理。

D-Bus 规范定义了有线协议:如何构建要在 D-Bus 连接内的进程之间交换的 D-Bus 消息。但是,它没有定义传递这些消息的底层传输方法。

相关实现

  • libdbus
  • GDBus
  • QtDBus
  • sd-bus
  • libnih-dbus
  • kdbus
  • zbus
  • Protocol::DBus

相关链接