D-Bus理论基础

  本章节整理了学习DBUS之前所需的理论基础,深入理解包括Linux系统进程间通信(Inter-Processing Communication)机制、Dbus术语等内容。

1. 进程间通信(IPC)方法

  本节介绍linux系统的进程通信方法,概述其区别和各自的优缺点。

通信机制介绍区别优缺点使用场景
管道(Pipe)匿名管道从一个进程连接到另一个进程的一个数据流称为一个“管道”,也称为共享文件。本质上是操作系统在内核中为进程创建的一块文件缓冲区,因此也只能实现半双工通信。只能在具有亲缘关系的进程间通信(父子进程间通信)优点:简单方便
缺点:
1. 半双工单向传输;
2. 仅父子进程之间可用;
3. 只能承载无格式字节流;
4. 缓冲区大小有限制(linux系统中大小限制为65536字节)。
在父子进程间实现单向通信(要实现父子间的双向通信,可以建立两个匿名管道)
命名管道(FIFO)允许在不相关的进程间通信优点:无亲缘关系的进程之间可用
缺点:因长期存在系统内核中,使用不当容易出错
在两个非父子关系的不同进程间实现通信
信号(Signal)由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。信号用于通知进程产生了某个事件优点:触发某些行为,也是唯一的异步通信机制
缺点:传递信息极少
用户传递的信息较少;用于通知接收进程某个事件已经发生;发送信号给进程本身
信号量(Semaphore)和PV操作是一个特殊的变量,本质是计数器,信号量里面记录了临界资源的数目,进程对其访问都是原子操作(pv操作,p:占用资源,v:释放资源)。
它的作用是调协进程对共享资源的访问,让一个临界区同一时间只有一个进程在访问它(多进程同步)。
信号量是用来同步进程(线程)的主要作为不同进程间、同一进程内不同线程间的同步手段,常与共享内存配合使用
消息队列(Message Queues)消息队列是消息的链接表,包括Posix消息队列systemV消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
允许任意进程通过共享消息队列来实现进程间通信.并由系统调用函数来实现消息发送和接收之间的同步.从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题。
克服了信号承载信息量少管道只能承载无格式字节流以及缓冲区大小受限等缺点优点:不再局限于父子进程,不再需要考虑同步问题,使用方便
缺点:
1. 消息队列中信息拷贝需要额外消耗CPU的时间; 2. 不适宜于信息量大或操作频繁的场合。
UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程,适用于传输用户自定义的数据结构。
共享内存(Shared Memory)使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息。优点:无须复制速度快,信息量大
缺点:
1. 存在冲突问题,必须由各进程利用其他同步方法解决(如借助信号量); 2. 由于内存实体存在于计算机系统中,所以只能由处于同一个计算机系统中的诸进程共享,不方便网络通信。
与其他通信机制(如信号量)配合使用,来实现进程间的同步与通信
套接字(Sockets)对于网站,通信模型是服务器与客户端之间的通信。两端都建立了一个 Socket 对象,然后通过 Socket 对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。优点:跨网络在不同计算机之间的进程通信
缺点:无限循环等待
用于不同进程的通信,包括不同计算机之间的进程通信
注意:
  在进程通信机制上存在System V和POSIX两种标准,注意两者的区别。

参考链接:
【万字详解Linux系列】进程间通信(IPC)
Linux 进程间通信(IPC)总结
进程间通讯的7种方式

2. 什么是D-Bus?

2.1 简介

  D-Bus是Desktop Bus的缩写,是针对桌面环境优化的IPC(InterProcess Communication)机制,用于进程间的通信或进程与内核的通信。它是为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支持点对点的消息传递以及广播/订阅式的通信。

优点:

  • 低延迟:DBus一开始就是用来设计成避免来回传递和允许异步操作的。因此虽然在Application和Daemon之间是通过socket实现的,但是又去掉了socket的循环等待,保证了操作的实时高效。
  • 低开销:DBus使用一个二进制的协议,不需要转化成像XML这样的文本格式。因为DBus是主要用来机器内部的IPC,而不是为了网络上的IPC机制而准备的.所以它才能够在本机内部达到最优效果。
  • 高可用性:DBus是基于消息机制而不是字节流机制。它能自动管理一大堆困难的IPC问题。同样的,DBus库被设计来让程序员能够使用他们已经写好的代码。而不会让他们放弃已经写好的代码,被迫通过学习新的IPC机制来根据新的IPC特性重写这些代码。

