Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A Device Driver for W2K Signals


August 2001/A Device Driver for W2K Signals


One major difference between Windows and Unix-based operating systems is the support for handling programmer-defined signals. Although the Standard C library provides some basic support for signal handling [2], it is not enough for the programmer who needs signals mainly for implementing interprocess communication. Indeed, the absence of such a mechanism in the context of Windows makes the asynchronous communication between the processes (threads) difficult and requires the usage of special data structures, like events, and the creation of dedicated threads that poll continuously the status of some conditions [6]. In this artice, I introduce SignalsLib, a library for the support of signal handling on Win32 programming platforms. The heart of the library is a device driver that provides the mechanisms needed to cause a signal handler in a target process to execute asynchronously, even if the target process is not in an alertable state.

This article provides a general overview of signal handling, an outline of the support Windows provides for signals and some basic mechanisms, the design and implementation of the library and driver, performance measurements of the implemented mechanisms, an API that is exported to the programmer, and possible uses and extensions of this work.

Overview of Signals

Signals are a mechanism for informing processes about asynchronous events. The basic idea is that each distinct signal has an associated integer code, and any process can register a handler (a callback function) for any signal (by specifying its integer code). When one process sends a specific signal to another process that has registered a handler for that signal, the target process will stop whatever it is doing and execute the handler function registered for that signal.

The signal mechanism is analogous to interrupt handling, in that a signal interrupts whatever code is currently executing in the target process. Just like interrupt handlers, signal handlers require careful coding — ordinary code must not access the same data as signal handlers unless they both use synchronization primitives to avoid corrupting each other’s work. Signals provide simple and convenient interprocess communication. Common traditional uses include: notifying a service that it should rotate its log files, notifying a parent process that the child process has completed initialization and is ready to start doing useful work, notifying a process that it should temporarily pause operation, notifying a process that it should perform a clean shutdown as soon as possible, and so on.

Each signal is associated with an action that will be executed by the kernel on behalf of the process that has received the signal. For most signals, the default action is the termination of the process, although a process can require some alternative action from the system. The various possible alternatives are:

1. Ignore the signal. In this case, the process is not informed about the signal.

2. Restore the default signal action.

3. Execute a specific signal-handling function. This is the case in which the process wants to perform some custom action when a specific signal arrives. The process registers a special function that gets invoked asynchronously when the associated signal occurs. After the signal handling function returns, whatever code it interrupted will resume executing.

Windows Support for Signals

Win32 offers a very specific kind of signal support in the form of SetConsoleCtrlHandler(). This function lets a console process catch a variety of system-generated signals (the user pressed Ctrl-C, the user is logging off, etc.). It does not offer any programmer-defined signals, nor does it offer any interprocess communication — it is strictly a means for the operating system to notify a process of a few specific events.

The only other signal-like mechanism that Windows offers is structured exception handling. However, Standard C requires support for the well-known signal()/raise() Unix functions and a constrained number of signals [2]. signal() sets the signal-handling function to be called for a given signal. raise() sends a specific signal to the current process, invoking either whatever handler was registered for that signal, or else the default action associated with that signal. Again, these signals are not extensible, and they are local to a given process. (Standard C defines no function for sending a signal to another process.)

Instead of signals, Windows supports APCs (Asynchronous Procedure Calls). An APC is a kernel-defined control object that represents a procedure that is called asynchronously. APCs have the following features [7]:

1. An APC always runs in a specific thread context.

2. An APC runs at OS predetermined times.

3. APCs can preempt the currently running thread.

4. APC routines can themselves be preempted.

There are three different types of APCs in the kernel [2,3]:

User-mode APCs. User-mode APCs are, by default, disabled; that is, they are queued to the user-mode thread, but are not executed, except at well-defined points in the program. Specifically, they can be executed only when an application has either called a wait service and has enabled alerts to occur or, called the test-alert service.

Normal kernel-mode APCs. These are much like user-mode APCs, except that they are executable by default. That is, they are enabled except when the thread is already executing a kernel-mode APC or is inside a critical code section.

