What Will You Learn?
Creating a daemon is not just calling fork() and running in the background. There is a specific, well-defined sequence of 7 steps that every proper daemon must follow. In this tutorial you will understand exactly why each step is needed and see the complete implementation of the becomeDaemon() function from TLPI.
Key Concepts
Every daemon creation follows this exact sequence. Skipping steps leads to subtle bugs. Let us go through each step deeply.
|
1
fork()
Parent exits |
β |
2
setsid()
New session |
β |
3
fork()
2nd fork |
β |
4
umask(0)
Clear mask |
β |
5
chdir(“/”)
Root dir |
β |
6
close()
All FDs |
β |
7
dup2()
/dev/null |
The first thing a daemon does is call fork(). The parent immediately exits, and the child continues. This step serves two important purposes:
When you launch a daemon from the command line, the shell is waiting for the parent to exit. When the parent exits, the shell sees the command as “done” and shows the next prompt. The child quietly continues in the background.
The child inherits the parent’s process group ID but gets its own new PID. Since PID β PGID, the child is NOT the process group leader. This is required before calling setsid() β only non-leaders can create new sessions.
pid_t pid = fork();
switch (pid) {
case -1:
perror("fork failed");
exit(EXIT_FAILURE);
case 0:
break; /* Child continues here */
default:
_exit(EXIT_SUCCESS); /* Parent exits immediately */
}
/* Only child reaches here */
_exit() instead of exit() to avoid flushing stdio buffers that are shared with the child. _exit() terminates immediately without calling atexit() handlers or flushing buffers.After the first fork, the child calls setsid(). This is the most critical step. setsid() creates a brand new session with the calling process as the session leader, and this new session has no controlling terminal.
| Before setsid() | After setsid() |
|---|---|
| Process belongs to the shell’s session | Process is now in its own new session |
| Inherits the controlling terminal from parent | New session has NO controlling terminal |
| Can receive SIGHUP, SIGINT, etc. | Terminal signals never arrive |
| SID = parent’s SID | SID = child’s own PID |
if (setsid() == -1) {
perror("setsid failed");
exit(EXIT_FAILURE);
}
/* Now the process is the session leader of a new session
* with no controlling terminal */
printf("New SID = %d\n", getsid(0));
After setsid(), the process is the session leader of a new session. On System V-derived systems (which Linux follows), a session leader can automatically acquire a controlling terminal by simply opening a terminal device.
To permanently prevent the daemon from ever reacquiring a controlling terminal, we perform a second fork. The parent (session leader) exits, and the grandchild continues. The grandchild is NOT the session leader and can never acquire a controlling terminal through an accidental open().
/* After setsid() ... */
switch (fork()) {
case -1:
perror("second fork failed");
exit(EXIT_FAILURE);
case 0:
break; /* Grandchild continues */
default:
_exit(EXIT_SUCCESS); /* First child exits */
}
/* Only grandchild reaches here.
* It is NOT the session leader, so it can NEVER acquire
* a controlling terminal on System V systems. */
O_NOCTTY flag when calling open() on any terminal device. This tells the kernel not to make this terminal the controlling terminal. However, the double-fork is safer because it works unconditionally.A process inherits its umask from its parent. The umask restricts the permissions of files created by the process. For example, if umask is 022, then a file created with permissions 0666 will actually get 0644 (022 bits are masked out).
A daemon must clear the umask to 0 so that when it creates files or directories with a specific permission set, those permissions are applied exactly as requested. If the daemon inherited a restrictive umask from a user’s shell, the files it creates might end up with wrong permissions.
/* Clear file creation mask.
* This ensures that files created by the daemon get
* exactly the permissions the daemon requests. */
umask(0);
/* After umask(0):
* If daemon creates a file with mode 0600, it gets 0600.
* Before umask(0) with umask=022: 0600 & ~022 = 0600 (OK here)
* But with umask=077: 0666 & ~077 = 0600 (unexpected!) */
A daemon must change its current working directory to the root filesystem (/). Here is why this matters:
Consider this scenario: A user launches a daemon from /home/ravi/projects/. The daemon inherits /home/ravi/projects/ as its working directory. Later, an administrator tries to unmount the filesystem containing /home. The unmount will FAIL because the daemon has an open file descriptor or current directory reference to a file on that filesystem.
By changing to /, the daemon ensures it never holds a reference to any filesystem that might need to be unmounted.
if (chdir("/") == -1) {
perror("chdir to / failed");
exit(EXIT_FAILURE);
}
/* Some daemons change to their working directory instead:
* chdir("/var/spool/cron"); // like cron does
* chdir("/var/log"); // for a logging daemon
* This is OK as long as that filesystem is never unmounted */
The daemon inherits all open file descriptors from its parent (typically the shell). These include:
stdin β connected to terminal
(useless for daemon)
stdout β connected to terminal
(useless for daemon)
stderr β connected to terminal
(useless for daemon)
Any files parent had open
(security risk)
/* Find the maximum number of file descriptors this process can have */
int maxfd = sysconf(_SC_OPEN_MAX);
if (maxfd == -1)
maxfd = 8192; /* Use a safe default if unknown */
/* Close all file descriptors */
for (int fd = 0; fd < maxfd; fd++)
close(fd); /* Ignore errors β most FDs won't be open */
/proc/self/fd/ to find only the actually-open file descriptors, avoiding the O(maxfd) loop. Some systems provide closefrom(n) but Linux does not have this by default.After closing all file descriptors, the lowest three FDs (0, 1, 2) become available. If the daemon or a library function it calls tries to read from stdin or write to stdout/stderr, the operation must not fail or produce garbage output.
The solution: open /dev/null (which absorbs all writes and returns EOF on reads) and redirect all three standard file descriptors to it.
/dev/null is a special device file that:
/* After closing all FDs, FD 0 is now the lowest available.
* Opening /dev/null will assign it FD 0 (stdin). */
close(STDIN_FILENO); /* FD 0 */
int fd = open("/dev/null", O_RDWR);
if (fd != STDIN_FILENO) {
/* This should not happen, but check anyway */
fprintf(stderr, "Expected fd=0, got fd=%d\n", fd);
exit(EXIT_FAILURE);
}
/* Duplicate FD 0 to FD 1 (stdout) and FD 2 (stderr) */
if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
exit(EXIT_FAILURE);
if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
exit(EXIT_FAILURE);
/* Now:
* FD 0 (stdin) β /dev/null (reads return EOF)
* FD 1 (stdout) β /dev/null (writes silently discarded)
* FD 2 (stderr) β /dev/null (writes silently discarded) */
This is the complete, production-quality implementation of becomeDaemon() from TLPI. The flags argument allows selectively skipping steps (useful for testing).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
/* Bit-mask flags for becomeDaemon() */
#define BD_NO_CHDIR 01 /* Don't chdir("/") */
#define BD_NO_CLOSE_FILES 02 /* Don't close all open files */
#define BD_NO_REOPEN_STD_FDS 04 /* Don't reopen stdin/stdout/stderr */
#define BD_NO_UMASK0 010 /* Don't do umask(0) */
#define BD_MAX_CLOSE 8192 /* Max FDs to close if sysconf indeterminate */
/*
* becomeDaemon() - Transform the calling process into a daemon.
*
* flags: bit mask of BD_* values to skip certain steps.
* Pass 0 to perform all steps.
*
* Returns: 0 on success, -1 on error.
*/
int becomeDaemon(int flags)
{
int maxfd, fd;
/* Step 1: Fork and parent exits */
switch (fork()) {
case -1: return -1;
case 0: break; /* Child continues */
default: _exit(EXIT_SUCCESS); /* Parent terminates */
}
/* Step 2: Become leader of a new session (detach from terminal) */
if (setsid() == -1)
return -1;
/* Step 3: Second fork β ensure we are NOT the session leader.
* This prevents the daemon from ever acquiring a controlling terminal
* by accidentally opening a terminal device. */
switch (fork()) {
case -1: return -1;
case 0: break;
default: _exit(EXIT_SUCCESS);
}
/* Step 4: Clear umask so daemon creates files with correct permissions */
if (!(flags & BD_NO_UMASK0))
umask(0);
/* Step 5: Change to root directory so we don't block filesystem unmounts */
if (!(flags & BD_NO_CHDIR))
chdir("/");
/* Step 6: Close all inherited open file descriptors */
if (!(flags & BD_NO_CLOSE_FILES)) {
maxfd = sysconf(_SC_OPEN_MAX);
if (maxfd == -1)
maxfd = BD_MAX_CLOSE;
for (fd = 0; fd < maxfd; fd++)
close(fd);
}
/* Step 7: Redirect stdin, stdout, stderr to /dev/null */
if (!(flags & BD_NO_REOPEN_STD_FDS)) {
close(STDIN_FILENO);
fd = open("/dev/null", O_RDWR);
if (fd != STDIN_FILENO)
return -1;
if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO)
return -1;
if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO)
return -1;
}
return 0; /* Success */
}
/* --- Test driver --- */
int main(void)
{
if (becomeDaemon(0) == -1) {
perror("becomeDaemon");
exit(EXIT_FAILURE);
}
/*
* At this point we are a proper daemon.
* We can't use printf anymore (it goes to /dev/null).
* Use syslog for output β see ch37-06-syslog-api.html
*/
/* Simulate daemon work: loop forever */
for (;;) {
sleep(10);
/* Do daemon work here */
}
}
Verify the daemon with ps:
# Compile and run
gcc -o test_daemon test_daemon.c
./test_daemon
# Check daemon properties
ps -C test_daemon -o "pid ppid pgid sid tty command"
# PID PPID PGID SID TT COMMAND
# 4731 1 4730 4730 ? ./test_daemon
#
# TT = ? means no controlling terminal
# PPID = 1 means parent is init
# PID != SID means not the session leader
A common daemon pattern: write PID to a file so only one instance runs, and clean up on exit.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <syslog.h>
#define PID_FILE "/var/run/mydaemon.pid"
#define DAEMON_NAME "mydaemon"
static volatile int running = 1;
void sigterm_handler(int sig)
{
syslog(LOG_INFO, "Received SIGTERM, shutting down...");
running = 0;
}
/*
* write_pid_file() - Write current PID to a file.
* Returns 0 on success, -1 if another instance is already running.
*/
int write_pid_file(const char *path)
{
int fd;
char buf[32];
/* O_EXCL ensures we fail if the file already exists */
fd = open(path, O_RDWR | O_CREAT, 0644);
if (fd == -1) {
syslog(LOG_ERR, "Cannot open PID file %s: %m", path);
return -1;
}
/* Try to lock the file β if another instance has it, fail */
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
if (fcntl(fd, F_SETLK, &fl) == -1) {
syslog(LOG_ERR, "Another instance of %s is already running", DAEMON_NAME);
close(fd);
return -1;
}
/* Truncate file and write our PID */
ftruncate(fd, 0);
snprintf(buf, sizeof(buf), "%d\n", getpid());
write(fd, buf, strlen(buf));
/* Keep fd open β file lock is held as long as we hold the fd */
return 0;
}
int main(void)
{
/* Open syslog BEFORE daemonizing (before chdir and close fds) */
openlog(DAEMON_NAME, LOG_PID | LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "Starting %s", DAEMON_NAME);
/* Become a daemon */
/* (Using inline steps here for clarity) */
pid_t pid = fork();
if (pid < 0) { syslog(LOG_ERR, "fork: %m"); exit(1); }
if (pid > 0) _exit(0); /* parent exits */
if (setsid() == -1) { syslog(LOG_ERR, "setsid: %m"); exit(1); }
pid = fork();
if (pid < 0) { syslog(LOG_ERR, "fork2: %m"); exit(1); }
if (pid > 0) _exit(0);
umask(0);
chdir("/");
/* Close and redirect standard FDs */
int devnull = open("/dev/null", O_RDWR);
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > 2) close(devnull);
/* Write PID file β ensures single instance */
if (write_pid_file(PID_FILE) == -1)
exit(EXIT_FAILURE);
/* Handle SIGTERM for clean shutdown */
signal(SIGTERM, sigterm_handler);
syslog(LOG_INFO, "%s started successfully (PID=%d)", DAEMON_NAME, getpid());
/* Main daemon loop */
while (running) {
syslog(LOG_DEBUG, "Daemon heartbeat");
sleep(5);
}
/* Cleanup */
unlink(PID_FILE);
syslog(LOG_INFO, "%s stopped", DAEMON_NAME);
closelog();
return 0;
}
Test:
gcc -o mydaemon mydaemon.c
sudo ./mydaemon
cat /var/run/mydaemon.pid # Should show PID
sudo ./mydaemon # Should fail: "Another instance already running"
sudo kill -TERM $(cat /var/run/mydaemon.pid) # Graceful shutdown
Answer: The first fork ensures the process is not a process group leader (needed before setsid()). The second fork after setsid() ensures the process is not the session leader, which prevents it from ever accidentally acquiring a controlling terminal on System V systems. The grandchild, being neither the group nor session leader, is the proper daemon.
Answer: setsid() creates a new session with the calling process as session leader, and this new session has no controlling terminal. This detaches the process from the terminal that launched it, ensuring terminal signals (SIGINT, SIGHUP, SIGTSTP) can no longer reach the daemon.
Answer: To prevent blocking filesystem unmounts. If a daemon’s current working directory is on a filesystem like /home or /mnt/data, that filesystem cannot be unmounted while the daemon holds the reference. Changing to “/” ensures the daemon never blocks any filesystem unmount.
Answer: A daemon inherits the umask from its parent (usually a user’s shell). This inherited umask could restrict the permissions of files the daemon creates. By setting umask to 0, the daemon ensures it can create files with any permissions it explicitly requests, without unexpected restrictions.
Answer: Two reasons: (1) Library functions that perform I/O on FDs 0, 1, 2 will not fail unexpectedly. (2) If the daemon later opens a file at FD 1 or 2 (because they were available), any library function that writes to stdout or stderr would corrupt that file. /dev/null safely absorbs all writes and returns EOF on reads.
Answer: O_NOCTTY is a flag passed to open() to prevent a terminal device from becoming the controlling terminal for that specific open() call. The double-fork approach is more comprehensive β by making the process a non-session-leader, it prevents terminal acquisition from ANY open() call without needing to remember to use O_NOCTTY every time.
Answer: After fork(), both parent and child share the same stdio buffers. If the parent calls exit(), it flushes all stdio buffers, which might cause data to be written twice (once by parent, once by child when it eventually exits). _exit() terminates immediately without flushing buffers or running atexit() handlers, preventing this double-write problem.
Chapter Summary
Creating a daemon requires 7 carefully ordered steps: fork (parent exits), setsid (new session), fork again (prevent terminal reacquisition), umask(0), chdir(“/”), close all FDs, redirect 0/1/2 to /dev/null. The becomeDaemon() function encapsulates all these steps cleanly.
