綜述
D-Bus是一種IPC(程序間通信)機制的實作,相比其它的IPC,D-Bus更加的輕量和高速,這些優勢得益于它的如下特點:基于連接配接(Connection-based),以二進制格式通信。
D-Bus有各種不同語言的實作(也并不一定是語言,可以是其它,比如某個特定的架構),最基礎的是C,有官方提供的參考代碼,也有Java、Python等,每種語言的實作被稱為D-Bus Binding。
D-Bus主要包含兩個元件,程序間通信需要調用的D-Bus庫(可以由不同的語言來實作)和D-Bus守護程序(Daemon),程序間的通信主要通過D-Bus守護程序來完成,是以對于一個程式來說,它要做的隻是通過D-Bus庫與守護程序互動,而不需要關注底層的細節,這對于多語言的實作也很有幫助。D-Bus守護程序就像一條總線(Bus,後面提到的D-Bus總線就是D-Bus守護程序),各類需要通信的程序都連接配接其上,并互動資訊(Message)。
一個系統中可以有多條D-Bus總線,即多個D-Bus守護程序。D-Bus總線有兩種類型,一種是系統總線(System Bus),另一種是會話總線(Session Bus)。前者用于系統級别的通信,比如當有新硬體接入時的通知;後者由單個使用者使用。下面是Ubuntu18.04(下同)的一個例子,紅框部分指定了類型(這裡有兩個D-Bus總線沒有明确指定是系統還是會話總線,但是通過配置檔案還是有指定的):

