CentOS 9系统下以systemctl服务运行的C++控制台应用如何实现停止检测与资源清理?
嘿,这个场景太常见了——把控制台程序转成systemd服务后,原来的交互式退出逻辑直接失效,毕竟服务是后台运行的,没人能按回车。核心解决思路是让你的C++程序监听systemd发送的终止信号,在收到信号后优雅地完成资源清理再退出。
先搞懂systemd会发什么信号
当你执行systemctl stop your-service时,systemd默认会先给进程发SIGTERM信号,这是一个“友好”的终止通知,程序可以捕获它并做清理。如果程序在指定时间内没退出,systemd才会发SIGKILL(这个信号无法捕获,会直接强制杀死进程)。另外,像systemctl reload会发SIGHUP,你也可以一起处理。
C++程序里捕获信号的实现
推荐用sigaction()来注册信号处理函数(比老式的signal()更可靠)。核心思路是:在信号处理函数里设置一个退出标志,然后主循环检查这个标志,一旦触发就执行清理逻辑并退出。
下面是一个完整的示例代码:
#include <atomic> #include <signal.h> #include <unistd.h> #include <fstream> #include <iostream> // 用原子变量确保多线程环境下的线程安全 std::atomic<bool> g_should_exit(false); // 信号处理函数:只做最基础的标志设置,避免调用非异步安全函数 void signal_handler(int signum) { g_should_exit = true; // 这里可以用write()输出日志(write是异步安全的),不要用cout/printf const char* msg = "Received termination signal, preparing to exit...\n"; write(STDOUT_FILENO, msg, sizeof(msg)-1); } int main() { // 配置信号处理 struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); // 不阻塞其他信号 sa.sa_flags = 0; // 捕获SIGTERM(systemd stop默认信号)和SIGHUP(重载信号) sigaction(SIGTERM, &sa, nullptr); sigaction(SIGHUP, &sa, nullptr); // 模拟你的业务资源:比如打开日志文件 std::ofstream log_file("/var/log/my_app.log", std::ios::app); if (!log_file.is_open()) { const char* err = "Failed to open log file\n"; write(STDERR_FILENO, err, sizeof(err)-1); return 1; } // 主业务循环 while (!g_should_exit) { // 这里写你的程序核心逻辑 log_file << "Application is running..." << std::endl; sleep(2); // 保留原有的控制台退出逻辑(可选) if (std::cin.peek() == '\n') { std::cin.ignore(); g_should_exit = true; } } // 资源清理阶段:关闭文件、释放内存、断开连接等 log_file << "Cleaning up resources..." << std::endl; log_file.close(); const char* exit_msg = "Application exited gracefully\n"; write(STDOUT_FILENO, exit_msg, sizeof(exit_msg)-1); return 0; }
配合systemd服务文件的优化
要让systemd更好地管理你的服务,需要创建一个.service文件,比如/etc/systemd/system/my-app.service:
[Unit] Description=My C++ Background Service After=network.target # 如果依赖网络,加上这个 [Service] Type=simple ExecStart=/usr/local/bin/my-app # 替换成你的程序路径 User=non-root-user # 尽量用非root用户运行,提升安全性 Group=non-root-group TimeoutStopSec=30 # 给程序30秒时间完成清理,按需调整 Restart=on-failure # 可选:程序崩溃时自动重启 [Install] WantedBy=multi-user.target
然后执行以下命令启用服务:
sudo systemctl daemon-reload sudo systemctl enable my-app.service sudo systemctl start my-app.service
关键注意事项
- 信号处理函数要异步安全:绝对不要在信号处理函数里调用
cout、printf或者C++标准库的大部分函数,这些不是异步安全的,可能导致程序崩溃。只做标志设置或用write()写日志。 - 多线程环境要注意同步:如果你的程序是多线程的,
g_should_exit必须是原子变量,或者用互斥锁保护,避免竞态条件。 - 测试清理逻辑:执行
systemctl stop my-app.service后,用journalctl -u my-app.service -f查看日志,确认清理代码是否执行。 - 避免SIGKILL:确保
TimeoutStopSec足够长,让程序能完成清理,不然systemd会强制杀死进程,导致资源泄漏。
内容的提问来源于stack exchange,提问作者Boppity Bop
相关产品推荐
相关产品推荐

