How to Detach a Daemon Process in C
In Unix-like operating systems, a daemon is a long-running background
service that operates independently of any user session or controlling
terminal. To prevent a process from receiving terminal-generated signals
(such as SIGHUP or SIGINT) and blocking system
shutdowns or filesystem unmounts, it must properly disassociate from the
terminal that spawned it. In C, this detachment requires a defined
sequence of POSIX system calls: forking to drop terminal association,
creating an independent session with setsid(), preventing
terminal reacquisition, resetting file masks, changing the working
directory, and redirecting standard I/O streams.
1. The Initial Fork and Parent Termination
The first step is invoking fork(). The parent process
exits immediately upon a successful fork, returning control to the
terminal shell. This serves two purposes: the shell perceives the
command as completed, and the surviving child process is guaranteed not
to be a process group leader, which is an absolute prerequisite for the
next step.
pid_t pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS); /* Parent exits */
}2. Creating a New Session
with setsid()
The child process calls setsid() to become the leader of
a new session and the leader of a new process group. Because the process
is not currently a process group leader, setsid()
succeeds.
At this exact point, the process is completely severed from its controlling terminal (controlling TTY). Signals sent to the original terminal will no longer affect this process.
if (setsid() < 0) {
exit(EXIT_FAILURE);
}3. The Second Fork (Preventing TTY Reacquisition)
Under System V-derived systems, a session leader without a
controlling terminal can automatically acquire one if it opens a
terminal device file (such as /dev/tty).
To permanently prevent this, the daemon forks a second time and terminates the first child. The grandchild process belongs to the newly created session, but it is not the session leader. Because only session leaders can acquire controlling terminals, the daemon is permanently protected from acquiring a TTY inadvertently.
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS); /* First child exits */
}4. Clearing the File Creation Mask
A daemon inherits its file creation mask (umask) from
the parent environment, which may restrict file permissions unexpectedly
when the daemon creates logs, configuration files, or sockets. Calling
umask(0) ensures the daemon has complete control over the
permissions of files it creates.
umask(0);5. Changing the Working Directory
A running process holds a reference to its current working directory,
preventing the underlying filesystem from being unmounted. Daemons
typically change their working directory to the root directory
(/) or a dedicated service directory (e.g.,
/var/run/mydaemon).
if (chdir("/") < 0) {
exit(EXIT_FAILURE);
}6. Closing and Redirecting File Descriptors
The daemon inherits file descriptors from the parent, specifically
standard input (stdin), standard output
(stdout), and standard error (stderr). Leaving
these open maintains a hidden tie to the terminal and can cause crashes
if the daemon attempts output after the terminal closes.
The daemon closes these descriptors and redirects file descriptors 0,
1, and 2 to /dev/null.
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
int null_fd = open("/dev/null", O_RDWR);
if (null_fd >= 0) {
dup2(null_fd, STDIN_FILENO);
dup2(null_fd, STDOUT_FILENO);
dup2(null_fd, STDERR_FILENO);
if (null_fd > STDERR_FILENO) {
close(null_fd);
}
}Complete Implementation Example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void daemonize(void) {
pid_t pid;
/* First fork */
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS);
/* Create new session and detach from controlling TTY */
if (setsid() < 0) exit(EXIT_FAILURE);
/* Second fork to prevent acquiring a TTY in the future */
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS);
/* Reset file mask */
umask(0);
/* Change directory */
if (chdir("/") < 0) exit(EXIT_FAILURE);
/* Redirect standard file descriptors to /dev/null */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
int fd = open("/dev/null", O_RDWR);
if (fd != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > 2) close(fd);
}
}
int main(void) {
daemonize();
/* Daemon background work loop */
while (1) {
sleep(60);
}
return 0;
}While GNU/Linux provides a convenience function called
daemon(3), executing this manual sequence remains the
POSIX-compliant standard for low-level process control, ensuring robust
isolation from any terminal interface.