如何编写一个每次被杀死后都会重新启动的进程

如何编写一个每次被杀死后都会重新启动的进程

我想要的恰恰相反这个问题。我想知道如何创建一个在被终止后不断重新启动的进程。有人能给我一个实现示例吗?

例如,让我们假设我有一个简单的过程,它将时间戳和递增计数器连续记录到日志文件中,如下所示:

#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(&current_time);
        time_info = localtime(&current_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();
  }
}

子进程看到forkreturn 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 本身并未受到终止保护;如果程序不可靠并且崩溃,需要重新启动,这非常有用。

相关内容