Special kernel-mode APCs. These cannot be blocked, except by running at a raised IRQL (interrupt request level). Special kernel-mode APCs are executed at IRQL APC_LEVEL, in kernel mode. They are used by the system to force a thread to execute a procedure in a thread’s context. A special kernel-mode APC can preempt the execution of a normal kernel-mode APC.

The Win32 API [4] provides QueueUserAPC(), which allows an application to queue an APC object to a thread. The queuing of an APC is a request for the thread to call the APC function. When a user-mode APC is queued, the thread is not directed to call the APC function unless it is in an alertable state. Unfortunately, a thread enters an alertable state only by using one of the following Win32 API functions: SleepEx(), SignalObjectAndWait(), WaitForSingleObjectEx(), WaitForMultipleObjectEx(), or MsgWaitForMultipleObjectsEx().

In kernel mode [1], the programmer initializes an APC object using KeInitializeApc(), defining the target thread, one kernel-mode and one user-mode callback routine, the type of the APC (kernel or user), and finally an argument to these two functions. Next, the APC is queued (KeInsertQueueApc()) to the target thread and can be executed without having the thread be in an alertable state.

The SignalsLib Interface

The library provides the necessary structures and mechanisms for elementary support of signal handling exporting an appropriate interface to the programmer. During the building process, the user can define the option for support per thread or the usage of a global table of signal handlers.

signals.h (Listing 1) defines the available signals, from zero to MAX_SIGNALS-1. This header file also defines the two functions that make up the interface to the signals library.

SetSignalHandler() sets a handler for a given signal. It returns zero for failure, or non-zero for success.

SendSignalToThread() sends a signal to the specified thread. You must specify the handle of the thread you want to signal, as well as the signal (integer code) you want to send. It returns zero for failure, or non-zero for success.

testapp.c (Listing 2) demonstrates how to use the signals library. This application creates a thread that sets a handler for a specific signal, and the main thread then sends that signal to the thread, resulting in the execution of the installed signal handler. Of course, the driver must be installed into the system and loaded, or else an appropriate message is printed, when the library’s DLLMain() is executed.

Design and Implementation

SignalsLib consists of a DLL and a kernel-mode device driver. The DLL provides a user-mode interface for applications, while the device driver is necessary for accessing the kernel-mode functions used to queue a kernel-mode APC to the target thread. The calling application simply makes calls to SetSignalHandler() and SendSignalToThread(), however — the DLL hides all the details of communicating with the device driver.

SetSignalHandler() is simple — it just stores the supplied function pointer in the appropriate position of a global array of signal handlers. When a signal actually occurs, the internal routine SignalsDriverRoutine() will get invoked and use this global array to determine what signal handler to call. Both of these routines are in signals.c (Listing 3).

SendSignalToThread() is where the DLL communicates with the device driver. DllMain() obtains a handle for the device driver when the DLL is first loaded and releases that handle when the DLL is unloaded. SendSignalToThread() uses that handle in a call to DeviceIoControl() to pass the device driver a SIGINFO structure:

typedef struct _SIGINFO
{
   HANDLE   hThread;   /* target thread */
   ULONG    SigNo;     /* signal number */
   ULONG    SigFunc;   /* address of DriverRoutine */
} SIGINFO, *PSIGINFO;

Note that SigFunc is not the address of an individual signal handler, but rather the address of SignalsDriverRoutine(), a function in the DLL that looks up and invokes the correct signal handler.

When SendSignalToThread() passes this information to DeviceIoControl(), it will cause the driver’s interrupt service routine to be invoked. The main source code for the driver is in sigdrv.c (Listing 4). The driver’s interrupt service routine invokes SigDriverSendTheSignal(), which is responsible for queuing an appropriate kernel-mode APC to the target thread. SigDriverSendTheSignal() takes a pointer to the ETHREAD data structure [2] of the target thread. It calls KeInitializeApc() to initialize a kernel-mode APC and then calls KeInsertQueueApc() to queue that APC for the target thread.