特性:

  • D-BUS的协议是低延迟而且低开销的,设计小巧且高效,以便最小化传送时间。从设计上避免往返交互并允许异步操作。
  • 协议是二进制的,而不是文本,排除序列化过程。
  • 考虑了字节序问题。
  • 易用性:按照消息而不是字节流来工作,并且自动地处理了许多困难的IPC问题,并且D-Bus库以可以封装的方式来设计,开发者可以使用框架里存在的对象/类型系统,而不用学习一种新的专用于IPC的对象/类型系统。
  • 请求时启动服务以及安全策略。
  • 支持多语言(C/C++/Java/C#/Python/Ruby),多平台(Linux/windows/maemo)。
  • 采用C语言,而不是C++。
  • 由于基本上不用于internet上的IPC,因此对本地IPC进行了特别优化。
  • 提供服务注册,理论上可以进行无限扩展。

功能与用途:

  • 服务注册和发现 (Service Registration and Discovery):应用程序和服务可以在 D-Bus 上注册自己,使得其他组件可以发现并与之通信。
  • 消息传递 (Message Passing):支持异步和同步消息传递,包括方法调用、信号(事件通知)和错误报告。

2.2 架构

  D-Bus是一种三层架构的IPC系统,包括:
  (1) 接口层:由libdbus库提供,实现了底层的API以及协议,他除了需要XML解析器以外没有必须的依赖。进程通过libdbus库使用D-Bus的能力,通过底层库的接口可以实现两个进程之间进行连接并发送消息。
  (2) 总线层:由消息总线守护进程(message bus daemon )提供,消息总线守护进程是基于libdbus底层库的,可以路由消息。消息总线守护进程负责进程间的消息路由和传递,其中包括Linux内核和Linux桌面环境的消息传递。
  (3) 封装层:封装层是一系列基于特定应用程序框架的Wrapper库,将D-Bus底层接口封装成方便用户使用的通用API,也就是各种包装库,例如libdbus-glib、libdbus-python以及后面重点学习的sdbus-c++。官方建议应用程序使用这层进行调用。

3. 理解D-Bus的重要术语

温馨提示:
  在学习理解DBUS相关术语之前,首先介绍一个DBUS工具d-feet,可以方便查看调试DBUS相关信息,此处也可以用来加深理解。
ubuntu系统使用d-feet工具预览系统DBUS信息

3.1 消息总线(Message Bus)

  总线是D-Bus的进程间通信机制,一个系统中通常存在多条总线,这些总线由D-Bus总线守护进程管理。最重要的总线为系统总线(System Bus),不同的程序可以通过总线进行某些操作,比如方法调用、发送信号和监听特定的信号。总线通常有两种,系统总线(system bus)和会话总线(session bus),系统总线通常只有一条,用户总线在用户登录时创建。

  • 系统总线(System Bus):系统总线是一个持久的总线,在系统启动时就创建,供系统内核和后台进程使用,具有较高的安全性。只有Linux内核、Linux桌面环境和权限较高的程序才能向该总线写入消息,以此保障系统安全性,防止有恶意进程假冒Linux发送消息。系统总线最常用是发送系统消息,比如:插入一个新的存储设备、有新的网络连接等。
  • 会话总线(Session Bus):会话总线是在某个用户登录后启动,属于某个用户私有,是某用户的应用程序用来通话的通道。在很多嵌入式系统中,只有一个用户ID登录运行,因此只有一个会话总线。

  一条消息总线就是一个消息路由器,是消息总线守护进程(message bus daemon process)的一个实例。
系统总线和会话总线的区别:

属性系统总线会话总线
运行时长伴随系统启动而启动,随系统关闭而关闭随用户会话开始而启动,随会话结束而关闭
权限要求高,需要系统级别的权限较低,一般用户级别权限即可
扮演角色场景系统服务、硬件事件等:网络管理、电源管理用户界面应用程序间的交互:即时通讯应用、媒体播放器
特点面向整个系统的服务,涉及底层操作面向单个用户会话,涉及用户交互

3.2 消息总线守护进程(Message Bus Daemon Process)

  dbus-daemon 是 D-Bus 的核心守护进程,负责处理客户端和服务端之间的交互。可以将它理解为:在系统和用户会话中进行消息传递和服务管理的角色,在最底层,D-Bus只支持点对点的通信,一般使用本地套接字(AF_UNIX)在应用和bus daemon之间通信。根据总线分类,守护进程也有两种,分别管理两种总线:

  • 系统总线守护进程:
    i. 由系统启动(通常由 systemd 启动),在系统范围内运行。
    ii. 提供系统级别的进程间通信,常用于系统服务和守护进程之间的通信。
    iii. 配置文件通常位于 /etc/dbus-1/system.conf,服务(Service)描述文件位于 /usr/share/dbus-1/system-services
  • 会话总线守护进程:
    i. 每个用户会话各有一个独立的会话总线守护进程,通常在用户登录时启动。
    ii. 提供用户级别的进程间通信,常用于用户应用程序之间的通信。
    iii. 配置文件通常位于 /etc/dbus-1/session.conf,服务描述文件位于 /usr/share/dbus-1/services~/.local/share/dbus-1/services

3.3 地址(Address)

  Address的格式像这样:unix:path=/var/run/dbus/system_bus_socket,每一条总线都有一个地址,进程通过总线的地址连接到总线上。根据是否使用dbus-daemon,DBUS不同进程间通信的方式分为以下两种,可以帮助理解Address的作用:

  • 一对一通信:任意一个都可以是server端,也可以是client端。server端绑定到Address上监听到来的连接,client端通过该Address连接到了server端;
  • 多对多通信:使用dbus daemon,dbus daemon会绑定Address并监听所有的连接,此时所有的进程(或应用程序)都是client端,每个进程(或应用程序)初始化连接时,都是靠这个Address连接到dbus daemon。

  系统总线和会话总线各自都有默认的Address,系统总线地址通常就是固定的/var/run/dbus/system_bus_socket,会话总线地址存储在系统变量DBUS_SESSION_BUS_ADDRESS中。
  由此个人理解,DBUS中的Address就类似socket连接中的端口号,一个绑定后做server监听,而client通过这个Address(类比端口号)进行建连和通信。

3.4 连接名(Bus Name)

  简单来说就是每一个连接的唯一名称,在此连接的整个生命周期,这个名字不变。
  假设我们在多个应用之间的通信是交给dbus daemon来管理,当一个App1连接上dbus-daemon以后,相当于有了一个Connection,但其他的App2、App3怎么找到App1,靠的就是bus name。这个bus name标识了App1的Connection,也就相当于标识了App1。
  有两种作用不同的Bus Name:

  • 公共名(well-known names):方便人看的通用标识,必须是有效的UTF-8字符串,如com.app1。(一个连接可以同时有多个不同的公共名)
  • 唯一名(Unique Connection Name):以冒号开头的唯一标识,总是临时分配无法确定(所以应用程序可以指定额外的公共名),如:34-907

3.5 对象(Object)和对象路径(Object Path)

  D-Bus的对象表示的是D-Bus通道中信息流向的端点。对象由客户进程创建,并在连接进程中保持不变。
  所有使用D-BUS的应用程序都包含一些对象, 当经由一个D-BUS连接收到一条消息时,消息是被发往一个对象而不是整个应用程序。应用程序框架中定义了这样的对象,如GObject,QObject等,在D-Bus中称为原生对象(native object)。
  对于底层的D-Bus协议,即libdbus API,并不理会原生对象,使用对象路径(object path)的概念。通过对象路径,高层API接口可以绑定到对象,允许远程应用指向对象。对象路径如同文件系统路径,如上图1所示存在一个对象其对象路径为/org/a11y/bus
  对象路径在全局(session或者system)是唯一的,用于消息的路由。

3.6 接口(Interface)

 d-ffet工具下某个DBus对象的接口示意图
  每个对象都有一个或者多个接口,一个接口就是多个方法和信号的集合。dbus使用简单的命名空间字符串来表示接口,如图2中所示的一个对象路径下多个接口,包括org.freedesktop.DBus.Properties,org.freedesktop.DBus.Peer等。可以说dbus接口相当于C++中的纯虚类。

3.7 方法(Method)和信号(Signal)

  每个对象都有一些成员,两种成员:方法(methods)和信号(signals),在对象中,方法可以被调用。信号会被广播,被定义为感兴趣的对象可以处理这个信号,同时信号中也可以带有相关的数据(payload)。
在DBus里的方法和信号作用上,可以分别与C++的函数、系统信号(如触发Kill -9信号)进行相似理解。

3.8 代理(Proxy)

  所谓代理,就是在当前进程使用一个对象去代表要通信的另一进程,这样方法调用就可以直接通过这个代理对象执行。
根据代码轻松理解代理的作用:

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();           
} 
  • 使用代理,指定对象,建连后直接在代理对象上调用Method即可:
Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
// 执行另一进程的Method调用,就像调用本进程的对象函数一样方便           
Object returnValue = proxy.MethodName(arg1, arg2);   

