Linux | D-Bus Overview

2020 11 16, Mon

DBus是一种基于Unix Domain Socket的新型IPC,已经实现了很多我们常用的功能。

最近做的事情和DBus有非常多的关系,稍微记录一下这半年一年接触到的DBus的内容。

IPC

IPC全程是进程间Inter Process通信Communication。比如说我们用浏览器,浏览器需要和服务器通信,这个过程中用的是套接字Sockets这种IPC。

比较常见的IPC有

  • 文件:对,文件可以用来传输数据,当然可以用作IPC啦
  • 管道:一对一的通信手段
    • 有名管道/FIFO:通过mkfifo创建的类似于文件的一对一的管道。
    • 无名管道:举个例子,ps aux | grep shed中,bash会产生ps和grep两个进程,这两个进程就是由无名管道连接的。
  • 信号,没错,信号可以传递数据的,但是信号一般是单向的。
  • 共享内存,可以映射到进程自己的内存空间中,从而交换信息。
  • Semaphore:进线程间同步用的通信手段。
  • 套接字:一般有一个监听的一端,有一个连接的一端,是双向的通信手段。
    • IP TCP/UDP Socket:我们最常用的IPC手段,不止可以在本机建立信道,在不同主机、不同网络之间也可以建立信道。在这之上可以建立应用层连接。
    • Netlink Raw Socket:内核提供的套接字类型,主要是用来在用户空间和内核空间之间通信。举个例子,可以用nl套接字配置网络。
    • Unix Domain Socket:文件形式的Socket,一般是本地应用之间通信用,监听的一端可以收到连接端的进程信息。Docker以及后面要说的DBus类应用都是依赖于Unix Domain Socket。

IPC解决了的问题,IPC没解决的问题

现在的系统越来越复杂,越来越难以在一个程序中处理一类事情,不同程序的协作几乎成了必然。IPC的诞生就是解决了不同进程间的通信问题:我们有管道在进程间做管道才能用grep、awk、sed、tr之类的工具处理文本,有文件才可以在本地持久化并且共享信息,有信号才可以及时通知其他进程发生了什么,有套接字才可以在不同主机间通信。

看起来很完美,我们还有什么不满足的?

很久以前我们每写一个程序都要自己发明一套通信手段,每和一个应用接入就要接入一整套SDK,然后建立两个进程间的信道,非常的重而且不可靠,搞不好效率也不高。我们要追求更高的生产力,更少的出错的可能性,就需要一个在大部分时间都通用的协议。由此出发,各路大神发明了非常多的新的IPC手段。

这些新型IPC中,最典型的一类就是RPC,远程过程调用,主要是在不同主机间进行通信的手段。其中出名的包括

  • RPC
    • xml-rpc,基于XML和http的文本协议
    • json-rpc,基于json和http的文本协议
    • grpc:基于protobuf和http2/h2c的二进制协议
  • IPC/RPC
    • dbus: 主要基于unix domain socket的二进制协议
    • varlink

其中今天最主要要提到的就是这个dbus。

DBus

dbus在centos6的时候就有了,起源于freedesktop项目,目的是为了统一桌面环境中进程间通信的方式。

通常来说,系统里面会有一个系统dbus服务,socket路径在/var/run/dbus/system_bus_socket,每个登录也会有自己的dbus服务,通过环境变量DBUS_SESSION_BUS_ADDRESS可以知道具体的路径。

一个进程一般会连接到其中一个bus上,连接以后dbus会分给连接一个unique name,例如:1.1,觉得这个名字不好看、其他程序不好找,我们也可以起一个有意义的名字,一般是 一个逆着写的域名加上一个具体的名字 ( Reverse domain name notation ) ,比如说org.freedesktop.system1

我们写http服务的时候,一般会在同一个同一个端口上提供同一个服务。但是对于dbus来说,情况可能会更复杂一些,systemd这样的应用需要在dbus上为不同的unit提供不同的对象,每类对象有不同的接口。所以dbus设计上,把服务和对象绑定了起来,我们需要把对象发布到一个dbus的 对象路径 ( Object Path ) 上,以对象为单位给其他应用提供服务,一个对象有多个接口,接口定义具体是做什么的。通常情况下,都会有一个路径每级都和unique name中的部分一一对应的对象,比如说org.freedesktop.systemd1就有一个位于/org/freedesktop/systemd1的对象,这个对象有org.freedesktop.DBus.Peerorg.freedesktop.DBus.Introspectableorg.freedesktop.DBus.Propertiesorg.freedesktop.systemd1.Manager四个接口,每个接口有不同的函数、信号和属性

大致来说要在dbus里面的要素包括

  • 是哪根bus上的服务
  • 提供服务的连接名是啥
  • 提供服务的对象的路径是啥
  • 需要对象上的哪个接口
  • 需要的是函数、信号还是属性
  • 名字叫啥

用packagekit举个例子,我要列举可以升级的软件包需要:

  • 建立一个事务:
    • 调用
      • system bus上的
      • org.freedesktop.PackageKit连接中
      • /org/freedesktop/PackageKit对象上的
      • org.freedesktop.PackageKit接口上的
      • CreateTransaction
      • 函数
    • 这个调用会返回一个字符串,用来表示transaction所对应的object在连接中的路径
  • 监听结果
    • 监听
      • system bus上的
      • org.freedesktop.PackageKit连接中
      • 刚才返回路径所表示的对象上的
      • org.freedesktop.PackageKit.Transaction接口上的
      • Package和Finished
      • 信号
  • 让PackageKit获取升级列表
    • 调用
      • system bus上的
      • org.freedesktop.PackageKit连接中
      • 刚才返回路径所表示的对象上的
      • org.freedesktop.PackageKit.Transaction接口上的
      • GetUpdates
      • 函数

如果需要的话我们也可以获取一些属性,比如说

  • 要获取是谁创建的transaction
    • system bus上的
    • org.freedesktop.PackageKit连接中
    • 刚才返回路径所表示的对象上的
    • org.freedesktop.PackageKit.Transaction接口上的
    • Uid
    • 属性

DBus通过这种方法为各种各样的服务提供了一种通用的通信机制。