OpenBMC实战:从零构建C语言SD-Bus服务与自启动应用

张开发
2026/4/17 6:08:55 15 分钟阅读

分享文章

OpenBMC实战:从零构建C语言SD-Bus服务与自启动应用
1. OpenBMC开发环境搭建在开始构建SD-Bus服务之前我们需要先准备好OpenBMC的开发环境。OpenBMC是一个开源的基板管理控制器(BMC)固件项目主要用于服务器和网络设备的远程管理。这里我推荐使用QEMU模拟器来搭建开发环境这样既方便调试又不需要额外的硬件设备。首先需要安装必要的依赖包。在Ubuntu系统上可以执行以下命令sudo apt-get update sudo apt-get install -y git build-essential libsdl2-dev gawk wget diffstat bison flex接下来克隆OpenBMC的代码仓库并初始化开发环境git clone --recursive https://github.com/openbmc/openbmc.git cd openbmc source setup evb-ast2500这个过程中可能会遇到一些依赖问题根据提示安装缺少的包即可。我曾经在这个步骤卡了很久后来发现是因为网络问题导致某些子模块没有正确克隆。如果遇到类似情况可以尝试多次执行git submodule update --init命令。环境搭建完成后我们可以使用以下命令启动QEMU模拟器bitbake obmc-phosphor-image runqemu evb-ast2500第一次编译会花费较长时间因为需要下载和编译大量依赖包。建议在晚上或者空闲时间执行这个操作。编译完成后后续的修改和测试就会快很多。2. 创建基础应用程序2.1 使用Yocto添加简单程序让我们从一个最简单的Hello World程序开始。在OpenBMC中我们使用Yocto的bitbake工具来管理软件包。首先在meta-phosphor/recipes-phosphor目录下创建一个新的recipemkdir -p meta-phosphor/recipes-phosphor/hello/files cd meta-phosphor/recipes-phosphor/hello/files创建hello.c文件内容如下#include stdio.h int main(int argc, char** argv) { printf(Hello OpenBMC!\n); return 0; }然后创建hello_1.0.bb文件DESCRIPTION Hello World example SRC_URI file://hello.c PV 1.0 LICENSE CLOSED do_compile() { ${CC} ${LDFLAGS} ../hello.c -o hello } do_install() { install -m 0755 -d ${D}${bindir} install -m 0755 ${S}/hello ${D}${bindir} } FILES:${PN} ${bindir}/hello这个recipe做了以下几件事定义了软件包的描述和版本信息指定了源代码文件提供了编译和安装的指令定义了最终打包的文件2.2 编译和测试程序在build/conf/local.conf文件中添加以下内容确保我们的程序会被包含到镜像中IMAGE_INSTALL:append hello然后执行编译命令bitbake -c build hello bitbake obmc-phosphor-image编译完成后启动QEMU模拟器就可以在系统中运行我们的hello程序了/usr/bin/hello如果一切正常你应该能看到Hello OpenBMC!的输出。我在第一次尝试时遇到了权限问题后来发现是因为没有正确设置文件的执行权限。这个细节提醒我们在嵌入式开发中权限设置往往比桌面环境更加严格。3. 实现SD-Bus服务3.1 SD-Bus基础概念SD-Bus是systemd提供的一个D-Bus实现相比传统的D-Bus它提供了更简洁的API和更好的性能。在OpenBMC中大部分服务间的通信都是通过SD-Bus完成的。一个典型的SD-Bus服务包含以下几个部分服务名称如org.freedesktop.Example对象路径如/org/freedesktop/LogControl1接口如org.freedesktop.LogControl1方法和属性服务提供的具体功能3.2 创建SD-Bus服务程序让我们修改之前的hello程序让它成为一个真正的SD-Bus服务。首先更新hello.c文件#include systemd/sd-bus.h #include stdio.h #include unistd.h static int method_hello(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { printf(Hello method called!\n); return sd_bus_reply_method_return(m, s, Hello from OpenBMC!); } static const sd_bus_vtable hello_vtable[] { SD_BUS_VTABLE_START(0), SD_BUS_METHOD(Hello, , s, method_hello, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; int main(int argc, char *argv[]) { sd_bus_slot *slot NULL; sd_bus *bus NULL; int r; r sd_bus_open_system(bus); if (r 0) { fprintf(stderr, Failed to connect to system bus: %s\n, strerror(-r)); goto finish; } r sd_bus_add_object_vtable(bus, slot, /org/freedesktop/Example, org.freedesktop.Example, hello_vtable, NULL); if (r 0) { fprintf(stderr, Failed to add object: %s\n, strerror(-r)); goto finish; } r sd_bus_request_name(bus, org.freedesktop.Example, 0); if (r 0) { fprintf(stderr, Failed to acquire service name: %s\n, strerror(-r)); goto finish; } printf(Service ready!\n); for (;;) { r sd_bus_process(bus, NULL); if (r 0) { fprintf(stderr, Failed to process bus: %s\n, strerror(-r)); goto finish; } if (r 0) continue; r sd_bus_wait(bus, (uint64_t) -1); if (r 0) { fprintf(stderr, Failed to wait on bus: %s\n, strerror(-r)); goto finish; } } finish: sd_bus_slot_unref(slot); sd_bus_unref(bus); return r 0 ? EXIT_FAILURE : EXIT_SUCCESS; }这个程序实现了一个简单的SD-Bus服务它提供了一个Hello方法当被调用时会返回Hello from OpenBMC!字符串。3.3 更新recipe文件我们需要更新hello_1.0.bb文件添加systemd库的依赖do_compile() { ${CC} ${LDFLAGS} ../hello.c -o hello -lsystemd }重新编译并运行程序后我们可以使用busctl工具来测试这个服务busctl tree org.freedesktop.Example busctl call org.freedesktop.Example /org/freedesktop/Example org.freedesktop.Example Hello如果一切正常第二个命令应该会返回Hello from OpenBMC!字符串。我在开发过程中发现SD-Bus对错误处理非常严格任何小的错误都可能导致服务无法正常启动。建议在开发时仔细检查每个返回值并使用journalctl -f来查看实时日志。4. 配置系统自启动4.1 创建systemd服务文件为了让我们的服务在系统启动时自动运行我们需要创建一个systemd服务文件。在files目录下创建hello.service[Unit] DescriptionOpenBMC Hello Service Afterdbus.service [Service] Typesimple ExecStart/usr/bin/hello Restartalways SyslogIdentifierhello [Install] WantedBymulti-user.target这个服务文件定义了服务描述和启动顺序在dbus服务之后服务类型和启动命令崩溃时自动重启日志标识符安装目标多用户模式4.2 更新recipe支持systemd我们需要修改hello_1.0.bb来包含这个服务文件inherit systemd SRC_URI file://hello.service SYSTEMD_SERVICE:${PN} hello.service SYSTEMD_AUTO_ENABLE:${PN} enable do_install() { install -m 0755 -d ${D}${bindir} install -m 0755 ${S}/hello ${D}${bindir} install -d ${D}${systemd_system_unitdir} install -m 0644 ${WORKDIR}/hello.service ${D}${systemd_system_unitdir} } FILES:${PN} ${bindir}/hello FILES:${PN} ${systemd_system_unitdir}/hello.service关键改动包括继承systemd类添加服务文件到SRC_URI定义SYSTEMD_SERVICE和SYSTEMD_AUTO_ENABLE变量更新do_install以安装服务文件添加服务文件到打包文件列表4.3 测试自启动功能重新编译并更新镜像后启动QEMU模拟器。系统启动后可以使用以下命令检查服务状态systemctl status hello journalctl -u hello如果配置正确你应该能看到服务已经自动启动并正常运行。我在第一次尝试时遇到了服务启动失败的问题后来发现是因为没有正确设置Afterdbus.service导致服务在DBus就绪前就尝试启动。这个经验告诉我在编写systemd服务文件时依赖关系的设置非常重要。5. 调试技巧与最佳实践5.1 日志输出技巧在OpenBMC开发中合理的日志输出非常重要。以下是几种常用的日志输出方式及其特点直接输出到控制台printf(This will appear on console if StandardOutputtty\n);使用systemd的日志接口sd_journal_print(LOG_INFO, This will go to journal directly);带优先级的日志输出sd_journal_print(LOG_ERR, Error message with priority);在实际项目中我发现以下几点特别有用对于关键路径使用LOG_ERR级别确保问题能被及时发现调试信息使用LOG_DEBUG级别避免污染生产环境日志使用sd_journal_print而不是printf因为它提供了更丰富的元数据5.2 调试SD-Bus服务调试SD-Bus服务时以下命令非常有用查看总线上的所有服务busctl list查看服务的对象树busctl tree org.freedesktop.Example查看服务的接口和方法busctl introspect org.freedesktop.Example /org/freedesktop/Example调用方法busctl call org.freedesktop.Example /org/freedesktop/Example org.freedesktop.Example Hello监控总线上的所有消息busctl monitor5.3 性能优化建议在开发OpenBMC服务时性能是一个重要考虑因素。以下是一些优化建议减少DBus消息大小避免传输大量数据必要时可以考虑使用共享内存使用信号而不是轮询对于状态变化使用DBus信号通知而不是让客户端不断查询合理设置超时对于可能阻塞的操作设置合理的超时时间批量操作对于多个小操作考虑提供批量处理接口我曾经优化过一个服务将多个小属性读取合并为一个批量读取接口性能提升了近10倍。这个经验告诉我在嵌入式环境中减少通信开销往往能带来显著的性能提升。6. 进阶主题使用sdbusplus C库6.1 sdbusplus简介虽然本文主要使用C语言开发但OpenBMC社区更推荐使用C和sdbusplus库。sdbusplus是OpenBMC提供的一个C库它封装了SD-Bus的底层细节提供了更易用的接口。主要优势包括类型安全编译时检查类型减少运行时错误代码生成可以从接口定义自动生成服务端和客户端代码更简洁的API隐藏了SD-Bus的许多细节6.2 使用代码生成工具sdbusplus提供了一个代码生成工具sdbus它可以从YAML接口定义生成C代码。例如给定以下YAML定义interface: org.freedesktop.Example methods: - name: Hello parameters: [] returns: string可以生成服务端和客户端代码大大简化开发工作。生成的代码处理了所有底层的DBus细节开发者只需要实现业务逻辑即可。6.3 迁移到C的考虑如果考虑从C迁移到C需要注意以下几点二进制大小会增加因为C运行时更大异常处理需要特别注意避免异常跨越DBus边界内存管理方式不同C通常使用RAII模式在资源受限的BMC环境中这些因素都需要仔细权衡。我的经验是对于简单的服务C语言可能更合适对于复杂的服务C的生产力优势会更加明显。

更多文章