3.9 服务(Service)

在消息总线守护进程中提到了服务描述文件,服务是DBUS的最高层次抽象,每个应用程序可以向总线注册一个服务,注册成功后,其他应用程序就可以检查总线上是否存在指定服务,若不存在可以要求启动它。

  疑惑1:一个进程(或应用程序)在连接到总线后,已经有自己的Bus Name进行标识,不同进程或应用就可以互相指定通信目标,为什么还要有服务这个东西?还存在查找服务这一说?
  Bus Name是标识一个连接,但Service可以用来标识一组特定的功能或接口的集合(即能提供一组特定功能和接口的实体),通过将不同的功能和接口组织成服务,能够更清晰地划分和组织不同的功能模块,增强系统的可维护性和可扩展性,让系统结构更加清晰。
   疑惑2:Service和Bus Name中的公共名,在名字定义上有什么区别或关联吗?
  名字定义上看,两者的规则相同,例如蓝牙模块的Bus Name表示为org.bluez,而Service表示为org.bluez.service。个人理解,根据服务的定义,不同的总线连接,其实可以把自己的部分接口或全部接口绑到同一个服务名称上面,然后其他连接可以根据服务去搜索属于该服务的接口。可以确定的是,Bus Name与Service不是一个概念,不可混淆。
  疑惑3:服务与总线之间的区别和联系?
  a. 区别:服务代表一种功能或者接口,而总线则是用于在不同进程之间进行通信的机制。服务通过在总线上注册来提供自己的功能,其他进程可以通过总线来调用这些功能。
  b. 联系:服务和总线的关系是一对多的关系,一个服务可以在多个总线上注册,但一个总线只能包含一个服务。

