Linux Development Magazine

Linux programming articles

How to create a Linux daemon

Posted by jajcarmona on 2010/05/18

In Linux, a daemon is a program that runs in the background, rather than under the direct control of a user. It can be initiated by the user or by the system at boot time (in this case, it would be a system service).

The parent process of a daemon forks a child process that do the work and immediately after the forking, the parent exits. The child process keep running and the interface that launched the daemon (the parent process) doesn’t block.

An example of a Python daemon will be the next:

daemon.py file:

#!/usr/bin/env python

import sys, os, time, atexit
from signal import SIGTERM

class Daemon:
    “””
    A generic daemon class.

    Usage: Subclass this class and override the ‘run’ method
    “””
    def __init__(self, pidfile, stdin=’/dev/null’, stdout=’/dev/null’, stderr=’/dev/null’):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile

    def daemonize(self):
        “””
        Do the ‘UNIX double-fork magic’
        “””
        try:
            pid = os.fork()
            if pid > 0:
                # Exit first parent
                sys.exit(0)
        except OSError, e:
            sys.stderr.write(“Fork #1 failed: %d (%s)\n” % (e.errno, e.strerror))
            sys.exit(1)

        # Decouple from parent environment
        os.chdir(“/”)
        os.setsid()
        os.umask(0)

        # Do second fork
        try:
            pid = os.fork()
            if pid > 0:
                # Exit from second parent
                sys.exit(0)
        except OSError, e:
            sys.stderr.write(“Fork #2 failed: %d (%s)\n” % (e.errno, e.strerror))
            sys.exit(1)

        # Redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = file(self.stdin, ‘r’)
        so = file(self.stdout, ‘a+’)
        se = file(self.stderr, ‘a+’, 0)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # Write pidfile
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile,’w+’).write(“%s\n” % pid)

    def delpid(self):
        os.remove(self.pidfile)

    def start(self):
        # Check for a pidfile to see if the daemon already runs
        try:
            pf = file(self.pidfile,’r’)
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if pid:
            message = “Pidfile %s already exist. Daemon already running?\n”
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)

        # Start the daemon
        self.daemonize()
        self.run()

    def stop(self):
        # Get the pid from the pidfile
        try:
            pf = file(self.pidfile,’r’)
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if not pid:
            message = “Pidfile %s does not exist. Daemon not running?\n”
            sys.stderr.write(message % self.pidfile)
            return # not an error in a restart

        # Try killing the daemon process
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find(“No such process”) > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        self.stop()
        self.start()

    def run(self):
        “””
        You should override this method when you subclass Daemon. It will be called after the process has been
        daemonized by start() or restart().
        “””

daemon-example.py file:

#!/usr/bin/env python

import sys, time
from daemon import Daemon

class MyDaemon(Daemon):
    def run(self):
        while True:
         # Do any task here
            time.sleep(1)

if __name__ == “__main__”:
    daemon = MyDaemon(‘/tmp/daemon-example.pid’)
    if len(sys.argv) == 2:
        if ‘start’ == sys.argv[1]:
            daemon.start()
        elif ‘stop’ == sys.argv[1]:
            daemon.stop()
        elif ‘restart’ == sys.argv[1]:
            daemon.restart()
        else:
            print “Unknown command”
            sys.exit(2)
        sys.exit(0)
    else:
        print “Usage: %s start|stop|restart” % sys.argv[0]
        sys.exit(2)

Note that in Daemon class, we check if a file named daemon-example.pid exists (the PID file), because if exists, it means that daemon is currently running and it’s not necessary to run it again. The file is created in a temporal files folder when we start the daemon and is deleted when we stop the daemon. If we want to run the daemon simultaneously in more than one user session, one solution could be the concatenation of the current user name to the PID file name (for example, daemon-example.currentusername.pid).

In a terminal, to start the daemon, we should run the next command:

python daemon-example.py start

to stop the daemon we should run the next command:

python daemon-example.py stop

and to restart the daemon we should run the next command:

python daemon-example.py restart

Try out to launch the daemon two times without stopping to see how the daemon doesn’t run again if is currently running.


Bibliography:

Article about Linux daemons: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: