什么是D-Bus?
D-BUS 是一种进程间通信的方式,从架构上来说,分为三层:
一个库,libdbus,允许2个进程间交换信息。
一个消息总线守护进程, 它使用libdbus库。其他进程都可以与它连接。它可以将消息从一个进程发给另外任意数量的其他进程。现在有一些基于特定应用框架的dbus库函数封装,例如libdbus-glib 和libdbus-qt,也有与一些语言绑定的形式,例如Python等。这些封装的API旨在令D-BUS编程更加简单,libdbus倾向于提供更低层次的调用。很多libdbus API只在绑定的组件中可用。
libdbus仅支持一对一的连接,就像原始 socket通讯方式一样。但它传递的不是以字节为单位的数据流,而是具有一定意义的消息包。消息的消息头部表示消息种类,消息体用来装载数据。Libdbus也可以允许实现特定的传输通道,从而来完成比如像认证之类的应用细节(libdbus also abstracts the exact transport used (sockets vs. whatever else), and handles details such as authentication.)。
消息总线守护进程将D-bus上连接的所有程序构成一个轮形hub。Libdbus为中心,它和应用程序建立一对一的连接。每个应用程序通过通道发送消息到消息总线,然后总线进程将消息转发到其他连接到hub的应用程序。可以把消息总线理解为一个路由器。Dbus服务在一个操作系统中存在多个进程。第一个进程是一个全局进程,就如sendmail 或Apache 的系统守护进程一样。这个进程具有高度的安全限制,一般用于系统进程间的通讯。其他的dbus进程都是用户进程,针对于每个登录的用户建立。这些实例允许用户会话中的应用程序相互通信。Dbus全局进程和用户进程是相互独立的,他们并没有内在的依赖关系。
D-Bus应用
有很多种IPC或者网络通信系统,如:CORBA,DCE,DCOM,DCOP,XML-RPC,SOAP,MBUS,ICE等。Dbus的目的主要是下面两点:
在同一个桌面会话中,进行桌面应用程序之间的通讯。
桌面程序和内核或者守护进程之间通信。
D-Bus概念
对象路径(Native Objects and Object Paths):D-Bus的底层接口,和libdbus相关,它提供一种叫对象路径(object path),用于让高层接口绑定到各个对象中去,允许远端应用程序指向他们。Object path就像一个文件路径。
方法和信号(Methods and Signals):每个对象都有一些成员,有两种成员:方法(methods)和信号(signals),在对象中,方法可以被调用。信号会被广播,感兴趣的对象可以处理这个信号,同时信号中也可以带有相关的数据。
接口(Interfaces):每个对象都有一个或者多个接口,一个接口就是多个方法和信号的集合。这个概念和Glib, Qt或者Java中的是一致的。接口定义了对象实例的类型。dbus使用简单的命名空间字符串来表示接口,如org.freedesktop.Introspectable。可以说dbus接口相当于C++中的纯虚类。
代理(Proxies):使用代理对象就是让调用者感觉在直接使用远程对象一样。d-bus的底层接口完成了一些比较低级和繁琐的调用过程,比如必须先调用创建方法形成消息包,然后发送,然后等待接受和处理返回的消息。所以,高层的接口就可以使用代理对象提供的接口屏蔽这些细节。所以,当调用代理对象的方法时,代理内部会转换成dbus的方法调用,等待消息返回,对返回结果解包,返回给相应的方法。可以看看下面的例子,使用dbus底层接口编写的代码:
Message message = new Message(”/remote/object/path”, “MethodName”, arg1, arg2);
Connection connection = getBusConnection();
connection.send(message);
Message reply = connection.waitForReply(message);
if (reply.isError()) {
} else {
Object returnValue = reply.getReturnValue();
}
使用代理对象编写的代码:
Proxy proxy = new Proxy(getBusConnection(), “/remote/object/path”);
Object returnValue = proxy.MethodName(arg1, arg2);
客户端代码减少很多。
总线名称(Bus Names):当一个应用程序连接上bus daemon时,daemon会分配一个唯一的名字给它。以冒号(:)开始,这些名字在daemon的生命周期中是不会改变的,可以认为这些名字就是一个IP地址。当这个名字映射到应用程序的连接上时,应用程序可以说拥有这个名字。同时应用可以声明额外的容易理解的名字,比如可以取一个名字 com.mycompany.TextEditor,可以认为这些名字就是一个域名。其他应用程序可以往这个名字发送消息,执行各种方法。
名字还有第二个重要的用途,可以用于跟踪应用程序的生命周期。当应用退出(或者崩溃)时,与bus的连接将被OS内核关掉,bus将会发送通知,告诉剩余的应用程序,该程序已经丢失了它的名字。名字还可以检测应用是否已经启动,这可以用来实现单实例启动程序。
地址(Addresses):使用d-bus的应用程序既可以是server也可以是client,server监听到来的连接,client连接到server,一旦连接建立,消息就可以流转。如果使用dbus daemon,所有的应用程序都是client,daemon监听所有的连接,应用程序初始化连接到daemon。dbus地址指明server将要监听的地方,client将要连接的地方,例如,地址:unix:path=/tmp/abcdef表明 server将在/tmp/abcdef路径下监听unix域的socket,client也将连接到这个socket。一个地址也可以指明是TCP /IP的socket,或者是其他的。
当使用bus daemon时,libdbus会从环境变量中(DBUS_SESSION_BUS_ADDRESS)自动认识“会话daemon”的地址。如果是系统 daemon,它会检查指定的socket路径获得地址,也可以使用环境变量(DBUS_SESSION_BUS_ADDRESS)进行设定。当dbus中不使用daemon时,需要定义哪一个应用是server,哪一个应用是client,同时要指明server的地址,这不是很通常的做法。
D-bus工作原理
Calling a Method – Behind the Scenes
在dbus中调用一个方法包含了两条消息,进程A向进程B发送方法调用消息,进程B向进程A发送应答消息。所有的消息都由daemon进行分派,每个调用的消息都有一个不同的序列号,返回消息包含这个序列号,以方便调用者匹配调用消息与应答消息。调用消息包含一些参数,应答消息可能包含错误标识,或者包含方法的返回数据。
方法调用的一般流程:
1.使用不同语言绑定的dbus高层接口,都提供了一些代理对象,调用其他进程里面的远端对象就像是在本地进程中的调用一样。应用调用代理上的方法,代理将构造一个方法调用消息给远端的进程。
2.在DBUS的底层接口中,应用需要自己构造方法调用消息(method call message),而不能使用代理。
3.方法调用消息里面的内容有:目的进程的bus name,方法的名字,方法的参数,目的进程的对象路径,以及可选的接口名称。
4.方法调用消息是发送到bus daemon中的。
5.bus daemon查找目标的bus name,如果找到,就把这个方法发送到该进程中,否则,daemon会产生错误消息,作为应答消息给发送进程。
6.目标进程解开消息,在dbus底层接口中,会立即调用方法,然后发送方法的应答消息给daemon。在dbus高层接口中,会先检测对象路径,接口,方法名称,然后把它转换成对应的对象(如GObject,QT中的QObject等)的方法,然后再将应答结果转换成应答消息发给daemon。
7.bus daemon接受到应答消息,将把应答消息直接发给发出调用消息的进程。
8.应答消息中可以包容很多返回值,也可以标识一个错误发生,当使用绑定时,应答消息将转换为代理对象的返回值,或者进入异常。
bus daemon不对消息重新排序,如果发送了两条消息到同一个进程,他们将按照发送顺序接受到。接受进程并需要按照顺序发出应答消息,例如在多线程中处理这些消息,应答消息的发出是没有顺序的。消息都有一个序列号可以与应答消息进行配对。
Emitting a Signal – Behind the Scenes
在dbus中一个信号包含一条信号消息,一个进程发给多个进程。也就是说,信号是单向的广播。信号可以包含一些参数,但是作为广播,它是没有返回值的。
信号触发者是不了解信号接受者的,接受者向daemon注册感兴趣的信号,注册规则是”match rules”,记录触发者名字和信号名字。daemon只向注册了这个信号的进程发送信号。
信号的一般流程如下:
当使用dbus底层接口时,信号需要应用自己创建和发送到daemon,使用dbus高层接口时,可以使用相关对象进行发送,如Glib里面提供的信号触发机制。
信号包含的内容有:信号的接口名称,信号名称,发送进程的bus name,以及其他参数。
任何进程都可以依据”match rules”注册相关的信号,daemon有一张注册的列表。
daemon检测信号,决定哪些进程对这个信号感兴趣,然后把信号发送给这些进程
每个进程收到信号后,如果是使用了dbus高层接口,可以选择触发代理对象上的信号。如果是dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。