The Windows Driver Model (WDM) provides a framework for device drivers that operate in two operating systems—Windows 98 and Windows 2000. Although to the end user these two systems are very similar, they work very differently on the inside. In this section, I'll present a brief overview of the two systems.
Figure 1-1 is my perspective poster of the Windows 2000 operating system, wherein I emphasize the features that are important to people who write device drivers. Software executes either in user mode (untrusted and restricted to authorized activities only) or in kernel mode (fully trusted and able to do anything). 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 some sort of platform-dependent system service interface to reach a kernel-mode support routine. In the case of a call to ReadFile, the mechanism involves making a user-mode call to an entry point named NtReadFile in a system dynamic-link library (DLL) named—redundantly, I've always thought—NTDLL.DLL. The user-mode NtReadFile function uses the system service interface to reach a kernel-mode routine that's also named NtReadFile.
Figure 1-1. The Windows 2000 architecture.
We often say that NtReadFile is part of a system component that we call 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 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 a major function code of IRP_MJ_READ (a constant in a DDK [Device Driver Kit] header file). Processing details at this point can differ, but a likely scenario is for a routine like 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.
A device driver might 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 Intel x86 computer, the HAL would use the IN instruction; on an Alpha, it would 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.
Figure 1-2 shows one way of thinking about Windows 98. 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 (VxD) in Microsoft Windows 3.0 was to virtualize a specific device to help the VMM create the fiction that each virtual machine had a full complement of hardware. The same VMM architecture introduced with Windows 3.0 is in Windows 98 today but with a bunch of accretions to handle new hardware and 32-bit applications.
Figure 1-2. The Windows 98 architecture.
Windows 98 doesn't handle I/O operations in quite as orderly a way as Windows 2000. There are major differences in how Windows 98 handles operations directed to disks, to communication ports, to keyboards, and so on. Windows 98 also services 32-bit and 16-bit applications in fundamentally different ways. See Figure 1-3.
Figure 1-3. I/O requests in Windows 98.
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 like KERNEL32.DLL services. But applications can only use ReadFile 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 2000 counterpart, too. The user-mode implementation of ReadFile, for example, validates parameters—a step done in kernel mode on Windows 2000—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 make the 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 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 Microsoft 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 (VCD) driver intercepts the port I/O operations to guard against having two different virtual machines access the same port simultaneously. In a weird way of thinking about the process, you might say that 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 2000 use a common data structure (the IRP), no such uniformity exists in Windows 98 even once 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, however, the interior architecture of Windows 98 is necessarily very similar to that of Windows 2000. A system module (NTKERN.VXD) contains Windows-specific implementations of a great many Microsoft Windows NT kernel support functions. NTKERN.VXD creates IRPs and sends them to WDM drivers in just about the same way as Windows 2000. WDM drivers almost cannot tell the difference between the two environments, in fact.