了解 D-BUS
D-BUS 是一种进程间通信 (IPC) 系统,提供了一种简单而强大的机制,允许应用程序相互对话、交流信息和请求服务。D-BUS 从零开始设计,旨在满足现代 Linux 系统的需求。D-BUS 的最初目标是取代 CORBA 和 DCOP,它们分别是 GNOME 和 KDE 中使用的远程对象系统。理想情况下,D-BUS 可以成为两种桌面环境通用的、不可知的 IPC 机制,满足它们的需求并引入新功能。
D-BUS 作为一个功能齐全的 IPC 和对象系统,有多种预期用途。首先,D-BUS 可以执行基本的应用程序 IPC,允许一个进程将数据传递给另一个进程——可以将其视为增强版的 UNIX 域套接字。其次,D-BUS 可以促进在系统中发送事件或信号,允许系统中的不同组件进行通信,并最终更好地集成。例如,蓝牙守护进程可以发送来电信号,您的音乐播放器可以拦截该信号,使音量静音直到通话结束。最后,D-BUS 实现了远程对象系统,允许一个应用程序从不同的对象请求服务和调用方法——可以将其视为没有复杂性的 CORBA。
D-BUS 与其他 IPC 机制的不同之处在于以下几个方面。首先,D-BUS 中 IPC 的基本单元是消息,而不是字节流。通过这种方式,D-BUS 将 IPC 分解为离散的消息,每个消息都带有标头(元数据)和有效负载(数据)。消息格式是二进制的、类型化的、完全对齐的和简单的。它是线路协议的固有组成部分。这种方法与其他 IPC 机制形成对比,在其他 IPC 机制中,通用语言是随机的字节流,而不是离散的消息。
其次,D-BUS 是基于总线的。最简单的通信形式是进程到进程。然而,D-BUS 提供了一个守护进程,称为消息总线守护进程,它在特定总线上的进程之间路由消息。通过这种方式,形成总线拓扑,允许进程同时与一个或多个应用程序对话。应用程序可以在总线上发送或侦听各种事件。
最后一个独特的特点是创建了两个总线,即系统总线和会话总线。系统总线是全局的、系统范围的,并在系统级别运行。系统中的所有用户都可以通过适当的权限在此总线上进行通信,从而允许系统范围事件的概念。然而,会话总线是在用户登录期间创建的,并在用户或会话级别运行。此总线仅供特定用户在特定登录会话中使用,作为用户应用程序的 IPC 和远程对象系统。
消息被发送到对象。对象使用路径名寻址,例如 /org/cups/printers/queue。消息总线上的进程与对象关联,并在该对象上实现接口。
D-BUS 支持多种消息类型,例如信号、方法调用、方法返回和错误消息。信号是特定事件已发生的通知。它们是简单、异步、单向的提示消息。方法调用消息允许应用程序请求在远程对象上调用方法。方法返回消息提供方法调用产生的返回值。错误消息提供响应方法调用的异常。
D-BUS 是完全类型化和类型安全的。消息的标头和有效负载都是完全类型化的。有效类型包括字节、布尔值、32 位整数、32 位无符号整数、64 位整数、64 位无符号整数、双精度浮点数和字符串。特殊的数组类型允许对类型进行分组。DICT 类型允许字典样式的键/值对。
D-BUS 是安全的。它实现了一个基于 SASL 配置文件的简单协议,用于验证一对一连接。在总线范围内,对来自特定接口的消息的读取和写入由安全系统控制。管理员可以控制对总线上任何接口的访问。D-BUS 守护进程从一开始就考虑了安全性而编写。
这些概念说起来不错,但好处是什么?首先,系统范围的消息总线是一个新概念。整个系统共享的单个总线允许事件传播,从内核(参见“内核事件层”侧边栏)到系统上最顶层的应用程序。Linux 具有明确定义的接口和清晰的层分离,但集成度不高。D-BUS 的系统消息总线在不损害精细工程实践的情况下提高了集成度。现在,诸如磁盘已满和打印机队列为空,甚至电池电量不足等事件都可以向上冒泡系统堆栈,供任何关心的应用程序使用,从而允许系统响应和反应。事件是异步发送的,无需轮询。
内核事件层
内核事件层是一种内核到用户的通信机制,它使用高速 netlink 套接字与用户空间异步通信。此机制可以与 D-BUS 绑定,允许内核发送 D-BUS 信号!
内核事件层与 sysfs 相关联,sysfs 是 kobject 树,位于现代 Linux 系统上的 /sys 中。sysfs 中的每个目录都与一个 kobject 相关联,kobject 是内核中用于表示对象的结构;sysfs 是作为文件系统导出的对象层次结构。
每个内核事件层事件都建模为好像它源自 sysfs 路径。因此,事件看起来像是从 kobject 发出的。sysfs 路径很容易转换为 D-BUS 路径,使内核事件层和 D-BUS 成为天然的组合。此内核事件层已合并到 2.6.10-rc1 内核中。
其次,会话总线为 IPC 和远程方法调用提供了一种机制,可能在 GNOME 和 KDE 之间提供统一的系统。D-BUS 旨在成为比 CORBA 更好的 CORBA 和比 DCOP 更好的 DCOP,在满足两个项目需求的同时提供其他功能。
而且,D-BUS 在实现所有这些功能的同时,保持了简单和高效。
核心 D-BUS API 是用 C 语言编写的,相当底层且庞大。在此 API 之上,绑定与编程语言和环境集成,包括 Glib、Python、Qt 和 Mono。除了提供语言包装器之外,绑定还提供特定于环境的功能。例如,Glib 绑定将 D-BUS 连接视为 GObject,并允许消息传递集成到 Glib 主循环中。D-BUS 的首选用途肯定是使用特定于语言和环境的绑定,这既是为了易用性,也是为了提高功能性。
让我们看一下 D-BUS 在您的应用程序中的一些基本用法。我们首先看一下 C API,然后使用 Glib 接口研究一些 D-BUS 代码。
使用 D-BUS 首先要包含其头文件
#include <dbus/dbus.h>
您可能要做的第一件事是连接到现有总线。回想一下我们最初的 D-BUS 讨论,D-BUS 提供了两个总线,会话总线和系统总线。让我们连接到系统总线
DBusError error; DBusConnection *conn; dbus_error_init (&error); conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error); if (!conn) { fprintf (stderr, "%s: %s\n", err.name, err.message); return 1; }
连接到系统总线是很好的第一步,但我们希望能够从众所周知的地址发送消息。让我们获取一项服务
dbus_bus_acquire_service (conn, "org.pirate.parrot", 0, &err); if (dbus_error_is_set (&err)) { fprintf (stderr, "%s: %s\n", err.name, err.message); dbus_connection_disconnect (conn); return; }
现在我们已连接到系统总线并获取了 org.pirate.parrot 服务,我们可以从该地址发送消息。让我们发送一个信号
DBusMessage *msg; DBusMessageIter iter; /* create a new message of type signal */ msg = dbus_message_new_signal( "org/pirate/parrot/attr", "org.pirate.parrot.attr", "Feathers"); /* build the signal's payload up */ dbus_message_iter_init (msg, &iter); dbus_message_iter_append_string (&iter, "Shiny"); dbus_message_iter_append_string (&iter, "Well Groomed"); /* send the message */ if (!dbus_connection_send (conn, msg, NULL)) fprintf (stderr, "error sending message\n"); /* drop the reference count on the message */ dbus_message_unref (msg); /* flush the connection buffer */ dbus_connection_flush (conn);
这将从 org.pirate.parrot.attr 发送 Feathers 信号,其有效负载由两个字段组成,每个字段都是字符串:Shiny 和 Well Groomed。任何在系统消息总线上侦听并具有足够权限的人都可以订阅此服务并侦听该信号。
断开与系统消息总线的连接是一个简单的函数
if (conn) dbus_connection_disconnect (conn);
Glib(发音为 gee-lib)是 GNOME 的基础库。Gtk+(GNOME 的 GUI API)和 GNOME 的其余部分都建立在 Glib 之上。Glib 提供了多个便利函数、可移植性包装器、一系列字符串函数以及完整的对象和类型系统——全部用 C 语言编写。
Glib 库提供了一个对象系统和一个主循环,使基于对象、事件驱动的编程成为可能,即使在 C 语言中也是如此。D-BUS Glib 绑定利用了这些功能。首先,我们要包含正确的头文件
#include <dbus/dbus.h> #include <dbus/dbus-glib.h>
使用 Glib 绑定连接到特定消息总线很容易
DBusGConnection *conn; GError *err = NULL; conn = dbus_g_bus_get (DBUS_BUS_SESSION, &err); if (!conn) { g_printerr ("Error: %s\n", error->message); g_error_free (error); }
在此示例中,我们连接到每个用户的会话总线。此调用将连接与 Glib 主循环关联,从而允许与 D-BUS 消息进行多路复用 I/O。
Glib 绑定使用代理对象的概念来表示与特定服务关联的 D-BUS 连接的实例化。代理对象是通过单个调用创建的
DBusGProxy *proxy; proxy = dbus_g_proxy_new_for_service (conn, "org.fruit.apple", "org/fruit/apple", "org.fruit.apple");
这次,我们不发送信号,而是执行远程方法调用。这是使用两个函数完成的。第一个函数调用远程方法;第二个函数检索返回值。
首先,让我们调用 Peel 远程方法
DBusGPendingCall *call; call = dbus_g_proxy_begin_call (proxy, "Peel", DBUS_TYPE_INVALID);
现在让我们检索 - 检查错误并检索方法调用的结果
GError *err = NULL; int ret; if (!dbus_g_proxy_end_call (proxy, call, &err, DBUS_TYPE_INT32, &ret, DBUS_TYPE_INVALID)) { g_printerr ("Error: %s\n", err->message); g_error_free (err); }
Peel 函数接受一个参数,即整数。如果此调用返回非零值,则表示成功,并且变量 ret 保存此函数的返回值。特定方法接受的数据类型由远程方法确定。例如,我们不能传递 DBUS_TYPE_STRING 而不是 DBUS_TYPE_INT32。
Glib 绑定的主要好处是主循环集成,它允许开发人员管理与其他 I/O 和 UI 事件交织在一起的多个 D-BUS 消息。头文件 <dbus/dbus-glib.h> 声明了多个用于将 D-BUS 连接到 Glib 主循环的函数。
D-BUS 是一种强大而简单的 IPC 系统,如果幸运的话,它将改进 Linux 系统的集成和功能。鼓励用户研究新的利用 D-BUS 的应用程序。有了本文,D-BUS 不应该是一个可怕的新依赖项,而是一个闪亮的新功能。“在线资源”列出了一些使用 D-BUS 的有趣应用程序。鼓励开发人员研究在其应用程序中实现 D-BUS 支持。还有一些网站提供了有关使用 D-BUS 的更多信息。当然,最好的参考是现有代码,谢天谢地,这方面有很多。
Robert Love 是 Novell Ximian 集团的内核黑客,也是 Linux 内核开发 的作者。Robert 深入参与了 Linux 内核和 GNOME 社区。他拥有佛罗里达大学计算机科学和数学学位,并且喜欢摄影。