UNIX Daemons and Services
A daemon is a background process that runs independently of direct user interaction. In UNIX-like operating systems, daemons manage long-running services, such as web servers (httpd), SSH endpoints (sshd), and system logging daemons (syslogd). By convention, the names of these processes terminate with the character ‘d’.
Characteristics of Daemons
To run detached from any terminal session and persist across the system lifetime, a traditional POSIX daemon must establish a specific execution environment.
- Orphan Status: The daemon detaches from the terminal session that launched it. It does this by calling
fork(). The parent process immediately exits, and the child process becomes an orphan, which is adopted by the system initialization process (initorsystemd, PID 1). - Session Disconnection: The process calls
setsid()to become the leader of a new process group and session. This severs its connection to any controlling terminal (TTY), preventing the process from receiving terminal-generated signals (such asSIGHUPorSIGINT). - Double Fork (Optional but Recommended): The process forks a second time. The parent of this second fork exits immediately. This ensures that the daemon is no longer a session leader and cannot accidentally acquire a controlling terminal if it opens a terminal device.
- Working Directory: The daemon changes its working directory to the root directory
/. This prevents the process from locking a mounted filesystem, which would otherwise prevent the administrator from unmounting it. - File Descriptors and Logging: Standard input (STDIN), output (STDOUT), and error (STDERR) descriptors are closed and redirected to
/dev/null. To output diagnostic logs, the daemon uses the system log (syslog) service.
The following C implementation demonstrates how to transform a process into a daemon using the standard POSIX double-fork method:
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <fcntl.h>
void daemonize(void) {
pid_t pid;
/* 1. Fork parent, exit to background the process */
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); /* Parent exits */
/* 2. Create new session and process group */
if (setsid() < 0) exit(EXIT_FAILURE);
/* 3. Second fork to prevent acquiring TTY */
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS);
/* 4. Set file mode creation mask to 0 */
umask(0);
/* 5. Change working directory to root */
if (chdir("/") < 0) {
exit(EXIT_FAILURE);
}
/* 6. Close standard file descriptors */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* 7. Redirect standard channels to /dev/null */
int dev_null = open("/dev/null", O_RDWR);
if (dev_null != -1) {
dup2(dev_null, STDIN_FILENO);
dup2(dev_null, STDOUT_FILENO);
dup2(dev_null, STDERR_FILENO);
}
/* 8. Open system log connection */
openlog("custom_daemon", LOG_PID, LOG_DAEMON);
syslog(LOG_INFO, "Daemon process detached and initialized.");
}
Init Systems and Service Management
Modern UNIX-like operating systems manage daemons through initialization supervisors such as systemd (Linux) or launchd (macOS). These init systems handle daemonization, output redirection, and user execution privileges on behalf of the developer. This allows services to be written as standard foreground programs.
Under systemd, a developer defines a service unit file to manage execution parameters:
[Unit]
Description=Custom Web Service Daemon
After=network.target
[Service]
# Execute the binary in the foreground
ExecStart=/usr/local/bin/my_web_service
# Restart the daemon automatically if it crashes
Restart=always
# Drop privileges to a non-root user
User=nobody
Group=nogroup
[Install]
WantedBy=multi-user.target
The service manager handles environment setup, logging to the journal (journald), and tracking the process lifecycle, rendering manual fork() and setsid() code unnecessary for modern system services.
Exercise: Daemon Lifecycles
Test your understanding of daemon setup steps in the scenario below:
A systems programmer is writing a custom C service to monitor CPU temperature. They use fork() to create a background process and exit the parent. However, when they close the SSH session they used to launch the program, the monitoring daemon terminates immediately.
Based on the required characteristics of a daemon, which initialization step did the programmer fail to execute?
References & Further Reading
For additional specifications and implementations of daemon processes, consult the following references:
- Stevens, W. R., & Rago, S. A. (2013). Advanced Programming in the UNIX Environment (3rd ed.). Addison-Wesley. (Covering Chapter 13: Daemon Processes and Chapter 3: File I/O).
- Kerrisk, M. (2010). The Linux Programming Interface. No Starch Press. (Covering Chapter 37: Daemons and Chapter 34: Process Groups, Sessions, and Job Control).
- systemd.service(5) Manual Page. Freedesktop.org.