如何通过DBus检测Linux系统触发的关机事件
如何通过DBus订阅系统关机信号执行异步任务(systemd服务/嵌入式Linux)
在嵌入式Linux的systemd服务场景下,要区分「服务正常停止」和「系统关机触发的服务停止」,并只在关机时执行异步任务,最可靠的方式是订阅systemd提供的DBus关机预备信号。
核心原理
systemd的org.freedesktop.login1.Manager接口会在系统进入关机/重启流程时发送PrepareForShutdown信号,这个信号携带一个布尔参数:true表示即将关机,false表示关机请求被取消。我们可以通过监听这个信号,触发对应的异步任务。
具体实现步骤
连接到System DBus
登录管理服务login1运行在系统总线(system bus)上,而非会话总线(session bus),所以你的服务必须连接到system bus才能接收信号。订阅信号匹配规则
需要设置的信号匹配条件:- 接口:
org.freedesktop.login1.Manager - 信号成员:
PrepareForShutdown - 对象路径:
/org/freedesktop/login1 - 发送方:
org.freedesktop.login1
- 接口:
实现信号回调逻辑
当收到PrepareForShutdown(true)信号时,启动你的异步任务;如果是false,则可以终止正在执行的任务(如果需要)。
代码示例(GDBus,C语言)
#include <gio/gio.h> static void on_shutdown_signal(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { gboolean is_shutting_down; g_variant_get(parameters, "(b)", &is_shutting_down); if (is_shutting_down) { // 启动你的异步任务,比如调用异步函数、启动线程等 g_print("System is shutting down, starting cleanup tasks...\n"); } else { // 可选:取消未完成的异步任务 g_print("Shutdown cancelled, stopping cleanup tasks...\n"); } } int main(int argc, char *argv[]) { GDBusConnection *conn; GError *error = NULL; guint signal_id; // 连接到system bus conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (!conn) { g_printerr("Failed to connect to system bus: %s\n", error->message); g_error_free(error); return 1; } // 订阅PrepareForShutdown信号 signal_id = g_dbus_connection_signal_subscribe( conn, "org.freedesktop.login1", // 发送方名称 "org.freedesktop.login1.Manager", // 接口名称 "PrepareForShutdown", // 信号名称 "/org/freedesktop/login1", // 对象路径 NULL, // 匹配规则(无) G_DBUS_SIGNAL_FLAGS_NONE, on_shutdown_signal, // 回调函数 NULL, // 用户数据 NULL // 销毁函数 ); // 运行主事件循环(根据你的服务框架调整,比如Qt事件循环、GLib主循环) GMainLoop *loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(loop); // 清理资源 g_dbus_connection_signal_unsubscribe(conn, signal_id); g_object_unref(conn); g_main_loop_unref(loop); return 0; }
systemd服务配置注意事项
为了确保你的异步任务能在关机时顺利完成,需要在.service文件中添加以下配置:
[Service] Type=dbus BusName=your.service.bus.name # 替换为你的服务的DBus总线名称(如果适用) TimeoutStopSec=30 # 设置足够长的停止超时时间,确保异步任务完成 ExecStart=/path/to/your/service
TimeoutStopSec:默认可能只有几秒,需要根据你的任务耗时调整,避免systemd强制杀死进程。- 如果你的服务不是DBus类型,也可以用
Type=simple,但要确保进程在收到SIGTERM后不会立即退出,而是等待异步任务完成后再退出。
为什么不用析构函数?
析构函数的执行时机无法区分「正常停止服务」和「系统关机」,而且异步任务需要依赖事件循环或线程继续运行,析构函数执行时进程可能已经进入退出流程,资源被回收,异步任务无法正常完成。
内容的提问来源于stack exchange,提问作者mkarpets
相关产品推荐
相关产品推荐

