Understanding Linux Daemons (with a simple Practical C Example)
Linux systems rely heavily on background processes that run without direct user interaction. These long‑running background processes are called daemons. They typically detach from the controlling terminal and continue running independently, providing core services such as logging, scheduling, networking, and more.
In this post, we will:
- Implement a minimal daemon in C.
- Walk through each step required to correctly daemonize a process.
- Briefly discuss how this relates to modern
systemdbased service management.
Prerequisites
To follow along with the code examples, you should have:
- A basic understanding of C programming.
- Familiarity with Linux command line and system calls. (e.g.,
fork(),setsid(),chdir(),umask(),signal()). - Understanding of process management in Linux.
Why Daemons Matter
Daemons power many of the services users rely on every day. Examples include:
- Web servers (e.g., Apache, Nginx)
- Job schedulers (e.g.,
cron) - Logging services (e.g.,
rsyslog,journald) - Network and system services (e.g.,
sshd,NetworkManager)
systemctl list-units -t service # list the active services (daemons) on a systemd-based Linux system
These processes are designed to be robust, long-lived, and independent of any interactive user session. A properly written daemon continues running even after the user who started it logs out or the terminal closes.
A Comprehensive Daemon in C
This example demonstrates every major step of the daemonization process, including the famous double fork, detaching from terminals, writing a PID file, signal handling, and logging to a file.
1#include <stdio.h>2#include <stdlib.h>3#include <unistd.h>4#include <signal.h>5#include <sys/types.h>6#include <sys/stat.h>7#include <time.h>8#include <fcntl.h>9#include <string.h>1011// Global flag to signal the daemon to exit12volatile sig_atomic_t terminate_daemon = 0;1314// Signal handler for clean shutdown15void sig_handler(int signo) {16 if (signo == SIGTERM || signo == SIGINT) {17 terminate_daemon = 1;18 }19}2021int main(int argc, char *argv[]) {22 pid_t pid;2324 // STEP 1: First Fork - Detach from the parent process25 // We want to detach from the controlling terminal. The parent will exit,26 // leaving the child to run independently. This is crucial.27 // Think of it as the child saying, "Bye, mom and dad, I'm off to conquer28 // the world (or at least log some messages)!"29 pid = fork();30 if (pid < 0) {31 perror("First fork failed");32 exit(EXIT_FAILURE);33 }34 // If we got a positive PID, it means we are the parent.35 // The parent should exit immediately.36 if (pid > 0) {37 exit(EXIT_SUCCESS); // Parent exits, child continues38 }3940 // STEP 2: Create a new session and become a session leader41 // `setsid()` makes the calling process a session leader and detaches it42 // from the controlling terminal. It also creates a new process group.43 // This prevents the daemon from getting any signals from the original44 // terminal.45 //46 // UNDERSTANDING SESSIONS AND PROCESS GROUPS:47 // - A session is a collection of one or more process groups.48 // - By calling setsid(), the calling process becomes the session leader.49 // - The session ID (SID) is set to the calling process's PID.50 // - The calling process also becomes the leader of a new process group,51 // and the process group ID (PGID) equals the calling process's PID.52 // - Most importantly: the process is disassociated from its controlling terminal.53 // This is crucial for creating daemons that should continue running even54 // after the user logs out or the terminal is closed.55 if (setsid() < 0) {56 perror("setsid failed");57 exit(EXIT_FAILURE);58 }5960 // STEP 3: Second Fork - Prevent re-acquiring a controlling terminal61 // WHY A SECOND FORK?62 // Even after calling setsid(), the process is still a session leader.63 // On some Unix systems, a session leader can automatically re-acquire64 // a controlling terminal by opening a terminal device.65 // By forking again, the new child is no longer a session leader66 // (its parent is the session leader), which prevents it from ever67 // re-acquiring a controlling terminal accidentally.68 // This is an extra safety measure to ensure true daemon behavior.69 pid = fork();70 if (pid < 0) {71 perror("Second fork failed");72 exit(EXIT_FAILURE);73 }74 if (pid > 0) {75 exit(EXIT_SUCCESS); // First child exits, second child continues76 }7778 // STEP 4: Reset file mode mask79 // `umask(0)` sets the file mode creation mask to 0 (000 in octal).80 // This means that new files created by the daemon will have their81 // permissions determined solely by the permissions specified in the82 // `open()` or `creat()` call, not modified by the umask.83 // This ensures predictable file permissions.84 umask(0);8586 // STEP 5: Change the current working directory to the root87 // This is good practice to prevent the daemon from holding open a88 // directory on a mounted filesystem that could otherwise be unmounted.89 // Imagine your daemon preventing you from unmounting a USB drive!90 if (chdir("/") < 0) {91 perror("chdir failed");92 exit(EXIT_FAILURE);93 }9495 // STEP 6: Write the daemon PID to a file96 // PID files are used to:97 // 1. Identify the running daemon process98 // 2. Prevent multiple instances from running simultaneously99 // 3. Allow management scripts to send signals to the daemon100 //101 // Standard locations: /var/run/ or /run/ (requires root/sudo)102 // For testing without sudo, use /tmp/ instead103 FILE *pidfile = fopen("/var/run/mydaemon.pid", "w");104 if (pidfile) {105 fprintf(pidfile, "%d\n", getpid());106 fclose(pidfile);107 } else {108 // Fallback: if we can't write to /var/run (no sudo), inform via printf109 // Note: This won't be visible after we close STDOUT, but it's here for110 // early debugging before the daemon fully detaches111 printf("Daemon PID: %d\n", getpid());112 perror("Failed to write PID file to /var/run/mydaemon.pid");113 // Not exiting - daemon can continue without PID file114 }115116 // STEP 7: Register signal handlers for graceful shutdown117 // We want our daemon to respond politely to signals like SIGTERM (terminate)118 // and SIGINT (interrupt) so it can clean up before exiting.119 signal(SIGTERM, sig_handler);120 signal(SIGINT, sig_handler);121122 // STEP 8: Close standard file descriptors123 // Daemons should not inherit open file descriptors from the parent.124 // We want to ensure it has no ties to the original terminal.125 // STDIN (0), STDOUT (1), and STDERR (2) are closed to fully detach126 // from the terminal. This prevents accidental reads/writes to a127 // nonexistent terminal and makes the daemon truly independent.128 close(STDIN_FILENO); // 0129 close(STDOUT_FILENO); // 1130 close(STDERR_FILENO); // 2131132 // STEP 9: Main daemon loop - Write timestamps to log file133 // The daemon now enters its infinite loop where it performs its actual work.134 // In this example, it writes a timestamp to a log file every 2 seconds.135 while (!terminate_daemon) {136 // Open log file in append mode with specific permissions137 // O_WRONLY: write-only138 // O_CREAT: create if doesn't exist139 // O_APPEND: write at the end of the file140 // 0644: rw-r--r-- (owner can read/write, others can read)141 int fd = open("/tmp/mydaemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);142143 if (fd != -1) {144 // Get current time and convert to string145 time_t now = time(NULL);146 char *timestamp = ctime(&now);147148 // Write timestamp to log file149 write(fd, timestamp, strlen(timestamp));150 close(fd);151 }152153 sleep(2); // Sleep for 2 seconds before next log entry154 }155156 // Clean exit when terminate signal is received157 // Optionally, you could log a shutdown message here158 // and remove the PID file159 unlink("/var/run/mydaemon.pid"); // Clean up PID file160 exit(EXIT_SUCCESS);161}
Breaking Down the Daemonization Steps
Each step in the code above corresponds to a well‑established pattern for turning a normal process into a daemon.
1. First fork() – Detach from the parent
- Concept:
fork()creates a child process. The parent exits, and the child continues execution. - System call:
pid_t fork(void); - Purpose: Ensures the new process is not a process group leader, which is required before calling
setsid(). It also detaches from the original shell/terminal. - Effect: The controlling terminal no longer manages the daemon's lifecycle.
- Return values:
< 0: Fork failed0: You are in the child process> 0: You are in the parent process, and the value is the child's PID
2. setsid() – Create a new session
- Concept:
setsid()creates a new session and makes the calling process the session leader of that session. - System call:
pid_t setsid(void); - Purpose: Detaches the process from any controlling terminal and process group.
- What happens internally:
- The calling process becomes a session leader
- The session ID (SID) is set to the calling process's PID
- A new process group is created with PGID = calling process PID
- The process is disassociated from its controlling terminal
- Effect: The daemon is isolated from terminal signals such as
Ctrl+Cin the original shell. - Why it matters: This is crucial for creating daemons that continue running even after the user logs out or the terminal is closed.
3. Second fork() – Prevent re-acquiring a controlling terminal
- Concept: Fork again after
setsid()to ensure the process can never acquire a controlling terminal. - System call:
pid_t fork(void);(called a second time) - Purpose: Even after
setsid(), a session leader can still open a terminal device and automatically make it the controlling terminal. By forking again, the new child is no longer a session leader, preventing this behavior. - Effect: Guarantees true daemon behavior with no possibility of terminal reattachment.
- This is the "double fork" technique: First fork detaches from parent, second fork prevents terminal acquisition.
4. umask(0) – Control file permissions
- Concept: Sets the process's file mode creation mask to 0.
- System call:
mode_t umask(mode_t mask); - Purpose: Ensures file permissions for newly created files are determined explicitly by the program, rather than being further restricted by an inherited umask.
- Effect: More predictable file and directory permissions.
- Example: If you create a file with
open(path, O_CREAT, 0644):- Without
umask(0): Final permissions depend on inherited umask (might be 0022, resulting in 0644 & ~0022 = 0644) - With
umask(0): Final permissions are exactly 0644 (rw-r--r--)
- Without
5. chdir("/") – Change working directory
- Concept: Changes the current working directory to root.
- System call:
int chdir(const char *path); - Purpose: Prevents the daemon from keeping any filesystem (e.g., a mounted volume) unnecessarily busy.
- Effect: Ensures that unmount operations are not blocked by a daemon holding open a directory.
- Real-world scenario: Imagine your daemon is started from
/mnt/usb/. If it doesn't change directory, you can't unmount/mnt/usb/while the daemon runs.
6. Writing a PID File
- Concept: Write the daemon's process ID to a file for management and monitoring.
- System calls:
FILE *fopen(const char *pathname, const char *mode);,int fprintf(FILE *stream, const char *format, ...); - Purpose:
- Allows system administrators to identify the running daemon
- Prevents multiple instances from running simultaneously (by checking if PID file exists)
- Management scripts can read the PID to send signals (e.g.,
kill -SIGTERM $(cat /var/run/mydaemon.pid))
- Standard locations:
/var/run/daemon-name.pid(traditional, requires root)/run/daemon-name.pid(modern, symlink to/var/run/)/tmp/daemon-name.pid(for testing without root privileges)
- Effect: Provides a reliable way to manage the daemon process.
7. Signal handling
- Concept: Signals notify a process of asynchronous events, like termination requests.
- System call:
void (*signal(int signum, void (*handler)(int)))(int); - Purpose: Handle
SIGTERM/SIGINTgracefully, allowing the daemon to perform cleanup before exiting. - Common signals:
SIGTERM(15): Polite termination request (default forkill)SIGINT(2): Interrupt from keyboard (Ctrl+C)SIGHUP(1): Hangup detected, often used to reload configuration
volatile sig_atomic_t: This type ensures the variable can be safely accessed in both signal handlers and main code without data races.- Effect: More predictable shutdown behavior and resource cleanup.
8. Closing file descriptors
- Concept: File descriptors represent open files, sockets, and other I/O resources.
- System calls:
int close(int fd); - Purpose: Daemons should not inherit open descriptors from their parent, especially the terminal descriptors (
stdin,stdout,stderr). - The three standard file descriptors:
STDIN_FILENO(0): Standard inputSTDOUT_FILENO(1): Standard outputSTDERR_FILENO(2): Standard error
- Effect: Avoids unintended I/O and resource leaks tied to the original environment. Makes the daemon truly independent.
9. Daemon Main Loop – File Logging
- Concept: The daemon performs its actual work in an infinite loop.
- System calls:
int open(const char *pathname, int flags, mode_t mode);ssize_t write(int fd, const void *buf, size_t count);time_t time(time_t *tloc);char *ctime(const time_t *timep);
- Purpose: Demonstrates continuous daemon operation with file-based logging.
- File open flags:
O_WRONLY: Open for writing onlyO_CREAT: Create file if it doesn't existO_APPEND: Write data at the end of the file
- File permissions:
0644meansrw-r--r--(owner read/write, group read, others read) - Effect: Creates a persistent log file that can be monitored to verify daemon operation.
Building and Running the Daemon
-
Save the code as
my_daemon.c. -
Compile:
gcc my_daemon.c -o my_daemon
-
Run:
./my_daemon
The process will daemonize immediately and the terminal will return control.
-
Verify that it is running:
ps aux | grep my_daemon
You should see an entry showing the daemon process with its PID.
-
Monitor the log file in real-time:
tail -f /tmp/mydaemon.log
This will show timestamps being written every 2 seconds. The
-fflag makestailcontinuously display new lines as they are added to the file. -
Watch the log file with automatic refresh:
watch -n 1 'tail -10 /tmp/mydaemon.log'
This uses the
watchcommand to executetail -10every 1 second, giving you a continuously updating view of the last 10 log entries. -
Check the PID file (if running with sudo):
cat /var/run/mydaemon.pid
Or without sudo:
# The PID was printed to stdout before the daemon detached
-
Stop the daemon gracefully:
Using the PID file:
kill -SIGTERM $(cat /var/run/mydaemon.pid) # Or simply: kill $(cat /var/run/mydaemon.pid)
Or using process name:
pkill my_daemon # Or more specific: killall my_daemon
The daemon will handle the signal, clean up (remove PID file), and exit.
-
Force stop (if graceful stop fails):
kill -SIGKILL $(cat /var/run/mydaemon.pid) # Or: pkill -9 my_daemon
Useful Commands for Daemon Management
# View all running daemons (background processes) ps aux | grep -E 'daemon|[d]$' # Monitor log file with color highlighting for timestamps watch -n 1 --color 'tail -10 /tmp/mydaemon.log' # Count log entries wc -l /tmp/mydaemon.log # View logs with timestamps in a more readable format tail -f /tmp/mydaemon.log | while read line; do echo "[$(date +%H:%M:%S)] $line"; done # Check if daemon is running and get its PID pgrep -l my_daemon # Get detailed process information ps -fp $(pgrep my_daemon) # View process tree to see daemon's isolation pstree -p $(pgrep my_daemon)
Additional Topics for Further Exploration
If you want to extend this example toward real‑world use cases, useful directions include:
- More robust error handling and retries.
- Configuration via files under etc.
- Secure privilege handling (dropping privileges, running under a dedicated user).
- Inter‑process communication (sockets, message queues, shared memory).
- Understanding the
daemon()library helper function where available.
Appendix: Managing the Daemon with systemd
The previous sections focused on the traditional, manual approach to daemonization. On modern Linux distributions, systemd is usually responsible for starting, supervising, and stopping services. In many production scenarios, you will let systemd handle daemonization and focus on your application logic.
This section shows:
- How to create a
systemdunit for the example daemon. - What a “systemd‑native” daemon might look like.
- How this changes your service configuration.
Running my_daemon Under systemd
Assume my_daemon is compiled and copied to /usr/local/bin/my_daemon.
-
Create a systemd service unit:
sudo nano /etc/systemd/system/my_daemon.service
Example unit file:
1[Unit]2Description=My Simple Daemon3After=network.target45[Service]6Type=forking7ExecStart=/usr/local/bin/my_daemon8ExecStop=/usr/bin/pkill my_daemon9Restart=on-failure10User=root11Group=root1213[Install]14WantedBy=multi-user.targetKey points:
Type=forkingis appropriate because the example program performs its ownfork()and becomes a background process.ExecStartpoints to the compiled binary.ExecStopusespkillfor a simple, signal‑based stop. In more advanced cases, you may implement a dedicated shutdown mechanism.- For production, replace
rootwith a dedicated, unprivileged user and group.
-
Reload systemd configuration:
sudo systemctl daemon-reload
-
Enable the service at boot:
sudo systemctl enable my_daemon.service
-
Start the service:
sudo systemctl start my_daemon.service
-
Check status:
sudo systemctl status my_daemon.service
-
Inspect logs:
sudo journalctl -u my_daemon.service -f
-
Stop and disable if needed:
sudo systemctl stop my_daemon.service sudo systemctl disable my_daemon.service
A Systemd‑Native Daemon
When you design a daemon specifically for systemd, you typically avoid manual daemonization steps. systemd takes care of session management, working directory, file descriptors, and logging.
Below is a simplified example that relies on systemd for process management:
1#include <stdio.h>2#include <stdlib.h>3#include <unistd.h>4#include <signal.h>56volatile sig_atomic_t terminate_daemon = 0;78void sig_handler(int signo) {9 if (signo == SIGTERM || signo == SIGINT) {10 terminate_daemon = 1;11 }12}1314int main(void) {15 signal(SIGTERM, sig_handler);16 signal(SIGINT, sig_handler);1718 fprintf(stdout, "Systemd-native daemon started. PID: %d\n", getpid());19 fflush(stdout);2021 while (!terminate_daemon) {22 fprintf(stdout, "Systemd-native daemon is running. PID: %d\n", getpid());23 fflush(stdout);24 sleep(5);25 }2627 fprintf(stdout, "Systemd-native daemon exiting.\n");28 fflush(stdout);29 return EXIT_SUCCESS;30}
A corresponding systemd unit file for this style might look like:
1[Unit]2Description=My Systemd-Native Daemon3After=network.target45[Service]6Type=simple7ExecStart=/usr/local/bin/my_systemd_daemon8Restart=on-failure9User=daemonuser10Group=daemonuser1112[Install]13WantedBy=multi-user.target
Here:
Type=simpleis appropriate because the process does notfork()itself into the background.- Logging is done via
stdout/stderrand captured bysystemd’s journal.
Summary
- The main body of this post focused on the classic daemonization steps implemented directly in C.
- In modern systems,
systemdis typically responsible for supervising daemons, providing process lifecycle management, restart policies, and centralized logging. - Understanding both approaches helps when debugging existing services or designing new ones that integrate cleanly with the host system.
