複製鏈接
請複製以下鏈接發送給好友

dbus

鎖定
DBUS,數據總線,是一個低延遲,低開銷,高可用性的ipc機制。
中文名
消息總線系統
外文名
a message bus system
特    點
低延遲,低開銷,高可用性
實    質
ipc機制

dbus進程間使用D-Bus通信

D-Bus是一種高級的進程間通信機制,它由freedesktop.org項目提供,使用GPL許可證發行。D-Bus最主要的用途是在Linux桌面環境為進程提供通信,同時能將Linux桌面環境和Linux內核事件作為消息傳遞到進程。D-Bus的主要概念為總線,註冊後的進程可通過總線接收或傳遞消息,進程也可註冊後等待內核事件響應,例如等待網絡狀態的轉變或者計算機發出關機指令。D-Bus已被大多數Linux發行版所採用,開發者可使用D-Bus實現各種複雜的進程間通信任務。

dbusD-Bus的基本概念

D-Bus是一個消息總線系統,其功能已涵蓋進程間通信的所有需求,並具備一些特殊的用途。D-Bus是三層架構的進程間通信系統,其中包括:
接口層:接口層由函數庫libdbus提供,進程可通過該庫使用D-Bus的能力。
總線層:總線層實際上是由D-Bus總線守護進程提供的。它在Linux系統啓動時運行,負責進程間的消息路由和傳遞,其中包括Linux內核和Linux桌面環境的消息傳遞。
包裝層:包裝層一系列基於特定應用程序框架的Wrapper庫。
D-Bus具備自身的協議,協議基於二進制數據設計,與數據結構和編碼方式無關。該協議無需對數據進行序列化,保證了信息傳遞的高效性。無論是libdbus,還是D-Bus總線守護進程,均不需要太大的系統開銷。
總線是D-Bus的進程間通信機制,一個系統中通常存在多條總線,這些總線由D-Bus總線守護進程管理。最重要的總線為系統總線(System Bus),Linux內核引導時,該總線就已被裝入內存。只有Linux內核、Linux桌面環境和權限較高的程序才能向該總線寫入消息,以此保障系統安全性,防止有惡意進程假冒Linux發送消息。
會話總線(Session Buses)由普通進程創建,可同時存在多條。會話總線屬於某個進程私有,它用於進程間傳遞消息。
進程必須註冊後才能收到總線中的消息,並且可同時連接到多條總線中。D-Bus提供了匹配器(Matchers)使進程可以有選擇性的接收消息,另外運行進程註冊回調函數,在收到指定消息時進行處理。匹配器的功能等同與路由,用於避免處理無關消息造成進程的性能下降。除此以外,D-Bus機制的重要概念有以下幾個。
對象:對象是封裝後的匹配器與回調函數,它以對等(peer-to-peer)協議使每個消息都有一個源地址和一個目的地址。這些地址又稱為對象路徑,或者稱之為總線名稱。對象的接口是回調函數,它以類似C++的虛擬函數實現。當一個進程註冊到某個總線時,都要創建相應的消息對象。
消息:D-Bus的消息分為信號(signals)、方法調用(method calls)、方法返回(method returns)和錯誤(errors)。信號是最基本的消息,註冊的進程可簡單地發送信號到總線上,其他進程通過總線讀取消息。方法調用是通過總線傳遞參數,執行另一個進程接口函數的機制,用於某個進程控制另一個進程。方法返回是註冊的進程在收到相關信息後,自動做出反應的機制,由回調函數實現。錯誤是信號的一種,是註冊進程錯誤處理機制之一。
服務:服務(Services)是進程註冊的抽象。進程註冊某個地址後,即可獲得對應總線的服務。D-Bus提供了服務查詢接口,進程可通過該接口查詢某個服務是否存在。或者在服務結束時自動收到來自系統的消息。

dbus建立服務的流程