The queued APC contains a pointer to another function in sigdrv.c (Listing 4): UserApcCallBack(). This function will get invoked in user mode and passed the SIGINFO structure. UserApcCallBack() uses the information in SIGINFO to invoke the DLL function SignalsDriverRoutine(), which will in turn look up and invoke the signal handler associated with the given signal.

Performance Evaluation

The choice of normal rather than special kernel-mode APCs has to do with the desired functionality, not with performance. If it’s important that the signal handler be preempted if another signal occurs, then you should use special kernel-mode APCs rather than normal kernel-mode APCs.

The APC mechanism performs extremely well; the signal handler is invoked as soon as the target thread is scheduled, typically within a few microseconds. It is important to mention that kernel-mode APCs achieve immediate signal delivery, independently of the system load (number of threads that run on the system). You might be able to reduce the response time further via thread priorities. For example, SendSignalToThread() could increase the priority of the target thread.

Conclusions

I’ve implemented the basic mechanisms of user-defined signals, mainly in order to enable asynchronous communication between threads of the same or different Win32 applications. The resulting DLL and device driver combination provides the two major functions for signal handling, similar to the signal() and kill() Unix system calls, and for signals similar to SIGUSR1 and SIGUSR2.

A possible extension of this library includes the implementation of the handlers for some Unix signals, like SIGSTOP, SIGCONT, and SIGTERM, and the support of the POSIX standard. It would also be interesting to integrate these mechanisms with the Standard C library. The library assists in implementing the pthread_kill() POSIX function and makes easier the development of applications that require notification mechanisms between their threads in user mode or from kernel to user mode, as the device driver needs only to know the address of the driver routine. Although the POSIX standard defines global signal handlers for all the threads of an application, the library can easily support per-thread signal handlers by using thread local storage; for simplicity, that was not done in this version.

To keep the code simple for publication, this implementation assumes that all participating processes will share a single instance of signals.dll. More specifically, SendSignalToThread() always passes the address of SignalsDriverRoutine() (in the context of the calling process) to the device driver, but the device driver will attempt to use that address in the context of the target process (which will likely be different). That will be disastrous if the target process loaded signals.dll at a different address than the calling process did. If a particular process cannot load signals.dll at its default address, you can always select another default address until you find one that does not collide with anything already loaded. You could solve the problem more elegantly by revising the interface so that the device driver has enough information to locate the correct callback address for a given thread.

Finally, an alternative implementation, which is also not presented here, is based on user-mode APCs. SendSignalToThread() uses the Win32’s QueueUserAPC() and next, through a kernel-mode device driver, manages to set the target thread in alertable state. This is possible by setting to one the memory address that corresponds to 0x4a bytes offset [5] from the base address of the ETHREAD data structure [2].

References

[1] E. N. Dekker and J. M. Newcomer. Developing Windows NT Device Drivers: A Programmer’s Handbook (Addison-Wesley, 1999).

[2] Microsoft Corporation. Microsoft Developer Network Library, msdn.microsoft.com/library.

[3] D. A. Solomon. Inside Windows NT, Second Edition (Microsoft Press, 1998).

[4] J. Ritcher. Advanced Windows: The Professional Developer’s Guide to the Win32 API for Windows NT 4.0 and Windows 95 (Microsoft Press, 1995).

[5] www.cmkrnl.com/arc-userapc.html

[6] www.microsoft.com/msj/0799/nerd/nerd0709.html

[7] www.osr.com/insider/1998/apc.html

[8] Microsoft Visual Studio\VC98\CRT\SRC\WINSIG.C

Panagiotis Hadjidoukas is a postgraduate student at High Performance Information Systems Laboratory, Department of Computer Engineering & Informatics, at the University of Patras in Greece. You can reach him by email at [email protected] or at the web page of his laboratory at http://www.hpclab.ceid.upatras.gr.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.