How to create a Linux Daemon

Reading Time: 5 minutes

Introduction

I developed several Linux daemons and Windows Services  in my career. The differences between the 2 systems are enormous.

I personally prefer to develop Linux daemons due to my love of Linux development. I feel like home working on Linux.

What is a Linux daemon and how does it differ from a traditional user-space program?

A Linux Daemon has the following characteristics and usually performs the following macro activities:

  • When it starts, it checks if there is another instance of the daemon is already running. If so, it dies.
  • Resets the  file creation mask
  • It forks itself and kills its original process leaving only its child alive. In this way the process detaches from the console. The child, at the moment the father dies, becomes child on init (Init is the root/parent of all processes executing on Linux). It becomes the session leader
  • It changes the current directory to the root file system.
  • Redirects standard input, output  and errors to /dev/null
  • Opens a syslogd stream for logging (on /var/log/messages for redhat for instance). Logging is performed through syslog daemon. (this may vary depending on your needs)
  • It creates/updates a .pid file into /va/run/ for single instancing. Usually the file has a .pid extension tough can be used a different extension
  • It reads its configuration file in /etc/ (This is optional anyway, depending on the nature of your service)
  • Spawns a signal handler thread to interact with OS

The above task list is a generic/basic implementation of a typical Linux Daemon.

Now, let’s see step by step what each operation  does and how typically it is implemented.


Startup

When it starts, it checks if there is another instance of the daemon is already running. If so, it dies. If not, it locks the situation in its favor.

Usually, a daemon is the master of a specific task. It doesn’t want any other friend dealing with its own business. Do you want, as a system admin,  two or more instances of your daemon running simultaneously? Usually not. So, this task is rather easy to be explained. It is a self protection system of the daemon to avoid concurrent instances running on the same OS.

How is it implemented? By storing the process ID of the daemon into a file under /var/run (this might change depending on your system). The name of the file depends on you. Is is usually stupid to call your custom file httpd.pid (just to provide an example) if it is not THE httpd process.

The file contains the PID of your daemon. To check if another process is running, you need to check the presence of the pid file under /var/run.

 

//This function takes a file descriptor for input and marks the file as locked for writing.
//More information on how it works (would take too long to explain here) in chapter 14 - "Advanced I/O"
//of the book "Advanced Programming in the Unix Environment"
int lockfile(int fd) {
    struct flock fl;

    fl.l_type = F_WRLCK;
    fl.l_start = 0;
    fl.l_whence = SEEK_SET;
    fl.l_len = 0;
    return(fcntl(fd, F_SETLK, &fl));
}


int DaemonAlreadyRunning(void){
     int fd;
     char buf[16];

     fd = open(LOCKFILE, O_RDWR|O_CREAT, LOCKMODE);
     if (fd < 0) {
          syslog(LOG_ERR, "can′t open %s: %s", LOCKFILE, strerror(errno));
          exit(1);
     }
     if (lockfile(fd) < 0) {
          if (errno == EACCES || errno == EAGAIN) {
               close(fd);
               return(1);
          }
          syslog(LOG_ERR, "can′t lock %s: %s", LOCKFILE, strerror(errno));
          exit(1);
     }
     ftruncate(fd, 0);
     sprintf(buf, "%ld", (long)getpid());
     write(fd, buf, strlen(buf)+1);
     return(0);
}
 

An interesting aspect of this piece of code is the truncate operation of the opened pid file. The truncate is needed to “reset” the file and avoid to overwrite the existing file.
What is the problem to overwrite without the truncate? The problem is about the PID. Let’s consider the case where you write the first time a PID with 5 digits:

12345

And, the second time, your new PID is 789, without the truncate operation, the result would be:

78945

where 45 are just 2 character from the former version of the file. Remember the write does not overwrite character buffers.
The above mechanism is the most efficient one. Some people prefer to delete the file and recreate it.

 

File Creation Mask

Resets the file creation mask

The process resets the file creation mask in order to imposed the wanted mask mode (usually 0). In fact, the process inherits the mask from the user which invoked the daemon to start.

By imposing the mask, the daemon becomes neutral from generic users actions.

//Clear file creation mask. This is one of the Key actions to be performed by a Linux Daemon.
//This resets the default mask for new files being created by the Process.
//Remember: Some files could be created by called System libraries that would inherit this mask.
umask(0);

 

Forking

It forks itself and kills its original process leaving only its child alive.

The daemon needs to fork itself in order to become the session leader. How to explain this? By example. Do you want to keep your process attached to the session of your shell program? I don’t think so. What happens if you kill your shell or your SSH session? The process dies with it.

So, here, the idea is to fork the process and keep only the child process alive while the original process kills itself.