在linux系统中深入理解Dbus的service概念:
系统总线的服务
  上图所示为系统总线的服务描述文件,其中可以查看系统总线对蓝牙服务的定义。而在会话总线的服务描述文件中同样存在一个蓝牙服务的定义:
会话总线的服务
  由此可见,DBus中的Service就是其通信中的一个机制,与linux系统的systemd对系统service的启动和管理,概念上如出一辙。在代码编写层面,一个应用程序可以对多个D-Bus连接统一管理,通常情况下也会把应用程序与一个Service相对应。关于linux系统的systemd,可访问官方文档systemd

3.10 关键术语的总结

使用关键术语表示DBus通信框架
  结合上文对DBus关键术语的认识,他们之间存在的关联以及各自所属职责,可以用上面图3表示,某些不清晰的地方也许会有新的理解。

4. D-Bus是如何完成进程间通信?

4.1 消息(Message)

  根据前文所述,我们已经熟悉了消息总线的概念,总线也可以被看做一台路由器,使得DBus在一对一通信的基础上实现了多对多通信,而DBus的进程间通信是依靠发送消息来实现的。本节有大量官方说明文档的原文内容,详见D-Bus Specification

4.1.1 数据类型

  首先,DBus消息中使用了统一的数据类型,基本数据类型分为定长类型和可变长string类型:
定长基本数据类型
可变长string类型

