点击此处查看详细分析解答
如何正确使用 libsystemd 中的 sd-bus 接口获取 D-Bus 属性
根据您提供的问题描述,您在使用 C 代码调用 libsystemd 提供的 sd-bus 接口时,无法成功获取 D-Bus 属性,返回错误码 -113(即 EHOSTDOWN 或 ECONNREFUSED),但在命令行使用 busctl --user 命令却能成功获取属性值 "CpuBoard"。
问题分析
1. 错误码 -113 的含义
在 Linux 系统中,-113 对应错误号 EHOSTDOWN,表示“主机已关闭”或“目标服务不可达”。但在 D-Bus 上下文中,该错误通常意味着:
- 连接到了错误的总线类型(system bus vs user bus)
- 要访问的服务未在当前总线中运行或已失效
关键点:busctl --user 表明目标服务运行在 用户总线(User Bus) 上,而您的 C 代码调用的是 sd_bus_default_system(&bus),即连接 系统总线(System Bus),总线不匹配导致服务无法查找。
正确使用 sd-bus 的方法
修正建议:使用 sd_bus_default_user() 而非 sd_bus_default_system()
由于 busctl --user 明确表明操作的是 用户会话总线(User Session Bus),您的 C 代码也必须连接到同一总线。
修正后的核心代码如下:
{
sd_bus *bus = NULL;
char *val = NULL;
int r;
FILE *file;
memset(out, 0, out_len);
file = fopen("/var/log/bus.log", "w+");
if (!file) {
return -1;
}
// 修改点:使用 user bus 而非 system bus
r = sd_bus_default_user(&bus);
if (r < 0) {
fprintf(file, "Failed to connect to user bus: %s\n", strerror(-r));
fclose(file);
return -1;
}
// 确保连接建立完成
r = sd_bus_flush_close_unref(bus); // 临时释放测试前先 flush
bus = NULL;
r = sd_bus_default_user(&bus);
if (r < 0) {
fprintf(file, "Reconnect failed: %s\n", strerror(-r));
fclose(file);
return -1;
}
// 获取属性(注意接口名大小写)
r = sd_bus_get_property_string(
bus,
"bmc.kepler.general_hardware", // 服务名
"/bmc/kepler/Systems/1/Boards/CpuBoard/CpuBoard_1_010102", // 对象路径
"bmc.kepler.Systems.Board", // 接口名
"BoardType", // 属性名(原文为 Boardtype,应为 BoardType)
NULL,
&val
);
fprintf(file, "r: %d\n", r);
if (r >= 0 && val) {
snprintf(out, out_len, "%s", val);
fprintf(file, "out: %s\n", out);
} else {
fprintf(file, "get_property failed, error: %s\n", strerror(-r));
sd_bus_flush_close_unref(bus);
fclose(file);
return -1;
}
sd_bus_flush_close_unref(bus); // 正确释放资源
fclose(file);
return 0;
}
关键说明
| 项目 |
说明 |
sd_bus_default_user() |
用于连接当前用户的会话总线(Session Bus),与 busctl --user 一致 |
sd_bus_default_system() |
连接的是系统总线,bmc.kepler.general_hardware 可能未在此总线上注册 |
属性名大小写敏感 |
D-Bus 属性名区分大小写。BoardType ≠ Boardtype,请确认实际接口定义 |
sd_bus_get_property_string() |
是高层封装函数,内部自动处理消息发送与类型解析 |
其他验证和调试建议
1. 确认用户总线环境变量
用户总线依赖环境变量 DBUS_SESSION_BUS_ADDRESS 或 XDG_RUNTIME_DIR,若 C 程序未在用户登录会话中运行(如后台服务或 SSH 启动),可能无法访问用户总线。
解决方案:
2. 使用更低级 API 以增强控制能力
如果高层函数仍失败,可改用底层 API 手动调用 org.freedesktop.DBus.Properties.Get 方法:
r = sd_bus_call_method(
bus,
"bmc.kepler.general_hardware",
"/bmc/kepler/Systems/1/Boards/CpuBoard/CpuBoard_1_010102",
"org.freedesktop.DBus.Properties",
"Get",
NULL, // 错误输出回调
NULL, // 回复消息指针(可选)
"ss", // 输入参数类型:service interface, property name
"bmc.kepler.Systems.Board", "BoardType"
);
然后解析返回的 variant 类型值。
3. 日志建议
您已记录 r 值,建议增加 strerror(-r) 输出具体错误信息,便于诊断:
fprintf(file, "Error: %s (%d)\n", strerror(-r), r);
参考信息(来自上下文知识库)
libsystemd 提供了 sd_bus_default_system 和 sd_bus_default_user 接口用于连接系统总线和用户总线。
busctl --user 操作的是 用户总线,因此所有程序必须与之一致。
- 您的应用场景属于 BMC 环境下的 Lua-C 协作系统,需确保 C 库与 Lua 所处的 D-Bus 上下文一致。
总结:解决方案步骤
- 修改代码:将
sd_bus_default_system 改为 sd_bus_default_user
- 校验属性名:确认
BoardType(而非 Boardtype)是正确的属性名
- 设置环境变量:确保程序运行在有效的用户会话环境中
- 增加错误日志:打印
strerror(-r) 获取详细错误信息
- 测试独立运行:先写一个独立的 C 可执行程序测试,成功后再封装为
.so
附加建议:Lua 中调用注意事项
当您从 Lua 调用此 .so 时,请确保:
- Lua 进程本身也在用户会话中启动(如通过
snlua 启动且已配置好环境)
.so 不应直接 free(val),而应将其复制到 Lua 可访问内存区,避免跨语言内存管理问题
如有需要,可进一步提供 dbus-monitor --session 输出来确认服务是否存在及属性是否可读。