建立一個dbus連接之後 -- dbus_bus_get(),為這個dbus連接(DbusConnection)起名 -- dbus_bus_request_name(),這個名字將會成為我們在後續進行遠程調用的時候的服務名,然後我們進入監聽循環 -- dbus_connection_read_write()。在循環中,我們從總線上取出消息 -- dbus_connection_pop_message(),並通過比對消息中的方法接口名和方法名 -- dbus_message_is_method_call(),如果一致,那麼我們跳轉到相應的處理中去。在相應的處理中,我們會從消息中取出遠程調用的參數。並且建立起回傳結果的通路 -- reply_to_method_call()。回傳動作本身等同於一次不需要等待結果的遠程調用。

dbus發送信號的流程

建立一個dbus連接之後,為這個dbus連接起名,建立一個發送信號的通道,注意,在建立通道的函數中,需要我們填寫該信號的接口名和信號名 -- dbus_message_new_signal()。然後我們把信號對應的相關參數壓進去 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic()。然後就可以啓動發送了 -- dbus_connection_send(); dbus_connection_flush。

dbus進行一次遠程調用的流程

建立好dbus連接之後,為這dbus連接命名,申請一個遠程調用通道 -- dbus_message_new_method_call(),注意,在申請遠程調用通道的時候,需要填寫服務器名,本次調用的接口名,和本次調用名(方法名)。壓入本次調用的參數 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic(),實際上是申請了一個首地址,我們就是把我們真正要傳的參數,往這個首地址裏面送(送完之後一般都會判斷是否內存越界了)。然後就是啓動發送調用並釋放發送相關的消息結構 -- dbus_connection_send_with_reply()。這個啓動函數中帶有一個句柄。我們馬上會阻塞等待這個句柄給我們帶回總線上回傳的消息。當這個句柄回傳消息之後,我們從消息結構中分離出參數。用dbus提供的函數提取參數的類型和參數 -- dbus_message_iter_init(); dbus_message_iter_next(); dbus_message_iter_get_arg_type(); dbus_message_iter_get_basic()。也就達成了我們進行本次遠程調用的目的了。

dbus信號接收流程