4.1.2 消息体的定义

  消息是由header和body两部分组成,header内依次包含:大小端格式、消息类型、特定含义的flag、发送端协议版本、body长度、消息序列号(用于识别对应的回复,不可为0)、头部域(0或多个header fields组成的数组,由消息类型决定)。消息的最大长度(包括header、alignment padding和body)为 2 ^ 27 或 134217728(128 MB)。其中,header具体定义如下所示:
消息体组成结构

4.1.3 消息类型

  在header中所含的Message Type共有以下四种,他们分别导致了header中的头部域header fields必须携带指定内容,头部域的字段定义详见后文4.1.4 头部域

  • method_call方法调用:
      这类消息会调用远程对象的操作,这些消息需要映射到对象的方法上去。方法调用消息需要有MEMBER头部域用以指明方法的名称,此消息还可能有一个INTERFACE域来给出接口,被调用的方法也是接口的一部分。在没有INTERFACE域时,如果同一对象上的两个接口有同名的方法名字,哪一个方法会被调用是没有定义的。在这种具有二义性的环境里,实现可以选择返回一个错误。但是,如果方法名是独一无二的,实现不能强制要求需要INTERFACE域。还包括一个PATH域,该域用以指明在哪个对象上调用该方法。如果此调用正在消息总线中传播,消息也有一个DESTINATION域用以给出接收此消息的连接名称。当应用程序处理方法调用消息时,它需要答复,答复由REPLY_SERIAL头部域确定 ,此头部域也表明了被答复的方法调用的序列号。
  • method_return方法返回:必须有REPLY_SERIAL头部域。
  • error错误:必须有REPLY_SERIAL头部域。
  • signal信号:
      信号发送不像方法调用需要答复。信号发送通常情况下只是简单的单一SIGNAL信息类型。它必须有三个头域,PATH给出发送信号的对象,加上INTERFACE和MEMBER给出信号的全称名字。INTERFACE头部域在信号中是必须的,尽管它在方法调用中是可选的。

4.1.4 头部域

  Header的头部域主要可以包含字段及其具体释义如下:
头部域组成

4.2 运行机制

4.2.1 Method调用流程

  进程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消息直接发给发出调用消息的进程。

4.2.2 Signal发送流程

  发送信号是单向广播的,信号的发送者不知道对信号作响应的有哪些进程,所以信号发送是没有返回值的。信号接收者通过向消息总线注册匹配规则(在d-feet工具中,会偶尔看到对象路径中包含一个叫Matcher或Match Rules的对象路径)来表示对某信号感兴趣,而匹配规则通常包含信号的发出者和信号名。
信号发送的流程如下:
  a. 当使用dbus底层接口时,信号需要应用进程自己创建和发送到消息总线;使用dbus高层API接口时,可以使用相关对象进行发送。信号消息包含有声明信号的接口名、信号名、所在进程对应的连接名和相关参数。
  b. 连接到消息总线上的进程如果对某个信号感兴趣,则注册相应的匹配规则。消息总线保持有匹配规则表。
  c. 消息总线根据匹配规则表,将信号发送到对信号感兴趣的进程。
  d. 每个进程收到信号后,如果使用dbus高级API接口,可以选择触发代理对象上的信号。如果使用dbus底层接口,需要检查发送者名称和信号名称,然后决定怎么做。

4.2.3 通信流程的总结

通信流程示意图
  根据前文所有内容的学习,再来阅读分析图4中的各个箭头和模块表示的含义,加深对整个通信流程的理解。

参考链接:
强烈推荐 D-Bus官方说明文档链接:D-Bus Specification
一、从零认识D-Bus DBus 本地进程通信机制
D-BUS详细分析 DBUS基础知识(非常全面)
dbus实例讲解
探索 Linux 高级进程间通讯 D-Bus的神秘妙用
D-Bus深度解析:系统总线与会话总线的区别与应用
D-Bus—— daemon 守护进程
D-Bus——Bus服务查找和启动

DBus专栏导航:
D-Bus理论基础
Linux系统DBus工具的使用
sdbus-c++中文版使用说明(一)——概括介绍与编译
sdbus-c++中文版使用说明(二)——基础层API
sdbus-c++中文版使用说明(三)——便捷层API
sdbus-c++中文版使用说明(四)——C++绑定层API

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值