1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

Wednesday, August 5, 2009

Daemon Process


What is a Daemon?

Every multitasking operating system supports a special type of process, a process that is usually kept a lower priority, performing a specific task over and over. Those processes are kept out of sight, performing their function in the background, without any direct intervention by the user. In the Unix operating system terminology, among other operating systems as well, background processes are called daemons.

This tutorial will introduce you to daemon programming. You will first write a simple BASH script daemon, just to get a grip on what daemons are, and what they are capable of, only to move on to coding your first daemon in C. Daemons can be easily created under GNU/Linux, they mostly follow a specific convention. To begin coding your first daemon, you will need to find a use for it. Daemons can handle many tasks that could otherwise bother users and affect their productivity, which brings us to the next section.

Putting Daemons to Use

Daemons usually handle tasks that require no user intervention, and consume low CPU power. CPU-intensive background tasks are not typically termed daemons, as bringing down computers to a sluggish performance can be hardly satisfying for any user, but decreasing the priority of these tasks can qualify them for that without sacrificing their performance, since CPUs are normally idle more than 90% of their time.

A daemon usually handles a single task, and accepts commands through means of IPC (Inter-Process Communication), which you will be briefly exposed to in this tutorial. Tasks handled by daemons include serving web pages, transferring emails, caching name service requests, logging, and for the purposes of this site, serving game clients.

Daemon game servers handle incoming game requests through a network, process these requests, update its persistent storage (database or flat files), and finally sends back responses to the clients. A major issue with game servers, among other servers, is that clients cannot be trusted, since a modified or reverse engineered version of the client can wreck havoc if the server is not ready to handle it. This enforces that clients should not keep a copy of the current game data, they should only send actions, not states, to the server, which would in turn validate those actions, and update the game status.

Structure of a Daemon

Daemons typically have the same structure, regardless of their functionality. A daemon starts off by initializing its variables. It then sets its IPC interface up, which could simply be signal handlers. The daemon then executes its body, which is almost always an infinite loop.

Most daemons start off by forking. Forking is a method that allows a process to clone itself, creating an identical child. A daemon, as a parent process, usually forks off and terminates (or dies), while its child is left executing the main loop. The child is usually called an orphan process. In Unixes, orphan processes are automatically adopted by the "init" process, and this action is known as re-parenting.

For a more practical approach, the following sections dissect the two previously mentioned daemons. Links to their sources are found at the end of the tutorial.

A Simple BASH Script Daemon

Prerequisites

You will need to have BASH, which is the current default shell for most, if not all, Unixes. You also could use some basic BASH programming knowledge.

The Code

The following BASH code simply logs its uptime every five seconds to a file. As a quick introduction to signal trapping, the logging can be disabled and enabled while the daemon is running. To run the following script, you first need to save it under "uptlogd.sh". Now, from the shell, type "chmod 755 uptlogd.sh" to make the file executable, and "./uptlogd.sh &" to run the file in the background.

#!/bin/bash


# uptlogd - A simple BASH script daemon

# Daemon initialization
echo "uptlogd PID is $$"
echo "Logging started ..."
logfile="LOGFILE_$$"

# Set the variables up
logging=true
timesec=0


The daemon starts by printing its PID ($$). It then uses a unique name for its log file using its PID. Variable initializations follow, where logging is on, and the uptime is zero initially.

# Set the signal handler up

trap 'if [$logging==true]; then logging=false; else logging=true; fi' SIGUSR1


The trap command allows the daemon to intercept a signal and handle it. The daemon handles "SIGUSR1" by triggering the logging, enabling it if it is disabled, and vice versa.

# Loop forever

while true
do
# Log the currently logged-in users if logging is enabled
if [ $logging == true ]
then
echo "Uptime: $timesec" >> $logfile
fi

timesec=`expr $timesec + 5`
# Sleep for 5 seconds
sleep 5
done


The daemon moves on to the main loop. It echoes the uptime to the log file if logging is enabled, updates its uptime, sleeps five seconds, and repeats. To interact with the daemon, you can use the "kill" command from the shell, which sends a signal to a running process. Using the PID reported by the daemon, use "kill " to terminate the daemon by sending the default signal SIGTERM. You can also use "kill -SIGUSR1 " to enable/disable logging.

The code is very simple, but who uses BASH to code daemons anyway?! A more practical approach is to use a programming language like C, which is exactly what you are going to do in the next section.

Fork off and Die: C/C++ Daemons

Prerequisites

