在 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