Controllo del processo daemon da un altro processo in C

Jinku Hu 12 ottobre 2023
  1. Usa le funzioni fork e setsid per creare un processo daemon
  2. Usa la funzione daemon per creare un processo daemon
Controllo del processo daemon da un altro processo in C

Questo articolo introdurrà più metodi su come controllare il processo daemon da un altro processo in C.

Usa le funzioni fork e setsid per creare un processo daemon

I processi daemon hanno diverse caratteristiche, come ad esempio che sono processi a esecuzione prolungata e che i daemon possono essere avviati all’avvio del sistema. Il processo del demone può essere controllato dai comandi dell’utente che lo costringono a terminare o mettere in pausa o addirittura disabilitare all’avvio. Tuttavia, lo scenario comune comporterebbe la terminazione di un demone all’arresto del sistema utilizzando alcuni script specifici del sistema. I demoni sono generalmente in esecuzione in background senza un terminale di controllo, ei programmi che impiegano tali caratteristiche dovrebbero implementare vari gestori di segnali per gestire gli interrupt esterni.

Esistono diversi metodi per creare un processo daemon e monitorarlo, ma in questo esempio, mostriamo la fase di creazione, dove chiamiamo una funzione fork per creare un processo figlio. Quindi, il processo genitore esce e il figlio continua l’esecuzione mentre diventa figlio del processo init (sui sistemi Linux, il processo init è il primo processo all’avvio). Il processo figlio chiama la funzione setsid per avviare una nuova sessione e rimuovere il processo dal terminale di controllo. Infine, chiamiamo fork ancora una volta ed usciamo dal processo genitore per assicurarci che il nostro demone non acquisisca un terminale di controllo. Ora stiamo eseguendo il processo daemon; è importante registrare un gestore di segnali SIGTERM per eseguire qualsiasi ripulitura delle risorse e un’uscita graduale quando il sistema o l’utente fornisce l’interrupt corrispondente.

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

Usa la funzione daemon per creare un processo daemon

Anche se l’esempio precedente dimostra un codice apparentemente corretto, mancano i passaggi per garantire che tutti i descrittori di file aperti ereditati dal genitore siano chiusi; la directory di lavoro è cambiata, reindirizza lo standard input a /dev/null e così via. Questi passaggi garantiscono che alcune funzioni non falliranno se il demone le invoca, e anche alcuni comportamenti strani non vengono osservati.

Ad esempio, se hai avviato il programma precedente dalla finestra del terminale e poi invii il segnale SIGTERM al processo daemon, la funzione write dal gestore del segnale cleanupRoutine stamperà sullo stesso terminale anche dopo che il nuovo prompt è stato visualizzato. Pertanto, la funzione daemon è fornita dalla libreria GNU C. Si occupa automaticamente dei passaggi precedenti per garantire un contesto pulito per il processo daemon appena creato. La funzione demone accetta due argomenti interi: il primo (se uguale a zero) che specifica se la directory di lavoro corrente deve essere modificata nella directory root. Il secondo numero intero (se uguale a zero) che indica se i flussi di I/O standard devono essere reindirizzati a /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);
}
Autore: 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

Articolo correlato - C Process