Cross-Platform Development


Portable Control of Multiple Daemon Processes

Joseph Kathan

The Unix and Windows process models are just different enough to present problems when writing portable code.


Introduction

Today many applications are being ported from Unix to Windows NT. Frequently the project guidelines require that all versions of the ported application use as many common algorithms and shared code sets as possible. My project to port several Unix daemon processes included this stipulation, which presented a number of significant challenges due to the many basic differences between the two operating systems.

One of the main algorithms I had to port for these daemon processes was a common method of shutting them down. In the Unix version of this method, a program called the daemon killer sent a signal to each of the daemon processes. As each daemon received its signal, it would shut down. I had to retain this feature in the ported programs, but I faced a problem because Windows NT does not have Unix signals. NT does, however, have message handlers, and I found that I could adapt them to act like a signal handler. The net result is a common, portable method that can be used on either a Unix platform or a Win32 platform to control processes.

A Word on terminology: in Unix, a daemon is a process that runs unattended in the background. These processes run for extended periods of time, handling such things as email, ftp connections, and administrative tasks. Windows NT has similar background processes to handle these tasks; however, in Windows jargon these processes are called services. For the sake of clarity, I refer to both as daemons throughout this article.

The Unix Implementation

The algorithm for shutting down the daemons requires each daemon process to have a signal handler. A signal handler is a function tasked with handling specific software interrupts (signals) that may occur while a given process is running. Unix has a wide variety of signals to indicate everything from arithmetic exceptions to program termination. All that is required to send a Unix process a signal is the process's identifier and the appropriate permissions [1].

The termination signal, SIGTERM, is the basis of the shutdown algorithm. The algorithm works as follows:

Listing 1, dmnunix.c, shows an example program that takes the role of the Unix daemon. Starting with function main, the first thing the daemon does is determine its process identifier through a call to the getpid function. The process identifier, or PID, is then passed to the register_daemon function for logging in the control list. In this implementation, I use a file for the control list, but the list could just as easily be held in a database table or shared memory structure.

Next, the daemon registers its SIGTERM signal handler with the operating system through a call to the signal function. The first parameter of the call specifies that the daemon is interested in the SIGTERM signal. The second parameter indicates that control should be passed to the sig_handler function when SIGTERM is sent to the daemon. Once the signal handler is registered, the daemon sets an exit point for the signal handler through a call to sigsetjmp. When sigsetjmp is called, it returns a zero. However, when the signal handler transfers control to this point, the value returned by sigsetjmp will be one [2]. I save the return value of sigsetjmp in kill_sig; the daemon enters the exit block only when kill_sig is non-zero.

The next thing the daemon does is set the jmpok flag to one. This indicates to the signal handler that everything is in place and ready for use. After this, the daemon does its work, which for this example is just an empty for loop.

The daemon continues doing its work until the signal handler, sig_handler, receives a SIGTERM signal. When this signal arrives, the sig_handler function removes itself as the registered SIGTERM handler for the daemon through a call to signal. It executes the siglongjmp function to return control to the point in main specified by the earlier call to sigsetjmp. This time, kill_sig will be assigned a value of one and will enter the exit block. In practice, prior to exiting, the daemon would do any cleanup needed, such as freeing resources or closing files. In the example, the daemon has nothing to cleanup, so it just exits.

Listing 2, dkunix.c, is an example of the Unix daemon killer. The daemon killer first opens the control list of process identifiers. For each process in the list, it invokes the kill function to send out the signal. The first parameter of the call is the process identifier that should be signaled; the second is the signal to send. After each process in the list has been signaled to shutdown, the daemon killer closes the control list and exits.

I have tested both Unix examples on IBM AIX 4.2 and Redhat Linux 5.1. For the AIX test, the compiler was the C for AIX 3.1.4.0 compiler. For the Redhat test, I used the GNU Project C and C++ Compiler 2.7.2.3. To run the daemon example on each Unix platform, I invoked it with the & on the command line to put the process in the background [3]. After several daemons were started, I invoked the daemon killer to signal each of the daemon instances to shut down.

The Windows Implementation