建立一個dbus連接之後,為這個dbus連接起名,為我們將要進行的消息循環添加匹配條件(就是通過信號名和信號接口名來進行匹配控制的) -- dbus_bus_add_match()。我們進入等待循環後,只需要對信號名,信號接口名進行判斷就可以分別處理各種信號了。在各個處理分支上。我們可以分離出消息中的參數。對參數類型進行判斷和其他的處理。
dbus_connection_read_write()
--------------------------------------
As long as the connection is open, this function will block until it can read or write, then read or write, then return #TRUE.
If the connection is closed, the function returns #FALSE.
dbus_connection_pop_message()
--------------------------------------
Returns the first-received message from the incoming message queue, removing it from the queue. The caller owns a reference to the returned message. If the queue is empty, returns #NULL.
dbus_connection_send()
--------------------------------------
Adds a message to the outgoing message queue. Does not block to write the message to the network; that happens asynchronously. To force the message to be written, call dbus_connection_flush(). Because this only queues the message, the only reason it can
fail is lack of memory. Even if the connection is disconnected, no error will be returned.
@param connection the connection.
@param message the message to write.
@param serial return location for message serial, or #NULL if you don't care
@returns #TRUE on success.
dbus_connection_send_with_reply()
--------------------------------------
Queues a message to send, as with dbus_connection_send(), but also returns a #DBusPendingCall used to receive a reply to the message. If no reply is received in the given timeout_milliseconds, this function expires the pending reply and generates a synthetic error reply (generated in-process, not by the remote application) indicating that a timeout occurred.
A #DBusPendingCall will see a reply message before any filters or registered object path handlers. See dbus_connection_dispatch() for details on when handlers are run.
A #DBusPendingCall will always see exactly one reply message, unless it's cancelled with dbus_pending_call_cancel().
If #NULL is passed for the pending_return, the #DBusPendingCall will still be generated internally, and used to track the message reply timeout. This means a timeout error will occur if no reply arrives, unlike with dbus_connection_send().
If -1 is passed for the timeout, a sane default timeout is used. -1 is typically the best value for the timeout for this reason, unless you want a very short or very long timeout. There is no way to avoid a timeout entirely, other than passing INT_MAX for the
timeout to mean "very long timeout." libdbus clamps an INT_MAX timeout down to a few hours timeout though.
@warning if the connection is disconnected, the #DBusPendingCall will be set to #NULL, so be careful with this.
@param connection the connection
@param message the message to send
@param pending_return return location for a #DBusPendingCall object, or #NULL if connection is disconnected
@param timeout_milliseconds timeout in milliseconds or -1 for default
@returns #FALSE if no memory, #TRUE otherwise.
dbus_message_is_signal()
--------------------------------------
Checks whether the message is a signal with the given interface and member fields. If the message is not #DBUS_MESSAGE_TYPE_SIGNAL, or has a different interface or member field, returns #FALSE.
dbus_message_iter_init()
--------------------------------------
Initializes a #DBusMessageIter for reading the arguments of the message passed in.
dbus_message_iter_next()
--------------------------------------
Moves the iterator to the next field, if any. If there's no next field, returns #FALSE. If the iterator moves forward, returns #TRUE.
dbus_message_iter_get_arg_type()
--------------------------------------
Returns the argument type of the argument that the message iterator points to. If the iterator is at the end of the message, returns #DBUS_TYPE_INVALID.
dbus_message_iter_get_basic()
--------------------------------------
Reads a basic-typed value from the message iterator. Basic types are the non-containers such as integer and string.
dbus_message_new_signal()
--------------------------------------
Constructs a new message representing a signal emission. Returns #NULL if memory can't be allocated for the message. A signal is identified by its originating object path, interface, and the name of the signal.
Path, interface, and signal name must all be valid (the D-Bus specification defines the syntax of these fields).
@param path the path to the object emitting the signal
@param interface the interface the signal is emitted from
@param name name of the signal
@returns a new DBusMessage, free with dbus_message_unref()
dbus_message_iter_init_append()
--------------------------------------
Initializes a #DBusMessageIter for appending arguments to the end of a message.
@param message the message
@param iter pointer to an iterator to initialize
dbus_message_iter_append_basic()
--------------------------------------
Appends a basic-typed value to the message. The basic types are the non-container types such as integer and string.
@param iter the append iterator
@param type the type of the value
@param value the address of the value
@returns #FALSE if not enough memory
dbus_message_new_method_call()
--------------------------------------
Constructs a new message to invoke a method on a remote object. Returns #NULL if memory can't be allocated for the message. The destination may be #NULL in which case no destination is set; this is appropriate when using D-Bus in a peer-to-peer context (no message bus). The interface may be #NULL, which means that if multiple methods with the given name exist it is which one will be invoked.
The path and method names may not be #NULL.
Destination, path, interface, and method name can't contain any invalid characters (see the D-Bus specification).
@param destination name that the message should be sent to or #NULL
@param path object path the message should be sent to
@param interface interface to invoke method on, or #NULL
@param method method to invoke
@returns a new DBusMessage, free with dbus_message_unref()
dbus_bus_get()
--------------------------------------
Connects to a bus daemon and registers the client with it. If a connection to the bus already exists, then that connection is returned. The caller of this function owns a reference to the bus.
@param type bus type
@param error address where an error can be returned.
@returns a #DBusConnection with new ref
dbus_bus_request_name()
--------------------------------------
Asks the bus to assign the given name to this connection by invoking the RequestName method on the bus.
First you should know that for each bus name, the bus stores a queue of connections that would like to own it. Only one owns it at a time - called the primary owner. If the primary owner releases the name or disconnects, then the next owner in the queue atomically takes over.
So for example if you have an application org.freedesktop.TextEditor and multiple instances of it can be run, you can have all of them sitting in the queue. The first one to start up will receive messages sent to org.freedesktop.TextEditor, but if that one exits another will become the primary owner and receive messages.
The queue means you don't need to manually watch for the current owner to disappear and then request the name again.
@param connection the connection
@param name the name to request
@param flags flags
@param error location to store the error
@returns a result code, -1 if error is set
給DBusConnection起名字(命名) -- 兩個相互通信的連接(connection)不能同名
命名規則: xxx.xxx (zeng.xiaolong)
dbus_bus_add_match()
--------------------------------------
Adds a match rule to match messages going through the message bus. The "rule" argument is the string form of a match rule.
@param connection connection to the message bus
@param rule textual form of match rule
@param error location to store any errors
dbus_pending_call_block()
--------------------------------------
Block until the pending call is completed. The blocking is as with dbus_connection_send_with_reply_and_block(); it does not enter the main loop or process other messages, it simply waits for the reply in question.
If the pending call is already completed, this function returns immediately.
@todo when you start blocking, the timeout is reset, but it should really only use time remaining since the pending call was created. This requires storing timestamps instead of intervals in the timeout
@param pending the pending call
dbus_pending_call_steal_reply()
--------------------------------------
Gets the reply, or returns #NULL if none has been received yet. Ownership of the reply message passes to the caller. This function can only be called once per pending call, since the reply message is tranferred to the caller.
@param pending the pending call
@returns the reply message or #NULL.
安裝D-Bus可在其官方網站下載源碼編譯,地址為http://dbus.freedesktop.org。或者在終端上輸入下列指令:
安裝後,頭文件位於"/usr/include/dbus-<版本號>/dbus"目錄中,編譯使用D-Bus的程序時需加入編譯指令"`pkg-config --cflags --libs dbus-1`"。
3. D-Bus的用例
在使用GNOME桌面環境的Linux系統中,通常用GLib庫提供的函數來管理總線。在測試下列用例前,首先需要安裝GTK+開發包(見22.3節)並配置編譯環境。該用例一共包含兩個程序文件,每個程序文件需單獨編譯成為可執行文件。
1.消息發送程序
"dbus-ding-send.c"程序每秒通過會話總線發送一個參數為字符串Ding!的信號。該程序的源代碼如下:
  1. #include // 包含glib庫
  2. #include // 包含  glib庫中D-Bus管理庫
  3. #include
  4. static gboolean send_ding(DBusConnection *bus);// 定義發送消息函數的原型
  5. int main ()
  6. {
  7. GMainLoop *loop; // 定義一個事件循環對象的指針
  8. DBusConnection *bus; // 定義總線連接對象的指針
  9. DBusError error; // 定義D-Bus錯誤消息對象
  10. loop = g_main_loop_new(NULL, FALSE); // 創建新事件循環對象
  11. dbus_error_init (&error); // 將錯誤消息對象連接到D-Bus
  12. // 錯誤消息對象
  13. bus = dbus_bus_get(DBUS_BUS_SESSION, &error);// 連接到總線
  14. if (!bus) { // 判斷是否連接錯誤
  15. g_warning("連接到D-Bus失敗: %s", error.message);
  16. // 使用GLib輸出錯誤警告信息
  17. dbus_error_free(&error); // 清除錯誤消息
  18. return 1;
  19. }
  20. dbus_connection_setup_with_g_main(bus, NULL);
  21. // 將總線設為接收GLib事件循環
  22. g_timeout_add(1000, (GSourceFunc)send_ding, bus);
  23. // 每隔1000ms調用一次send_ding()函數
  24. // 將總線指針作為參數
  25. g_main_loop_run(loop); // 啓動事件循環
  26. return 0;
  27. }
  28. static gboolean send_ding(DBusConnection *bus) // 定義發  送消息函數的細節
  29. {
  30. DBusMessage *message; // 創建消息對象指針
  31. message = dbus_message_new_signal("/com/burtonini/dbus/ding",
  32. "com.burtonini.dbus.Signal",
  33. "ding"); // 創建消息對象並標識路徑
  34. dbus_message_append_args(message,
  35. DBUS_TYPE_STRING, "ding!",
  36. DBUS_TYPE_INVALID); //將字符串Ding!定義為消息
  37. dbus_connection_send(bus, message, NULL); // 發送該消息
  38. dbus_message_unref(message); // 釋放消息對象
  39. g_print("ding!\n"); // 該函數等同與標準輸入輸出
  40. return TRUE;
  41. }
