設定 - D-Bus
概要
D-Busは、オープンソースのプロセス間通信(IPC:Inter Process Communication)機構であり、freedesktop.orgプロジェクトの一部である。
IPCとは、1台のコンピュータ上で動作する複数のプログラムの間で情報を交換するシステムのことである。
IPCには、パイプ、名前付きパイプ、シグナル、共有メモリ、Unixソケット、ループバックソケット等がある。
D-Busもリンク層はUnixソケットで動作しているが、手順とフォーマット(プレゼンテーション層)が既定されていることが、「生の」Unixソケットとは異なる。
開発当初はGNOME等のGUIの制御を目的としていが、今日では、GUIに限らず幅広いソフトウェアで使用されており、
デスクトップの通知、メディアプレーヤー制御、XDGポータル等、多くのfreedesktop.org標準がD-Busをベースに構築されている。
IPCとは、あるプロセスから別のプロセスへ情報を取得する方法を説明するために使用することができる。
これは、データの交換、メソッドの呼び出し、イベントのリスニング等がある。
- デスクトップにおけるIPCの使用例
- スクリプト(ユーザが共通の環境でスクリプトを実行して、実行中の様々なソフトウェアと対話または制御する)
- 集中型サービスへのアクセスの提供
- 協調型ソフトウェアの複数のインスタンス間の調整
- スクリプト(ユーザが共通の環境でスクリプトを実行して、実行中の様々なソフトウェアと対話または制御する)
- D-Busの使用例
- freedesktop.orgのnotification仕様
- これは、ソフトウェアは通知を中央サーバ(Plasma等)に送信して、中央サーバは通知を表示して、通知が閉じられたりアクションが実行されたりといったイベントを送り返すものである。
- freedesktop.orgのnotification仕様
- IPCの他の使用例
- ユニークなソフトウェアのサポート
- これは、ソフトウェアの起動時に、まず、同じソフトウェアの他の実行中のインスタンスを確認して、
- もし存在すれば、IPCを介して実行中のインスタンスにメッセージを送信して、自分自身を表示して終了させる。
- ユニークなソフトウェアのサポート
D-Busは、言語やツールキットに囚われないため、あらゆるプロバイダのソフトウェアやサービスが相互作用することができる。
デーモン(軽量サービスプロバイダ)とそれを利用したいソフトウェアが、必要なサービス以上のことを知らなくても通信できるようにするためによく利用される。
Qtは、D-Busと対話するためのクラスとツールのセットを提供している。
D-Busの基礎
D-Busとは
D-Busは、メッセージをオブジェクトに届ける仕組みである。
メッセージはデータであり、オブジェクトはデータの受け手であるプログラムのことである。
D-Busのメッセージには、手続き呼び出し(METHOD_CALL)、手続きの戻り値(METHOD_RETURN)、エラー(ERROR)、シグナル(SIGNAL)の4種類がある。
手続き呼び出し(METHOD_CALL)と手続きの戻り値(METHOD_RETURN)はペアで使用されており、
他のプロセスが提供するサービス機能を関数の呼び出しのように使用することができるという一種のRPC(Remote Procedure Call)のようなものとして扱うことができる。
ただし、D-Busは同一PC上のIPCに特化しており、RPCのようなネットワークを介した異なるPC間の通信は原則としてサポートしていない。
シグナル(SIGNAL)は、片方向の一斉通知として使用することができ、ネットワークが落ちた時、USBデバイスの検出、再起動の要請等の通知メカニズムとして多用されている。
オブジェクトは、サービスに内包された操作対象を指す名前である。
D-Busサービス名とは
D-Busにおけるサービス名は、サービス提供プロセスがD-Busサーバに接続する時に登録する名前のことである。
"org.freedesktop.DBus"や"org.bluez"等のように、.(コロン)
区切りの文字列(慣習として、サービスプログラム開発元の公式ドメイン)が使用される。
※注意
D-Busサービス名は、バス名、コネクション名等、異なる名前で呼ばれる場合もある。
D-Busの仕様としては、バス名が正式名称であり、名前を明示的に指定したバス名をサービス名と呼ぶこともある。
プロセスはサービス名を明示せずに、D-Busサーバに接続することも可能である。
この場合、":1.128"のようにD-Busサーバが適当に生成した数字の羅列が割り当てられる。
D-Busオブジェクトとは
オブジェクトは、バス名(D-Busサービス名)に対して送られるメッセージに含まれる、そのバス名(D-Busサービス名)内におけるメッセージの宛先である。
/
から始まり、/(スラッシュ)
区切りの文字列で表記されており、これを、オブジェクトパス、あるいは、パス名、パスと呼ぶこともある。
慣習として、パス名は、サービス名の.
を/
に置き換えた接頭辞(プレフィックス)を持たせる。
例えば、BlueZのbluetoothデーモンは、サービス名org.bluezを持つが、BlueZのオブジェクトは/org/bluez/hci0のような名前である。
HCIインターフェースを複数持つシステムの場合、/org/bluez/hci1、/org/bluez/hci2のようなオブジェクト名により、どのインターフェースに対するメッセージかを識別することになる。
サービスが持つオブジェクトの一覧は、イントロスペクト(Introspect)という機能により検索することができる。
例えば、BlueZ 5のサービスに対して、dbus-send
コマンドを実行する。
dbus-send --print-reply --system --dest=org.bluez / --type=method_call org.freedesktop.DBus.Introspectable.Introspect # 出力例 ...略 </interface><node name="org"/></node>"
上記の出力例のように、出力された<node name="org"/>の項目は、以下に示す内容を意味する。
- バス名(D-Busサービス名)
org.bluez
のD-Busオブジェクト/
に対して、 - 手続きメッセージ
org.freedesktop.DBus.Introspectable.Introspect
を送信した時、 /
の下にはorg
というオブジェクトがあるというメッセージを受信した。
D-Busインターフェース (D-Busメッセージ)
上記セクションにあるような"org.freedesktop.DBus.Introspectable.Introspect"を手続きメッセージの例として扱っている。
これは、freedesktop.orgで既定された標準メッセージの一部であるが、各サービスごとに独自のメッセージを実装することもできる。
例えば、BlueZ 5において、HCIインターフェースを検索可能状態にするメッセージは、以下に示すようなものになる。
dbus-send --print-reply \ --system \ --dest=org.bluez \ /org/bluez/hci0 \ --type=method_call \ org.bluez.Adapter1.StartDiscovery
D-Busインターフェースは、オブジェクト指向の考えに基づいており、同じD-Busインターフェースに属する手続きは、異なるD-Busオブジェクト(サービス)間でも同じ機能として動作する。
例えば、イントロスペクト機能のorg.freedesktop.DBus.Introspectable.IntrospectというD-Busインターフェースは、
org.freedesktop.DBusサービスの/オブジェクトに対しても、org.bluezサービスの/org/bluez/hci0オブジェクトに対しても、同様に動作する。
D-Busプロパティ
org.freedesktop.DBusの標準インターフェースとして、D-Busプロパティがある。(プロパティと呼ぶこともある)
D-Busプロパティは、名前 + 値のセットであり、これにより、D-Busオブジェクトの持つ状態や設定等を共通の枠組みで扱うことができる。
D-Busプロパティのインターフェースはorg.freedesktop.DBus.Propertiesであり、Get
メソッドやSet
メソッド等が定義されている。
以下の例では、org.bluezサービス、/org/bluez/hci0オブジェクトの持つプロパティの一覧が表示される。
dbus-send --print-reply \ # 相手からの応答を出力 --system \ # D-Busのタイプ (システムバスまたはセッションバス) --dest=org.bluez \ # バス名 (D-Busサービス名) /org/bluez/hci0 \ # D-Busオブジェクト名 --type=method_call \ # メッセージタイプ # メソッドの場合はmethod_call
、シグナルの場合はsignal
を指定する # 省略した場合は、signal
を指定したことになる org.freedesktop.DBus.Properties.GetAll \ # D-Busインターフェース名(org.freedesktop.DBus.Properties)とメソッド名(GetAll) string:org.bluez.Adapter1 # メソッドの引数
バス
D-Busは、ソフトウェアが相互に通信するため、複数のメッセージバスを提供する。
各バスには独自の接続機能があり、異なるカテゴリーのメッセージを分離することができる。
あるバスで送信されたメッセージは他のバスからアクセスできないが、同じバスに接続されたソフトウェアは全て互いに通信することができる。
任意のバスに複数のソフトウェアを同時に接続することができ、また、1つのソフトウェアが複数のバスに同時に接続することも可能である。
これにより、バスごとに異なるセキュリティポリシーを適用できると共に、グローバルメッセージとローカルメッセージの両方を効率的に共有することができる。
D-Busは、2つの定義済みバス(システムバスとセッションバス)を予め用意されており、一般的なD-Busの使用方法をほぼカバーすることができる。
- システムバス
- ハードウェア管理等のシステムグローバルサービスに使用される。
- これはユーザー間で共有され、通常、厳格なセキュリティ・ポリシーが付属しています。
- セッションバス
- 各デスクトップセッション(ログインしているユーザ等)には、セッションバスが存在する。
- これは、GUIソフトウェア等が最も頻繁に使用する傾向がある。
さらに、ソフトウェアは、必要に応じて複数の独自のバスを作成することができる。
メッセージ
メッセージは、バスにおける通信の基本単位である。
TCP/IPのパケットと同様に、バスでやり取りされる情報は、全てメッセージで行われる。
しかし、ネットワークのパケットとは異なり、D-Busの各メッセージには、送受信されるデータ1式が含まれていることが保証されている。
メッセージには、送信されるデータだけでなく、送信者と受信者が誰であるかも記録されており、適切なルーティングを可能にする。
メッセージは、メソッドコール、シグナルエミッション、メソッドの戻り値であり、エラー情報も含むことがある。
名前空間とアドレス
複数のソフトウェアが同一のバス上に存在し、1つのソフトウェアが複数のオブジェクトを提供してメッセージを送信することができるため、
特定の住宅やオフィスを一意に識別するのと同様に、任意のバス上の任意のオブジェクトを効果的かつ一意にアドレス指定する手段を持つことが必要である。
インターフェース、サービス、オブジェクト名の3つの情報を組み合わせることにより、バス上の任意のオブジェクトに一意のアドレスを作成することができる。
インターフェイス
インターフェイスは、バス上に公表される呼び出し可能なメソッドとシグナルのセットである。
インターフェースは、メッセージを渡すソフトウェアの間で、インターフェースの名前、パラメータ(もしあれば)、戻り値(もしあれば)を定義する"契約"を提供する。
これらのメソッドは、インターフェイスを使用しているソフトウェアのメソッドやAPIに1対1で直接マッピングされることもある。(直接マッピングされないこともある)
これにより、複数のソフトウェアが、内部の実装に関係なく、類似または同一のインターフェイスを提供することができ、
一方で、ソフトウェアは、ソフトウェアの内部設計を気にすることなく、これらのインターフェイスを使用できるようになる。
インターフェイスは、文書化やコードの再利用を目的として、XMLで記述する。
ユーザや開発者は、インターフェースのXML記述を参照できるだけでなく、開発者はXMLから自動生成されたクラスを使用することができる。
このため、D-Busの使用は非常に簡単で、エラーが発生しにくくなる。(例. コンパイラがコンパイル時にメッセージの構文を確認することができる)
サービス
サービスとは、ソフトウェアとバスの接続を表すものである。
ここでいうサービスとは、D-Busの用語でいうバス名に相当するものである。
(バス名という用語は、バス上の接続名であり、バスの名前ではない。そのため、Qtのドキュメントで記載されているように、サービスという用語を使用する)
これらは、複数のコンポーネントの名前空間を必要とする他の多くのシステムで見られるように、"逆ドメイン名"アプローチを使用することにより、一意に保たれる。
KDEプロジェクトのソフトウェアが提供する多くのサービスでは、サービス名にorg.kdeというプレフィックスを使用している。
そのため、セッションバスにおいて、org.kde.screensaverが宣伝されているのを見掛けるかもしれない。
サービス名には、開発者の組織やソフトウェアのドメイン名を使用することを推奨する。
例えば、開発者のドメインがawesomeapps.org、ソフトウェアの名前がwickedwidgetの場合、バス上のサービス名はorg.awesomeapps.wickedwidgetとなる。
ソフトウェアが複数のバスに接続している場合、または、同一のソフトウェアの複数のインスタンスが同時にアクティブになっている場合は、接続ごとに一意なサービス名を使用する必要がある。
多くの場合、プロセスIDをサービス名に付加することでこれを実現する。
オブジェクト
ソフトウェアは、バス上の複数のオブジェクトへのアクセスを宣伝する可能性が高い。
オブジェクトとサービスの間のこの多対1の関係は、アドレスにパスコンポーネントを提供することで対応される。
サービスに関連付けられた各パスは、異なる一意のオブジェクトを表す。(例. /MainInterface、/Documents/Doc1)
実際のパス構造は完全に任意であり、どのようなパスにするかは、サービスを提供するソフトウェア次第である。
これらのパスは、他のソフトウェアにメッセージを送信するソフトウェアのために、オブジェクトを識別し、論理的にグループ化する方法を提供する。
一部のライブラリは、オブジェクトを適切に名前空間化するために、オブジェクトパスの先頭に"リバースドメイン"を付けてエクスポートする。
これは、任意のサービスに参加するライブラリやプラグインではよくあることで、そのため、ソフトウェアや他のコンポーネントによりエクスポートされたオブジェクトとの全ての衝突を避けなければならない。
しかし、KDEソフトウェアやライブラリでは、この方法は使用されていない。
オブジェクトはインターフェースへのアクセスを提供しており、オブジェクトは同時に複数のインタフェースへのアクセスを提供することができる。
アドレスの例
D-Busメッセージには、上記で記載したコンポーネントで構成されるアドレスが含まれており、正しいソフトウェア、オブジェクト、メソッドコールにルーティングされる。
# 例. KRunnerのアドレス org.kde.krunner /App org.kde.krunner.App.display org.kde.krunner : サービス /App : オブジェクトへのパス org.kde.krunner.App : オブジェクトがエクスポートするインターフェース display : インターフェース内のメソッド
もし、/Appオブジェクトがorg.kde.krunner.Appインターフェースを提供するのみの場合(または、実装するサービスの中で表示メソッドが一意の場合)、これはアドレスとして同様に機能する。
org.kde.krunner /App display
このようにして、可能性のあるそれぞれの宛先を一意かつ確実にアドレスで指定することができる。
メソッドとシグナル
バス上の任意のエンドポイントをアドレス指定する方法を使用して、メッセージを送受信する場合の可能性を検討する。
メソッド
受信側のソフトウェアにおいて、メソッド(関数)を実行するために送信されるメッセージのことである。
メソッドの呼び出しに失敗した場合(アドレスの間違い、または、要求されたソフトウェアが動作していない等の理由でメソッドが利用できない場合)、呼び出し側のソフトウェアにエラーが返る。
メソッドの呼び出しに成功した場合、呼び出し元のソフトウェアにオプションの戻り値が返る。(戻り値が提供されない場合でも、成功メッセージが返る)
このラウンドトリップにはオーバーヘッドがあり、パフォーマンスが重要なコードではこれを念頭に置くことが重要である。
メソッド呼び出しは、常に呼び出し元のソフトウェアによって開始されて、結果として生じるメッセージは正確に1つの送信元アドレスと1つの送信先アドレスを持つ。
シグナル
シグナルはメソッドコールに似ているが、"逆方向"に発生すること、単一の宛先に縛られないことが特徴である。
シグナルは、インターフェイスをエクスポートしているソフトウェアから発信されて、同一バス上のどのソフトウェアでも利用できる。
これにより、ソフトウェアは、状態の変化やその他のイベントを、それらの変化を追跡するソフトウェアに自発的に知らせることができる。
これは、Qtのシグナルとスロットの仕組みに似ており、これは、同じ機能のシステム版である。
D-Busコマンド
以下の例では、Linuxでのシャットダウン、再起動、ログアウト等のD-Busサービスの一覧を取得している。
dbus-send --print-reply=literal \ --system \ --dest=org.freedesktop.login1 \ /org/freedesktop/login1 \ --type=method_call \ org.freedesktop.DBus.Introspectable.Introspect
以下の例では、D-Busサービスを使用してシャットダウンおよび再起動を実行している。
# シャットダウン dbus-send --print-reply=literal \ --system \ # バスタイプ --dest=org.freedesktop.login1 \ # バス名(D-Busサービス名) /org/freedesktop/login1 \ # D-Busオブジェクト名(オブジェクトパス) # バス名(D-Busサービス)に送信するメッセージに含まれるそのバス名(D-Busサービス)内におけるメッセージの宛先 "org.freedesktop.login1.Manager.PowerOff" \ # D-Busインターフェース名(org.freedesktop.login1.Manager)とメソッド名(PowerOff) boolean:true # メソッド名の引数 (引数の型(boolean)と値(true)) # 再起動 dbus-send --print-reply=literal \ --system \ # バスタイプ --dest=org.freedesktop.login1 \ # バス名(D-Busサービス名) /org/freedesktop/login1 \ # D-Busオブジェクト名(オブジェクトパス) # バス名(D-Busサービス)に送信するメッセージに含まれるそのバス名(D-Busサービス)内におけるメッセージの宛先 "org.freedesktop.login1.Manager.Reboot" \ # D-Busインターフェース名(org.freedesktop.login1.Manager)とメソッド名(Reboot) boolean:true # メソッド名の引数 (引数の型(boolean)と値(true))
D-Bus向け設定ファイル
メッセージ・バス・デーモンは、特定のアプリケーションに特化した設定ファイルを持っている。
例えば、ある設定ファイルではメッセージバスをシステム全体のメッセージバスとして設定し、別の設定ファイルではユーザログインセッションごとのバスとして設定することができる。
また、設定ファイルでは、リソースの制限やセキュリティパラメータ等も設定できる。
これらの設定ファイルは、相互運用性の仕様の一部ではなく、後方互換性は保証されていないことに注意すること。
標準的なシステム全体およびセッションごとのメッセージバスの設定は、/usr/share/dbus-1/system.confファイルおよび/usr/share/dbus-1/session.confファイルで設定される。
これらのファイルは、通常、/etc/dbus-1/system-local.confファイルまたはsession-local.confファイルをインクルードしている。
主な設定ファイルを変更しないように、これらのファイルにローカルなオーバーライドすることができる。
標準のシステムバスは通常、/usr/share/dbus-1/system.dディレクトリから追加のXMLファイルを読み込む。
サードパーティ製のソフトウェアは、dbus 1.10 (2015年にリリース)からサポートされている正しい動作に必要なデフォルトポリシーを、そのディレクトリにインストールする必要がある。
標準のシステムバスは、通常、/etc/dbus-1/system.dディレクトリからXMLファイルも読み込むため、システム管理者がデフォルトポリシーを上書きしたい場合に使用する。
ただし、サードパーティ製のソフトウェアは、歴史的に、/etc/dbus-1/system.dディレクトリにXMLファイルをインストールしていたが、現在は非推奨とされており、
/etc/dbus-1/system.dディレクトリは、システム管理者のために予約されたものとして扱われるべきである。
D-Busサービスファイル
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.example.HogeHelper"/>
</policy>
<policy context="default">
<allow send_destination="org.example.HogeHelper"/>
<allow receive_sender="org.example.HogeHelper"/>
</policy>
</busconfig>
<policy user="root">
セクションは、rootユーザに適用されるポリシーを定義する。
- own (所有) の許可
- rootユーザに対して、"org.example.HogeHelper"という名前のD-Busサービスを「所有」する権限を与える。
- サービスの起動と管理
- D-Busサービスを「所有する」とは、そのサービスを起動および管理する権限を持つことを意味する。
- 排他的な制御
- rootユーザのみがこのサービスを起動できる。
- 他のユーザはこのサービスを所有したり、起動したりすることはできない。
- セキュリティの確保
- 特権的な操作を行うサービスを、rootユーザのみが制御できるようにすることにより、セキュリティを高めている。
この設定は、D-Busサービスが確実にroot権限で、かつroot権限でのみ実行されることを保証するものである。
これにより、特権的な操作を安全に実行できる環境を確立している。
一般ユーザはサービスとの通信は可能であるが、サービスの起動や直接的な制御はrootユーザに制限されるため、セキュリティと機能性のバランスが取れた構成となっている。
次に、<policy context="default">
セクションは、特に指定されていない全てのユーザに適用されるデフォルトのポリシーを定義する。
- <allow send_destination="org.example.HogeHelper"/>
- 全てのユーザがorg.example.HogeHelperサービスにメッセージを送信することを許可する。
- つまり、どのユーザでもこのD-Busサービスのメソッドを呼び出すことができる。
- <allow receive_sender="org.example.HogeHelper"/>
- 全てのユーザがorg.example.HogeHelperサービスからのメッセージを受信することを許可する。
- これにより、サービスからの応答や信号を全てのユーザが受け取ることができる。
この設定は、一般ユーザがHogeHelperサービスと通信できることを保証するものである。
ただし、サービス自体はroot権限で実行されるため、特権的な操作を安全に行うことができる。
サービスへのアクセスは許可しつつ、サービス自体の所有と実行はrootに制限することにより、特権的な操作を制御された方法で行うことができる。
D-Busサービス定義ファイル
D-Busシステムは必要に応じて、ヘルパーサービスを自動的に起動することができる。
これにより、クライアントアプリケーションは、サービスの起動を意識することなく、単にD-Bus経由でサービスにアクセスするだけで済むようになる。
[D-BUS Service]
Name=org.example.HogeHelper
Exec=/usr/local/bin/HogeHelper
# 任意
User=root
# 任意
Environment=<環境変数>
# 任意
# Systemdサービスユニットを使用して、D-Busサービスを実行・管理する場合のみ
SystemdService=HogeHelper.service
- Name
- D-Busサービスの名前を指定する。
- クライアントがこの名前を使用して、D-Busサービスにアクセスする。
- Exec
- サービスを起動するための実行ファイルのパスを指定する。
- User (オプション)
- サービスを実行するユーザ名を指定する。
- root権限で起動する場合は、rootと指定する。
- SystemdService (オプション)
- 対応するSystemdサービスユニットの名前を指定する。
- これが指定されている場合、D-BusはSystemdを使用してサービスを起動する。
- Environment (オプション)
- サービス用の環境変数。
SystemdService
行を含めることにより、D-BusはSystemdを使用してサービスを管理することができる。
これにより、より柔軟な起動制御や依存関係の管理が可能になる。
- 動作の仕組み
- クライアントがサービスにアクセスしようとすると、D-Busはこのファイルを参照する。
- 該当するD-Busサービスが実行されていない場合、D-BusはExec行で指定されたコマンドを使用して、サービスを起動する。
SystemdService
行が指定されている場合、D-BusはSystemdにサービスの起動を要求する。
便利なツール
D-Busのバスの検索やD-Busを使用したソフトウェアの開発には、いくつかのエンドユーザ向けの便利なツールが存在する。
qdbus
qdbus
コマンドは、コマンドラインツールであり、与えられたバス上のサービス、オブジェクト、インタフェースを表示したり、バス上の与えられたアドレスにメッセージを送信するために使用できる。
これは、セッションバスおよびシステムバスの両方を検索するために使用することができる。
--system
オプションを付加する場合は、qdbusはシステムバスに接続する。
--system
オプションを付加しない場合は、セッションバスを使用する。
qdbus
コマンドは、与えられた引数を、与えられたオブジェクトに渡すためのアドレス、および、パラメータ(存在する場合)として使用する。
もし、完全なアドレスが与えられなかった場合、バス上のその地点から利用可能な全てのオブジェクトを表示する。
例えば、アドレスが提供されない場合は、利用可能なサービスのリストが表示される。
サービス名を指定する場合、オブジェクトのパスが提供される。
パスを指定する場合、全てのインターフェースの全てのメソッドが表示される。
このように、非常に簡単にバス上のオブジェクトを検索して操作することができる。
qdbusviewer
qdbusviewer
は、Qtで作成されたGUIソフトウェアであり、qdbus
コマンドがCUIから提供する機能と基本的に同一のものをGUIとして提供している。
qdbusviewer
は、Qt本体と同梱されており、オブジェクトアドレス等のD-Busの基本概念に慣れていれば、誰でも簡単に使用できる。