Programming the Microsoft Windows Driver Model

An Overview of the Operating Systems

The Windows Driver Model provides a framework for device drivers that operate in two operating systems Windows 98/Windows Me and Windows 2000/Windows XP. As discussed in the preceding historical summary, these two pairs of operating systems are the products of two lines of parallel evolution. In fact, I ll refer to the former pair of systems with the abbreviation 98/Me to emphasize their common heritage and to the latter pair simply as XP. Although to the end user these two pairs of systems are similar, they work quite differently on the inside. In this section, I ll present a brief overview of the two systems.

Windows XP Overview

Figure 1-1 is a highly abbreviated functional diagram of the Windows XP operating system, wherein I emphasize the features that are important to people who write device drivers. Every platform where Windows XP runs supports two modes of execution. Software executes either in user mode or in kernel mode. A user-mode program that wants to, say, read some data from a device would call an application programming interface (API) such as ReadFile. A subsystem module such as KERNEL32.DLL implements this API by invoking a native API function such as NtReadFile. Refer to the sidebar for more information about the native API.

Figure 1-1. Windows XP architecture.

We often say that NtReadFile is part of a system component called the I/O Manager. The term I/O Manager is perhaps a little misleading because there isn t any single executable module with that name. We need a name to use when discussing the cloud of operating system services that surrounds our own driver, though, and this name is the one we usually pick.

Many routines serve a purpose similar to NtReadFile. They operate in kernel mode in order to service an application s request to interact with a device in some way. They all validate their parameters, thereby ensuring that they don t inadvertently allow a security breach by performing an operation, or accessing some data, that the user-mode program wouldn t have been able to perform or access by itself. They then create a data structure called an I/O request packet (IRP) that they pass to an entry point in some device driver. In the case of an original ReadFile call, NtReadFile would create an IRP with the major function code IRP_MJ_READ (a constant in a DDK [Driver Development Kit] header file). Processing details at this point can differ, but a likely scenario is for a routine such as NtReadFile to return to the user-mode caller with an indication that the operation described by the IRP hasn t finished yet. The user-mode program might continue about its business and then wait for the operation to finish, or it might wait immediately. Either way, the device driver proceeds independently of the application to service the request.

The Native API

NtReadFile is part of the so-called native API of Windows XP. The reason there is a native API is historical. The original Windows NT operating system contained a number of subsystems to implement the semantics of several new and existing operating systems. There was an OS/2 subsystem, a POSIX subsystem, and a Win32 subsystem. The subsystems were implemented by making user-mode calls to the native API, which was itself implemented in kernel mode.

A user-mode DLL named (rather redundantly, I ve always thought) NTDLL.DLL implements the native API for Win32 callers. Each entry in this DLL is a thin wrapper around a call to a kernel-mode function that actually carries out the function. The call uses a platform-dependent system service interface to transfer control across the user-mode/kernel-mode boundary. On newer Intel processors, this system service interface uses the SYSENTER instruction. On older Intel processors, the interface uses the INT instruction with the function code 0x2E. On other processors, still other mechanisms are employed. You and I don t need to understand the details of the mechanism to write drivers, though. All we need to understand is that the mechanism allows a program running in user mode to call a subroutine that executes in kernel mode and that will eventually return to its user-mode caller. No thread context switching occurs during the process: all that changes is the privilege level of the executing code (along with a few other details that only assembly language programmers would ever notice or care about).

The Win32 subsystem is the one most application programmers are familiar with because it implements the functions one commonly associates with the Windows graphical user interface. The other subsystems have fallen by the wayside over time. The native API remains, however, and the Win32 subsystem still relies on it in the way I m describing by example in the text.

A device driver may eventually need to actually access its hardware to perform an IRP. In the case of an IRP_MJ_READ to a programmed I/O (PIO) sort of device, the access might take the form of a read operation directed to an I/O port or a memory register implemented by the device. Drivers, even though they execute in kernel mode and can therefore talk directly to their hardware, use facilities provided by the hardware abstraction layer (HAL) to access hardware. A read operation might involve calling READ_PORT_UCHAR to read a single data byte from an I/O port. The HAL routine uses a platform-dependent method to actually perform the operation. On an x86 computer, the HAL would use the IN instruction; on some other future Windows XP platform, it might perform a memory fetch.

