Worldscope

Linux Daemon

Palavras-chave:

Publicado em: 05/08/2025

Understanding Linux Daemons: Building a Basic Daemon in C

This article explores the concept of Linux daemons – background processes that run without direct user interaction. We'll cover the fundamental principles behind daemonizing a process and walk through the implementation of a simple daemon in C.

Fundamental Concepts / Prerequisites

Before diving into the implementation, it's important to have a basic understanding of the following:

  • Processes: The fundamental unit of execution in Linux.
  • Process IDs (PIDs): A unique identifier for each process.
  • Session IDs: A unique identifier for a process session.
  • File Descriptors: An integer representing an open file. Standard file descriptors (0, 1, and 2) represent standard input, standard output, and standard error, respectively.
  • Signal Handling: Mechanisms for a process to react to asynchronous events.
  • Syslog: A system logging facility used to record messages from various programs.

Implementation in C


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>

// Signal handler for graceful shutdown
void signal_handler(int sig) {
  switch (sig) {
    case SIGTERM:
      syslog(LOG_INFO, "Received SIGTERM, exiting.");
      closelog(); // Close the syslog connection
      exit(EXIT_SUCCESS);
      break;
    default:
      syslog(LOG_WARNING, "Unhandled signal %d.", sig);
      break;
  }
}

int main() {
  pid_t pid, sid;

  // Fork off the parent process
  pid = fork();
  if (pid < 0) {
    perror("Fork failed");
    exit(EXIT_FAILURE);
  }
  // If we got a good pid, then we can exit the parent process.
  if (pid > 0) {
    exit(EXIT_SUCCESS);
  }

  // Change the file mode mask
  umask(0);

  // Open any logs here
  openlog("my_daemon", LOG_PID|LOG_CONS, LOG_DAEMON);

  //Create a new SID for the child process
  sid = setsid();
  if (sid < 0) {
    syslog(LOG_ERR, "setsid failed");
    exit(EXIT_FAILURE);
  }

  // Change the current working directory
  if ((chdir("/")) < 0) {
    syslog(LOG_ERR, "chdir failed");
    exit(EXIT_FAILURE);
  }

  // Close out the standard file descriptors
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);

  // Signal handling
  signal(SIGTERM, signal_handler);
  signal(SIGHUP, SIG_IGN);  // Ignore hangup signal

  // Daemon-specific initialization goes here
  syslog(LOG_INFO, "Daemon started");

  // The daemon's main loop
  while (1) {
    // Do some task here ...
    syslog(LOG_INFO, "Daemon is running...");
    sleep(30); // Sleep for 30 seconds
  }

  closelog(); // Will never reach here unless signal is caught and handled
  return EXIT_SUCCESS;
}

Code Explanation

The code above demonstrates the basic steps involved in creating a daemon process:

Forking: The fork() function creates a child process that is a copy of the parent. The parent process exits, leaving the child to continue as the daemon.

File Mode Mask: umask(0) sets the file mode creation mask to 0, allowing the daemon to create files with full permissions (subject to the user's ownership and ACLs).

Opening Logs: openlog() initializes the connection to the syslog facility. This allows the daemon to log messages for debugging and monitoring. The parameters specify the identity string ("my_daemon"), options (logging the process ID and logging to the console if syslogd is unavailable), and the facility (DAEMON).

Session ID: setsid() creates a new session. The calling process becomes the session leader. This detaches the daemon from the controlling terminal, ensuring it doesn't receive input from it.

Changing Directory: chdir("/") changes the current working directory to the root directory. This prevents the daemon from holding any directory open, which might prevent unmounting of the directory. A daemon should ideally change to a directory where it won't interfere with users.

Closing File Descriptors: close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); closes the standard input, standard output, and standard error file descriptors. This further detaches the daemon from the controlling terminal. Closing them prevents the daemon from writing to or reading from the terminal. Any future attempts to read from stdin or write to stdout/stderr will cause an error.

Signal Handling: The signal() function sets up signal handlers. SIGTERM is caught to allow the daemon to shut down gracefully (e.g., close files, clean up resources). SIGHUP (hangup) is ignored, but in a real daemon, you might want to re-read configuration files on a hangup signal. signal_handler function is defined to handle SIGTERM signal. It closes the syslog connection and then exits gracefully.

Main Loop: The while(1) loop represents the daemon's main processing loop. In this example, it simply logs a message every 30 seconds. In a real daemon, this loop would perform the daemon's intended task.

Closing Logs: closelog() closes the connection to the syslog facility.

Complexity Analysis

Time Complexity: The daemon's overall time complexity depends on the task it performs within the main loop. The daemonization process itself (forking, setting session ID, etc.) is a relatively quick sequence of operations. The sleep(30) within the example main loop adds a delay, but doesn't affect the computational complexity. For the daemon itself, the core operations outside of the main loop like fork(), setsid(), umask(), chdir() all have a time complexity of O(1).

Space Complexity: The space complexity of the daemon also depends on the data structures and algorithms used within the main loop. The initial daemonization process requires a relatively small amount of memory for the process context. The `syslog()` function may consume some memory for buffering log messages, depending on the syslog configuration. The memory footprint primarily hinges on what operations the daemon performs during its operation. Since we are only logging a string every 30 seconds and sleeping, memory requirements will stay consistent throughout the daemon's lifecycle.

Alternative Approaches

One alternative approach to creating daemons is using the systemd service manager. Instead of writing the daemonization logic yourself, you create a unit file that describes how the service should be started, stopped, and managed. systemd handles the daemonization process and provides features such as automatic restart on failure, dependency management, and logging. Using systemd simplifies daemon management but introduces a dependency on systemd being present on the system.

Conclusion

Linux daemons are essential for running background tasks. This article provided a fundamental overview of daemonizing a process in C, covering important steps like forking, creating a new session, closing file descriptors, and setting up signal handling. While this example demonstrates the core principles, more sophisticated daemons often use event loops, configuration files, and more robust error handling. Understanding the core concepts explained here is the crucial first step in designing and building reliable Linux daemons.