在 C 语言中从另一个进程控制守护进程

Jinku Hu 2023年10月12日
  1. 使用 forksetsid 函数创建守护进程
  2. 使用 daemon 函数创建守护进程
在 C 语言中从另一个进程控制守护进程

本文将介绍有关如何从 C 语言中的另一个进程控制守护进程的多种方法。

使用 forksetsid 函数创建守护进程

守护程序进程具有多个特征,例如它们是长时间运行的进程,并且守护程序可以在系统启动时启动。守护进程可以从用户命令中进行控制,这些命令可以强制守护进程终止或暂停,甚至在启动时禁用。虽然,常见的情况是需要使用某些特定于系统的脚本的守护程序在系统关闭时终止。守护程序通常在没有控制终端的情况下在后台运行,采用这种功能的程序应实现各种信号处理程序以处理外部中断。

有多种方法可以创建守护进程并对其进行监视,但是在此示例中,我们演示了创建阶段,在此阶段中,我们调用 fork 函数来创建子进程。然后,父进程退出,子进程继续成为 init 进程的子进程(在 Linux 系统上,init 进程是启动时的第一个进程),子进程继续执行。子进程调用 setsid 函数以启动新会话并从控制终端中删除该进程。最后,我们再次调用 fork 并退出父进程,以确保我们的守护程序不会获得控制终端。现在我们正在执行守护进程;重要的是注册一个 SIGTERM 信号处理程序,以便在系统或用户传递相应的中断时进行任何资源清除和正常退出。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

void cleanupRoutine(int signal_number) {
  write(STDERR_FILENO, "hello", 5);
  _exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  switch (fork()) {
    case -1:
      errExit("fork");
    case 0:
      break;
    default:
      _exit(EXIT_SUCCESS);
  }
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  if (setsid() == -1) errExit("setsid");

  switch (fork()) {
    case -1:
      errExit("fork");
    case 0:
      break;
    default:
      _exit(EXIT_SUCCESS);
  }
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  struct sigaction sigterm_action;

  memset(&sigterm_action, 0, sizeof(sigterm_action));
  sigterm_action.sa_handler = &cleanupRoutine;
  sigterm_action.sa_flags = 0;

  // Mask other signals from interrupting SIGTERM handler
  if (sigfillset(&sigterm_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  // Register SIGTERM handler
  if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) {
    perror("sigaction SIGTERM");
    exit(EXIT_FAILURE);
  }

  while (1) {
    getpid();
  }

  exit(EXIT_SUCCESS);
}

使用 daemon 函数创建守护进程

即使前面的示例演示了看似正确的代码,也缺少确保关闭从父级继承的所有打开文件描述符的步骤;工作目录已更改,将标准输入重定向到/dev/null,依此类推。这些步骤可确保某些功能在守护程序调用时不会失败,并且不会观察到某些奇怪的行为。

例如,如果你从终端窗口启动了先前的程序,然后将 SIGTERM 信号发送到守护进程,那么 cleanupRoutine 信号处理程序中的 write 函数即使在显示新的提示后也会打印到同一个终端。因此,GNU C 库提供了 daemon 函数。它会自动执行上述步骤,以确保新创建的守护进程具有干净的上下文。daemon 函数采用两个整数参数:第一个(如果等于零)指定是否应将当前工作目录更改为根目录。第二个整数(如果等于零)指示是否将标准 I/O 流重定向到/dev/null

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

void cleanupRoutine(int signal_number) {
  write(STDERR_FILENO, "hello", 5);
  _exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  if (daemon(0, 0) == -1) errExit("daemon");

  fprintf(stderr, "[pid - %d] running...\n", getpid());

  struct sigaction sigterm_action;

  memset(&sigterm_action, 0, sizeof(sigterm_action));
  sigterm_action.sa_handler = &cleanupRoutine;
  sigterm_action.sa_flags = 0;

  // Mask other signals from interrupting SIGTERM handler
  if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");

  // Register SIGTERM handler
  if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) errExit("sigaction");

  while (1) {
    getpid();
  }

  exit(EXIT_SUCCESS);
}
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 创始人。Jinku 在机器人和汽车行业工作了8多年。他在自动测试、远程测试及从耐久性测试中创建报告时磨练了自己的编程技能。他拥有电气/电子工程背景,但他也扩展了自己的兴趣到嵌入式电子、嵌入式编程以及前端和后端编程。

LinkedIn Facebook

相关文章 - C Process