You'll need to have GCC (The GNU Compiler Collection) installed. Enter "gcc --version" at the shell, and it should echo back the current GCC version. You will also need to have glibc, the GNU standard C library. You could try compiling a simple "Hello World" program just to make sure everything is setup properly, which is usually the case, but you should make sure anyway. Also, some hands-on experience with the C programming language will definitely prove useful.

The Code

The following code implements the very same daemon explained in the previous section, but using C. To compile your daemon, you first need to save the file under "uptlogd.c". At the shell, enter "gcc -o uptlogd uptlogd.c" to compile and link your daemon. You can now use "./uptlogd" to start it.

// uptlogd - A simple C daemon


#include
#include
#include
#include
#include
#include

#define FILENAME_SIZE 15
#define LINE_SIZE_MAX 100

int logfd = -1;
int logging = 1;
char *logfile = NULL;
char *line = NULL;


The file basically has a little more includes than your average "Hello World" program! The header files are "stdio.h", which defines "printf", among other standard I/O functions, "unistd.h", which defines lots of constants and functions, including "getpid", "fork", and others, "fcntl.h", which defines the arguments used by the "open" function, "sys/types.h", which defines several data types, among which is "pid_t", "errno.h", which is responsible for the basic error reporting facilities, and "signal.h" for handling signals.

The global variables are "logfd", the file descriptor for the log file, "logging", the logging flag, "logfile", the name of the log file, and "line", which temporarily holds the log message before it is written to the log file.

void trigger_logging(int signum)

{
logging = (logging == 1) ? 0 : 1;
}

void clean_up(int signum)
{
// Free up the resources
if (logfd != -1)
close(logfd);

if (logfile != NULL)
free(logfile);

if (line != NULL)
free(line);

exit(0);
}


Those two functions are defined as "callbacks"; they are called automatically when their associated signal is intercepted. The "trigger_logging" function enables/disables logging, while the "clean_up" function closes the file and deallocates the strings before exiting.

int main(int argc, char *argv[])

{
// Fork off
pid_t pid = fork();
if (pid == -1)
{
// Out of memory: not likely to happen!
printf ("Error: not enough memory to initiate!\n");
return 1;
}
else if (pid != 0)
{
// ... and die!
printf("uptlogd PID is %d\n", (int) pid);
printf("Logging started ...\n");
return 0;
}


The main function starts off by forking through calling the "fork" function. "fork" returns -1 on failure, which only happens if not enough memory for cloning the current process is available, which is quite uncommon. "fork" returns the child PID in the parent's thread, and zero in the child's thread. Here, the parent outputs the child's PID and exits, while the child continues to execute the remaining code.

	// Child continues executing here

// Set the signal handlers up
signal(SIGUSR1, trigger_logging);
signal(SIGTERM, clean_up);


The "signal" function sets up a signal handler. The code handles SIGUSR1 by calling "trigger_logging", and SIGTERM by calling "clean_up", refer to those functions to get the whole picture.

	// Set the log file up

pid = getpid();
logfile = malloc(sizeof (char) * FILENAME_SIZE);
sprintf(logfile, "LOGFILE_%06d", (int) pid);

// Open the log file
logfd = open(logfile, O_CREAT | O_WRONLY, 00644);
if (logfd == -1)
{
printf("%s\n", strerror(errno));
return 1;
}


The log filename is allocated, and a unique name is used by concatenating the PID to the string "LOGFILE_". A handle to the file is acquired using "open". A defensive programming practice is to make sure the acquired file handles are valid, several problems can cause "open" to return -1.

	// Prepare the loop variables

int count;
int timesec = 0;
line = malloc(sizeof (char) * LINE_SIZE_MAX);

// Loop forever
while (1) {
// Write the log to the file, if logging is enabled
if (logging == 1)
{
sprintf(line, "Uptime: %d seconds\n", timesec);
count = write(logfd, line, strlen (line));
if (count == -1)
{
printf("%s\n", strerror (errno));
return 1;
}
}

timesec += 5;
// Sleep for 5 seconds
usleep(5000000);
}

return 0;
}


The daemon now executes its main loop. The loop starts by writing the log message, formatted in the "line" variable, to the log file. Another good programming practice is to check the length of the written message, which is the return value of "write", compare with it the expected size, and act accordingly, but for simplicity purposes, the code only checks if the function failed. The daemon then updates its uptime, and sleeps for five seconds, only to repeat the loop all over again.

Where to Go from Here

The next step is totally up to you, but what matters is that you apply what you've learned so far, so if you haven't started coding already, don't let anything hold you anymore. Game servers are to be discussed in greater detail in following tutorials, sheding more light on other fields, including game programming, socket programming, and more. Stay tuned!

 
# #