In some situations, for reasons of elegance, modularity, configuration flexibility, robustness, or even (in some cases) performance, it is desirable to run device drivers in user mode, as ``semi-ordinary'' application programs. This is done as a matter of course by some microkernels. There is nothing in the OSKit device driver framework that prevents its device drivers from executing in user mode, and in fact the framework was deliberately designed with support for user-mode device drivers in mind.
Figure:
Using the framework to create user-mode device drivers
Figure 6.2 illustrates an example system in which device drivers are located in user-mode processes. In this case, all of the code within a given driver set is part of the user-level device driver process, and the ``surrounding'' OS-specific code, which makes calls to the drivers through the driver interface, and provides the functions in the ``kernel interface,'' is not actually kernel code at all but, rather, ``glue'' code that handles communication with the kernel and other processes. For example, many of the functions in the driver-kernel interface, such as the calls to allocate interrupt request lines, will be implemented by this glue code as system calls to the ``actual'' kernel, or as remote procedure calls to servers in other processes.
Device driver code running in user space will typically run in the context of ordinary threads; the execution environment required by the driver framework can be built on top of these threads in different ways. For example, the OS-specific glue code may run on only a single thread and use a simple coroutine mechanism to provide a separate stack for each outstanding process-level device driver operation; alternately, multiple threads may be used, in which case the glue code will have to use locking to provide the nonpreemptive environment required by the framework.
Dispatching interrupt handlers in these user-mode drivers can be handled in various ways, depending on the environment and kernel functionality provided. For example, interrupt handlers may be run as ``signal handers'' of some kind ``on top of'' the thread(s) that normally execute process-level driver code; alternatively, a separate thread may be used to run interrupt handlers. In the latter case, the OS-specific glue code must use appropriate locking to ensure that process-level driver code does not continue to execute while interrupt handlers are running.
One particularly difficult problem for user-level drivers in general, and especially for user-level drivers built using this framework, is supporting shared interrupt lines. Many platforms, including PCI-based PCs, allow multiple unrelated devices to send interrupts to the processor using a single request line; the processor must then sort out which device(s) actually caused the interrupt by checking each of the possible devices in turn. With user-level drivers, the code necessary to perform this checking is typically part of the user-mode device driver, since it must access device-specific registers. Thus, in a ``naive'' implementation, when the kernel receives a device interrupt, it must notify all of the drivers hooked to that interrupt, possibly causing many unnecessary context switches for every interrupt.
The typical solution to this problem is to allow device drivers to ``download'' small pieces of ``disambiguation'' code into the kernel itself; the kernel then chains together all of the code fragments for a particular interrupt line, and when an interrupt occurs, the resulting code sequence determines exactly which device(s) caused the interrupt, and hence, which drivers need to be notified. This solution works fine for ``native'' drivers designed specifically for the kernel in question; however, there is no obvious, straightforward way to support such a feature in the driver framework.
For this reason, until a better solution can be found, the following policy applies to using shared interrupts in this framework: for a given shared interrupt line, either the kernel must unconditionally notify all registered drivers running under this framework, and take the resulting performance hit; or else the drivers running under this framework will not support shared interrupts at all. (Native drivers written specifically for the kernel in question can still use the appropriate facilities to support shared interrupt lines efficiently.)