Processo de controle daemon de outro processo em C

Jinku Hu 12 outubro 2023
  1. Use as funções fork e setsid para criar um processo Daemon
  2. Use a função daemon para criar um processo daemon
Processo de controle daemon de outro processo em C

Este artigo irá apresentar vários métodos sobre como controlar o processo daemon de outro processo em C.

Use as funções fork e setsid para criar um processo Daemon

Os processos daemon têm várias características, como o fato de serem processos de longa execução e os daemons podem ser iniciados na inicialização do sistema. O processo daemon pode ser controlado a partir dos comandos do usuário que o forçam a encerrar, pausar ou até mesmo desabilitar na inicialização. Embora, o cenário comum implicaria em um daemon encerrando no desligamento do sistema usando alguns scripts específicos do sistema. Os daemons geralmente são executados em segundo plano, sem um terminal de controle, e os programas que empregam tais recursos devem implementar vários manipuladores de sinal para lidar com interrupções externas.

Existem vários métodos para criar um processo daemon e monitorá-lo, mas neste exemplo, demonstramos o estágio de criação, onde chamamos uma função fork para criar um processo filho. Em seguida, o processo pai sai e o filho continua a execução à medida que se torna filho do processo init (em sistemas Linux, o processo init é o primeiro processo na inicialização). O processo filho chama a função setsid para iniciar uma nova sessão e remover o processo do terminal de controle. Finalmente, chamamos fork mais uma vez e saímos do processo pai para garantir que nosso daemon não adquira um terminal de controle. Agora estamos executando o processo daemon; é importante registrar um manipulador de sinal SIGTERM para conduzir qualquer limpeza de recursos e saída normal quando o sistema ou o usuário entregar a interrupção correspondente.

#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);
}

Use a função daemon para criar um processo daemon

Mesmo que o exemplo anterior demonstre um código aparentemente correto, ele não possui as etapas para garantir que todos os descritores de arquivo abertos herdados do pai sejam fechados; o diretório de trabalho mudou, redirecione a entrada padrão para /dev/null e assim por diante. Essas etapas garantem que certas funções não falharão se o daemon as invocar e também que algum comportamento estranho não seja observado.

Por exemplo, se você iniciou o programa anterior a partir da janela do terminal e, em seguida, enviar o sinal SIGTERM para o processo daemon, a função write do manipulador de sinal cleanupRoutine será impressa no mesmo terminal, mesmo após o novo prompt ser exibido. Portanto, a função daemon é fornecida pela biblioteca GNU C. Ele automaticamente cuida das etapas acima para garantir um contexto limpo para o processo daemon recém-criado. A função daemon leva dois argumentos inteiros: o primeiro (se igual a zero) especificando se o diretório de trabalho atual deve ser alterado para o diretório raiz. O segundo inteiro (se igual a zero) indicando se os fluxos de E/S padrão devem ser redireccionados para /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);
}
Autor: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook

Artigo relacionado - C Process