# How to Write a Unix Daemon with Python

DONG Yuxuan @ Dec 23, 2019 Asia/Shanghai

This article discusses how to write daemons without service managers. It’s also how service managers like systemd work.

In Unix, a daemon is a program that runs as a background process. A daemon usually executes some periodic tasks like downloading mails. They don’t need interact with users often and they need keep running during the system is running.

Nowadays, writing daemons has no diffrence with writing normal programs. We can just write the main loop that performs the periodic task and make service manager like systemd to handle daemon things. This article is to talk about ancient technologies, wrting daemons without a service manager.

Let’s start with a simple program daemon1.py.

#!/usr/bin/env python3

from time import sleep

while True:
# Some computation
sleep(0.1)


We hope the computation being executed in the background. However, if we runs the program from a terminal,

$python3 daemon1.py  the terminal will be blocked until we press CTRL+C to terminate. An experienced Unix user knows it can be ran in the background by the follwing command. $ python3 daemon1.py &


However, it will be terminated after we close the terminal if the huponexit option is on.

You may think to call the system function setsid to detach from the session(terminal). However, setsid can’t be called from a process group leader and the program is a learder as you lunch it from the terminal.

Easy to think of, we can make the program starts a subprocess and the parent process exits immediately. The subprocess calls setsid to detach from the terminal session and do the computation.

#!/usr/bin/env python3
# FILENAME: daemon2.py

from time import sleep
import sys
import os

if os.fork() > 0:
sys.exit(0)

os.setsid()
while True:
# Some computation
sleep(0.1)


The advantage of daemon2.py is not just it can run without a terminal. Also you can run it in the background without adding the & in the command.

If you’re writing a special-purpose daemon, the above code is enough. The rest things you should be careful is not to try to interact with a terminal in your computation code. For example, not to read from stdin, not to write to stdout and so on.

However, if you want to build a library or framework which works fine on all Unix systems, for example, the function below,

def daemonize(computation):
 Execute computation() in a daemon process 

if os.fork() > 0:
return

os.setsid()
computation()


the above code is not enough. Because you can’t ensure the computation function will not try to take control of a terminal again. This could happen if computation tries to open a terminal-associated device in System V-based systems. See POSIX.1-2008 Section 11.1.3, “The Controlling Terminal”.

The controlling terminal for a session is allocated by the session leader in an implementation-defined manner. If a session leader has no controlling terminal, and opens a terminal device file that is not already associated with a session without using the O_NOCTTY option (see open()), it is implementation-defined whether the terminal becomes the controlling terminal of the session leader. If a process which is not a session leader opens a terminal file, or the O_NOCTTY option is used on open(), then that terminal shall not become the controlling terminal of the calling process.

A process can take control of a terminal only if it’s a session leader. Because the subprocess called setsid, it is a session leader so this can really happen. To avoid the case, we create a sub-subprocess. This is the famous “double-fork trick”.

def daemonize(computation):
 Execute computation() in a daemon process 

if os.fork() > 0:
return

# From now, we're in the subprocess
os.setsid() # The subprocess becomes the session leader to detach from the terminal

if os.fork() > 0:
return

# From now, we're in the sub-subprocess which is not a session leader
computation()


Now, if computation opens a terminal device, the daemon will not attach to a terminal. So our program becomes a real daemon, it will not exit after the terminal being closed.

The basic theory is over here. You can free play based on the code. But I think I’m obliged to tell you how other people usually play with the rest part.

• People usually redirect stdin, stdout, and stderr to /dev/null. This is to avoid computation reading/writing a closed file.

• People usually call os.chdir('/') to set the working directory to the root path.

• People usually call os.umask(0) to avoid permission problems.

The most usual daemonize is like the follwing.

def daemonize(computation):
 Execute computation() in a daemon process 

if os.fork() > 0:
return

# From now, we're in the subprocess

os.setsid() # The subprocess becomes the session leader to detach from the terminal

os.chdir('/')

if os.fork() > 0:
return

# From now, we're in the sub-subprocess which is not a session leader

sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')

os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

computation()


These are not what you must do. Personally, I usually provide a logfile argument to daemonize and redirect stderr to it.