每條總線有一個位址(Address),名稱格式類似“/tmp/.hiddensocket”(從名稱上也可以看出來,D-Bus的底層實作其實是Socket,這個到以後如果講D-Bus實作時會說明),通過該位址其它程序就可以連接配接到該總線,這需要通過D-Bus庫調用接口,并使用該總線位址來打開(Open)與D-Bus守護程序的連接配接(Connection)。
D-Bus守護程序的啟動依賴于指令dbus-launch,它會調用另外的一個指令dbus-daemon(這個說法來自https://www.freedesktop.org/wiki/IntroductionToDBus/,但似乎可以直接調用了dbus-daemon),後者帶一個參數--config-file,可以指定配置檔案,通常的配置檔案位于/etc/dbus-1/目錄下,也分為用于系統總線和會話總線的兩種配置。如下所示:
配置檔案基于XML,具體以後再介紹。
D-Bus實作中的一些概念(後面幾節中将陸續介紹):
A... | is identified by a(n)... | which looks like... | and is chosen by... |
Bus | address | unix:path=/var/run/dbus/system_bus_socket | system configuration |
Connection | bus name | :34-907 (unique) com.mycompany.TextEditor (well-known) | D-Bus (unique) or the owning program (well-known) |
Object | path | /com/mycompany/TextFileManager | the owning program |
Interface | interface name | org.freedesktop.Hal.Manager | the owning program |
Member | member name | ListNames | the owning program |
連接配接
程序與D-Bus總線的連接配接可以通過一個或多個名字找到,這些名字被稱為連接配接的總線名(Connection's Bus Name,注意它是連接配接的名字,而不是總線的名字,前面已經講過,總線通過位址找到),該名字的一般格式是com.acme.Foo,名字由點(.)分隔的字元串組成,字元串可以包含大小寫字元、數字、連接配接線(-)、下劃線(_)。這裡我們可以說連接配接擁有一個總線名com.acme.Foo。
當一個連接配接建立,D-Bus總線會給該連接配接配置設定一個不可變且唯一的總線名稱,它被稱為唯一連接配接名(Unique Connection Name),隻要該D-Bus總線還在,該名字就會被保留着,即使連接配接已經消失而新連接配接已經建立。這類總線名以冒号(:)開頭(隻有唯一連接配接名會以冒号開頭),格式如下:“:34-987”,這裡需要關注的隻是冒号,而不是後面的數字,它們本身沒有意義,隻是為了保證唯一性。
連接配接還可以有其它的名稱,比如說用可讀的名稱來提供服務(Service),這種名字需要包含兩個及以上的點,比如“com.acm.PortableHole”。同一時間,D-Bus總線上隻有一個連接配接可以擁有指定名稱。
關于不同的名稱,這裡做補充說明。連接配接的總線名可以認為是程序用的名稱,是可讀的名稱,唯一連接配接名是D-Bus總線用來标記連接配接的名稱,重點在于唯一性,這兩個名稱其實都表示的是同一個連接配接。至于最後一個跟服務有關的名稱,目前還不确定具體的作用。
對象模型
D-Bus實作中包含了一些面向對象的概念。 D-Bus總線上通信的是兩個程序,但是我們不直接讨論程序,而是将連接配接的兩端稱為對象(Object),對象存在與連接配接中,隻有在連接配接的上下文中對象才有意義。一個程序可以包含若幹個對象,對象由程序代碼建立并存在于程序的上下文。程序通過對象進行互動,互動的内容有以下的部分:
- 從一個程序(該程序稱為客戶程序(Client))發送給另一個程序,并被其對象接收的請求(Request);
- 另一個程序通過對象傳回的對請求的回應;
- 某程序中對象發出的請求,屬于廣播,而不是針對特定程序,任何對該請求有興趣的程序都可以響應;
是以D-Bus總線上存在着一對一和一對多的請求資訊(Message)。D-Bus總線本身必須要存在至少一個對象。客戶程序可以通過向該對象發送請求來擷取D-Bus總線的狀态。
D-Bus對象可以通過稱為代理(Proxy)的實體來通路,之是以稱為代理,是因為它是對象在客戶程序中的代表,程序通過這個代表來通路真正的D-Bus對象。關于代理和對象本體的處理,不同的語言實作會有不同,這裡暫時不管。
對象有一個名稱,也稱為路徑(Path),它的格式類似“/org/kde/kspread/sheets/3/cell”,在一個連接配接的上下文中,路徑必須是唯一的。由于對象存在與連接配接當中,是以可以通過連接配接的總線名加對象路徑通路到此對象。在代碼中,一旦對象被找到,通常會儲存為變量供後續使用,避免每次使用都要重新定位。
當客戶程序向一個對象發送請求時,其目的是調用對象的方法(Method),即對象執行某個特定的動作。如果請求的對象不存在,則通常會報錯。調用的方法可以存在入參(Input Parameter),它也包含在請求中。對于每一個請求,對象都會回複,可以是一個結果(Output Parameter),也可以是異常,異常的格式必須包含異常的名稱和錯誤資訊。關于參數的組織形式,由實作的語言決定,對于這些資料的所有處理,被稱為Marshalling。
D-Bus實作中,對方法的調用跟函數調用最大的不同在于,函數調用之後通常是等待函數傳回然後再執行後續的操作,而向D-Bus對象發送請求之後不需要等待,而是繼續處理其它的事務。這種處理方式一般被稱為異步(Asynchronous)調用。
客戶程序不止會向一個對象發送請求,也可以廣播請求,這被稱為發信号(Signal)。客戶程序可以通過特定的名稱向D-Bus總線注冊感興趣的信号,當有對象發送信号時,所有感興趣的程序都會收到該信号的一份拷貝。信号可以被接收、也可以不被接收,可以被多個程序接收,也可以隻被一個程序接收。程序處理信号之後并不會傳回結果,是以發送信号的對象并不知道是否有人接收和處理。信号中也可以帶有參數。信号通常是一些程序可能會關注的事件,比如某個程序的連接配接斷開了,這個由D-Bus總線本身的對象發出。
所有的對象都支援方法,并能夠發送信号,這些方法和信号被稱為對象的成員(Member)。所有的這些成員通過接口(Interface)指定(Specified)。在面向對象的程式設計語言中,比如Java,接口是定義的集合,而成員是對接口的實作,這些實作與接口保持一緻參數。
當客戶程序調用方法或者監聽信号時,必須指定對象和成員,還可以指定成員所對應的接口,因為在一個對象中,可能包含兩個接口,而這兩個接口都實作了同一個名稱的成員。這種情況不常見,是以通常接口可以被忽略。
激活服務
前面提到的對象都是程序(非D-Bus守護程序)主動連接配接D-Bus總線并建立對象,還有一種方式來讓D-Bus守護程序來喚醒一般程序并提供服務。當有程序想要使用某個特定的代理,但是它還為處于非活躍狀态(即沒有連接配接)時,D-Bus守護程序會嘗試激活該程序,這依賴于特定的檔案.service,該檔案指定了需要啟動的程序以及它提供的總線名,下面是一個例子:
# (Lines starting with hash marks are comments)
# Fixed section header (do not change):
[D-BUS Service]
Names=com.bigmoneybank.Deposits;com.bigmoneybank.Withdrawals
Exec=/usr/local/bin/bankcounter
這裡的/usr/local/bin/bankcounter是需要激活的客戶程序,它可以提供兩個連接配接的總線名com.bigmoneybank.Deposits和com.bigmoneybank.Withdrawals。.service檔案所在位置寫在D-Bus配置檔案的<servicedir>子產品下,通常是/usr/share/dbus-1/services/:
代碼實作
dbus是D-Bus的官方參考實作,可以在如下網頁下載下傳到:
https://gitlab.freedesktop.org/dbus/dbus/-/tree/master
它有不同的對應版本,目前比較常用的是dbus-1.10和dbus-1.12。至于其它Binding,可以在https://www.freedesktop.org/wiki/Software/DBusBindings/找到,下圖是1.10的版本:
其中最重要的是:bus目錄中的代碼用來生成dbus-daemon,即D-Bus守護程序;dbus目錄中的代碼相當于D-Bus庫,提供了API的實作;tools包含了一些常用的指令,比如dbus-launch、dbus-monitor等。
在dbus目錄源代碼中,函數分為兩類,一類是以“_”開頭,或者不是以“DBus”,“dbus_”,“DBUS_”開頭的函數,它們都是内部函數;另一類就是外部可調用的函數。dbus提供給外部調用的接口,可以參考https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html和https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html。DBusConnection表示的是D-Bus連接配接,對應的代碼在dbus\dbus-connection.h,而DBusMessage就是DBusConnection之間傳遞的資訊,對應的代碼在dbus\dbus-message.h。
參考資料
DBUS的基礎介紹https://www.freedesktop.org/wiki/IntroductionToDBus/。
更具體的内容可以參考https://dbus.freedesktop.org/doc/dbus-specification.html。