How to Control Daemon Process From Another Process in C

Jinku Hu Feb 02, 2024
  1. Use fork and setsid Functions to Create a Daemon Process
  2. Use the daemon Function to Create a Daemon Process
How to Control Daemon Process From Another Process in C

This article will introduce multiple methods about how to control the daemon process from another process in C.

Use fork and setsid Functions to Create a Daemon Process

Daemon processes have several characteristics, such as that they are long-running processes, and daemons may be started at the system startup. Daemon process can be controlled from the user commands that force it to terminate or pause or even disable at startup. Although, the common scenario would entail a daemon terminating at the system shutdown using some system-specific scripts. Daemons are generally running in the background without a controlling terminal, and the programs that employ such features should implement various signal handlers to deal with external interrupts.

There are multiple methods of creating a daemon process and monitoring it, but in this example, we demonstrate the creation stage, where we call a fork function to create a child process. Then, the parent process exits, and the child continues the execution as it becomes the child of the init process (on Linux systems, the init process is the first process on startup). The child process calls the setsid function to start a new session and remove the process from the controlling terminal. Finally, we call fork once again and exit from the parent process to ensure our daemon will not acquire a controlling terminal. Now we are executing the daemon process; it’s important to register a SIGTERM signal handler to conduct any cleanup of resources and graceful exit when the system or the user delivers the corresponding interrupt.

#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 the daemon Function to Create a Daemon Process

Even though the previous example demonstrates seemingly correct code, it lacks the steps to ensure all open file descriptors inherited from the parent are closed; the working directory changed, redirect standard input to /dev/null, and so on. These steps guarantee that certain functions won’t fail if the daemon invokes them, and also some weird behavior is not observed.

For example, if you started the previous program from the terminal window and then send the SIGTERM signal to the daemon process, the write function from the cleanupRoutine signal handler will print to the same terminal even after the new prompt is displayed. Thus, the daemon function is provided by the GNU C library. It automatically takes care of the above steps to ensure a clean context for the newly created daemon process. daemon function takes two integer arguments: the first (if equal to zero) specifying if the current working directory should be changed to the root directory. The second integer (if equal to zero) indicating if the standard I/O streams should be redirected to /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);
}
Author: 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

Related Article - C Process