参考: 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提供一种高效的进程间通信机制,主要用于进程间函数调用以及进程间信号广播。
- 函数调用
dbus可以实现进程间函数调用,进程A发生函数调用的请求(Methdcall消息),技工总线转发至进程B。
进程B将应答函数返回值(Methodreturn消息)或错误消息(Error消息)。
- 消息广播
进程间消息广播(signal消息)不需要响应,接收方需要向总线注册感兴趣的消息类型,当总线接收到 “signal消息”类型的消息时,会将消息转发至希望接收的进程。
dbus通信特点
DBus三大优点:低延迟、低开销和高可用性。
- 低延迟:dbus一开始就是用来设计成避免来回传递和允许异步操作的。因此虽然在Application和Daemon之间是通过socket实现的,但是有去掉了socket的循环等待,保证了操作的实时高效。
- 低开销:dbus使用一个二进制的协议,不需要转化成像XML这样的文本格式。因为dbus主要用来机器内部的IPC,而不是为了网络上的IPC机制而准备,所以能在机器内部达到最有效果。
- 高可用性:dbus是基于消息机制而不是字节流机制。它能自动管理一大堆困难的IPC问题。同样的,dbus库被设计来让程序员能够使用他们已经写好的代码。而不会让他们放弃写好的代码,被迫通过学习新IPC机制来根据新IPC特性重写代码。
机制剖析
一、实现原理
dbus进程间通信主要有三层架构:
- 底层接口层:主要通过libdbus函数库,给予系统使用dbus的能力。
- 总线层:主要Message bus daemon这个总线守护进程提供的,在Linux系统启动时运行,负责进程间的消息路由和传递,其中Linux内核和Linux桌面环境的消息传递。总线守护进程可同时与多个应用程序相连,并把来自一个应用程序的消息路由到零或多个其他程序。
- 应用封装层:通过一系列基于特定应用程序框架将dbus的底层接口封装成友好的Wrapper库,供不同的开发人员使用
如上图所示,进程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函数对连接进行注册名称。
_public_ int sd_bus_default_system(sd_bus **ret) /*连接总线*/
_public_ int sd_bus_add_object_vtable( /*注册接口函数到总线*/
sd_bus *bus,
sd_bus_slot **slot,
const char *path,
const char *interface,
const sd_bus_vtable *vtable,
void *userdata)
_public_ int sd_bus_request_name_async( /*获取总线注册名称*/
sd_bus *bus,
sd_bus_slot **ret_slot,
const char *name,
uint64_t flags,
sd_bus_message_handler_t callback,
void *userdata)
三、Method调用
调用一个远程函数,需要先创建一个消息(dbusMessage),然后通过注册在 dbus上的名称指定发送的对象。然后追加相应的参数,调用方法分为两种,一种是阻塞式的,另一种为异步调用。异步调用的时候会得到一个“dbusMessage *” 类型的返回消息,从这个返回消息中可以获取一些返回的参数。
_public_ int sd_bus_call_method( /*阻塞式调用*/
sd_bus *bus,
const char *destination,
const char *path,
const char *interface,
const char *member,
sd_bus_error *error,
sd_bus_message **reply,
const char *types, ...)
_public_ int sd_bus_message_new_method_call( /*创建一个方法调用的消息*/
sd_bus *bus,
sd_bus_message **m,
const char *destination,
const char *path,
const char *interface,
const char *member)
_public_ int sd_bus_message_appendv( /*为消息添加参数*/
sd_bus_message *m,
const char *types,
va_list ap)
_public_ int sd_bus_call( /*获取返回消息,阻塞式*/
sd_bus *bus,
sd_bus_message *_m,
uint64_t usec,
sd_bus_error *error,
sd_bus_message **reply)
_public_ int sd_bus_call_async( /*异步调用,获取返回消息*/
sd_bus *bus,
sd_bus_slot **slot,
sd_bus_message *_m,
sd_bus_message_handler_t callback,
void *userdata,
uint64_t usec)
四、Method接收
注册时已经告诉总线,哪些消息感兴趣,总线收到对应的消息,就会转发给相对应的应用,应用从消息中获取参数进行函数执行,最后创建返回消息,并发送消息到总线,由总线转发至调用的进程。
_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) /*读取消息参数*/
_public_ int sd_bus_reply_method_return( /*返回消息处理完之后的应答消息*/
sd_bus_message *call,
const char *types, ...)
_public_ int sd_bus_message_new_method_return( /*创建一个方法返回的消息*/
sd_bus_message *call,
sd_bus_message **m)
_public_ int sd_bus_message_appendv( /*为返回消息添加参数*/
sd_bus_message *m,
const char *types,
va_list ap)
_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) /*消息发送*/
五、Signal发送
dbus中信号是一种广播的消息,当发出一个信号,所有连接到dbus总线上并注册了接收对应信号的进程,都会收到该信号。进程发出一个信号前,需要创建一个dbusMessage对象来代表信号,然后追加上一些需要发出的参数,就可以发向总线了。发完后需要释放消息对象。
_public_ int sd_bus_emit_signal( /*信号发送*/
sd_bus *bus,
const char *path,
const char *interface,
const char *member,
const char *types, ...)
_public_ int sd_bus_message_new_signal( /*创建一个信号的消息*/
sd_bus *bus,
sd_bus_message **m,
const char *path,
const char *interface,
const char *member)
_public_ int sd_bus_message_appendv( /*向信号的消息体添加参数*/
sd_bus_message *m,
const char *types,
va_list ap)
_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie) /*发送信号*/
六、Signal接收
进程接收信号时,需先告知总线进程感兴趣的消息,然后等待接收消息。和Method接收类似。
_public_ int sd_bus_match_signal_async( /*告知感兴趣的消息*/
sd_bus *bus,
sd_bus_slot **ret,
const char *sender,
const char *path,
const char *interface,
const char *member,
sd_bus_message_handler_t callback,
sd_bus_message_handler_t install_callback,
void *userdata)
_public_ int sd_bus_match_signal( /*告知感兴趣的消息*/
sd_bus *bus,
sd_bus_slot **ret,
const char *sender,
const char *path,
const char *interface,
const char *member,
sd_bus_message_handler_t callback,
void *userdata)