dbus

dbus

DBUS,數據匯流排,是一個低延遲,低開銷,高可用性的ipc機制。

基本介紹

  • 中文名:訊息匯流排系統
  • 外文名:a message bus system
  • 特點:低延遲,低開銷,高可用性
  • 實質ipc機制
進程間使用D-Bus通信,D-Bus的基本概念,建立服務的流程,傳送信號的流程,進行一次遠程調用的流程,信號接收流程,

進程間使用D-Bus通信

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

D-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_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_message_new_signal()。然後我們把信號對應的相關參數壓進去 -- dbus_message_iter_init_append(); dbus_message_iter_append_basic()。然後就可以啟動傳送了 -- dbus_connection_send(); dbus_connection_flush。

進行一次遠程調用的流程

建立好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_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()函式會被調用。
如果需要確定在接收訊息時如何處理,可通過檢測訊息頭實現。若收到的訊息為匯流排斷開信號,則主事件循環將被終止,因為監聽的匯流排已經不存在了。若收到其他的訊息,首先將收到的訊息與期待的訊息進行比較,兩者相同則輸出其中參數,並退出程式。兩者不相同則告知匯流排並沒有處理該訊息,這樣訊息會繼續保留在匯流排中供別的程式處理。

相關詞條

熱門詞條

聯絡我們