//First Fork. This allows the process to become a session leader and releases the attachment to the console.
//The attachment to the console si lost since the original process cease to exist.
//On UNIX system V an additional fork would be advisable. I didn't care about this situation. We are running on RHEL
if ((processID= fork()) < 0)
 err_quit("%s: can′t fork", cmd);
else if (processID != 0) //From now on, the function is forked for parent and child. In case of the father, he needs to die
 exit(0);

//Creates a new session and becomes the sessions leader. Remember, if we are here, it is for sure the child.
//the father has been killed after the fork with the exit call.
setsid();

//Ensure future opens won′t allocate controlling TTYs.
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
 err_quit("%s: can′t ignore SIGHUP", cmd);
if ((processID= fork()) < 0)
 err_quit("%s: can′t fork", cmd);
else if (processID != 0) /* parent */
 exit(0);

To understand this, look at the exit(0) operation. It checks if the fork occurred, if so, it kills itself (the father).

Current Directory

It changes the current directory to the root file system.

This is a very important step. The process needs to change the directory where it operates, to a place, it knows won’t require to be unmounted. By definition, the only location is the root of the file system. If we don’t perform this operation, the file system from where the process is exectuted, won’t be able to be unmounted anymore unless the daemon is killed.

No one want’s such situation for obvious reasons.

The way to accomplish this is:


//The daemon switches the current working directory from the root filesystem
//If this operation is not performed, the file system from where the command was operated could not be unmounted
if (chdir("/") < 0)
     err_quit("%s: can′t change directory to /", cmd);

 

Standard Input/Output

Redirects standard input, output  and errors to /dev/null

Have you ever seen a daemon using the standard input and output in the way we use them traditionally (screen and keyboard)? I would say no.

There could be cases (in the past particularly) where a daemon are/was leveraging on them. The cloud be situations where nowadays someone is still using it. But traditionally, you receive and dump data in different ways.

So, to cut a long story short, to redirect standard output and input (and error):

//Attach file descriptors 0, 1, and 2 to /dev/null. We don't want our output to go on the console.
//The daemon uses syslogd remember!
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);

 

Syslog

Opens a syslogd stream for logging

I usually redirect the logging of my daemons on syslog facility. It makes logging extremely easy and removes me from the cumbersome to re-invent the wheel.

I usually proceed in the following way:

//Initialize syslog(), if running as daemon.
void log_open(const char *ident, int option, int facility){
     if (log_to_stderr == 0)
          openlog(ident, option, facility);
}

//Let's start the syslog session!
openlog(cmd, LOG_CONS, LOG_DAEMON);

if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
     syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
     exit(1);
}

The openlog function takes the following parameters:

  • The “cmd” char buffer is the name of your daemon. This char buffer is put to any log line you will log in the syslog facility. If you use the generic facility, it is usually a good idea to specify it.
  • The instruction to log on console directly if an error occurs (remember, we redirected our own standard output)
  • informs syslog that to use the logging facility dedicated to generic daemons. Here you can use your own facility if you wish.

 

Configuration File

It reads its configuration file

I think this step is rather self explanatory. Use the format and the libraries you prefer for this.

 

 

Signal Handler Thread

Spawns a signal handler thread to interact with OS

This is the most peculiar operation a Daemon does. Many people skip this part and go for a single thread model (you check the signals during the main loop somehow).

I think anyway, the right way is to perform the following operation:

  • Spawn a new thread
  • The new thread has the only scope to “listen” for a signal from the OS
  • Once a signal is intercepted, it performs the right required operation or updates Daemon’s status variables (i.e. time to die)

The thread code looks like the following:

void * threadSignalHandler (){
 int err, signo;

 for (;;) {
      err = sigwait(&mask, &signo);
      if (err != 0) {
           syslog(LOG_ERR, "sigwait failed");
           exit(1);
      }

      switch (signo) {
      case SIGHUP:
           >>>> do your stuff here
           break;
      case SIGTERM:
          >>>> do your stuff here
      break;
      default:
          syslog(LOG_INFO, "unexpected signal %d\n", signo);
          break;
      }
 }
 return(0);
}

 

While the thread itself is created in the simplest way possible


/*
* Create a thread to handle SIGHUP and SIGTERM.
*/
err = pthread_create(&tid, NULL, threadSignalHandler, 0);
if (err != 0)
err_exit(err, "can′t create thread");

The only scope of the signal handler thread is to stay there and wait for signals to be handled. It is a good practice to log signals requests that the thread does not expect to receive (the default of the main switch operation)

 

 

Soon I will post a .c file containing the simplest version of a basic linux daemon.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.