D-Bus 完整教程 / 10 - systemd 集成
第 10 章:systemd 集成
10.1 systemd 与 D-Bus 的关系
systemd 是 Linux 系统上最核心的初始化和服务管理系统,它与 D-Bus 有深度集成:
┌──────────────────────────────────────────────────┐
│ systemd │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ PID 1 │ │ systemd │ │ systemd │ │
│ │ (init) │ │ -logind │ │ -resolved │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ ═════╪══════════════╪═══════════════╪══════════ │
│ │ System Bus (D-Bus) │ │
│ ═════╪══════════════╪═══════════════╪══════════ │
│ │ │ │ │
│ ┌────┴─────┐ ┌────┴─────┐ ┌──────┴───────┐ │
│ │ sd-bus │ │ busctl │ │ D-Bus 激活 │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└──────────────────────────────────────────────────┘
| 集成点 | 说明 |
|---|---|
dbus.service | systemd 管理的 D-Bus 守护进程 |
dbus.socket | System Bus 的套接字激活 |
| D-Bus Activation | 通过 .service 文件激活服务 |
BusName= | 单元文件中声明拥有的总线名称 |
sd-bus | systemd 内置的轻量级 D-Bus 客户端库 |
systemctl --user | 用户服务管理(Session Bus) |
journal | 通过 D-Bus 访问系统日志 |
10.2 D-Bus 服务激活(Service Activation)
D-Bus 激活允许守护进程在首次被调用时自动启动,而不是开机就常驻。
10.2.1 激活流程
客户端调用 org.example.Service
│
↓
dbus-daemon 检查服务是否在线
│
在线?├─ 是 → 直接路由
│
↓ 否
查找 /usr/share/dbus-1/system-services/org.example.Service.service
│
↓
根据 Exec= 行启动服务
│
↓
服务注册 BusName
│
↓
转发原始调用
10.2.2 D-Bus 激活文件
传统 D-Bus 激活文件位于:
| 目录 | 总线类型 |
|---|---|
/usr/share/dbus-1/system-services/ | System Bus |
/usr/share/dbus-1/services/ | Session Bus |
/etc/dbus-1/system-services/ | System Bus(管理员自定义) |
~/.local/share/dbus-1/services/ | Session Bus(用户自定义) |
激活文件格式:
# /usr/share/dbus-1/system-services/org.example.MyService.service
[D-BUS Service]
Name=org.example.MyService
Exec=/usr/bin/my-service
User=root
SystemdService=my-service.service
| 键 | 说明 |
|---|---|
Name | 要激活的总线名称 |
Exec | 启动命令(传统方式) |
SystemdService | 关联的 systemd 服务(推荐方式) |
User | 运行用户(仅 System Bus) |
AssumedAppArmorLabel | AppArmor 标签 |
10.2.3 systemd 服务激活(推荐)
现代系统更推荐使用 systemd 进行激活:
# /etc/systemd/system/my-service.service
[Unit]
Description=My D-Bus Service
After=network.target
[Service]
Type=dbus
BusName=org.example.MyService
ExecStart=/usr/bin/my-service
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# /usr/share/dbus-1/system-services/org.example.MyService.service
[D-BUS Service]
Name=org.example.MyService
SystemdService=my-service.service
对应配置文件:
<!-- /usr/share/dbus-1/system.d/org.example.MyService.conf -->
<!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.MyService"/>
</policy>
<policy context="default">
<allow send_destination="org.example.MyService"/>
</policy>
</busconfig>
# 启用并启动服务
sudo systemctl daemon-reload
sudo systemctl enable my-service
sudo systemctl start my-service
# 验证状态
systemctl status my-service
# 测试 D-Bus 激活
# 先停止服务
sudo systemctl stop my-service
# 通过 D-Bus 调用会自动启动服务
busctl call org.example.MyService /org/example org.example.Interface Ping
10.3 BusName= 单元配置
systemd 单元文件中的 BusName= 指令用于声明服务拥有的总线名称:
[Service]
Type=dbus
BusName=org.example.MyService
ExecStart=/usr/bin/my-service
# 可选:设置总线名称获取超时
TimeoutSec=30
Type=dbus 行为 | 说明 |
|---|---|
| 启动条件 | ExecStart 进程必须在超时前获取 BusName |
| 停止行为 | 进程退出后 systemd 会释放 BusName |
| 重启策略 | Restart=on-failure 适用于获取名称失败 |
验证 BusName:
# 检查 BusName 是否已被获取
busctl list | grep org.example.MyService
# systemd 属性检查
systemctl show my-service -p BusName
10.4 用户服务(User Services)
systemd 支持用户级服务,使用 Session Bus:
10.4.1 用户服务单元
# ~/.config/systemd/user/my-app.service
[Unit]
Description=My User D-Bus Service
[Service]
Type=dbus
BusName=org.example.MyApp
ExecStart=/usr/local/bin/my-app
Restart=on-failure
[Install]
WantedBy=default.target
10.4.2 管理用户服务
# 启用用户服务
systemctl --user enable my-app
# 启动
systemctl --user start my-app
# 查看状态
systemctl --user status my-app
# 查看日志
journalctl --user -u my-app
# 用户服务的 Session Bus 激活文件
# ~/.local/share/dbus-1/services/org.example.MyApp.service
10.4.3 用户 D-Bus 激活文件
# ~/.local/share/dbus-1/services/org.example.MyApp.service
[D-BUS Service]
Name=org.example.MyApp
SystemdService=my-app.service
10.5 套接字激活(Socket Activation)
套接字激活是 systemd 最强大的功能之一,允许在服务启动前就接受连接。
10.5.1 传统 D-Bus 套接字激活
# /etc/systemd/system/my-db-service.socket
[Unit]
Description=My D-Bus Service Socket
[Socket]
ListenStream=/run/my-service.sock
[Install]
WantedBy=sockets.target
# /etc/systemd/system/my-db-service.service
[Unit]
Description=My D-Bus Service
Requires=my-db-service.socket
[Service]
Type=dbus
BusName=org.example.MyService
ExecStart=/usr/bin/my-service
10.5.2 D-Bus Broker 套接字激活
现代系统使用 dbus-broker 进行更高效的激活:
# /usr/share/dbus-1/system-services/org.example.MyService.service
[D-BUS Service]
Name=org.example.MyService
SystemdService=my-service.service
dbus-broker 使用 systemd 的套接字传递机制,提供零拷贝消息传递和更好的性能。
10.5.3 sd-daemon 套接字激活
#include <systemd/sd-daemon.h>
#include <stdio.h>
int main(void) {
int n = sd_listen_fds(0);
if (n > 0) {
printf("从 systemd 接收了 %d 个套接字\n", n);
int fd = SD_LISTEN_FDS_START; // 第一个套接字的 fd
// 使用 fd 进行通信
// ...
} else {
printf("没有从 systemd 接收套接字\n");
}
return 0;
}
10.6 sd-bus — systemd 内置 D-Bus 库
sd-bus 是 systemd 提供的轻量级 D-Bus 客户端库,适合 systemd 集成场景。
10.6.1 sd-bus vs GDBus 对比
| 特性 | sd-bus | GDBus |
|---|---|---|
| 依赖 | libsystemd | libgio (GLib) |
| 二进制大小 | ~50KB | ~2MB+ |
| 主循环 | 支持 sd-event、自定义 | GLib MainLoop |
| 代码生成 | 不支持 | gdbus-codegen |
| 性能 | 高 | 中 |
| 类型安全 | 手动解析 | GVariant |
| 适用场景 | systemd 服务、嵌入式 | GNOME 桌面应用 |
10.6.2 sd-bus 客户端示例
#include <systemd/sd-bus.h>
#include <stdio.h>
int main(void) {
sd_bus *bus = NULL;
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *reply = NULL;
int r;
/* 连接到 System Bus */
r = sd_bus_open_system(&bus);
if (r < 0) {
fprintf(stderr, "连接 System Bus 失败: %s\n", strerror(-r));
return 1;
}
/* 调用方法 */
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1", /* dest */
"/org/freedesktop/systemd1", /* path */
"org.freedesktop.systemd1.Manager", /* interface */
"ListUnits", /* method */
&error,
&reply,
"" /* 签名 (无参数) */
);
if (r < 0) {
fprintf(stderr, "调用失败: %s\n", error.message);
sd_bus_error_free(&error);
return 1;
}
/* 解析回复 */
const char *name, *description, *load_state, *active_state;
uint32_t pid;
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r >= 0) {
printf("systemd 单元列表:\n");
while ((r = sd_bus_message_read(reply, "(ssssssouso)",
&name, &description, &load_state, &active_state,
NULL, NULL, NULL, &pid, NULL, NULL)) > 0) {
printf(" %-40s %-15s %s\n", name, active_state, description);
}
sd_bus_message_exit_container(reply);
}
sd_bus_message_unref(reply);
sd_bus_unref(bus);
return 0;
}
编译:
gcc -o sd-bus-example sd-bus-example.c -lsystemd
10.6.3 sd-bus 读取属性
/* 读取 systemd 的 Version 属性 */
sd_bus_message *reply;
sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.DBus.Properties",
"Get",
&error,
&reply,
"ss",
"org.freedesktop.systemd1.Manager",
"Version"
);
if (r >= 0) {
const char *version;
sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "s");
sd_bus_message_read(reply, "s", &version);
printf("systemd 版本: %s\n", version);
sd_bus_message_exit_container(reply);
sd_bus_message_unref(reply);
}
10.6.4 sd-bus 监听信号
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
static int on_unit_new(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
const char *id, *path;
sd_bus_message_read(m, "so", &id, &path);
printf("新单元: %s (%s)\n", id, path);
return 0;
}
int main(void) {
sd_bus *bus = NULL;
sd_event *event = NULL;
sd_bus_open_system(&bus);
sd_event_default(&event);
sd_bus_attach_event(bus, event, 0);
/* 订阅 UnitNew 信号 */
sd_bus_match_signal(
bus,
NULL,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"UnitNew",
on_unit_new,
NULL
);
/* 运行事件循环 */
sd_event_run(event, (uint64_t)-1);
sd_bus_unref(bus);
sd_event_unref(event);
return 0;
}
编译:
gcc -o sd-bus-signals sd-bus-signals.c -lsystemd
10.7 通过 D-Bus 管理 systemd
10.7.1 查看 systemd 状态
# 查看 systemd 版本
busctl get-property org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
Version
# 列出所有单元
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
ListUnits
# 获取特定单元状态
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
GetUnit \
s "dbus.service"
10.7.2 操作 systemd 单元
# 启动服务
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
StartUnit \
ss "my-service.service" "replace"
# 停止服务
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
StopUnit \
ss "my-service.service" "replace"
# 重启服务
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
RestartUnit \
ss "my-service.service" "replace"
# 查看单元属性
busctl call org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
GetUnit \
s "dbus.service"
# 获取单元的所有属性
busctl get-property org.freedesktop.systemd1 \
/org/freedesktop/systemd1/unit/dbus_2eservice \
org.freedesktop.systemd1.Unit \
--all
10.7.3 Python 管理 systemd
#!/usr/bin/env python3
"""通过 D-Bus 管理 systemd 服务"""
import dbus
bus = dbus.SystemBus()
# 获取 systemd 代理
systemd = bus.get_object(
'org.freedesktop.systemd1',
'/org/freedesktop/systemd1'
)
manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager')
# 列出活跃服务
units = manager.ListUnits()
print("活跃服务:")
for name, desc, load, active, sub, job_type, job_path, job_id, job_obj_path, follows in units:
if active == 'active' and sub == 'running':
print(f" ✅ {name}: {desc}")
# 启动服务
try:
job_path = manager.StartUnit('nginx.service', 'replace')
print(f"启动作业: {job_path}")
except dbus.exceptions.DBusException as e:
print(f"启动失败: {e}")
# 获取服务状态
props = dbus.Interface(systemd, 'org.freedesktop.DBus.Properties')
unit_path = manager.GetUnit('dbus.service')
unit = bus.get_object('org.freedesktop.systemd1', unit_path)
unit_props = dbus.Interface(unit, 'org.freedesktop.DBus.Properties')
active_state = unit_props.Get('org.freedesktop.systemd1.Unit', 'ActiveState')
print(f"\ndbus.service 状态: {active_state}")
10.8 logind 集成
# 查看当前会话
busctl call org.freedesktop.login1 \
/org/freedesktop/login1 \
org.freedesktop.login1.Manager \
ListSessions
# 锁屏
busctl call org.freedesktop.login1 \
/org/freedesktop/login1 \
org.freedesktop.login1.Manager \
LockSession \
s "auto"
# 查询能力
busctl call org.freedesktop.login1 \
/org/freedesktop/login1 \
org.freedesktop.login1.Manager \
CanPowerOff
# 注销当前会话
busctl call org.freedesktop.login1 \
/org/freedesktop/login1 \
org.freedesktop.login1.Manager \
TerminateSession \
s "auto"
本章小结
| 概念 | 说明 |
|---|---|
| D-Bus 激活 | 按需启动服务,减少资源占用 |
| BusName= | systemd 单元文件中声明总线名称 |
Type=dbus | systemd 服务类型,等待 BusName 获取 |
| 用户服务 | systemctl --user 管理的服务 |
| 套接字激活 | 在服务启动前就接受连接 |
| sd-bus | systemd 内置的轻量级 D-Bus 库 |