What is IPC
IPC [Inter-Process Communication] 進程間通信,指至少兩個進程或線程間傳送數據或信號的一些技術或方法。在Linux/Unix中,提供了許多IPC。Unix七大IPC:
- Pipe:無名管道,最基本的IPC,單向通信,僅在父/子進程之間,也就是將一個程序的輸出直接交給另一個程序的輸入。常見使用為
ps -ef|grep xxx
- FIFO [
(First in, First out)
] 或 有名管道(named pipe
):與Pipe不同,FIFO可以讓兩個不相關的進程可以使用FIFO。單向。 - Socket 和 Unix Domain Socket:socket和Unix套接字,雙向。適用於網絡通信,但也可以在本地使用。適用於不同的協議。
- 消息隊列
Message Queue
: SysV 消息隊列、POSIX 消息隊列。 - Signal: 信號,是發送到正在運行的進程通知以觸發其事件的特定行為,是IPC的一種有限形式。
- Semaphore:信號量,通常用於IPC或同一進程內的線程間通信。他們之間使用隊列進行消息傳遞、控制或內容的傳遞。(常見SysV 信號量、POSIX 信號量)
- Shared memory:(常見SysV 共享內存、POSIX 共享內存)。共享內存,是在進程(程序)之間傳遞數據的有效方式,目的是在其之間提供通信。
每種IPC都有不通的特點,每種方式對資源的使用及性能都是不通的
- 管道 I/O是最快的,但為單向通信,需要工作在 父/子 進程關系之間。
- UNIX 套接字可以在本地連接不同的進程,並且具有更高的帶寬,並且沒有固有的消息邊界。
- TCP/IP套接字可以連接任何進程。並且可以通過網絡連接,但是對資源會有更多的開銷,同樣的沒有固定的消息邊界。
Reference
What is D-Bus
提到,D-Bus就不能不提一下freedesktop,而 D-Bus 僅僅作為freedesktop.org
的一部分。
D-Bus 桌面總線 (Desktop Bus
),的簡寫,也是Linux- IPC機制,不同於Unix 7大基礎IPC的是,D-Bus是在這些IPC類型之上實現的中間件IPC,D-Bus使用了基礎IPC中一種過多種,其設計的目的是在Linux桌面環境,提供服務的標准化。但目前並沒有合入主線內核中。
作為中間件IPC,D-Bus的性能較低與其他IPC模式,因為在通信過程中會進行很多上下文切換,如果通過Dbus來發送消息,會先將其發送到內核,然后將其送回D-Bus。AF_BUS
補丁是新的套接字類型,用來減少D-Bus上下文的切換。
更多可參考:https://en.wikipedia.org/wiki/D-Bus
D-Bus組成
D-Bus是 一個IPC的實現方式,在架構上分位三層。
- Layer 1 libdbus:freedesktop機構提供的一個免費開源的一個由C語言編寫的
low-level API
。是提供dbus功能的庫。是高級API綁定的低級API。 - Layer 2 dbus daemon:dbus實現的IPC守護進行,隨Linux啟動,通過不通進程對其的連接,實現了多進程間消息的路由(包含內核、網絡、桌面等)
- Layer 3 Wapper libraries (high-level API): 對
low-level API
libdbus的封裝 ,例如libdbus-qt
libdbus-python
github.com/godbus/dbus
,這些不同編程語言實現的Wapper是不同開發者應該使用的lib,其簡化了D-Bus的開發難度。
Reference
dbus 基本概念
總線
在 D-Bus 中,bus是一個核心概念。它是應用程序可以進行方法調用、發送信號和偵聽信號的通道。有兩種預定義的bus:會話總線和系統總線。
-
會話總線(Session Bus):普通進程創建,可同時存在多條。會話總線屬於某個進程私有,它用於進程間傳遞消息。
-
系統總線(System Bus):在引導時就會啟動,它由操作系統和后台進程使用,安全性非常好,以使得任意的應用程序不能欺騙系統事件。當然,如果一個應用程序需要接受來自系統總線的消息,他也可以直接連接到系統總線中,但是他能發送的消息是受限的。系統總線最常見的用途是在系統范圍事件發生時發送系統范圍的通知。添加新的存儲設備、網絡連接更改事件和關閉相關事件都是系統總線何時更適合通信總線的示例。
通常情況下只存在一個System Bus
,但可以存在多個Session Bus
(每個桌面會話一個)。
總線以dbus-daemon的形式存在與系統中,該進程專門將消息從一個進程傳遞到另一個進程。該守護進程還將向總線上的所有應用程序轉發通知。
bus name
總線名稱 Bus Name,不能單單以字面意思 總線名稱 來理解,官方對其解釋為:Connections have one or more bus names associated with them. A connection has exactly one bus name that is a unique connection name.,可以出bus name其實是用來連接名稱。主要是用來標識一個應用和消息總線的連接。總線名稱主要分為兩類:唯一名稱與公共名稱。
- 唯一連接名稱
unique connection names
:以冒號(':')字符開頭的 bus name是唯一的連接名稱。例如:1.0
。每個連接都有一個唯一名。在一個 消息總線的生命期內,不會有兩個連接有相同的唯一名。 - 公共連接名稱
well-known bus names
:公共名稱是以反向DNS域名(小寫)例如:org.fedoraproject.FirewallD1
。- 如果DNS 域名包含連字符/減號,則應將其替換為下划線,如果包含數字,則應通過添加下划線進行轉義。例如: 7-zip.org的bus name應該定義為
org._7_zip.Archiver
。
- 如果DNS 域名包含連字符/減號,則應將其替換為下划線,如果包含數字,則應通過添加下划線進行轉義。例如: 7-zip.org的bus name應該定義為
Reference
對象路徑
對象路徑(Object Paths
) 是用於引用對象實例的名稱(類似於 C++ 或 Java 對象)。從概念上來說,D-Bus在消息交換中每個參與者都有任意個對象實例,如文件系統一樣,Dbus中的參與者中的對象實例也會形成一個層次樹。如,在CentOS7中 firewalld開發的D-Bus API 使用了/org/fedoraproject/FirewallD1
的層次結構。
在定義一個對象路徑時,需要注意以下:
- 路徑可以是任意長度
- 路徑必須以 ASCII '/'(整數 47)字符開頭,並且必須由以斜杠字符分隔的元素組成。
- 每個元素只能包含 ASCII 字符
[AZ][az][0-9]_
- 不允許出現 空字符串
- 多個
/
字符不能依次出現。 除非路徑是根路徑(單個/
字符),否則不允許尾隨/
字符。
接口名稱
interface
,在每個 Object Path
都包含多個接口,一般情況下接口名稱應以反向 DNS 域名開頭(小寫),(同 Java 中的接口名稱)。在命名規則上,與bus name相同。
例如:CentOS7中 firewalld開發的D-Bus API 定義的管理zone的接口 org.fedoraproject.FirewallD1.config.zone
。如果DNS名稱中包含-
,則應將其替換為下划線 _
。如果DNS 域名包含緊跟在 .
之后的數字,則接口名稱應在數字之前添加一個下划線。例如,如果 7-zip.org
插件定義了一個接口,應該被命名為org._7_zip.Plugin
.
成員方法名稱
成員方法名稱,Member names
,對於定義了接口后,需要實現其接口的放法,如需要獲得firewalld的zone時,就可以調用 org.fedoraproject.FirewallD1.getDefaultZone
。在D-Bus中Member names
通常由“駝峰式”(camel-case
)命名 。
dbus
在Linux中,如CentOS dbus包括 dbus daemon及一些cli commad。這些包
dbuslib
D-Bus的消息
最基本的D-Bus協議是一對一的通信協議。與直接使用socket不同,D-Bus是面向消息的協議。 D-Bus的所有功能都是通過在連接上流動的消息完成的。
而在D-Bus中有四種類型的消息
- METHOD_CALL 方法調用
- METHOD_RETURN 方法返回
- ERROR 錯誤
- SIGNAL 信號:與方法調用不同,信號發射沒有響應。信號發射只是一個類型為
SIGNAL
的消息。它必須具有三個標頭字段:PATH
給出發出信號的對象,加上INTERFACE
並MEMBER
給出信號的完全限定名稱。
消息返回的類型
Conventional name | 十進制值 | 說明 |
---|---|---|
INVALID |
0 | 這是個無效類型 |
METHOD_CALL |
1 | 方法調用,該方法會有提示 |
METHOD_RETURN |
2 | 方法返回的數據 |
ERROR |
3 | 錯誤返回,第一個是其錯誤的信息 |
SIGNAL |
4 | 信號的發射 |
CentOS的dbus服務管理
在CentOS7中,作為systemd的一部分D-BUS會從Systemd獲取套接字文件描述符,並使用D-Bus交換當前進程生成的socket信息。而PID 1 不使用 PolicyKit 來控制對特權操作的訪問,而是完全依賴於 low-level API D-Bus 。(這樣做是為了避免 PolicyKit 和 systemd/PID 1 之間的循環依賴。)而有些特權進程(例如關機/重啟/掛起/登陸)可以通過logind進行管理的。
由此,可以知道在CentOS中,dbus相關的服務大概有 dbus
,與 logind
。
dbus包含:
-
dbus-daemon:dbus架構中 layer 2的 dbus-damon
-
dbus-send: dbus提供的命令行工具,可以用dbus-send來發送消息。
-
dbus-monitor: dbus提供的命令行工具,用於監視總線上流動的消息。
-
dbus-launch: shell腳本啟動消息總線的命令行工具
dbus配置文件說明
dbus-daemon守護進程,有兩個配置文件,一個為 session bus,另外一個為 system bus。
標准的system bus文件 /usr/local/share/dbus-1/system.conf
session bus配置 /usr/local/share/dbus-1/session.conf
中配置。在一般情況下,不會操作這兩個文件,因其會引入 /etc/dbus-1
中的system.conf
或 session.conf
。
配置文件包含的標簽:
更多的注釋可以參考:dbus-daemon
# 根元素
<busconfig>
<!-- 根據指定的 -system或 -session 來選擇的配置文件 -->
<type>system</type>
<!-- dbus-daemon運行的用戶 -->
<user>dbus</user>
<!-- Fork into daemon mode -->
<fork/>
<!-- We use system service launching using a helper -->
<standard_system_servicedirs/>
<!-- This is a setuid helper that is used to launch system services -->
<servicehelper>//usr/libexec/dbus-1/dbus-daemon-launch-helper</servicehelper>
<!-- Write a pid file -->
<pidfile>/run/dbus/messagebus.pid</pidfile>
<!-- Enable logging to syslog -->
<syslog/>
<!-- 指定授權機制。如果不存在,所有的機制都被允許。 -->
<auth>EXTERNAL</auth>
<!-- 總線監聽的地址,支持unix socket,tcp,system等
-->
<listen>unix:path=/run/dbus/system_bus_socket</listen>
<listen>unix:path=/tmp/foo</listen>
<listen>tcp:host=localhost,port=1234</listen>
<policy context="default">
<!-- All users can connect to system bus -->
<allow user="*"/>
<!-- Holes must be punched in service configuration files for
name ownership and sending method calls -->
<deny own="*"/>
<deny send_type="method_call"/>
<!-- Signals and reply messages (method returns, errors) are allowed
by efault -->
<allow send_type="signal"/>
<allow send_requested_reply="true" send_type="method_return"/>
<allow send_requested_reply="true" send_type="error"/>
<!-- All messages may be received by default -->
<allow receive_type="method_call"/>
<allow receive_type="method_return"/>
<allow receive_type="error"/>
<allow receive_type="signal"/>
<!-- Allow anyone to talk to the message bus -->
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus" />
<allow send_destination="org.fedoraproject.FirewallD1"
send_interface="org.fedorapproject.FirewallD1" />
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Introspectable"/>
<!-- But disallow some specific bus services -->
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus"
send_member="UpdateActivationEnvironment"/>
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Debug.Stats"/>
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.systemd1.Activator"/>
</policy>
<!-- Only systemd, which runs as root, may report activation failures. -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.systemd1.Activator"/>
</policy>
<!-- root may monitor the system bus. -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Monitoring"/>
</policy>
<!-- If the Stats interface was enabled at compile-time, root may use it.
Copy this into system.local.conf or system.d/*.conf if you want to
enable other privileged users to view statistics and debug info -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Debug.Stats"/>
</policy>
<!-- Include legacy configuration location -->
<include ignore_missing="yes">/etc/dbus-1/system.conf</include>
<!-- 包含的子配置文件. -->
<includedir>system.d</includedir>
<includedir>/etc/dbus-1/system.d</includedir>
<!-- This is included last so local configuration can override what's
in this standard file -->
<include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>
<include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
</busconfig>
通過命令行發送dbus消息
dbus支持通過命令發送一個dbus消息,如獲取可用的dbus 服務。
dbus-send --session \
--dest=org.freedesktop.DBus \
--type=method_call \
--print-reply \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListNames
method return time=1631452206.288425 sender=org.freedesktop.DBus -> destination=:1.29 serial=3 reply_serial=2
array [
string "org.freedesktop.DBus"
string "org.freedesktop.login1"
string "org.freedesktop.systemd1"
string "org.fedoraproject.FirewallD1"
string "org.freedesktop.PolicyKit1"
string ":1.17"
string ":1.0"
string ":1.29"
string ":1.18"
string ":1.1"
]
返回org.freedesktop.DBus service
dbus-send --session \
--dest=org.freedesktop.DBus \
--type=method_call \
--print-reply \
/org/freedesktop/DBus \
org.freedesktop.DBus.Introspectable.Introspect
使用dbus api操作linux防火牆
Firewalld是一個基於動態區域的防火牆守護進程,自 2009 年左右開始開發,目前為Fedora 18 以及隨后的 RHEL7 和 CentOS 7 中的默認防火牆機制。
Firewalld被配置為systemd D-Bus 服務。請注意下面的“Type=dbus”指令。
# cat /usr/lib/systemd/system/firewalld.service
[Unit]
Description=firewalld - dynamic firewall daemon
Before=network.target
Before=libvirtd.service
Before=NetworkManager.service
Conflicts=iptables.service ip6tables.service ebtables.service
[Service]
EnvironmentFile=-/etc/sysconfig/firewalld
ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS
ExecReload=/bin/kill -HUP $MAINPID
# supress to log debug and error output also to /var/log/messages
StandardOutput=null
StandardError=null
Type=dbus
BusName=org.fedoraproject.FirewallD1
[Install]
WantedBy=basic.target
Alias=dbus-org.fedoraproject.FirewallD1.service
知道了firewalld服務是基於D-Bus的,就可以通過D-Bus來操作防火牆。
查看dbus注冊的服務是否包含firewalld,這里需要注意的是,firewalld依賴dbus服務,每次啟動firewalld時注冊到dbus總線內。所以需要先啟動dbus-daemon
與 firewalld
服務。
dbus-send --system --dest=org.freedesktop.DBus --type=method_call --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep FirewallD
查看得知 org.fedoraproject.FirewallD1
為firewalld接口
查看接口所擁有的方法、屬性、信號等信息
dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply \
/org/fedoraproject/FirewallD1 org.freedesktop.DBus.Introspectable.Introspect
獲得zone
firewall-cmd --get-zones
dbus-send --system \
--dest=org.fedoraproject.FirewallD1 \
--print-reply \
--type=method_call /org/fedoraproject/FirewallD1 \
org.fedoraproject.FirewallD1.zone.getZones
查看zone內的條目信息
# firewall-cmd --zone=public --list-all
dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply --type=method_call \
/org/fedoraproject/FirewallD1 org.fedoraproject.FirewallD1.getZoneSettings string:"public"