I've already talked about Unix programming here, but today I want to go a step   ahead an really implement a daemon, this time I am going to use PHP. You may have already heard of daemons here or there, but I'll give you some facts about them anyway:

  1. daemons are long running processes,
  2. they run in the background,
  3. you can't interact with them via a TTY
  4. originated from the Unix world
  5. a common convention is to put a trailing "d" on their name, e.g. httpd

These are the more obvious attributes of a daemon, but there's a little more to say especially about 2. Let's start PHP in the terminal and put the process in the background:

[code lang="shell"]
php -a &
[/code]

Now if you look through the processes on your system with ps -fax | grep php you will see something like that:

[gist id=1942496 file=psfax_php_a.sh]

The first number is of no interest for us, but the second and the third are. These are the process ID (pid) and the parent process ID (ppid) of our little PHP process. As you can see the parent process is (in my case) 480 and if you do another grep'ing for 480 you can see that this is the shell where you've typed php -a & in. What happens if you log off the shell? All child processes of the shell will die, so will your PHP process.

But daemons should not die when a user logs off, they should run forever! If you go the tree of processes up on your system you might find a process that has the ppid 1. It's the first process that is started when your OS starts and the last one to exit. So we should run our daemon as a child of that process.

Preconditions

Before we can implement our first daemon make sure you have the pcntl extension for PHP installed. Most Unix-based programming languages have the necessary functions implemented in the core, but not PHP. Just search for pcntl in your package manager and install the package.

Our first daemon: mattd

It does nothing, except printing dots to /dev/null, but that shouldn't matter. It's a demonstration daemon. Here's the dump, I'll go through it and explain what and why we need each line of code in it and what it does:

[gist id=1942496 file=mattd.php]

These are the steps we need to do in the daemon:

 

  1. fork a new child process
  2. pcntl_fork() returns two times, the PID of the parent process and with a 0 for the forked child process
  3. so there are two ways the code runs from here, one is the parent, the other is the child process (if forking was possible)
  4. the parent immediately exits (it's ppid is the shell you started the daemon from)
  5. the child goes in the else branch
  6. a daemon has now working directory, so reset it to /
  7. close all the file descriptors that are bound to the shell used, that is STDIN/OUT/ERR
  8. start a forever-running loop that does something useful

As I said before we need to run our daemon as a child of the pid 1. We can do this by forking and the exiting the parent process. The then new process runs as a child of our php process first but as this process exits, it has no parent anymore.
But having no parent is not possible, so it will get the new ppid 1.

Daemon != Cronjob

Typically, PHP developers are more familiar with developing cronjobs than with daemons and although both are used for similar tasks, they don't share a common idiom. The main difference is that cronjobs are mostly long-running processes whereas daemons are forever-running processes.

The other thing is that cronjobs are always started in the context of a shell. Daemons don't run in any context, their parent process ID is always 1, the first process started on the system. You can easily test this: create a small PHP script that has an endless loop in it and put it in your crontab. Now wait until the script gets started and do ps -fax | grep you_cronjob and you'll see that your cronjob has a PPID other than 1.

That's it for now ...

A short disclaimer to the end: I have not tested this daemon in production nor will I. I am just keen to learn what goes on in the background of Unix programs and I love to share my knowledge to my readers. If I am wrong and you know better, please leave a comment here, thanks :)