D-Bus是Desktop Bus的缩写,是针对桌面环境优化的IPC(InterProcess Communication)机制,用于进程间的通讯或进程与内核的通讯。
D-Bus是为Linux系统开发的进程间通讯(IPC)和远程过程调用(RPC)机制,使用统一的通讯协议来代替现有的各类IPC解决方案。D-Bus容许系统级进程(如:打印机和硬件驱动服务)和普通用户进程进行通讯。
D-Bus使用一个快速的二进制消息传递协议,D-Bus协议的低延迟和低消耗特色适用于同一台机器的通讯。D-Bus的规范目前由freedesktop.org项目定义,可供全部团体使用。
D-Bus不和低层的IPC直接竞争,好比sockets,shared memory或message queues。低层IPC有本身的特色,和D-Bus并不冲突。
与其余重量级的进程间通讯技术不一样,D-Bus是非事务的。D-Bus使用了状态以及链接的概念,比UDP等底层消息传输协议更“聪明”。但另外一方面,D-Bus传送的是离散消息,与TCP协议将数据看作“流”有所不一样。D-Bus支持点对点的消息传递以及广播/订阅式的通讯。c++
不一样IPC通讯机制的特色以下:
A、CORBA是用于面向对象编程中复杂IPC的一个强大的解决方案。
B、Bonobo是一个只用于GNOME的解决方案,基于CORBA并依赖于GObject。
C、DCOP是一个较轻量级的IPC框架,功能较少,但能够很好地集成到KDE桌面环境中。
D、SOAP和XML-RPC设计用于Web服务,于是使用HTTP做为其传输协议。
E、D-BUS设计用于桌面应用程序和OS通讯。express
A、D-BUS的协议是低延迟并且低开销的,设计小巧且高效,以便最小化传送时间。从设计上避免往返交互并容许异步操做。
B、协议是二进制的,而不是文本,排除序列化过程。
C、考虑了字节序问题。
D、易用性:按照消息而不是字节流来工做,而且自动地处理了许多困难的IPC问题,而且D-Bus库以能够封装的方式来设计,开发者可使用框架里存在的对象/类型系统,而不用学习一种新的专用于IPC的对象/类型系统。
E、请求时启动服务以及安全策略。
F、支持多语言(C/C++/Java/C#/Python/Ruby),多平台(Linux/windows/maemo)。
G、采用C语言,而不是C++。
H、因为基本上不用于internet上的IPC,所以对本地IPC进行了特别优化。
I、提供服务注册,理论上能够进行无限扩展。编程
D-Bus是按必定的层次结构实现的,整体上D-Bus分为三层:
A、接口层——接口层由libdbus库提供,进程经过libdbus库使用D-Bus的能力。经过底层库的接口能够实现两个进程之间进行链接并发送消息。
B、总线层——由消息总线守护进程(message bus daemon )提供,消息总线守护进程是基于libdbus底层库的,能够路由消息。消息总线守护进程负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。
C、封装层——封装层是一系列基于特定应用程序框架的Wrapper库,将D-Bus底层接口封装成方便用户使用的通用API。windows
libdbus只支持点对点的通讯,即只支持一进程与另外的一个进程进行通讯。通讯是基于消息的,消息包含头部和消息体。
libdbus提供C语言的底层API,API是为了将D-Bus绑定到特定的对象或是语言而设计的,官方文档中建议不要在应用上直接使用D-Bus的底层接口,推荐使用D-Bus的绑定,如QtDBus、GDBus、dbus-c++等实现。安全
D-Bus总线层由消息总线守护进程(message bus daemon )提供。消息总线守护进程是一个后台进程,是/usr/bin/dbus-daemon的一个运行实例, 负责消息的转发,dbus-daemon运行时会调用libdus的库。应用程序调用特定的应用程序框架的Wrapper库与dbus-daemon进行通讯。应用程序经过D-Bus与其它进程通讯必须先创建到消息总线守护进程实例的链接。
最多见的基于dbus的程序也符合C/S结构。好比开发两个程序A和B,其中A是客户端,B是服务端。假设A要调用B的一个函数func,那么实际的消息流动方向是:A告诉dbus-daemon请求要调用B的func函数,而后dbus-daemon去调用B的func函数,若是func有返回值的话,B会把返回值告诉dbus-daemon,而后dbus- daemon再把返回值告诉A。若是B进程尚未启动,则dbus-daemon会自动的先把B进程启动起来。
一般状况下,Linux会有两个dbus-daemon进程,一个属于system,一个属于session,在用户登陆的时候由dbus-launch启动。
大多数普通程序,都是使用session的dbus-daemon,默认状况下,A就是将消息发给属于session的dbus-daemon。
dbus-daemon是有地址的,环境变量DBUS_SESSION_BUS_ADDRESS用于表示当前登陆用户的session的dbus-daemon进程的地址,可使用echo $DBUS_SESSION_BUS_ADDRESS查看。
当用户登陆进桌面环境的时候,系统启动脚本会调用到dbus-launch来启动一个dbus-daemon进程,同时会把启动的dbus-daemon地址赋予环境变量DBUS_SESSION_BUS_ADDRESS。
通常状况下,不须要考虑DBUS_SESSION_BUS_ADDRESS,但某些时候,单独启动一个dbus-daemon有助于程序的调试。
利用dbus-daemon自启动机制运行的服务进程,都是后台进程,标准输出设备已经被重定向,若是B进程有一些调试用的打印信息输出,则很难直接查看。此时,能够单独启动一个dbus-daemon,让A和B都使用本身启动的dbus-daemon,此时,dbus-daemon能把B的打印信息显示出来。
先在终端下启动一个dbus-daemon,命令以下形式以下:
DBUS_VERBOSE=1 dbus-daemon --session --print-address
如此启动的dbus-daemon会前台执行,而且打印出地址。
而后,在执行A程序的时候,设置环境变量DBUS_SESSION_BUS_ADDRESS为刚才获得的地址值。DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-7MlJMxxGnW,guid=437c0e6060516670cfccacc15afc43c6 ./A
此时运行程序A和B,使用本身启动的dbus-daemon来转发消息,而且会把B的打印信息显示出来。
消息总线守护进程是一个特殊的进程,用于管理系统内的总线,能够将一个进程的消息路由给另一个进程。若是有不少应用程序链接到消息总线守护进程的总线上,总线能把消息路由到对应的一个或多个进程中去。所以在总线层上,实现了点对点通讯的支持,也实现了广播/订阅通讯方式。
在最底层,D-Bus只支持点对点的通讯,通常使用本地套接字(AF_UNIX)在应用和消息总线守护进程之间通讯。D-Bus的点对点是通过bus daemon抽象过的,由bus daemon来完成寻址和发送消息,所以每一个应用没必要关心要把消息发给哪一个进程。D-Bus发送消息一般包含以下步骤:
A、应用程序建立和发送消息给消息总线守护进程。
B、消息总线守护进程对收到的消息进行分发处理。
C、目标程序接收到消息,而后根据消息的种类,作不一样的响应:确认、应答、忽略。
总线是D-Bus的进程间通讯机制,一个系统中一般存在多条总线,总线由D-Bus总线守护进程管理。
最重要的总线为系统总线(System Bus),Linux内核引导时,系统总线就已被装入内存。只有Linux内核、Linux桌面环境和权限较高的程序才能向系统总线写入消息,以此保障系统安全性,防止有恶意进程假冒Linux发送消息。
会话总线(Session Buses)由普通进程建立,可同时存在多条。会话总线属于某个进程私有,用于进程间传递消息。网络
D-Bus封装层是将libdbus底层API绑定到特定的对象系统或是语言中,將不便使用的libdbus底层API封裝成能够在应用层使用的高級API,如libdbus-glib、libdbus-qt等。
D-Bus在不少不一样的编程语言上都有其接口实现。不一样语言的接口封装了D-Bus低级API,提供了更符合编程语言的语法结构。
实现D-Bus接口的语言正在逐渐增长。在C语言中,有最底层的API,但其实现及使用上很是复杂。C语言中另外一个实用化的实现基于GLib。在Java、Perl、Python等都有D-Bus接口实现。session
总线是消息总线守护进程(message bus daemon)的运行实例,每一个总线都有一个地址,应用进程就是经过总线地址和相应的总线链接的。总线上的每个链接都有一个链接名,链接名也称bus name。每一个链接上有至少一个对象,一般有多个对象,对象使用对象路径惟一标识。对象要实现一个或多个接口,每一个接口包含一组方法和信号。多线程
在D-Bus中,总线(bus)是核心的概念:不一样的程序能够经过总线进行某些操做,好比方法调用、发送信号和监听特定的信号。总线一般有两种,系统总线(system bus)和用户总线(session bus),系统总线一般只有一条,用户总线在用户登陆时建立。
系统总线是一个持久的总线,在系统启动时就建立,供系统内核和后台进程使用,具备较高的安全性。系统总线最经常使用是发送系统消息,好比:插入一个新的存储设备、有新的网络链接等。
会话总线是在某个用户登陆后启动,属于某个用户私有,是某用户的应用程序用来通话的通道。在不少嵌入式系统中,只有一个用户ID登陆运行,所以只有一个会话总线。
一条消息总线就是一个消息路由器,是消息总线守护进程(message bus daemon)的一个实例。架构
使用d-bus的应用程序既能够是server端也能够是client端,server端监听到来的链接,client端链接到server端,一旦链接创建,消息就能够流转。若是使用dbus daemon,全部的应用程序都是client端,dbus daemon监听全部的链接,应用程序初始化链接到dbus daemon。
每一条总线都有一个地址,进程经过总线的地址链接到总线上。一个D-Bus的地址是指server端用于监听,client端将要链接的地方,例如unix:path=/tmp/abcedf标识server端将在路径/tmp/abcedf的UNIX domain socket监听,client端将要链接到这个地址。地址能够是指定的TCP/IP socket或者其余在或者将在D-Bus协议中定义的传输方式。
若是使用bus daemon,libdbus将经过读取环境变量DBUS_SESSION_BUS_ADDRESS自动获取session bus damon的地址,经过检查一个指定的UNIX domain socket路径获取system bus的地址。
若是使用D-bus,但不是daemon,须要定义哪一个应用是server端,哪一个是client端,并定义一套机制用于承认server端的地址。并发
总线上的每一个链接都有一个或多个名字。当链接创建之后,D-Bus 服务会分配一个不可改变的链接名,称为惟一链接名(unique connection name),惟一链接名即便在进程结束后也不会再被其余进程所使用。惟一链接名以冒号开头,如“:34-907”。但惟一链接名老是临时分配,没法肯定,也难以记忆,所以应用能够要求有另一个名字公共名(well-known name)来对应惟一链接名。例如可使用“com.mycompany”来映射“:34-907”。应用程序可能会要求拥有额外的公共名(well-known name)。例如,能够写一个规范来定义一个名字叫作 com.mycompany.TextEditor。协议能够指定本身拥有名字为com.mycompany.TextEditor的链接,一个路径为/com/mycompany/TextFileManager的对象,对象拥有接口org.freedesktop.FileHandler。应用程序就能够发送消息到总线上的链接名字,对象和接口以执行方法调用。
链接名能够用于跟踪应用程序的生命周期。当应用退出(或者崩溃)时,与总线的链接将被OS内核关掉,总线将会发送通知,告诉剩余的应用程序。
D-Bus的对象和面向对象语言中的对象含义是不一样的,D-Bus的对象表示的是D-Bus通道中信息流向的端点。对象由客户进程建立,并在链接进程中保持不变。
全部使用D-BUS的应用程序都包含一些对象, 当经由一个D-BUS链接收到一条消息时,消息是被发往一个对象而不是整个应用程序。应用程序框架中定义了这样的对象,如GObject,QObject等,在D-Bus中称为原生对象(native object)。
对于底层的D-Bus协议,即libdbus API,并不理会原生对象,使用对象路径(object path)的概念。经过对象路径,高层API接口能够绑定到对象,容许远程应用指向对象。对象路径如同文件系统路径,例如一个对象可能叫作“/org/kde/kspread/sheets/3/cells/4/5”。
对象路径在全局(session或者system)是惟一的,用于消息的路由。
每个对象支持一个或者多个接口,接口是一组方法和信号的集和,接口定义一个对象实体的类型。D-Bus对接口的命名方式,相似org.freedesktop.Introspectable。开发人员一般使用编程语言名字做为接口名字。
每个对象有两类成员:方法和信号。方法是一段函数代码,带有输入和输出;信号是广播给全部兴趣的其余实体,信号能够带有数据payload。
客户向某对象发送一个请求,即对象被请求执行一个明确的、有名称的动做。若是客户请求执行一个目标对象未提供的方法,将会产生一个错误。方法的定义能够支持输入参数。对于每一个请求,都有一个包含请求结果以及结果数据(输出参数)的响应返回给请求者。当请求没法完成时,响应中将包含异常信息,其中至少有异常名称以及错误信息。
大多数语言都将这些封装在自身的语言机制中,好比将参数包装进消息包,将异常信息转换成语言自身的异常等等。在这些实现中,向远程对象传递一个字符串参数就好像是在本地执行一个字符串参数的函数同样简单。此时再也不须要数据类型转换、数据复制等繁琐工做,语言自己封装了一切底层实现。
信号依然听从面向对象概念,信号是从对象发出但没有特定目的地址的单向数据广播。客户进程能够预先注册其感兴趣的信号,如特定名称的信号或从某个对象发出的信号等。当对象发出信号后,全部订阅了该信号的客户进程将收到此信号的复本。接收端可能有多种状况出现,或者有一个客户进程,或者有多个客户进程,或者根本没有客户进程对这个信号感兴趣。对于信号来讲没有响应消息,发出信号的对象不会知道是否是有客户进程在接收,有多少客户进程接收,以及从客户进程收到任何反馈。
信号能够有参数。但信号是单向通讯,所以不可能像方法同样具备输入输出参数。D-Bus容许客户进程经过参数比对过滤其须要的信号。
信号通常用来广播一些客户可能会感兴趣的事件,好比某个其它的客户进程与总线的链接断开等。这些信号来自总线对象,所以从信号中客户进程能够分辨断线是因为正常退出、被杀掉或者程序崩溃。
代理对象用来表示其余的remote object。当触发了proxy对象的method时,将会在D-Bus上发送一个method_call的消息,并等待答复,根据答复返回。
总线上的对象通常经过代理来访问。总线上的对象位于客户进程之外,而客户能够调用本地接口与对象通讯,此时,本地接口充当了代理的角色。当触发了代理对象的方法时,将会在D-Bus上发送一个method_call的消息,并等待答复返回,就象使用一个本地对象同样。
一些语言的代理支持“断线重连”。好比所链接的对象在某段时间里暂时断开了与总线的链接,代理会自动重连到相同的链接名并从新找到对象,程序甚至不会知道目标对象有段时间不可用。并非全部的语言都支持这一特性,在GLib中的两种代理中的一种支持。
好比不用代理时的代码以下:
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);
服务是 D-BUS 的最高层次抽象,服务的实现当前还在不断发展变化。应用程序能够经过一个总线来注册一个服务,若是成功,则应用程序就已经得到了服务。其余应用程序能够检查在总线上是否已经存在一个特定的服务,若是没有能够要求总线启动它。
服务是 D-BUS 的最高层次抽象,服务的实现当前还在不断发展变化。应用程序能够经过一个总线来注册一个服务,若是成功,则应用程序就已经得到了服务。其余应用程序能够检查在总线上是否已经存在一个特定的服务,若是没有能够要求总线启动它。
当经过总线进行通讯时,应用程序会获取服务名称。服务名称是应用程序如何选择同一总线上的其余应用程序的依据。服务名称由总线守护进程进行代理,用于将消息从一个应用程序路由到另外一个应用程序。服务名称的相似概念是IP地址和主机名:计算机一般具备一个IP地址,而且根据其向网络提供的服务,能够具备与之相关联的一个或多个主机名。
另外一方面,若是不使用总线,服务名称也不会使用。若是再将它与计算机网络进行比较,这将等同于点到点网络:由于对等方是已知的,因此不须要使用主机名来查找它或它的IP地址。
总线服务名称的格式实际上与主机名很是类似:它是字母和数字的点分隔序列。常见的作法是根据服务定义的组织的域名来命名本身的服务名称。例如,D-Bus服务由freedesktop.org定义,可使用org.freedesktop.DBus服务名称在总线上找到它。
D-Bus通讯机制是经过进程间发送消息实现的,最基本的D-Bus协议是一对一的通讯协议。与socket通讯不一样,D-Bus是面向消息的协议。但若是使用高层的绑定接口,不会直接接触到D-Bus的消息。
D-Bus 有四种类型的消息:
A、method_call方法调用
B、method_return方法返回
C、error错误
D、signal信号
代理中的远程对象调用涉及到了消息总线以及method_call和method_return两类消息。
消息有消息头(header)和消息体(body)。消息头包含消息体的路由信息,消息体是净荷,一般包含的是参数。消息头一般包含发送进程的链接名(Bus Name)、方法或者信号名等等,其中有一字段是用于描述消息体中的参数的类型的,例如“i”标识32位整数,“ii”表示2个32位整数。
进程A要调用进程B的一个method,进程A发送method_call消息到进程B,进程B回复method_return消息。在发送消息时,发送方会在消息中添加不一样的序列号,一样,回复消息中也会含有序列号,以便对应。
调用method的流程以下:
A、在发送method_call消息时,若是使用了代理,进程A要调用进程B的某方法,不用构造method_call消息,只需调用代理的本地方法,代理会自动生成method_call消息发送到消息总线上。
B、若是使用底层API,进程A须要构造一个method_call消息。method_call消息包含对应进程B的链接名、方法名、方法所需参数、进程B中的对象路径和进程B中声明此方法的接口。
C、将method_call消息发送到消息总线上。
D、信息总线检查消息头中的目的链接名,当找到一个进程与此链接名对应时发送消息到该进程。当找不到一个进程与此链接名对应时,返回给进程A一个error消息。
E、进程B解析消息,若是是采用底层API方式,则直接调用方法,而后发宋应答消息到消息总线。若是是D-BUs高级API接口,会先检测对象路径、接口、方法名称,而后把消息转换成对应的本地对象(如GObject,QT中的QObject等)的方法,调用本地对象方法后再将应答结果转换成应答消息发给消息总线。
F、消息总线接收到method_return消息,将把method_return消息直接发给发出调用消息的进程。
消息总线不会对总线上的消息进行重排序。若是发送了两条消息到同一个进程,将按照发送顺序接收到。接收进程不须要按照顺序发出应答消息,例如在多线程中处理这些消息,应答消息的发出是没有顺序的。消息都有一个序列号能够与应答消息进行配对。
发送信号是单向广播的,信号的发送者不知道对信号做响应的有哪些进程,因此信号发送是没有返回值的。信号接收者经过向消息总线注册匹配规则来表示对某信号感兴趣,而匹配规则一般包含信号的发出者和信号名。
信号发送的流程以下:
A、当使用dbus底层接口时,信号须要应用进程本身建立和发送到消息总线;使用dbus高层API接口时,可使用相关对象进行发送。信号消息包含有声明信号的接口名、信号名、所在进程对应的链接名和相关参数。
B、链接到消息总线上的进程若是对某个信号感兴趣,则注册相应的匹配规则。消息总线保持有匹配规则表。
C、消息总线根据匹配规则表,将信号发送到对信号感兴趣的进程。
D、每一个进程收到信号后,若是使用dbus高级API接口,能够选择触发代理对象上的信号。若是使用dbus底层接口,须要检查发送者名称和信号名称,而后决定怎么作。
D-Bus提供了两个小工具:dbus-send 和dbus-monitor。能够用dbus-send发送消息,用dbus-monitor监视通道上流动的消息。
dbus-send
用于发送一个消息到消息通道上,使用格式以下:
dbus-send [--system | --session] --type=TYPE --print-reply --dest=链接名对象路径接口名.方法名参数类型:参数值参数类型:参数值 dbus-send --session --type=method_call --print-reply --dest=链接名对象路径接口名.方法名 参数类型:参数值 参数类型:参数值
dbus-send支持的参数类型包括:string, int32, uint32, double, byte, boolean。
dbus-monitor
用于打印消息通道上的消息,使用格式以下:
dbus-monitor [--system | --session | --address ADDRESS] [--profile | --monitor] [watch expressions] dbus-monitor "type='signal', sender='org.gnome.TypingMonitor', interface='org.gnome.TypingMonitor'"
消息总线是一个特殊的应用,主要关于消息总线上的方法和信号。
A、Introspection
消息总线上有一个接口org.freedesktop.DBus.Introspectable,接口中声明了一个方法Introspect,不带参数,将返回一个XML string,XML字符串描述接口、方法、信号。
B、消息总线上的方法和信号
能够经过向名称为“org.freedesktop.DBus”的链接上的对象“/”发送消息来调用消息总线提供的方法。消息总线对象支持标准接口"org.freedesktop.DBus.Introspectable",能够调用org.freedesktop.DBus.Introspectable.Introspect方法查看消息总线对象支持的接口。dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect
用户总线对象支持标准接口“org.freedesktop.DBus.Introspectable”和接口 “org.freedesktop.DBus”。接口“org.freedesktop.DBus”有18个方法和3个信号。
<interface name="org.freedesktop.DBus"> <method name="Hello"> <arg direction="out" type="s"/> </method> <method name="RequestName"> <arg direction="in" type="s"/> <arg direction="in" type="u"/> <arg direction="out" type="u"/> </method> <method name="ReleaseName"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="StartServiceByName"> <arg direction="in" type="s"/> <arg direction="in" type="u"/> <arg direction="out" type="u"/> </method> <method name="UpdateActivationEnvironment"> <arg direction="in" type="a{ss}"/> </method> <method name="NameHasOwner"> <arg direction="in" type="s"/> <arg direction="out" type="b"/> </method> <method name="ListNames"> <arg direction="out" type="as"/> </method> <method name="ListActivatableNames"> <arg direction="out" type="as"/> </method> <method name="AddMatch"> <arg direction="in" type="s"/> </method> <method name="RemoveMatch"> <arg direction="in" type="s"/> </method> <method name="GetNameOwner"> <arg direction="in" type="s"/> <arg direction="out" type="s"/> </method> <method name="ListQueuedOwners"> <arg direction="in" type="s"/> <arg direction="out" type="as"/> </method> <method name="GetConnectionUnixUser"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="GetConnectionUnixProcessID"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="GetAdtAuditSessionData"> <arg direction="in" type="s"/> <arg direction="out" type="ay"/> </method> <method name="GetConnectionSELinuxSecurityContext"> <arg direction="in" type="s"/> <arg direction="out" type="ay"/> </method> <method name="ReloadConfig"> </method> <method name="GetId"> <arg direction="out" type="s"/> </method> <signal name="NameOwnerChanged"> <arg type="s"/> <arg type="s"/> <arg type="s"/> </signal> <signal name="NameLost"> <arg type="s"/> </signal> <signal name="NameAcquired"> <arg type="s"/> </signal> </interface> <interface name="org.freedesktop.DBus.Introspectable"> <method name="Introspect"> <arg direction="out" type="s"/> </method> </interface>