我想要的恰恰相反这个问题。我想知道如何创建一个在被终止后不断重新启动的进程。有人能给我一个实现示例吗?
例如,让我们假设我有一个简单的过程,它将时间戳和递增计数器连续记录到日志文件中,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define FILE_PATH "/home/lincoln/date.log"
int main() {
time_t current_time;
struct tm *time_info;
int counter = 0;
while (1) {
FILE *file = fopen(FILE_PATH, "a");
if (file == NULL){
perror("Failed to open the file");
return 1;
}
// Get the current timestamp
time(¤t_time);
time_info = localtime(¤t_time);
// Format the timestamp
char timestamp[25];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", time_info);
// Write timestamp and counter to the file
fprintf(file, "%s | Counter: %d\n", timestamp, counter++);
fflush(file);
fclose(file);
sleep(5); // Sleep for 5 seconds
}
return 0;
}
为了生成二进制文件(ELF 文件),我使用了以下命令:
gcc logger_file.c -o date_test
(请注意,这只是一个例子。它可能是一个不断打印的“hello world”程序。我选择日志记录是因为我想观察它连续运行的效果。)
来自引用的问题(如果我错了,请纠正我),它是通过监视子进程的父进程来实现的。如果子进程被终止,父进程会重新启动它。我理解这个概念,但不知道它在实践中是如何实现的。
有人可以演示如何使用提供的二进制示例来实现它吗? (也欢迎链接参考我的研究更多内容。)
如果需要更多信息,请告诉我。
编辑:在我的实现中,我计划在嵌入式设备上进行。基本上,在我的工作环境中,系统初始化作为启动系统和BusyBox v1.23.2作为在armv7处理器上运行的3.18.44内核上的工具集(我正在使用gcc-12-arm-linux-gnueabi-base
工具链)。
答案1
进程一旦死亡就无法自行复活。它死了,它实际上不能再做任何事情了(这就是重点)。
您可以让一个主管检查进程的状态,并在旧进程死亡时生成一个新进程。
几乎所有(除了一些利基或面向小型容器的)Linux 发行版都使这一点变得微不足道:systemd 可以做到这一点,您所需要做的就是编写一个 systemd 服务单元文件并设置Restart=
财产至always
、 或on-abnormal
、 或on-abort
。就是这样!
然后,systemd 充当主管(无需编写自己的主管来摆弄 procfs 或类似的东西),并且一切开箱即用,您仍然可以明确地通过 停止服务重新启动systemctl
。
现在,您问在实践中如何做到这一点。我提到过procfs
;这是方法之一:您只需查看 /proc/{PID} 目录或例如其中的内存映射。
然而,监管者就是这样通常这样做(systemd 的 service.c 也是这样做的),就是它们注册一个处理程序SIGCHLD
:当进程的子进程退出时,父进程会收到一个信号。因为主管实际上是通过 启动受监督进程作为子进程fork
,所以这是有效的。
所以,你大致需要以下内容松散地摘自古代甲骨文指南和 Linux 手册页man waitpid
:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
void proc_exit() {
int wstat;
pid_t pid;
while (1) {
int retval = waitpid(pid, &wstat, WNOHANG);
if (retval == 0)
return;
else if (retval == -1)
return;
else
printf("Return code: %d\n", WEXITSTATUS(wstat));
}
}
int main() {
signal(SIGCHLD, proc_exit);
switch (fork()) {
case -1:
perror("main: fork");
exit(0);
case 0:
printf("I'm alive (temporarily)\n");
exit(42);
default:
pause();
}
}
子进程看到fork
return 0,所以它是打印 的进程"I'm alive…
。在这里,如果您想生成一个不同的可执行文件(即您的受监督进程),请调用execve
.
答案2
如果您足够幸运,系统上有一个常规init
守护进程,您可以在 中运行该进程/etc/inittab
,您可以在其中指定该进程在终止时重新启动。
在/etc/inittab
您添加一个像这样的条目:
dtst:345:respawn:/path/to/date_time
这dtst
只是一个标签;345
意味着它在运行级别 3、4 和 5 中处于活动状态,并且respawn
意味着如果进程终止(我们想要的),该进程将重新启动。
然后使用telinit
命令,或者kill -HUP 1
告诉init
重新读取该文件。
可能有一些复杂的方法可以在使用systemd
而不是的系统上执行类似的操作init
。
BusyBoxinit
忽略runlevels
条目中的字段inittab
;该id
字段有一些特殊含义;如果不为空,则设置控制 TTY。
另一种方法是使用“保姆”shell 脚本来执行此操作:
#!/bin/sh
while true; do
/path/to/date_test
done
我们可以设置它,以便如果程序正常终止且状态成功,则循环将终止:
while !/path/to/date_test; do : ; done
当date_test
程序未成功或异常终止时,运行 do-nothing null 命令:
并重复。
不过,运行此循环的 shell 本身并未受到终止保护;如果程序不可靠并且崩溃,需要重新启动,这非常有用。