我有一个正在运行的小型 C 程序,它已经作为 systemd 的守护进程运行。目前restart
设置为always
.我想调整该单元,以便它定期重新启动,比如每天重新启动,以防我的代码出现一些内存泄漏。
我知道 systemd 提供了各种重新启动选项,例如WatchdogSec
或RuntimeMaxSec
,但这些似乎会产生一个SIGABRT
信号,我相信我无法在代码中捕获该信号,因此无法正常关闭。
无论如何,systemd 单元是否可以定期关闭并重新启动其服务?或者,是否可以SIGABRT
在我的代码中监听信号并优雅地处理它?
答案1
不,您不需要修改代码中的任何内容。就像简单一样,您可以用于crontab
定期重新启动服务。
- 检查你的进程是否还活着。
- 如果它死了,请重新启动它。
如果它占用了太多内存,请终止它并重新启动它。
PID_YOURS = `ps -a | grep "YOUR_PROCESS" | awk -F" " '{print $1}'` MEM_USES = `ps -eo pid,rss | grep "$PID_YOURS" | awk -F" " '{print $2}'`
然而,最好的方法是改进代码以减少内存泄漏。我希望你能尽可能简单地解决它。
聚苯乙烯您无法在内部捕获关键异常。特别是,由于内存泄漏导致的错误无法在内部处理,因为它们通常涉及堆损坏。因此,被内核强行终止的情况是不可避免的。此外,还有一种不同的方式来处理它;
while ( condition for keeping a service running ) {
system("your_program");
// blocked in above code. if the flow reached this point,
// it means that your program is dead.
}
或者,您可以注册一个SYSABRT
处理程序来捕获它。但是,您无法恢复正常工作的流量。您应该只执行紧急操作来保护处理程序中的数据SYSABRT
。 (但SYSABRT
处理程序不能保证在这种情况下会被执行)
_set_abort_behavior(0, _WRITE_ABORT_MSG); // suppress warnings.
signal(SIGABRT, abrtHandler); // register handler
...
void abrtHandler(int signo) {
if(signo == SIGABORT) {
// do something for storing your datas.
signal(SIGABRT, SIG_DFL); // restore an original handler.
}
}
不要在SYSABRT
处理程序上花费太多时间。不,内核立即杀死它。此外,在处理程序中执行内存分配操作SYSABRT
可能会导致严重异常。所以你需要构造紧急内存来分配内存(暂时)。
static uint8_t g_emergency[64 * 1024];
static uint8_t g_situation = 0;
static uint8_t* g_allocs = g_emergency;
void* emergency_alloc(size_t sz) {
...
if(g_situation) {
g_allocs += sz;
return g_allocs - sz;
}
...
}
最后,首先,您可以用自己的实现替换服务重启。您可以通过 Unix 套接字或文件或其他方法向您的程序发送一条消息......这不是一个复杂的问题。
为您的守护进程提供真正的解决方案。
改进您的守护进程以与 Systemd 交互。 Systemd 支持启动、重新启动、停止机制,您可以根据需要指定它们。
IE:
ExecStart=/your/daemon/path/and/binary start
ExecStop=/your/daemon/path/and/binary stop
Restart=always
WatchdogSec=10000
MemoryMax=2048M
更重要的是,它支持前置操作和后置操作,您也可以指定它们。您可以使用编程方法来处理它。如果未指定重新启动机制,则在执行 ExecStart 之前执行 ExecStop。
另外,在最新版本的systemd中,它支持对内存进行限制。
MemoryLimit
已弃用,但MemoryMax
仍然可用。您可以通过重新启动选项设置限制。你可以阅读这个文件来自 freedesktop.org。
如果你同时实现 systemd 和守护进程的交互,你就可以完美地处理它。您可以将 pid 保存到文件中,并在需要处理时获取它stop
。更重要的是,您可以重用它来检查您的守护进程是否已经在运行。如果您的系统在意外状态下关闭,您必须检查其 pid 是否正确或存在。
另外,如果您实现此方法,您可以使用它重新加载守护程序的配置。只需打开一个像mysqld这样的unix套接字,将操作作为数据即可。它允许您的守护进程可以通过存储必须存储的重要数据的操作自行停止或重新加载。
crontab 或 system() 方法可能会使您的系统陷入资源短缺。即使SYSABRT
信号也可能无法正确执行(因为它代表紧急情况)。轮询您的守护进程将使您陷入与 crontab 或 system() 方法相同的情况。
所有强行杀死它的方法都会使您的数据不被保存。您必须使用命令行参数和交互systemd
的管理周期来处理它。systemctl kill
并且kill
为了杀死你的守护进程,它们也会导致这一点。所以,我不推荐他们。
最后,您需要使用命令行参数实现交互例程,并包含SYSABRT
用于优雅处理它的处理程序。
此外,您可以通过在代码中分叉流来监视您的守护进程,并打开一些 IPC 通道,例如共享内存或 Unix Socket。
pid_t pid = fork();
if(pid == 0) {
// children.
} else {
pid = fork();
if(pid == 0) {
// parent. you can monitor a child with IPC channel and,
// you can check your child process is alive yet.
// if your child doesn't response with IPC, you can kill it in here.
} else exit(0);
}
如果您只想使用 systemd 进行控制, 你可以阅读这个文件。
请参阅该文档中的“高级单元文件”部分,您可以阅读这个文件也。它的情况与您的情况类似,例如如果守护进程不再响应,则该守护进程尚未死亡。