Windows NT does not use signals. Instead, Windows uses messages to inform processes of the asynchronous events that may occur during their process lifetimes. These messages inform the process about a wide range of conditions, as well as allow for a way to indicate that the process should terminate [4]. This termination message feature provides the way to port the shutdown algorithm to Windows NT.

In the NT version of the algorithm, each daemon process must have a message handler to process the termination message when it arrives:

Listing 3, dmnwin.c, shows the Win32 version of the daemon program. Just like in the Unix version, the first thing the daemon does in the main function is retrieve its process identifier through a call to GetCurrentProcessID. The process identifier returned is sent to the register_daemon function for logging in the control list.

Next, the daemon installs its message handler through a call to SetConsoleCtr Handler. The parameters indicate all messages for the daemon should be sent to MyHandler. Once the message handler is in place, the daemon does its work, which for this example is just an empty for loop. The daemon continues doing its work until the message handler receives a message telling it to shutdown. The message handler gets its process handle through a call to GetCurrentProcess. Using the handle, the message handler retrieves the exitcode through a call to GetExitCodeProcess. Prior to exiting, a production daemon would do any cleanup needed at this point, such as freeing resources or closing files. In the example, the daemon has nothing to cleanup, so it just exits.

Listing 4, dkwin.c, is an example of the Windows daemon killer. As in the Unix version, the daemon killer first opens the control list of process identifiers. For each process in the list, it finds the handle of the process through a call to OpenProcess. The first two parameters set the access level required, and the third indicates the process identifier of interest to the daemon killer. Once the handle is retrieved, the daemon killer sends the shutdown message through a call to TerminateProcess.

I have tested both example Win32 programs on Windows 95 and Windows NT 4.0. The tests were carried out using the Microsoft Visual C++ 5.0 and 6.0 compilers. To run the daemon example on Windows 95 or Windows NT 4.0, I opened a DOS window for each instance of the daemon I executed. After several were started, I opened an additional DOS window and invoked the daemon killer to signal each of the daemon instances to shut down.

Concluding Details

When using this algorithm in production code, several other details will need to be considered. First and foremost, the account that runs the daemon killer must have the appropriate permissions to send the signals and messages to the processes to be terminated. What this generally means on a Unix box is that if these processes are running as multiple users, the account that runs the demon killer must have root access. Similarly, the log file may need certain permissions so that all the processes can read and write to it.

A second consideration is that sometimes programs terminate unexpectedly, power goes out, or some other incident causes one or more of the entries in the control list to be invalid. In my production environments I handle this by having the control list reset every time the machine boots, cleaning out any old entries. Then, when my daemon killer is invoked, I have it check to make sure the process registered in the control list is the same one that is still running.

The last detail is the desire to have common code across platforms. You can achieve this through conditional compilation. Conditional compilation can be easily added to the daemon killer program as shown in Listing 5, dkcombo.c. It doesn't always make for pretty code, but it does get the job done.

Notes and References

[1] For more information on Unix programming, including an entire chapter devoted to signal processing, check out Advanced Programming in the UNIX Environment by W. Richard Stevens, published by Addison-Wesley.

[2] sigsetjmp behaves similarly to the C setjmp function. For a discussion of setjmp, see Pete Becker's "Journeyman's Shop" column in the February 1999 issue of CUJ.

[3] I could have just as easily added a fork in the Unix code so each daemon would go in the background automatically. Fork, however, presents another porting issue because Windows NT does not have that function. The solution to the absence of fork in Windows NT is to create a service, but that is beyond the scope of this article. If you are interested in how to create a Windows NT service, you can refer to "A Wrapper Class for NT Services," by John T. Bell, in the August 1998 issue of CUJ.

[4] When it comes to information on Windows programming, I recommend the MSDN Library CD-ROMs published by Microsoft. Another good reference is Advanced Windows by Jeffery Richter, published by Microsoft Press.

Joseph Kathan is a Senior Software Engineer for Medic Computer Systems, Inc. He has worked extensively in Unix and Windows environments, and specializes in designing and developing interfaces between disparate applications in enterprise systems. He can be reached at jgk@pagesz.net.