2013年(5)
分类: LINUX
2013-03-28 16:34:01
原文地址:D-BUS分析—2 原理 作者:zsfly
一、名词解释
- 消息
消息由头部和消息体组成,如果你把消息当作一个package,那头部就是地址,消息体就是包的内容。消息发送系统使用头部的信息来知道把消息送往何处,如何解释消息,接收者则解释消息体。消息体可以没有或有多个参数,这些参数是具有类型的值,如integer或byte数组。消息头和消息体都使用相同类型的type系统及格式来序列化数据。每种值的类型都有wire格式,从某种别的表示把值转换为wire格式叫作列集(marshalling),而把wire格式转回去则叫散集(unmarshalling).消息头部是有固定签名和意义的值块。消息体是另外的值块,带有在头部中指定的签名。头部必须字节对齐,这样当在一个缓冲区中存贮整个消息时可以允许消息体以8对齐开始。如果头部不是自然地终止于8字节边界上,则必须加上最多7字节的nul初始化的对齐填充。消息体不需要字节对齐。消息的最大长度,包括头,头对齐填充以及消息体是2的27次幂即134217728。实现不能发送或接收超过此大小的消息。头部的签名是: "yyyyuua(yv)",以更为可读的方式写出是:
BYTE, BYTE, BYTE, BYTE, UINT32, UINT32, ARRAY of STRUCT of (BYTE,VARIANT)
含义(摘自D-BUS spec:):
Value Description 1st BYTE
Endianness flag; ASCII 'l' for little-endian or ASCII 'B' for big-endian. Both header and body are in this endianness. 2nd BYTE
Message type. Unknown types must be ignored. Currently-defined types are described below. 3rd BYTE
Bitwise OR of flags. Unknown flags must be ignored. Currently-defined flags are described below. 4th BYTE
Major protocol version of the sending application. If the major protocol version of the receiving application does not match, the applications will not be able to communicate and the D-Bus connection must be disconnected. The major protocol version for this version of the specification is 1. 1st UINT32
Length in bytes of the message body, starting from the end of the header. The header ends after its alignment padding to an 8-boundary. 2nd UINT32
The serial of this message, used as a cookie by the sender to identify the reply corresponding to this request. ARRAY
ofSTRUCT
of (BYTE
,VARIANT
)An array of zero or more header fields where the byte is the field code, and the variant is the field value. The message type determines which fields are required. 在消息头尾部的数组包含了头部域。
消息有四种类型:
METHOD_CALL
,METHOD_RETURN
,ERROR
, andSIGNAL
a.METHOD_CALL(方法调用消息)
这类消息会调用远程对象的操作,这些消息需要映射到对象的方法上去。
方法调用消息需要有MEMBER头部域用以指明方法的名称,此消息还可能有一个INTERFACE域来给出接口,被调用的方法也是接口的一部分。在没有INTERFACE域时,如果同一对象上的两个接口有同名的方法名字,哪一个方法会被调用是没有定义的。在这种具有二义性的环境里,实现可以选择返回一个错误。但是,如果方法名是独一无二的,实现不能强制要求需要interface域。还包括一个PATH域,该域用以指明在哪个对象上调用该方法。如果此调用正在消息总线中传播,消息也有一个DESTINATION域用以给出接收此消息的连接名称。当应用程序处理方法调用消息时,它需要答复,答复由REPLY_SERIAL头部域确定 ,此头部域也表明了被答复的方法调用的序列号。答复类型有两种,METHOD_RETURN和ERROR。
b.METHOD_RETURN &
ERROR
答复消息的参数就是方法调用的返回值或"out parameters"
,如果答复类型为ERROR,那么会抛出例外,方法调用失败,此时没有返回值提供。对于同一个方法调用发送多个答复是没有意义的。 即使一个方法调用没有返回值,一个METHOD_RETURN的答复也是必须的,这样调用者就能知道方法是否被成功地处理了。
METHOD_RETURN 和
ERROR答复消息必须有
REPLY_SERIAL
头部域。
d.SIGNAL
信号发射不象方法调用需要答复。信号发射只是简单单一信息类型SIGNAL。它必须有三个头域,PATH给出发送信号的对象,加上INTERFACE和MEMBER给出信号的全称名字。INTERFACE头部域在信号中是必须的,尽管它在方法调用中是可选的。
- Bus Name
可以说是连接的名字,一个连接只有一个总线名字并且是惟一的,在此连接的整个生命周期,这个名字不变。总线名字是STRING类型的,意味着它必须是有效的UTF-8字符串。有两种作用不同的Bus Name,一个叫公共名(well-known names),还有一个叫唯一名(Unique Connection Name)。- 对象
对象是处理消息的一个实例。对象有一个或多个接口,在每个接口有一个或多个的方法,每个方法实现了具体的消息处理。在一对一的通讯中,对象通过一个连接直接和另一个客户端应用程序连接起来。在多对多的通讯中,对象通过一个连接和Dbus守护进程连接起来。对象有一个路径用于指明该对象的存放位置,消息传递时通过该路径找到该对象。- 接口
每个对象都有一个或者多个接口,一个接口就是多个方法和信号的集合。dbus使用简单的命名空间字符串来表示接口,如org.freedesktop.Introspectable。可以说dbus接口相当于C++中的纯虚类。- 方发和信号
每个对象都有一些成员,两种成员:方法(methods)和信号(signals),在对象中,方法可以被调用。信号会被广播,感兴趣的对象可以处理这个信号,同时信号中也可以带有相关的数据。每一个方法或者信号都可以用一个名字来命名,如”Frobate” 或者 “OnClicked”。- 对象路径
一个对象路径是一个用于引用对象实例的名字,从概念上讲,D-Bus信息交换中每个参与者可能有任意数量的对象实例并且每个这样的实例都有一个路径,就象文件系统一样,一个应用中的对象实例形成一个层次树。- 服务
服务是 D-BUS 的最高层次抽象,它们的实现当前还在不断发展变化。应用程序可以通过一个总线来注册一个服务,如果成功,则应用程序就已经 获得 了那个服务。其他应用程序可以检查在总线上是否已经存在一个特定的服务,如果没有可以要求总线启动它。- dbus-daemon
dbus的后台程序(守护进程),libdbus运行时会自动创建dbus-daemon进程。daemon就像一个路由器,将从发送者连接得到的消息转发到由消息中的接收者连接名指定的接收者连接中。- 连接
连接是一个双向的消息传递通道。一个连接将对象和Dbus(dubs-daemon)或客户端应用连接起来,连接支持非阻塞式的异步消息发送和阻塞式的同步消息发送。消息通过连接到达目的端后,连接会将挂起在该连接上的进程唤醒,由该进程将消息取走。每个连接都有一个唯一的名字和可选的其他多个名字,用于在多对多通讯时指明消息的发送者和接收者- 认证协议(Authentication Protocol)
消息流开始之前,两个应用之间必须进行认证,简单的纯文本协议用来认证,即SASL档案,相当简单地直接从SASL规范映射过来。没有使用消息编码,只是纯文本信息。
二、运行机制
客户端应用是请求消息的发起者。客户端应用通过和自身的相连的一个连接将请求消息发送出去,也通过该连接接收回应的消息、错误消息、系统更新消息等。在一对一的通讯中,请求消息直接到达对象。在多对多的通讯中,请求消息先到达Dbus,Dbus将消息转发到目的对象。每个连接使用bus name来标识的,bus name有两种,一种是公共名(well-known Name),一种是唯一名(Unique Connection Name)。
所有使用D-BUS的应用程序都包含一些对象,它们一般映射为GObject、QObject、C++对象、或者[[Python">Python对象。当经由一个D-BUS连接收到一条消息时,该消息是被发往一个对象而不是整个应用程序。这一点和我们常见的消息机制是不太一样的。这个对象其实很像C++中虚函数
为了允许消息能指定接收对象,还要提供引用对象的方法。但是这个引用一般实现为与应用程序相关的内存地址,因此无法在应用程序之间传递。为了解决这一问题,D-BUS为每个对象引入名字。这些名字看起来像是文件系统路径,例如一个对象可能叫做 “/org/kde/kspread/sheets/3/cells/4/5”。路径名以容易阅读为佳,没有具体的硬性规定。
在dbus中调用一个方法包含了两条消息,进程A向进程B发送方法调用消息,进程B向进程A发送应答消息。所有的消息都由daemon进行分派,每个调用的消息都有一个不同的序列号,返回消息包含这个序列号,以方便调用者匹配调用消息与应答消息。调用消息包含一些参数,应答消息可能包含错误标识,或者包含方法的返回数据。
1.方法调用的一般流程:
a.使用不同语言绑定的dbus高层接口,都提供了一些代理对象,调用其他进程里面的远端对象就像是在本地进程中的调用一样。应用调用代理上的方法,代理将构造一个方法调用消息给远端的进程。
b.在DBUS的底层接口中,应用需要自己构造方法调用消息(method call message),而不能使用代理。
c.方法调用消息里面的内容有:目的进程的bus name,方法的名字,方法的参数,目的进程的对象路径,以及可选的接口名称。
d.方法调用消息是发送到bus daemon中的。
e.bus daemon查找目标的bus name,如果找到,就把这个方法发送到该进程中,否则,daemon会产生错误消息,作为应答消息给发送进程。
f.目标进程解开消息,在dbus底层接口中,会立即调用方法,然后发送方法的应答消息给daemon。在dbus高层接口中,会先检测对象路径,接口,方法名称,然后把它转换成对应的对象(如GObject,QT中的QObject等)的方法,然后再将应答结果转换成应答消息发给daemon。
g.bus daemon接受到应答消息,将把应答消息直接发给发出调用消息的进程。
h.应答消息中可以包容很多返回值,也可以标识一个错误发生,当使用绑定时,应答消息将转换为代理对象的返回值,或者进入异常。
bus daemon不对消息重新排序,如果发送了两条消息到同一个进程,他们将按照发送顺序接受到。接受进程并需要按照顺序发出应答消息,例如在多线程中处理这些消息,应答消息的发出是没有顺序的。消息都有一个序列号可以与应答消息进行配对。
在dbus中一个信号包含一条信号消息,一个进程发给多个进程。也就是说,信号是单向的广播。信号可以包含一些参数,但是作为广播,它是没有返回值的。
信号触发者是不了解信号接受者的,接受者向daemon注册感兴趣的信号,注册规则是”match rules”,记录触发者名字和信号名字。daemon只向注册了这个信号的进程发送信号。
2.信号的一般流程如下:
a.当使用dbus底层接口时,信号需要应用自己创建和发送到daemon,使用dbus高层接口时,可以使用相关对象进行发送,如Glib里面提供的信号触发机制。
b.信号包含的内容有:信号的接口名称,信号名称,发送进程的bus name,以及其他参数。
c.任何进程都可以依据”match rules”注册相关的信号,daemon有一张注册的列表。
d.daemon检测信号,决定哪些进程对这个信号感兴趣,然后把信号发送给这些进程。
e.每个进程收到信号后,如果是使用了dbus高层接口,可以选择触发代理对象上的信号。如果是dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。
三、将消息调用映射为本地API
D-Bus的API可以把方法调用映射为特定编程语言(如C++)中的方法调用,或者把IDL中的方法调用映射为D-Bus消息。
在这些方法调用中,方法的参数被标为"in"(指明是METHOD_CALL传入参数)或者"out"(指明是METHOD_CALL的传出参数),象CORBA的一些API具有"inout"参数,它表明该参数既传入又传出,即调用者传入的值将会被修改。映射到D-Bus上,"inout"参数等价于"in"参数后跟着一个"out"参数,你不能在总线上传引用,所以"inout"参数完全是幻想。如果一个方法具有0个或一个返回值,后跟着0个或多个参数,这里的每个参数可能是"in", "out"或"inout",调用者通过按顺序加上"in"或者"inout"参数来构造消息。"out"参数不会出现在调用者的消息里。接收者构造出答复,如果有返回值,则把第一个返回加到答复上,然后按顺序加上每个"out"或"inout"参数,"in"参数不会出现在答复消息里。如果使用的语言中有例外错误答复消息正常情况下被映射为例外。在转换本地API到D-Bus的API时,把D-Bus命名规范("FooBar")自动地映射到本地命名规范(fooBar/foo_bar)或许好一些。只要本地API是专门写给D-Bus就行。这样当写一个对象实现且此实现将被导出到总线上是最好的了。对象代理用于调用远程D-Bus对象,而远程D-Bus对象可能需要可以调用任何D-Bus方法的能力,因此像这样魔术般的名字映射可能是个问题。