Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: July 6, 2024
Although a process is a fundamental concept in operating systems, sometimes there might be confusion about different types of processes, such as the difference between daemons and services.
In this tutorial, we’ll focus on the differences between processes, daemons, and services in Linux.
A process is an active program that is being executed by the operating system. In addition to the code of the program, a process has dedicated memory and resources. A program, on the other hand, is a collection of instructions written in a specific programming language to perform a specific task. We can envisage programs as static entities while processes as the dynamic counterparts of programs.
Every process in the system has a unique identifier, PID (Process ID), which is a number. Additionally, each process is in one of several states. For example, a process might be in the Running state while another might be in the Interruptible Sleep state, waiting for resources.
Processes also have scheduling policies and priorities. For example, a process having a real-time scheduling policy has a higher priority than processes with other scheduling policies. Indeed, threads in a multi-threaded process might have different scheduling policies and priorities.
We may also categorize processes as either foreground or background processes. A process connected to a terminal is a foreground process, while a background process is detached from the terminal.
In the subsequent sections, we’ll see that daemons and services are special processes running in the background.
In this section, we’ll discuss the characteristics of daemons and the typical steps for initializing a daemon. We’ll also present examples and an analysis of the daemon processes.
Daemons are special processes that are started when the system is started and stopped when the system is shut down. They run in the background and are detached from a controlling terminal.
Some of the daemons in the system run in kernel mode, while others run in user mode. Each daemon process performs a specific job. For example, the ksoftirqd kernel daemon handles deferred software interrupts, while the chronyd daemon running in user mode synchronizes time between different nodes in a network.
There are several recommended steps for the initialization of a daemon to avoid undesirable interactions. Let’s go over the basic steps for implementing a daemon in C:
Let’s write an example daemon using the following program, first_daemon.c, that implements the steps listed in the previous section:
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
void convert_to_daemon()
{
pid_t pid;
int fd0,fd1,fd2;
/* Clear file creation mask */
umask(0);
/* Create child daemon process and let parent exit */
pid = fork();
if (pid != 0) /* parent */
exit(0);
/* Become a session leader */
setsid();
/* Change the current working directory */
chdir("/");
/* Close open file descriptors */
close(0);
close(1);
close(2);
/* Attach stdin, stdout, and stderr to /dev/null */
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
}
int main() {
convert_to_daemon();
while (1) {
/* The daemon performs its task here */
sleep(1);
}
return 0;
}
The convert_to_daemon() function implements the steps at startup. After calling it in main(), the daemon performs its task in an infinite loop.
There might be other steps in the startup of a daemon. For example, we might write the PID of the daemon to a PID file to have a single instance of the daemon running. We haven’t implemented such steps in our example to keep the daemon code short.
As an alternative to the implementation in first_daemon.c, we can write another program, second_daemon.c:
#include <unistd.h>
int main() {
daemon(0, 0);
/* The daemon performs its task here */
while (1) {
sleep(1);
}
return 0;
}
This code is much simpler than the one in first_daemon.c. In this case, we only call the daemon() function before the infinite while loop in which the daemon performs its task.
The daemon() function is for detaching from the controlling terminal and running in the background as a daemon. It implements the steps recommended for initializing a daemon. If its first argument is 0, it changes the current working directory of the process to the root directory. Moreover, if the second argument is 0, it redirects the standard input, the standard output, and the standard error to /dev/null.
First, let’s build first_daemon.c and second_daemon.c using gcc:
$ gcc -o first_daemon first_daemon.c
$ gcc -o second_daemon second_daemon.c
The names of the generated executables are first_daemon and second_daemon.
Having generated the executables, it’s time to run the daemons:
$ ./first_daemon
$ ./second_daemon
Let’s check the running processes using ps. We filter the output of ps through grep to list only the two daemon processes:
$ ps -efj | grep _daemon | grep -v grep
centos 3901 2314 3901 3901 0 15:21 ? 00:00:00 ./first_daemon
centos 3909 2314 3909 3909 0 15:21 ? 00:00:00 ./second_daemon
Both processes are detached from the terminal as expected, so we can continue using the same terminal after starting the daemons.
The second column in the output of ps displays the PIDs of the processes, 3901 and 3909 in this example. The fourth and fifth columns list the PGIDs and SIDs (Session IDs). The PGIDs and SIDs of both processes are the same as their PIDs. Therefore, they’re both process group leaders and session leaders, as expected.
The third column displays the PIDs of the parent processes. Both processes have the same parent process whose PID is 2314. This parent process is the per-user instance of systemd in our case. The system-wide systemd service, whose PID is 1, starts it when the user logs on and stops it when the user logs off.
The eighth column lists the terminals the processes are attached to. The question marks in this column mean that the daemon processes are detached from the controlling terminals, as expected.
Let’s check the current working directories of the two processes using pwdx:
$ pwdx 3901
3901: /
$ pwdx 3909
3909: /
The current working directory of both processes is the root directory (/) as expected.
Finally, let’s check the open file descriptors of the two processes from the /proc file system:
$ sudo ls -l /proc/3901/fd
total 0
lrwx------. 1 centos centos 64 Feb 9 11:28 0 -> /dev/null
lrwx------. 1 centos centos 64 Feb 9 11:28 1 -> /dev/null
lrwx------. 1 centos centos 64 Feb 9 11:28 2 -> /dev/null
$ sudo ls -l /proc/3909/fd
total 0
lrwx------. 1 centos centos 64 Feb 9 11:29 0 -> /dev/null
lrwx------. 1 centos centos 64 Feb 9 11:29 1 -> /dev/null
lrwx------. 1 centos centos 64 Feb 9 11:29 2 -> /dev/null
As is apparent from the output, both daemon processes have only the file descriptors 0, 1, and 2 open. They point to /dev/null, as expected.
Therefore, the daemon() function in second_daemon.c implements the steps in first_daemon.c.
The daemons discussed in the previous section are traditional SysV daemons. However, it’s possible to implement daemons using the infrastructure provided by systemd. The daemons implemented using systemd are also known as new-style daemons. Indeed, systemd names these daemons as services.
systemd services don’t need the initialization steps of SysV daemons. Therefore, this simplifies their implementation. It’s easier to supervise and control the execution of services at runtime.
There are several recommended steps for the implementation of a service just like the implementation of SysV daemons:
These are the steps we’ll implement in the next section. However, there might be other steps depending on the requirements. For example, if we need to integrate the daemon with D-Bus, then we’ll need to define and expose the daemon’s control interface using the D-Bus IPC system.
Let’s write the C program systemd_daemon.c as an example of a service:
#include <unistd.h>
#include <signal.h>
#include <systemd/sd-daemon.h>
int signal_captured = 0;
void signal_handler(int signum, siginfo_t *info, void *extra)
{
signal_captured = 1;
}
void set_signal_handler(void)
{
struct sigaction action;
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = signal_handler;
sigaction(SIGTERM, &action, NULL);
}
int main()
{
set_signal_handler();
sd_notify(0, "READY=1\nSTATUS=Running");
while(!signal_captured) {
/* The daemon performs its task here */
sleep(1);
}
sd_notify(0, "STOPPING=1");
return 0;
}
The program implements the steps in the previous section.
The set_signal_handler() function sets the signal_handler() function as the signal handler for the SIGTERM signal. In other words, the operating system calls the signal_handler() function when we send a SIGTERM signal to the service. This function only sets the signal_captured global variable to 1.
In addition to the notification message READY=1, we also send the status message STATUS=Running to systemd using sd_notify(). Later, we’ll check the status using systemctl status.
The initial value of signal_captured is 0. We constantly check its value in the while loop. Whenever signal_handler() sets its value to 1, we exit from the while loop. We send the notification message STOPPING=1 to systemd and exit from the program.
Let’s write the unit file, simple_service.service, for integrating our service with systemd:
[Unit]
Description=The minimal systemd daemon service
[Service]
Type=notify
ExecStart=/home/centos/work/daemon/systemd_daemon
[Install]
WantedBy=multi-user.target
systemd uses the unit file for controlling the service.
The Description option in the Unit section describes the purpose of the service. The systemctl status command displays the description.
The Type option in the Service section describes how systemd handles the service. Its value, notify, specifies that the service issues notification messages to systemd. We start the service using the executable specified in the ExecStart option.
The WantedBy option of the Install section specifies at which run-level systemd starts the service. multi-user.target is equivalent to SysV run-levels 2, 3, and 4.
First, let’s build systemd_daemon.c using gcc:
$ gcc -o systemd_daemon systemd_daemon.c -lsystemd
The name of the executable is systemd_daemon. We need to link it together with the libsystemd.so library to use the sd_notify() function.
Now, it’s time to start the service using systemd:
$ pwd
/etc/systemd/system
$ sudo cp /home/centos/work/daemon/simple_service.service .
$ sudo systemctl enable simple_service.service
Created symlink /etc/systemd/system/multi-user.target.wants/simple_service.service → /etc/systemd/system/simple_service.service.
$ sudo systemctl start simple_service.service
We enable and start the service using systemctl enable and systemctl start commands after copying the unit file to the /etc/systemd/system directory. We use these commands together with the sudo command as we need root privileges.
Let’s use ps to check whether the service is running:
$ ps -efj | grep systemd_daemon | grep -v grep
root 3402 1 3402 3402 0 14:48 ? 00:00:00 /home/centos/work/daemon/systemd_daemon
We’re successful in starting the service. Notably, the question mark in the eighth column of the output of ps implies that the service isn’t attached to a terminal.
Let’s check the status of the service using systemctl status:
$ sudo systemctl status simple_service
● simple_service.service - The minimal systemd daemon service
Loaded: loaded (/etc/systemd/system/simple_service.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2024-02-12 15:46:14 +03; 9s ago
Main PID: 3402 (systemd_daemon)
Status: "Running"
Tasks: 1 (limit: 11270)
Memory: 468.0K
CGroup: /system.slice/simple_service.service
└─7382 /home/centos/work/daemon/systemd_daemon
Feb 12 15:46:14 host1 systemd[1]: Starting The minimal systemd daemon service...
Feb 12 15:46:14 host1 systemd[1]: Started The minimal systemd daemon service.
The service is active and running. Its status is Running, which we’ve set by sending the status message STATUS=Running to systemd using sd_notify().
The PID, PGID, and SID of the process have the same value, which is 3402. This behavior is the same as the SysV daemons in the previous section. However, the PPID (Parent Process ID) of the service is 1. The parent is the system-wide systemd daemon, which is the daemon that manages other daemons.
Let’s check the current working directory of the service:
$ sudo pwdx 3402
3402: /
The current working directory of the service is the root directory (/) as expected.
Finally, let’s check the open file descriptors of the service from the /proc file system:
$ sudo ls -l /proc/3402/fd
total 0
lr-x------. 1 root root 64 Feb 12 15:36 0 -> /dev/null
lrwx------. 1 root root 64 Feb 12 15:36 1 -> 'socket:[53876]'
lrwx------. 1 root root 64 Feb 12 15:36 2 -> 'socket:[53876]'
The service has the file descriptors 0, 1, and 2 open. The file descriptor 0 points to /dev/null as before. However, unlike SysV daemons, the file descriptors 1 and 2 point to a socket since systemd services use the logging component of systemd, namely journald.
In this article, we discussed the differences between processes, daemons, and services. We learned that daemons and services are special processes running in the background as long as the system is running. They’re detached from a controlling terminal.
We saw that systemd names traditional SysV daemons as services. We also learned how to implement traditional SysV daemons and systemd services to have a better understanding of the differences in their implementation. Additionally, we compared their runtime behaviors.