参考: DBus学习笔记- https://www.cnblogs.com/zhoug2020/p/4516144.html

概述

systemd是Linux系统下的一套中央化系统及设置管理程序(init软件),dbus是systemd的核心组件之一。

功能简介

dbus是一种IPC机制,有freedesktop.org项目提供,使用GPL许可证发行,用于进程间通信或进程与内核的通信。dbus支持进程间一对一和多对多的对等通信,在多对多的通信时,需要后台进程的角色去分转消息,当一个进程发消息给另外一个进程时,先发消息到后台进程,再通过后台进程将信息转发到目的进程。dbus后台进程充当路由器角色。
dbus中主要概念为总线,连接到总线的进程可通过总线接收或传递消息,总线收到消息时,根据不同的消息类型进行不同的处理。dbus中消息分为四类:

  • Methodcall消息:将触发一个函数调用
  • Methodreturn消息:触发函数调用返回的结果
  • Error消息:触发的函数调用返回一个异常
  • Signal消息:同时,可看作为时间消息

    基本概念

    会话总线(Session Buses)普通进程创建,可同时存在多条。会话总线属于某个进程私有,它用于进程间传递消息。

系统总线(System Bus)在引导时就会启动,它由操作系统和后台进程使用,安全性很好,以使得任意的应用程序不能欺骗系统事件。

Bus Name:连接名称,主要是用来标识一个应用和消息总线的链接。总线名称主要分为两类:

  • “org.kde.StatusNotifierWatcher”这种形式的称为公共名(well-knownname)
  • “:1.3”这种形式称为唯一名(Unique Name)

公共名提供总所周知的服务。其他应用通过这个名称来使用对应的服务。可能有多个连接要求提供同个公共名的服务,即多个应用连接到消息总线,要求提供同个公共名的服务。消息总线会把这些连接排在链表中,并选择一个连接提供公共名代表的服务。可以说这个提供服务的连接拥有了这个公共名。如果这个连接退出了,消息总线会从链表中选择下一个连接提供服务。
唯一名以“:”开头,“:”后面通常是圆点分隔的两个数字,例如“:1.0”。每个连接都有一个唯一名。在一个消息总线的生命期内,不会有两个连接有相同的唯一名。拥有公众名的连接同样有唯一名,例如“org.kde.StatusNotifierWatcher”的唯一名是“:1.51”。
每个连接都有一个唯一名,但不一定有公共名。
只有唯一名而没有公共名叫做私有连接,因为它们没有提供可以通过公共名访问的服务

Object Paths
org.kde.StatusNotifierWatcher”这个连接中有三个Object Paths,标识这这个连接中提供了三个不同的服务,每个Object Paths表示一个服务。这个路径在连接中是唯一的。

Interfaces
在每个Object Paths下都包含有多个接口(Interfaces)

Methods和Signals
Methods表示可以被具体调用的方法
Signals则表示的是信号,此信号可以被广播,而连接了这个信号的对象在接收到信号时就可以进行相应的处理。和Qt中的信号应该是一个意思

dbus场景

根据dbus消息类型可知,dbus提供一种高效的进程间通信机制,主要用于进程间函数调用以及进程间信号广播。

  1. 函数调用

dbus可以实现进程间函数调用,进程A发生函数调用的请求(Methdcall消息),技工总线转发至进程B。
进程B将应答函数返回值(Methodreturn消息)或错误消息(Error消息)。

  1. 消息广播

进程间消息广播(signal消息)不需要响应,接收方需要向总线注册感兴趣的消息类型,当总线接收到 “signal消息”类型的消息时,会将消息转发至希望接收的进程。

dbus通信特点

DBus三大优点:低延迟、低开销和高可用性。

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

    机制剖析

    一、实现原理

    dbus进程间通信主要有三层架构:
  1. 底层接口层:主要通过libdbus函数库,给予系统使用dbus的能力。
  2. 总线层:主要Message bus daemon这个总线守护进程提供的,在Linux系统启动时运行,负责进程间的消息路由和传递,其中Linux内核和Linux桌面环境的消息传递。总线守护进程可同时与多个应用程序相连,并把来自一个应用程序的消息路由到零或多个其他程序。
  3. 应用封装层:通过一系列基于特定应用程序框架将dbus的底层接口封装成友好的Wrapper库,供不同的开发人员使用

Dbus.jpg

如上图所示,进程1(Process1)需先连接到总线(dbus_bus_get),其次构造消息(dbus_message_new_signal),然后发送消息(dbus_connection_send)到后台进程。后台进程接收消息,然后根据消息类型对消息进行不同处理(bus_dispatch_matches)。
进程2(Process2)接收消息前需要连接到总线,并告知总线自己希望得到的消息类型(dbus_bus_add_match),然后等待接收消息(dbus_connection_pop_message)。进程2(Process2)收到总线转发的消息时会根据消息类型,做不同的处理(若是信号类型则不需要发送返回值给总线)。

二、连接总线