After a driver has finished with an I/O operation, it completes the IRP by calling a particular kernel-mode service routine. Completion is the last act in processing an IRP, and it allows the waiting application to resume execution.

Windows 98/Windows Me Overview

Figure 1-2 shows one way of thinking about Windows 98/Windows Me. The operating system kernel is called the Virtual Machine Manager (VMM) because its main job is to create one or more virtual machines that share the hardware of a single physical machine. The original purpose of a virtual device driver in Windows 3.0 was to virtualize a specific device in order to help the VMM create the fiction that each virtual machine has a full complement of hardware. The same VMM architecture introduced with Windows 3.0 is in place today in Windows 98/Me, but with a bunch of accretions to handle new hardware and 32-bit applications.

Figure 1-2. Windows 98/Me architecture.

Windows 98/Me doesn t handle I/O operations in quite as orderly a way as Windows XP. There are major differences in the way Windows 98/Me handles operations directed to disks, to communication ports, to keyboards, and so on. There are also fundamental differences between how Windows services 32-bit and 16-bit applications. See Figure 1-3.

Figure 1-3. I/O requests in Windows 98/Me.

The left column of Figure 1-3 shows how 32-bit applications get I/O done for them. An application calls a Win32 API such as ReadFile, which a system DLL such as KERNEL32.DLL services. But applications can use ReadFile only for reading disk files, communication ports, and devices that have WDM drivers. For any other kind of device, an application must use some ad hoc mechanism based on DeviceIoControl. The system DLL contains different code than its Windows XP counterpart too. The user-mode implementation of ReadFile, for example, validates parameters a step done in kernel mode on Windows XP and uses one or another special mechanism to reach a kernel-mode driver. There s one special mechanism for disk files, another for serial ports, another for WDM devices, and so on. The mechanisms all use software interrupt 30h to transition from user mode to kernel mode, but they re otherwise completely different.

The middle column of Figure 1-3 shows how 16-bit Windows-based applications (Win16 applications) perform I/O. The right column illustrates the control flow for MS-DOS-based applications. In both cases, the user-mode program calls directly or indirectly on the services of a user-mode driver that, in principle, could stand alone by itself on a bare machine. Win16 programs perform serial port I/O by indirectly calling a 16-bit DLL named COMM.DRV, for example. (Up until Windows 95, COMM.DRV was a stand-alone driver that hooked IRQ 3 and 4 and issued IN and OUT instructions to talk directly to the serial chip.) A virtual communications device driver (VCD) intercepts the port I/O operations in order to guard against having two different virtual machines access the same port simultaneously. In a weird way of thinking about the process, these user-mode drivers use an API interface based on interception of I/O operations. Virtualizing drivers like VCD service these pseudo-API calls by simulating the operation of hardware.

Whereas all kernel-mode I/O operations in Windows XP use a common data structure (the IRP), no such uniformity exists in Windows 98/Me, even after an application's request reaches kernel mode. Drivers of serial ports conform to a port driver function-calling paradigm orchestrated by VCOMM.VXD. Disk drivers, on the other hand, participate in a packet-driven layered architecture implemented by IOS.VXD. Other device classes use still other means.

When it comes to WDM drivers, though, the interior architecture of Windows 98/Me is necessarily very similar to that of Windows XP. A system module (NTKERN.VXD) contains Windows-specific implementations of a great many Windows NT kernel support functions. NTKERN creates IRPs and sends them to WDM drivers in just about the same way as Windows XP. WDM drivers can almost not tell the difference between the two environments, in fact.

Despite the similarities in the WDM environments of Windows XP and Windows 98/Me, however, there are some significant differences. You'll find compatibility notes throughout this book that highlight the differences that matter to most driver programmers. Appendix A outlines a scheme whereby you can use the same binary driver on Windows 2000/XP and Windows 98/Me despite these differences.

Категории