main()函數創建一個GLib事件循環,獲得會話總線的一個連接,並將D-Bus事件處理集成到GLib事件循環之中。然後它創建了一個名為send_ding()函數作為間隔為一秒的計時器,並啓動事件循環。send_ding()函數構造一個來自於對象路徑"/com/burtonini/dbus/ding"和接口"com.burtonini.dbus.Signal"的新的Ding信號。然後,字符串Ding!作為參數添加到信號中並通過總線發送。在標準輸出中會打印一條消息以讓用户知道發送了一個信號。
2.消息接收程序
dbus-ding-listen.c程序通過會話總線接收dbus-ding-send.c程序發送到消息。該程序的源代碼如下:
  1. #include // 包含glib庫
  2. #include // 包含glib庫中D-Bus管理庫
  3. static DBusHandlerResult signal_filter // 定義接收消息函數的原型
  4. (DBusConnection *connection, DBusMessage *message, void *user_data);
  5. int main()
  6. {
  7. GMainLoop *loop; // 定義一個事件循環對象的指針
  8. DBusConnection *bus; // 定義總線連接對象的指針
  9. DBusError error; // 定義D-Bus錯誤消息對象
  10. loop = g_main_loop_new(NULL, FALSE); // 創建新事件循環對象
  11. dbus_error_init(&error); // 將錯誤消息對象連接到D-Bus
  12. // 錯誤消息對象
  13. bus = dbus_bus_get(DBUS_BUS_SESSION, &error); // 連接到總線
  14. if (!bus) { // 判斷是否連接錯誤
  15. g_warning("連接到D-Bus失敗: %s", error.message);
  16. // 使用GLib輸出錯誤警告信息
  17. dbus_error_free(&error); // 清除錯誤消息
  18. return 1;
  19. }
  20. dbus_connection_setup_with_g_main(bus, NULL);
  21. // 將總線設為接收GLib事件循環
  22. dbus_bus_add_match(bus, "type='signal',interface  ='com.burtonini.dbus.Signal'"); // 定義匹配器
  23. dbus_connection_add_filter(bus, signal_filter, loop, NULL);
  24. // 調用函數接收消息
  25. g_main_loop_run(loop); // 啓動事件循環
  26. return 0;
  27. }
  28. static DBusHandlerResult // 定義接收消息函數的細節
  29. signal_filter (DBusConnection *connection,   DBusMessage *message, void *user_data)
  30. {
  31. GMainLoop *loop = user_data; // 定義事件循環對象的指針,並與主函數中的同步
  32. if (dbus_message_is_signal // 接收連接成功消息,判斷是否連接失敗
  33. (message, DBUS_INTERFACE_ORG_FREEDESKTOP_LOCAL,  "Disconnected")) {
  34. g_main_loop_quit (loop); // 退出主循環
  35. return DBUS_HANDLER_RESULT_HANDLED;
  36. }
  37. if (dbus_message_is_signal(message, "com.burtonini.dbus.Signal",
  38. "Ping")) {
  39. // 指定消息對象路徑,判斷是否成功
  40. DBusError error; // 定義錯誤對象
  41. char *s;
  42. dbus_error_init(&error); // 將錯誤消息對象連接到D-Bus錯誤
  43. // 消息對象
  44. if (dbus_message_get_args // 接收消息,並判斷是否有錯誤
  45. (message, &error, DBUS_TYPE_STRING, &s,   DBUS_TYPE_INVALID)) {
  46. g_print("接收到的消息是: %s\n", s); // 輸出接收到的消息
  47. dbus_free (s); // 清除該消息
  48. }
  49. else { // 有錯誤時執行下列語句
  50. g_print("消息已收到,但有錯誤提示: %s\n", error.message);
  51. dbus_error_free (&error);
  52. }
  53. return DBUS_HANDLER_RESULT_HANDLED;
  54. }
  55. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  56. }
該程序偵聽dbus-ping-send.c程序正在發出的信號。main()函數和前面一樣啓動,創建一個到總線的連接。然後它聲明願意在使用com.burtonini.dbus.Signal接口的信號被髮送時得到通知,將signal_filter()函數設置為通知函數,然後進入事件循環。當滿足匹配的消息被髮送時,signal_func()函數會被調用。
如果需要確定在接收消息時如何處理,可通過檢測消息頭實現。若收到的消息為總線斷開信號,則主事件循環將被終止,因為監聽的總線已經不存在了。若收到其他的消息,首先將收到的消息與期待的消息進行比較,兩者相同則輸出其中參數,並退出程序。兩者不相同則告知總線並沒有處理該消息,這樣消息會繼續保留在總線中供別的程序處理。