进程间通信前,需要连接到总线、注册总线、获取对应名称。调用sd_bus_default_system函数连接进程到总线,建立进程和总线之间的连接(dbusConnection)。建立连接后,通过sd_bus_add_object_vtable向dbus注册应用的接口函数,方便后面对这个连接进行操作,需要为这个连接注册名称,调用sd_bus_request_name_async函数对连接进行注册名称。

  1. _public_ int sd_bus_default_system(sd_bus **ret) /*连接总线*/
  2. _public_ int sd_bus_add_object_vtable( /*注册接口函数到总线*/
  3. sd_bus *bus,
  4. sd_bus_slot **slot,
  5. const char *path,
  6. const char *interface,
  7. const sd_bus_vtable *vtable,
  8. void *userdata)
  9. _public_ int sd_bus_request_name_async( /*获取总线注册名称*/
  10. sd_bus *bus,
  11. sd_bus_slot **ret_slot,
  12. const char *name,
  13. uint64_t flags,
  14. sd_bus_message_handler_t callback,
  15. void *userdata)

三、Method调用

调用一个远程函数,需要先创建一个消息(dbusMessage),然后通过注册在 dbus上的名称指定发送的对象。然后追加相应的参数,调用方法分为两种,一种是阻塞式的,另一种为异步调用。异步调用的时候会得到一个“dbusMessage *” 类型的返回消息,从这个返回消息中可以获取一些返回的参数。

  1. _public_ int sd_bus_call_method( /*阻塞式调用*/
  2. sd_bus *bus,
  3. const char *destination,
  4. const char *path,
  5. const char *interface,
  6. const char *member,
  7. sd_bus_error *error,
  8. sd_bus_message **reply,
  9. const char *types, ...)
  10. _public_ int sd_bus_message_new_method_call( /*创建一个方法调用的消息*/
  11. sd_bus *bus,
  12. sd_bus_message **m,
  13. const char *destination,
  14. const char *path,
  15. const char *interface,
  16. const char *member)
  17. _public_ int sd_bus_message_appendv( /*为消息添加参数*/
  18. sd_bus_message *m,
  19. const char *types,
  20. va_list ap)
  21. _public_ int sd_bus_call( /*获取返回消息,阻塞式*/
  22. sd_bus *bus,
  23. sd_bus_message *_m,
  24. uint64_t usec,
  25. sd_bus_error *error,
  26. sd_bus_message **reply)
  27. _public_ int sd_bus_call_async( /*异步调用,获取返回消息*/
  28. sd_bus *bus,
  29. sd_bus_slot **slot,
  30. sd_bus_message *_m,
  31. sd_bus_message_handler_t callback,
  32. void *userdata,
  33. uint64_t usec)

四、Method接收

注册时已经告诉总线,哪些消息感兴趣,总线收到对应的消息,就会转发给相对应的应用,应用从消息中获取参数进行函数执行,最后创建返回消息,并发送消息到总线,由总线转发至调用的进程。

  1. _public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) /*读取消息参数*/
  2. _public_ int sd_bus_reply_method_return( /*返回消息处理完之后的应答消息*/
  3. sd_bus_message *call,
  4. const char *types, ...)
  5. _public_ int sd_bus_message_new_method_return( /*创建一个方法返回的消息*/
  6. sd_bus_message *call,
  7. sd_bus_message **m)
  8. _public_ int sd_bus_message_appendv( /*为返回消息添加参数*/
  9. sd_bus_message *m,
  10. const char *types,
  11. va_list ap)
  12. _public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) /*消息发送*/

五、Signal发送

dbus中信号是一种广播的消息,当发出一个信号,所有连接到dbus总线上并注册了接收对应信号的进程,都会收到该信号。进程发出一个信号前,需要创建一个dbusMessage对象来代表信号,然后追加上一些需要发出的参数,就可以发向总线了。发完后需要释放消息对象。

  1. _public_ int sd_bus_emit_signal( /*信号发送*/
  2. sd_bus *bus,
  3. const char *path,
  4. const char *interface,
  5. const char *member,
  6. const char *types, ...)
  7. _public_ int sd_bus_message_new_signal( /*创建一个信号的消息*/
  8. sd_bus *bus,
  9. sd_bus_message **m,
  10. const char *path,
  11. const char *interface,
  12. const char *member)
  13. _public_ int sd_bus_message_appendv( /*向信号的消息体添加参数*/
  14. sd_bus_message *m,
  15. const char *types,
  16. va_list ap)
  17. _public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) /*发送信号*/

六、Signal接收

进程接收信号时,需先告知总线进程感兴趣的消息,然后等待接收消息。和Method接收类似。

  1. _public_ int sd_bus_match_signal_async( /*告知感兴趣的消息*/
  2. sd_bus *bus,
  3. sd_bus_slot **ret,
  4. const char *sender,
  5. const char *path,
  6. const char *interface,
  7. const char *member,
  8. sd_bus_message_handler_t callback,
  9. sd_bus_message_handler_t install_callback,
  10. void *userdata)
  11. _public_ int sd_bus_match_signal( /*告知感兴趣的消息*/
  12. sd_bus *bus,
  13. sd_bus_slot **ret,
  14. const char *sender,
  15. const char *path,
  16. const char *interface,
  17. const char *member,
  18. sd_bus_message_handler_t callback,
  19. void *userdata)