Now that we've looked at the high-level architecture of Windows 2000, let's delve deeper into the internal structure and the role each of the key operating system components plays. Figure 2-3 is a more detailed and complete diagram of the Windows 2000 system architecture and components than was shown earlier in the chapter (in Figure 2-2).
The following sections elaborate on each major element of this diagram. Chapter 3 explains the primary control mechanisms the system uses (such as the object manager, interrupts, and so forth). Chapter 4 describes the process of starting and shutting down Windows 2000, and Chapter 5 details management mechanisms such as the registry, service processes, and Windows Management Instrumentation (WMI). Then the remaining chapters explore in even more detail the internal structure and operation of key areas such as processes and threads, memory management, security, the I/O manager, storage management, the cache manager, the Windows 2000 file system (NTFS), and networking.
Figure 2-3 Windows 2000 architecture
As shown in Figure 2-3, Windows 2000 has three environment subsystems: OS/2, POSIX, and Win32. As we'll explain shortly, of the three, the Win32 subsystem is special in that Windows 2000 can't run without it. (It owns the keyboard, mouse, and display, and it is required to be present even on server systems with no interactive users logged in.) In fact, the other two subsystems are configured to start on demand, whereas the Win32 subsystem must always be running.
The subsystem startup information is stored under the registry key HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems. Figure 2-4 shows the values under this key.
Figure 2-4 Registry Editor showing Windows 2000 startup information
The Required value lists the subsystems that load when the system boots. The value has two strings: Windows and Debug. The Windows value contains the file specification of the Win32 subsystem, Csrss.exe, which stands for Client/Server Run-Time Subsystem.* Debug is blank (it's used for internal testing) and therefore does nothing. The Optional value indicates that the OS/2 and POSIX subsystems will be started on demand. The registry value Kmode contains the filename of the kernel-mode portion of the Win32 subsystem, Win32k.sys (explained later in this chapter).
The role of an environment subsystem is to expose some subset of the base Windows 2000 executive system services to application programs. Each subsystem can provide access to different subsets of the native services in Windows 2000. That means that some things can be done from an application built on one subsystem that can't be done by an application built on another subsystem. For example, a Win32 application can't use the POSIX fork function.
Each executable image (.exe) is bound to one and only one subsystem. When an image is run, the process creation code examines the subsystem type code in the image header so that it can notify the proper subsystem of the new process. This type code is specified with the /SUBSYSTEM qualifier of the link command in Microsoft Visual C++ and can be viewed with the Exetype tool in the Windows 2000 resource kits.
Function calls can't be mixed between subsystems. In other words, a POSIX application can call only services exported by the POSIX subsystem, and a Win32 application can call only services exported by the Win32 subsystem. As you'll see later, this restriction is the reason that the POSIX subsystem, which implements a very limited set of functions (only POSIX 1003.1), isn't a useful environment for porting UNIX applications.
As mentioned earlier, user applications don't call Windows 2000 system services directly. Instead, they go through one or more subsystem DLLs. These libraries export the documented interface that the programs linked to that subsystem can call. For example, the Win32 subsystem DLLs (such as Kernel32.dll, Advapi32.dll, User32.dll, and Gdi32.dll) implement the Win32 API functions. The POSIX subsystem DLL implements the POSIX 1003.1 API.
Viewing the Image Subsystem Type
You can see the image subsystem type by using either the Exetype tool in the Windows 2000 resource kits or the Dependency Walker tool (Depends.exe) in the Windows 2000 Support Tools and Platform SDK. For example, notice the image types for two different Win32 images, Notepad.exe (the simple text editor) and Cmd.exe (the Windows 2000 command prompt):
C:\>exetype \winnt\system32\notepad.exe File "\winnt\system32\notepad.exe" is of the following type: Windows NT 32 bit machine Built for the Intel 80386 processor Runs under the Windows GUI subsystem C:\>exetype \winnt\system32\cmd.exe File "\winnt\system32\cmd.exe" is of the following type: Windows NT 32 bit machine Built for the Intel 80386 processor Runs under the Windows character-based subsystem
In reality, there is just one Windows subsystem, not separate ones for graphical images and for character-based, or console, images. Also, Windows 2000 isn't supported on the Intel 386 processor (or the 486 for that matter)—the text output by the Exetype program hasn't been updated.
When an application calls a function in a subsystem DLL, one of three things can occur:
Some functions can be a combination of the second and third items above, such as the Win32 CreateProcess and CreateThread functions.
Although Windows 2000 was designed to support multiple, independent environment subsystems, from a practical perspective, having each subsystem implement all the code to handle windowing and display I/O would result in a large amount of duplication of system functions that, ultimately, would have negatively affected both system size and performance. Because Win32 was the primary subsystem, the Windows 2000 designers decided to locate these basic functions there and have the other subsystems call on the Win32 subsystem to perform display I/O. Thus, the POSIX and OS/2 subsystems call services in the Win32 subsystem to perform display I/O. (In fact, if you examine the subsystem type for these images, you'll see that they are Win32 executables.)
Let's take a closer look at each of the environment subsystems.
The Win32 subsystem consists of the following major components:
The kernel-mode device driver (Win32k.sys) contains:
Applications call the standard USER functions to create user interface controls, such as windows and buttons, on the display. The window manager communicates these requests to the GDI, which passes them to the graphics device drivers, where they are formatted for the display device. A display driver is paired with a video miniport driver to complete video display support.
The GDI provides a set of standard two-dimensional functions that let applications communicate with graphics devices without knowing anything about the devices. GDI functions mediate between applications and graphics devices such as display drivers and printer drivers. The GDI interprets application requests for graphic output and sends the requests to graphics display drivers. It also provides a standard interface for applications to use varying graphics output devices. This interface enables application code to be independent of the hardware devices and their drivers. The GDI tailors its messages to the capabilities of the device, often dividing the request into manageable parts. For example, some devices can understand directions to draw an ellipse; others require the GDI to interpret the command as a series of pixels placed at certain coordinates. For more information about the graphics and video driver architecture, see the Design Guide section of the book Graphics Drivers in the Windows 2000 DDK.
Prior to Windows NT 4, the window manager and graphics services were part of the user-mode Win32 subsystem process. In Windows NT 4, the bulk of the windowing and graphics code was moved from running in the context of the Win32 subsystem process to a set of callable services running in kernel mode (in the file Win32k.sys). The primary reason for this shift was to improve overall system performance. Having a separate server process that contains the Win32 graphics subsystem required multiple thread and process context switches, which consumed considerable CPU cycles and memory resources even though the original design was highly optimized.
For example, for each thread on the client side there was a dedicated, paired server thread in the Win32 subsystem process waiting on the client thread for requests. A special interprocess communication facility called fast LPC was used to send messages between these threads. Unlike normal thread context switches, transitions between paired threads via fast LPC don't cause a rescheduling event in the kernel, thereby enabling the server thread to run for the remaining time slice of the client thread before having to take its turn in the kernel's preemptive thread scheduler. Moreover, shared memory buffers were used to allow fast passing of large data structures, such as bitmaps, and clients had direct but read-only access to key server data structures to minimize the need for thread/process transitions between clients and the Win32 server. Also, GDI operations were (and still are) batched. Batching means that a series of graphics calls by a Win32 application aren't "pushed" over to the server and drawn on the output device until a GDI batching queue is filled. You can set the size of the queue by using the Win32 GdiSetBatchLimit function, and you can flush the queue at any time with GdiFlush. Conversely, read-only properties and data structures of GDI, once they were obtained from the Win32 subsystem process, were cached on the client side for fast subsequent access.
Despite these optimizations, however, the overall system performance was still not adequate for graphics-intensive applications. The obvious solution was to eliminate the need for the additional threads and resulting context switches by moving the windowing and graphics system into kernel mode. Also, once applications have called into the window manager and the GDI, those subsystems can access other Windows 2000 executive components directly without the cost of user-mode or kernel-mode transitions. This direct access is especially important in the case of the GDI calling through video drivers, a process that involves interaction with video hardware at high frequencies and high bandwidths.
Is Windows 2000 Less Stable with Win32 USER and GDI in Kernel Mode?
Some people wondered whether moving this much code into kernel mode would substantially affect system stability. The reason the impact on system stability has been minimal is that prior to Windows NT 4 (and this is still true today), a bug (such as an access violation) in the user-mode Win32 subsystem process (Csrss.exe) resulted in a system crash. This crash occurs because the parent process of Csrss (the session manager, Smss, which is described in the section "Session Manager (Smss)") does a wait operation on the process handle to Csrss, and if the wait ever returns, Smss crashes the system—because the Win32 subsystem process was (and still is) a vital process to the running of the system. Because it was the process that contained the data structures that described the windows on the display, the death of that process would kill the user interface. However, even a Windows 2000 system operating as a server, with no interactive processes, can't run without this process, since server processes might be using window messaging to drive the internal state of the application. With Windows 2000, an access violation in the same code now running in kernel mode simply crashes the system more quickly, since exceptions in kernel mode result in a system crash.
There is, however, one additional theoretical danger that didn't exist prior to moving the windowing and graphics system into kernel mode. Because this body of code is now running in kernel mode, a bug (such as the use of a bad pointer) could result in corrupting kernel-mode protected data structures. Prior to Windows NT 4, such references would have caused an access violation because kernel-mode pages aren't writable from user mode. But a system crash would have then resulted, as described earlier. With the code now running in kernel mode, a bad pointer reference that caused a write operation to some kernel-mode page might not immediately cause a system crash, but if it corrupted some data structure, a crash would likely result soon after. There is a small chance, however, that such a reference could corrupt a memory buffer (rather than a data structure), possibly resulting in returning corrupt data to a user program or writing bad data to the disk.
Another area of possible impact can come from the move of the graphics drivers into kernel mode. Previously, some portions of a graphics driver ran within Csrss, and others ran in kernel mode. Now, the entire driver runs in kernel mode. Although Microsoft doesn't develop all of the graphics device drivers supported in Windows 2000, it does work directly with hardware manufacturers to help ensure that they are able to produce reliable and efficient drivers. All drivers shipped with the system are submitted to the same rigorous testing as other executive components.
Finally, it's important to understand that this design (running the windowing and graphics subsystem in kernel mode) is not fundamentally risky. It is identical to the approaches many other device drivers use (for example, network card drivers and hard disk drivers). All these drivers have been operating in kernel mode since the inception of Windows NT with a high degree of reliability.
Some people speculated that the move of the window manager and the GDI into kernel mode would hurt the preemptive multitasking capability of Windows 2000. The theory was that with all the additional Win32 processing time spent in kernel mode, other threads would have less opportunity to be run preemptively. This view was based on a misunderstanding of the Windows 2000 architecture. It is true that in many other nominally preemptive operating systems, executing in kernel mode is never preempted by the operating system scheduler—or is preempted only at a certain limited number of predefined points of kernel reentrancy. In Windows 2000, however, threads running anywhere in the executive are preempted and scheduled alongside threads running in user mode, and all code within the executive is fully reentrant. Among other reasons, this capability is necessary to achieve a high degree of system scalability on SMP hardware.
Another line of speculation was that SMP scaling would be hurt by this change. The theory went like this: Previously, an interaction between an application and the window manager or the GDI involved two threads, one in the application and one in Csrss.exe. Therefore, on an SMP system, the two threads could run in parallel, thus improving throughput. This analysis shows a misunderstanding of how Windows NT technology worked prior to Windows NT 4. In most cases, calls from a client application to the Win32 subsystem process run synchronously; that is, the client thread entirely blocks waiting on the server thread and begins to run again only when the server thread has completed the call. Therefore, no parallelism on SMP hardware can ever be achieved. This phenomenon is easily observable with a busy graphics application using the Performance tool on an SMP system. The observer will discover that on a two-processor system each processor is approximately 50 percent loaded, and it's relatively easy to find the single Csrss thread that is paired off with the busy application thread. Indeed, because the two threads are fairly intimate with each other and sharing state, the processors' caches must be flushed constantly to maintain coherency. This constant flushing is the reason that with Windows NT 3.51 a single-threaded graphics application typically runs slightly slower on an SMP machine than on a single processor system.
As a result, the changes in Windows NT 4 increased SMP throughput of applications that make heavy use of the window manager and the GDI, especially when more than one application thread is busy. When two application threads are busy on a two-processor Windows NT 3.51-based machine, a total of four threads (two in the application plus two in Csrss) are battling for time on the two processors. Although only two are typically ready to run at any given time, the lack of a consistent pattern in which threads run results in a loss of locality of reference and cache coherency. This loss occurs because the busy threads are likely to get shuffled from one processor to another. In the Windows NT 4 design, each of the two application threads essentially has its own processor, and the automatic thread affinity of Windows 2000 tends to run the same thread on the same processor indefinitely, thus maximizing locality of reference and minimizing the need to synchronize the private per-processor memory caches.
So in summary, moving the window manager and the GDI from user mode to kernel mode has provided improved performance without any significant decrease in system stability or reliability.
So, what remains in the user-mode process part of the Win32 subsystem? All the drawing and updating for console or text windows are handled by it, since console applications have no notion of repainting a window. It's easy to see this activity—simply open a command prompt and drag another window over it, and you'll see the Win32 subsystem process running like crazy as it repaints the console window. But other than console window support, only a few Win32 functions result in sending a message to the Win32 subsystem process anymore: process and thread creation and termination, network drive letter mapping, and creation of temporary files. In general, a running Win32 application won't be causing many, if any, context switches to the Win32 subsystem process.
POSIX, an acronym loosely defined as "a portable operating system interface based on UNIX," refers to a collection of international standards for UNIX-style operating system interfaces. The POSIX standards encourage vendors implementing UNIX-style interfaces to make them compatible so that programmers can move their applications easily from one system to another.
Windows 2000 implements only one of the many POSIX standards, POSIX.1, formally known as ISO/IEC 9945-1:1990 or IEEE POSIX standard 1003.1-1990. This standard was included primarily to meet U.S. government procurement requirements set in the mid-to-late 1980s that mandated POSIX.1 compliance as specified in Federal Information Processing Standard (FIPS) 151-2, developed by the National Institute of Standards and Technology. Windows NT 3.5, 3.51, and 4 have been formally tested and certified according to FIPS 151-2.
Because POSIX.1 compliance was a mandatory goal for Windows 2000, the operating system was designed to ensure that the required base system support was present to allow for the implementation of a POSIX.1 subsystem (such as the fork function, which is implemented in the Windows 2000 executive, and the support for hard file links in the Windows 2000 file system). However, because POSIX.1 defines a limited set of services (such as process control, interprocess communication, simple character cell I/O, and so on), the POSIX subsystem that comes with Windows 2000 isn't a complete programming environment. And because applications can't mix calls between subsystems on Windows 2000, by default, POSIX applications are limited to the strict set of services defined in POSIX.1. This restriction means that a POSIX executable on Windows 2000 can't create a thread or a window or use remote procedure calls (RPCs) or sockets.
To address this limitation, Microsoft provides a product called Interix, which includes an enhanced POSIX subsystem environment that provides nearly 2000 UNIX functions and 300 UNIX-like tools and utilities. (See www.microsoft.com/WINDOWS2000/guide/server/solutions/interix.asp for more information on Microsoft Interix.) With this enhancement, it is more viable to port UNIX applications to the POSIX subsystem. However, because the programs are still linked as POSIX executables, they cannot call Win32 functions.
To port UNIX applications to Windows 2000 and allow the use of Win32 functions, you can purchase a UNIX-to-Win32 porting library, such as the one included with the MKS NuTCRACKER Professional product available from Mortice Kern Systems Inc. (www.mks.com). With this approach, a UNIX application can be recompiled and relinked as a Win32 executable and can slowly start to integrate calls to native Win32 functions.
Watching the POSIX Subsystem Start
The POSIX subsystem is configured by default to start the first time a POSIX executable is run, so you can watch it start by running a POSIX program, such as one of the POSIX utilities that comes with the Windows 2000 resource kits. (You can find these POSIX utilities in the \Apps\POSIX folder on the resource kit CD—they are not installed as part of the resource kit tools installation.) Follow these steps to watch the POSIX subsystem start:
- Start a command prompt.
- Type tlist /t, and check that the POSIX subsystem isn't already running (that is, that there's no Psxss.exe process underneath Smss.exe).
- Run one of the POSIX utilities available on the Windows 2000 resource kit CDs, such as \Apps\POSIX\Ls.exe.
- You'll notice a slight pause while the POSIX subsystem starts and the Ls command displays the directory contents.
- Run tlist /t again. This time, notice the existence of Psxss.exe as a child of Smss.exe.
- Rerun Ls.exe a second time; you'll notice a quicker response (now that the POSIX subsystem is already started).
- Rerun Ls.exe, but pause the output by pressing Ctrl+S; issue a tlist /t from another command prompt, and notice that the POSIX support image (Posix.exe) was the process created from the first command prompt and that it in turn created the Ls.exe process. You should see something similar to the following annotated output:
To compile and link a POSIX application in Windows 2000 requires the POSIX headers and libraries from the Platform SDK. POSIX executables are linked against the POSIX subsystem library, Psxdll.dll. Because by default Windows 2000 is configured to start the POSIX subsystem on demand, the first time you run a POSIX application, the POSIX subsystem process (Psxss.exe) must be started. It remains running until the system reboots. (If you kill the POSIX subsystem process, you won't be able to run more POSIX applications until you reboot.) The POSIX image itself isn't run directly—instead, a special support image called Posix.exe is launched, which in turn creates a child process to run the POSIX application.
For more information about the POSIX subsystem and about porting UNIX applications to Windows 2000, do a search for POSIX and UNIX in MSDN Library.
The OS/2 environment subsystem, like the built-in POSIX subsystem, is fairly limited in usefulness in that it supports only OS/2 1.2 16-bit character-based or video I/O (VIO) applications. Although Microsoft did sell a replacement OS/2 1.2 Presentation Manager subsystem for Windows NT 4, it didn't support OS/2 2.x (or later) applications (and it isn't available for Windows 2000).
Also, because Windows 2000 doesn't allow direct hardware access by user applications, OS/2 programs that contain I/O privilege segments that attempt to perform IN/OUT instructions (to access some hardware device) as well as advanced video I/O (AVIO) aren't supported. Applications that use the CLI/STI instructions are supported—but all the other OS/2 applications in the system and all the other threads in the OS/2 process issuing the CLI instructions are suspended until an STI instruction is executed. Also worth noting is the special support for calling 32-bit DLLs from OS/2 16-bit applications on Windows 2000, which can be useful in porting programs.
The 16-MB memory limitation on native OS/2 1.2 doesn't apply to Windows 2000—the OS/2 subsystem uses the 32-bit virtual address space of Windows 2000 to provide up to 512 MB of memory to OS/2 1.2 applications, as illustrated in Figure 2-5.
Figure 2-5 OS/2 subsystem virtual memory layout
The tiled area is 512 MB of virtual address space that is reserved up front and then committed or decommitted when 16-bit applications need segments. The OS/2 subsystem maintains a local descriptor table (LDT) for each process, with shared memory segments at the same LDT slot for all OS/2 processes.
As we'll discuss in detail in Chapter 6, threads are the elements of a program that execute, and as such they must be scheduled for processor time. Although Windows 2000 priority levels range from 0 through 31, the 64 OS/2 priority levels (0 through 63) are mapped to Windows 2000 dynamic priorities 1 through 15. OS/2 threads never receive Windows 2000 real-time priorities 16 through 31.
As with the POSIX subsystem, the OS/2 subsystem starts automatically the first time you activate a compatible OS/2 image. It remains running until the system is rebooted.
For more information on how Windows 2000 handles running POSIX and OS/2 applications, see the section "Flow of CreateProcess" in Chapter 6 of this book.
Ntdll.dll is a special system support library primarily for the use of subsystem DLLs. It contains two types of functions:
The first group of functions provides the interface to the Windows 2000 executive system services that can be called from user mode. There are more than 200 such functions, such as NtCreateFile, NtSetEvent, and so on. As noted earlier, most of the capabilities of these functions are accessible through the Win32 API. (A number are not, however, and are for Microsoft internal use only.)
For each of these functions, Ntdll contains an entry point with the same name. The code inside the function contains the architecture-specific instruction that causes a transition into kernel mode to invoke the system service dispatcher (explained in more detail in Chapter 3), which after verifying some parameters, calls the actual kernel-mode system service that contains the real code inside Ntoskrnl.exe.
Ntdll also contains many support functions, such as the image loader (functions that start with Ldr), the heap manager, and Win32 subsystem process communication functions (functions that start with Csr), as well as general run-time library routines (functions that start with Rtl). It also contains the user-mode asynchronous procedure call (APC) dispatcher and exception dispatcher. (APCs and exceptions are explained in Chapter 3.)
The Windows 2000 executive is the upper layer of Ntoskrnl.exe. (The kernel is the lower layer.) The executive includes the following types of functions:
The executive contains the following major components, each of which is covered in detail in a subsequent chapter of this book:
In addition, the executive contains four main groups of support functions that are used by the executive components just listed. About a third of these support functions are documented in the DDK because device drivers also use them. These are the four categories of support functions:
The kernel consists of a set of functions in Ntoskrnl.exe that provide fundamental mechanisms (such as thread scheduling and synchronization services) used by the executive components, as well as low-level hardware architecture-dependent support (such as interrupt and exception dispatching), that are different on each processor architecture. The kernel code is written primarily in C, with assembly code reserved for those tasks that require access to specialized processor instructions and registers not easily accessible from C.
Like the various executive support functions mentioned in the preceding section, a number of functions in the kernel are documented in the DDK (search for functions beginning with Ke) because they are needed to implement device drivers.
The kernel provides a low-level base of well-defined, predictable operating system primitives and mechanisms that allow higher-level components of the executive to do what they need to do. The kernel separates itself from the rest of the executive by implementing operating system mechanisms and avoiding policy making. It leaves nearly all policy decisions to the executive, with the exception of thread scheduling and dispatching, which the kernel implements.
Outside the kernel, the executive represents threads and other shareable resources as objects. These objects require some policy overhead, such as object handles to manipulate them, security checks to protect them, and resource quotas to be deducted when they are created. This overhead is eliminated in the kernel, which implements a set of simpler objects, called kernel objects, that help the kernel control central processing and support the creation of executive objects. Most executive-level objects encapsulate one or more kernel objects, incorporating their kernel-defined attributes.
One set of kernel objects, called control objects, establishes semantics for controlling various operating system functions. This set includes the APC object, the deferred procedure call (DPC)object, and several objects the I/O manager uses, such as the interrupt object.
Another set of kernel objects, known as dispatcher objects, incorporates synchronization capabilities that alter or affect thread scheduling. The dispatcher objects include the kernel thread, mutex (called mutant internally), event, kernel event pair, semaphore, timer, and waitable timer. The executive uses kernel functions to create instances of kernel objects, to manipulate them, and to construct the more complex objects it provides to user mode. Objects are explained in more detail in Chapter 3, and processes and threads are described in Chapter 6.
The other major job of the kernel is to abstract or isolate the executive and device drivers from variations between the hardware architectures supported by Windows 2000. This job includes handling variations in functions such as interrupt handling, exception dispatching, and multiprocessor synchronization.
Even for these hardware-related functions, the design of the kernel attempts to maximize the amount of common code. The kernel supports a set of interfaces that are portable and semantically identical across architectures. Most of the code that implements this portable interface is also identical across architectures.
Some of these interfaces are implemented differently on different architectures, however, or some of the interfaces are partially implemented with architecture-specific code. These architecturally independent interfaces can be called on any machine, and the semantics of the interface will be the same whether or not the code varies by architecture. Some kernel interfaces (such as spinlock routines, which are described in Chapter 3) are actually implemented in the HAL (described in the next section) because their implementation can vary for systems within the same architecture family.
The kernel also contains a small amount of code with x86-specific interfaces needed to support old MS-DOS programs. These x86 interfaces aren't portable in the sense that they can't be called on a machine based on any other architecture; they won't be present. This x86-specific code, for example, supports calls to manipulate global descriptor tables (GDTs) and LDTs, hardware features of the x86.
Other examples of architecture-specific code in the kernel include the interface to provide translation buffer and CPU cache support. This support requires different code for the different architectures because of the way caches are implemented.
Another example is context switching. Although at a high level the same algorithm is used for thread selection and context switching (the context of the previous thread is saved, the context of the new thread is loaded, and the new thread is started), there are architectural differences among the implementations on different processors. Because the context is described by the processor state (registers and so on), what is saved and loaded varies depending on the architecture.
As mentioned at the beginning of this chapter, one of the crucial elements of the Windows 2000 design is its portability across a variety of hardware platforms. The HAL is a key part of making this portability possible. The HAL is a loadable kernel-mode module (Hal.dll) that provides the low-level interface to the hardware platform on which Windows 2000 is running. It hides hardware-dependent details such as I/O interfaces, interrupt controllers, and multiprocessor communication mechanisms—any functions that are both architecture-specific and machine-dependent.
So rather than access hardware directly, Windows 2000 internal components as well as user-written device drivers maintain portability by calling the HAL routines when they need platform-dependent information. For this reason, the HAL routines are documented in the Windows 2000 DDK. To find out more about the HAL and its use by device drivers, refer to the DDK.
Although several HALs are included on the Windows 2000 CD (see Table 25), only one is chosen at installation time and copied to the system disk with the filename Hal.dll. (Other operating systems, such as VMS, select the equivalent of the HAL at system boot time.) Therefore, you can't assume that a system disk from one x86 installation will boot on a different processor if the HAL that supports the other processor is different.
Table 2-5 List of HALs
|HAL File Name||Systems Supported|
|Halacpi.dll||Advanced Configuration and Power Interface (ACPI) PCs|
|Halapic.dll||Advanced Programmable Interrupt Controller (APIC) PCs|
|Halaacpi.dll||APIC ACPI PCs|
|Halmacpi.dll||Multiprocessor ACPI PCs|
|Halborg.dll||Silicon Graphics Workstation (no longer marketed)|
Determining Which HAL You're Running
There are two ways to determine which HAL you're running:
- Open the file \Winnt\Repair\Setup.log, search for Hal.dll, and look at the filename after the equals sign. This is the name of the HAL on the distribution media extracted from Driver.cab.
- In Device Manager (right-click on the My Computer icon on your desktop, select Properties, click on the Hardware tab, and then click Device Manager), look at the name of the "driver" under the Computer device type. For example, the following screen shot is from a system running the ACPI HAL:
Although device drivers are explained in detail in Chapter 9, this section provides a brief overview of the types of drivers and explains how to list the drivers installed and loaded on your system.
Device drivers are loadable kernel-mode modules (typically ending in .sys) that interface between the I/O manager and the relevant hardware. They run in kernel mode in one of three contexts:
As stated in the preceding section, device drivers in Windows 2000 don't manipulate hardware directly, but rather they call functions in the HAL to interface with the hardware. Drivers are typically written in C (sometimes C++) and therefore, with proper use of HAL routines, can be source code portable across the CPU architectures supported by Windows 2000 and binary portable within an architecture family.
There are several types of device drivers:
Because installing a device driver is the only way to add user-written kernel-mode code to the system, some programmers have written device drivers simply as a way to access internal operating system functions or data structures that are not accessible from user mode (but that are documented and supported in the DDK). For example, many of the utilities from www.sysinternals.com combine a Win32 GUI application and a device driver that is used to gather internal system state not accessible from the Win32 API.
Windows 2000 adds support for Plug and Play, Power Options, and an extension to the Windows NT driver model called the Windows Driver Model (WDM). Windows 2000 can run legacy Windows NT 4 drivers, but because these don't support Plug and Play and Power Options, systems running these drivers will have reduced capabilities in these two areas.
From the WDM perspective, there are three kinds of drivers:
In the WDM driver environment, no single driver controls all aspects of a device: a bus driver is concerned with reporting the devices on its bus to the PnP manager, while a function driver manipulates the device.
In most cases, lower-level filter drivers modify the behavior of device hardware. For example, if a device reports to its bus driver that it requires four I/O ports when it actually requires 16 I/O ports, a lower-level device-specific function filter driver could intercept the list of hardware resources reported by the bus driver to the PnP manager, and update the count of I/O ports.
Upper-level filter drivers usually provide added-value features for a device. For example, an upper-level device filter driver for a keyboard can enforce additional security checks.
Interrupt processing is explained in Chapter 3. Further details about the I/O manager, WDM, Plug and Play, and Power Options are included in Chapter 9.
Viewing the Installed Device Drivers
You can list the installed drivers by running Computer Management. (From the Start menu, select Programs, Administrative Tools, and then Computer Management; or from Control Panel, open Administrative Tools and select Computer Management.) From within Computer Management, expand System Information and then Software Environment, and open Drivers. Here's an example output of the list of installed drivers:
This window displays the list of device drivers defined in the registry, their type, and their state (Running or Stopped). Device drivers and Win32 service processes are both defined in the same place: HKLM\SYSTEM\CurrentControlSet\Services. However, they are distinguished by a type code—type 1 is a kernel-mode device driver, and type 2 is a file system driver. For further details on the information stored in the registry for device drivers, see the Technical Reference to the Windows 2000 Registry help file (Regentry.chm) in the Windows 2000 resource kits under the topic HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.
Alternatively, you list the currently loaded device drivers with the Drivers utility (Drivers.exe in the Windows 2000 resource kits) or the Pstat utility (Pstat.exe in the Platform SDK). Here is a partial output from the Drivers utility:
C:\>drivers ModuleName Code Data Bss Paged Init LinkDate ---------------------------------------------------------------- ntoskrnl.exe 429184 96896 0 775360 138880 Tue Dec 07 18:41:11 1999 hal.dll 25856 6016 0 16160 10240 Tue Nov 02 20:14:22 1999 BOOTVID.DLL 5664 2464 0 0 320 Wed Nov 03 20:24:33 1999 ACPI.sys 92096 8960 0 43488 4448 Wed Nov 10 20:06:04 1999 WMILIB.SYS 512 0 0 1152 192 Sat Sep 25 14:36:47 1999 pci.sys 12704 1536 0 31264 4608 Wed Oct 27 19:11:08 1999 isapnp.sys 14368 832 0 22944 2048 Sat Oct 02 16:00:35 1999 compbatt.sys 2496 0 0 2880 1216 Fri Oct 22 18:32:49 1999 BATTC.SYS 800 0 0 2976 704 Sun Oct 10 19:45:37 1999 intelide.sys 1760 32 0 0 128 Thu Oct 28 19:20:03 1999 PCIIDEX.SYS 4544 480 0 10944 1632 Wed Oct 27 19:02:19 1999 pcmcia.sys 32800 8864 0 23680 6240 Fri Oct 29 19:20:08 1999 ftdisk.sys 4640 32 0 95072 3392 Mon Nov 22 14:36:23 1999 ---------------------------------------------------------------- Total 4363360 580320 0 3251424 432992
Each loaded kernel-mode component (Ntoskrnl, the HAL, as well as device drivers) is shown, along with the sizes of the sections in each image.
The Pstat utility also shows the loaded driver list, but only after it first displays the process list and the threads in each process. Pstat includes one important piece of information that the Drivers utility doesn't: the load address of the module in system space. As we'll explain later, this address is crucial to mapping running system threads to the device driver in which they exist.
Just examining the names of the exported or global symbols in key system images (such as Ntoskrnl.exe, Hal.dll, or Ntdll.dll) can be enlightening—you can get an idea of the kinds of things Windows 2000 can do versus what happens to be documented and supported today. Of course, just because you know the names of these functions doesn't mean that you can or should call them—the interfaces are undocumented and are subject to change. We suggest that you look at these functions purely to gain more insight into the kinds of internal functions Windows 2000 performs, not to bypass supported interfaces.
For example, looking at the list of functions in Ntdll.dll gives you the list of all the system services that Windows 2000 provides to user-mode subsystem DLLs versus the subset that each subsystem exposes. Although many of these functions map clearly to documented and supported Win32 functions, several are not exposed via the Win32 API. (See the article "Inside the Native API" from www.sysinternals.com, a copy of which is on this book's companion CD.)
Conversely, it's also interesting to examine the imports of Win32 subsystem DLLs (such as Kernel32.dll or Advapi32.dll) and which functions they call in Ntdll.
Another interesting image to dump is Ntoskrnl.exe—although many of the exported routines that kernel-mode device drivers use are documented in the Windows 2000 DDK, quite a few are not. You might also find it interesting to take a look at the import table for Ntoskrnl and the HAL; this table shows the list of functions in the HAL that Ntoskrnl uses and vice versa.
Table 2-6 lists most of the commonly used function name prefixes for the executive components. Each of these major executive components also uses a variation of the prefix to denote internal functions—either the first letter of the prefix followed by an i (for internal) or the full prefix followed by a p (for private). For example, Ki represents internal kernel functions, and Psp refers to internal process support functions.
Table 2-6 Commonly Used Prefixes
|Ex||Executive support routines|
|FsRtl||File system driver run-time library|
|Hal||Hardware abstraction layer|
|Lpc||Local Procedure Call|
|Lsa||Local security authentication|
|Nt||Windows 2000 system services (most of which are exported as Win32 functions)|
|Wmi||Windows Management Instrumentation|
|Zw||Mirror entry point for system services (beginning with Nt) that sets previous access mode to kernel, which eliminates parameter validation, since Nt system services validate parameters only if previous access mode is user|
Listing Undocumented Functions
You can dump the export and import tables of an image by using the Dependency Walker tool (Depends.exe), which is contained in the Windows 2000 Support Tools and the Platform SDK. To examine an image in the Dependency Walker, select Open from the File menu to open the desired image file.
Here is a sample of output you can see by viewing the dependencies of Ntoskrnl using this tool:
Notice that Ntoskrnl is linked against the HAL, which is in turn linked against Ntoskrnl. (They both use functions in each other.) Ntoskrnl is also linked against Bootvid.dll, the boot video driver that is used to display the new GUI startup screen in Windows 2000.
For a detailed description of the information displayed by this tool, see the Dependency Walker help file (Depends.hlp).
You can decipher the names of these exported functions more easily if you understand the naming convention for Windows 2000 system routines. The general format is:
In this format, Prefix is the internal component that exports the routine, Operation tells what is being done to the object or resource, and Object identifies what is being operated on.
For example, ExAllocatePoolWithTag is the executive support routine to allocate from paged or nonpaged pool. KeInitializeThread is the routine that allocates and sets up a kernel thread object.
The following system processes appear on every Windows 2000 system. (Two of these—Idle and System—are not full processes, as they are not running a user-mode executable.)
To help you understand the relationship of these processes, use the tlist /t command in the Windows 2000 Support Tools to display the process "tree," that is, the parent/child relationship between processes. Here is a partial annotated output from tlist /t:
System Process (0) Idle process System (8) System process (default home for system threads) smss.exe (144) Session Manager csrss.exe (172) Win32 subsystem process winlogon.exe (192) Logon process (also contains NetDDE service) services.exe (220) Service control manager svchost.exe (384) Generic service host image spoolsv.exe (480) Spooler service regsvc.exe (636) Remote registry service mstask.exe (664) Task Scheduler service lsass.exe (232) Local security authentication server
The next sections explain the key system processes shown in this output. Although these sections briefly indicate the order of process startup, Chapter 4 contains a detailed description of the steps involved in booting and starting Windows 2000.
Despite the name shown, the first process listed in the preceding sample tlist /t output (process ID 0) is actually the System Idle process. As we'll explain in Chapter 6, processes are identified by their image name. However, this process (as well as process ID 8, named System) isn't running a real user-mode image. Hence, the names shown by the various system display utilities differ from utility to utility. Although most utilities call process ID 8 "System," not all do. Table 27 lists several of the names given to the Idle process (process ID 0). The Idle process is explained in detail in Chapter 6.
Table 2-7 Names for Process ID 0 in Various Utilities
|Utility||Name for Process ID 0|
|Task Manager||System Idle Process|
|Process Viewer (Pviewer.exe)||Idle|
|Process Status (Pstat.exe)||Idle Process|
|Process Explode (Pview.exe)||System Process|
|Task List (Tlist.exe)||System Process|
Now let's look at system threads and the purpose of each of the system processes that are running real images.
The System process (always process ID 8) is the home for a special kind of thread that runs only in kernel mode: a kernel-mode system thread. System threads have all the attributes and contexts of regular user-mode threads (such as a hardware context, priority, and so on) but are different in that they run only in kernel-mode executing code loaded in system space, whether that is in Ntoskrnl.exe or in any other loaded device driver. In addition, system threads don't have a user process address space and hence must allocate any dynamic storage from operating system memory heaps, such as paged or nonpaged pool.
System threads are created by the PsCreateSystemThread function (documented in the DDK), which can be called only from kernel mode. Windows 2000 as well as various device drivers create system threads during system initialization to perform operations that require thread context, such as issuing and waiting for I/Os or other objects or polling a device. For example, the memory manager uses system threads to implement such functions as writing dirty pages to the page file or mapped files, swapping processes in and out of memory, and so forth. The kernel creates a system thread called the balance set manager that wakes up once per second to possibly initiate various scheduling and memory management-related events. The cache manager also uses system threads to implement both read-ahead and write-behind I/Os. The file server device driver (Srv.sys) uses system threads to respond to network I/O requests for file data on disk partitions shared to the network. Even the floppy driver has a system thread to poll the floppy device (polling is more efficient in this case because an interrupt-driven floppy driver consumes a large amount of system resources). Further information on specific system threads is included in the chapters in which the component is described.
By default, system threads are owned by the System process, but a device driver can create a system thread in any process. For example, the Win32 subsystem device driver (Win32k.sys) creates system threads in the Win32 subsystem process (Csrss.exe) so that they can easily access data in the user-mode address space of that process.
When you're troubleshooting or going through a system analysis, it's useful to be able to map the execution of individual system threads back to the driver or even to the subroutine that contains the code. For example, on a heavily loaded file server, the System process will likely be consuming considerable CPU time. But the knowledge that when the System process is running "some system thread" is running isn't enough to determine which device driver or operating system component is running.
So if the System process is running, look at the execution of the threads within that process (for example, with the Performance tool). Once you find the thread (or threads) that is running, look up in which driver the system thread began execution (which at least tells you which driver likely created the thread) or examine the call stack (or at least the current address) of the thread in question, which would indicate where the thread is currently executing.
Both of these techniques are illustrated in the following experiments.
Identifying System Threads in the System Process
You can see that the threads inside the System process must be kernel-mode system threads because the start address for each thread is greater than the start address of system space (which by default begins at 0x80000000, unless the system was booted with the /3GB Boot.ini switch). Also, if you look at the CPU time for these threads, you'll see that those that have accumulated any CPU time have run only in kernel mode.
To find out which driver created the system thread, look up the start address of the thread (which you can display with Pviewer.exe) and look for the driver whose base address is closest to (but before) the start address of the thread. Both the Pstat utility (at the end of its output) as well as the !drivers kernel debugger command list the base address of each loaded device driver.
To quickly find the current address of the thread, use the !stacks 0 command in the kernel debugger. Here is an example output from a live system (using LiveKd):
kd> !stacks 0 Proc.Thread Thread ThreadState Blocker [System] 8.000004 8146edb0 BLOCKED ntoskrnl!MmZeroPageThread+0x5f 8.00000c 8146e730 BLOCKED ?? Kernel stack not resident ?? 8.000010 8146e4b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.000014 8146d030 BLOCKED ?? Kernel stack not resident ?? 8.000018 8146ddb0 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.00001c 8146db30 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.000020 8146d8b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.000024 8146d630 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.000028 8146d3b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.00002c 8146c030 BLOCKED ntoskrnl!ExpWorkerThread+0x73 8.000030 8146cdb0 BLOCKED ntoskrnl!ExpWorkerThreadBalanceManager+0x55 8.000034 8146b470 BLOCKED ntoskrnl!MiDereferenceSegmentThread+0x44 8.000038 8146b1f0 BLOCKED ntoskrnl!MiModifiedPageWriterWorker+0x31 8.00003c 8146a030 BLOCKED ntoskrnl!KeBalanceSetManager+0x7e 8.000040 8146adb0 BLOCKED ntoskrnl!KeSwapProcessOrStack+0x24 8.000044 8146a5b0 BLOCKED ntoskrnl!FsRtlWorkerThread+0x33 8.000048 8146a330 BLOCKED ntoskrnl!FsRtlWorkerThread+0x33 8.00004c 81461030 BLOCKED ACPI!ACPIWorker+0x46 8.000050 8143a770 BLOCKED ntoskrnl!MiMappedPageWriter+0x4d 8.000054 81439730 BLOCKED dmio!voliod_loop+0x399 8.000058 81436c90 BLOCKED NDIS!ndisWorkerThread+0x22 8.00005c 813d9170 BLOCKED ltmdmntt!WakeupTimerThread+0x27 8.000060 813d8030 BLOCKED ltmdmntt!WriteRegistryThread+0x1c 8.000070 8139c850 BLOCKED raspptp!MainPassiveLevelThread+0x78 8.000074 8139c5d0 BLOCKED raspptp!PacketWorkingThread+0xc0 8.00006c 81384030 BLOCKED rasacd!AcdNotificationRequestThread+0xd8 8.000080 81333330 BLOCKED rdbss!RxpWorkerThreadDispatcher+0x6f 8.000084 813330b0 BLOCKED rdbss!RxSpinUpRequestsDispatcher+0x58 8.00008c 81321db0 BLOCKED ?? Kernel stack not resident ?? 8.00015c 81205570 BLOCKED INO_FLTR+0x68bd 8.000160 81204570 BLOCKED INO_FLTR+0x80e9 8.000178 811fcdb0 BLOCKED irda!RxThread+0xfa 8.0002d0 811694f0 BLOCKED ?? Kernel stack not resident ?? 8.0002d4 81168030 BLOCKED ?? Kernel stack not resident ?? 8.000404 811002b0 BLOCKED rdbss!RxpWorkerThreadDispatcher+0x6f 8.000430 810f4990 READY parallel!ParallelThread+0x3e 8.00069c 80993030 READY rdbss!RxpWorkerThreadDispatcher+0x6f
The first column is the process ID and thread ID (in the form "process ID.thread ID"). The second column is the current address of the thread. The third column indicates whether the thread is in a wait state, ready state, or running state. (See Chapter 6 for a description of thread states.) The last column is the top-most address on the thread's stack. The information in this last column makes it easy to see which driver each thread started in. For the threads in Ntoskrnl, the name of the function gives a further indication of what the thread is doing.
However, if the thread running is one of the system worker threads (ExpWorkerThread), you still don't really know what the thread is doing, since any device driver can submit work to a system worker thread. Therefore, the only way to trace back worker thread activity is to set a breakpoint at ExQueueWorkItem. When you reach the breakpoint, type !dso work_queue_item esp+4. This command will dump the first argument to ExQueueWorkItem (a work queue structure), which in turn contains the address of the worker routine to be called in the context of the worker thread. Alternatively, you can look at the caller by using the k command in the kernel debugger, which displays the current call stack. The current call stack will show the driver that is queuing the work to the worker thread (as opposed to the routine to be called from the worker thread).
Mapping a System Thread to a Device Driver
In this experiment, we'll find the Raw Mouse Input thread, a system thread in the Win32 subsystem device driver (Win32k.sys) that determines which threads should be notified of mouse movements and events. To cause this system thread to run, simply move the mouse back and forth rapidly while monitoring process CPU time (using Task Manager, the Performance tool, or the Windows 2000 resource kit QuickSlice utility), and notice that the Csrss process runs for a short period.
The following steps show how to go down to the thread granularity to find out which driver contains the system thread that is running. Although this example demonstrates system thread activity in the Csrss process, the technique applies to mapping system thread activity in the System process back to the device driver that created the thread.
First, we need to set up the Performance tool to watch the activity of each system thread on the System Monitor:
- Run the Performance tool.
- Select System Monitor, and click the Add button (the plus sign on the toolbar).
- Select the Thread object in the Performance Object drop-down list box.
- Select the % Processor Time counter (or % Privileged Time—the value will be identical).
- In the Instances box, scroll down to the thread named csrss/0. Click on this entry, and drag the mouse down until you've selected all the threads in the csrss process. (If you were monitoring threads in the System process, you would select the threads from System/0 through to the last numbered thread in the System process.)
- Click Add.
- Your screen at this point should look similar to the screen shot below.
- Click Close.
Now change the vertical scale maximum from 100 to 10. This change will make it easier to see system thread activity because system threads usually run for very brief periods of time. To change the vertical scale, follow these steps:
- Right-click on the graph.
- Select Properties.
- Click on the Graph tab.
- Change the Vertical Scale Maximum from 100 to 10.
- Click OK.
Now that we're monitoring the Csrss process, let's generate some activity artificially by moving the mouse around, causing threads in the Win32k.sys device driver to run. Then we'll confirm which driver created the system thread that is running when we move the mouse:
- Move the mouse rapidly back and forth until you see one or two of the system threads running in the Performance tool's display.
- Press Ctrl+H to turn on highlighting mode. (This highlights the currently selected counter in white.)
- Scroll through the counters to identify a thread that was running when you moved the mouse (more than one might have run).
- Notice the relative thread number in the Instance column on the bottom of the Performance tool's graph window (thread 7 in csrss in the following screen shot).
In this case, thread 7 in csrss ran. Now we need to find the start address of the thread by using Process Viewer in the Windows 2000 Support Tools.
- Run Process Viewer.
- Click on the csrss process line in the process display list box.
- Scroll through the list of threads until you find the thread with the same relative thread number you obtained in step 17.
- Select this thread.
If you found one of the system threads in csrss, the start address (displayed at the bottom of the Process Viewer window in the Thread Information section) should be 0xa0009cbf. (This address might be different if you're running a newer version of Windows 2000.)
- Finally, run Pstat.exe, from the Platform SDK.
At the end of the output from Pstat is a listing of each loaded device driver, including its start address in system virtual memory. Find the driver that has the closest start address before the start address of the thread in question. The following is an excerpt from this last section of the Pstat utility:
ModuleName Load Addr Code Data Paged LinkDate ---------------------------------------------------------------- ntoskrnl.exe 80400000 429184 96896 775360 Tue Dec 07 18:41:11 1999 hal.dll 80062000 25856 6016 16160 Tue Nov 02 20:14:22 1999 BOOTVID.DLL F7410000 5664 2464 0 Wed Nov 03 20:24:33 1999 ACPI.sys BFFD8000 92096 8960 43488 Wed Nov 10 20:06:04 1999 WMILIB.SYS F75C8000 512 0 1152 Sat Sep 25 14:36:47 1999 pci.sys F7000000 12704 1536 31264 Wed Oct 27 19:11:08 1999 isapnp.sys F7010000 14368 832 22944 Sat Oct 02 16:00:35 1999 win32k.sys A0000000 1520960 54944 0 Tue Nov 30 03:51:03 1999 rdbss.sys BEC8F000 27808 1952 86656 Tue Nov 30 03:52:29 1999 mrxsmb.sys BEBF7000 91616 21824 237568 Tue Nov 30 03:52:10 1999 ntdll.dll 77F80000 294912 12288 16384 Wed Oct 27 16:06:08 1999 ---------------------------------------------------------------- Total 4375648 547040 3164960
The start address of the thread in question, 0xa0009cbf, is clearly part of Win32k.sys, since there is no other driver with a closer start address.
If the address falls within Ntoskrnl.exe, then the thread is executing a subroutine in the main kernel image. This information alone is not enough to determine what the thread is really doing—you need to find out the name of the function at that address. You can do that by looking it up in the list of global symbols contained in the associated symbol table file Ntoskrnl.dbg.
The easiest way to generate the list of global symbols in Ntoskrnl is to start the kernel debugger (either by connecting to a live system or by opening a crash dump file) and type the x nt!* command in the kernel debugger with just Ntoskrnl.dbg loaded. Before typing the x nt!* command, use the .logopen command to create a log file of your kernel-debugging session. That way, you can save the output in a file and then search for the addresses in question. You can also use the Visual C++ Dumpbin utility (type dumpbin /symbols ntoskrnl.dbg), but you then have to search for the address minus the base address of Ntoskrnl, since only the offsets are listed.
Alternatively, as noted in the previous experiment, the kernel debugger !stacks 0 command can be used to display the name of the driver and function at the current address for a thread (assuming you have the proper symbols loaded).
The Session Manager (\Winnt\System32\Smss.exe) is the first user-mode process created in the system. The kernel-mode system thread that performs the final phase of the initialization of the executive and kernel creates the actual Smss process.
The Session Manager is responsible for a number of important steps in starting Windows 2000, such as opening additional page files, performing delayed file rename and delete operations, and creating system environment variables. It also launches the subsystem processes (normally just Csrss.exe) and the Winlogon process, which in turn creates the rest of the system processes.
Much of the configuration information in the registry that drives the initialization steps of Smss can be found under HKLM\SYSTEM\CurrentControlSet\Control\Session Manager. You'll find it interesting to examine the kinds of data stored there. (For a description of the keys and values, see the Registry Entries help file, Regentry.chm, in the Windows 2000 resource kits.)
After performing these initialization steps, the main thread in Smss waits forever on the process handles to Csrss and Winlogon. If either of these processes terminates unexpectedly, Smss crashes the system, since Windows 2000 relies on their existence. Meanwhile, Smss waits for requests to load subsystems, new subsystems starting up, and debug events. It also acts as a switch and monitor between applications and debuggers.
The Windows 2000 logon process (\Winnt\System32\Winlogon.exe) handles interactive user logons and logoffs. Winlogon is notified of a user logon request when the secure attention sequence (SAS) keystroke combination is entered. The default SAS on Windows 2000 is the combination Ctrl+Alt+Delete. The reason for the SAS is to protect users from password-capture programs that simulate the logon process. Once the username and password have been captured, they are sent to the local security authentication server process (described in the next section) to be validated. If they match, Winlogon extracts the value of the Userinit registry value under the registry key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon and creates a process to run each executable image listed in that value. The default is to run a process named Userinit.exe.
Viewing Multiple Sessions
If Terminal Services are installed, Session Manager creates a separate instance of both Csrss and Winlogon for each connected user session. The list of sessions can be displayed with the !session command in the kernel debugger. In the following example, there were three sessions active on the system. (The !session command shows only the Csrss.exe process in each session, even though there are more processes in each session.)
kd> !session **** NT ACTIVE SESSION DUMP **** PROCESS 813531e0 Cid: 00c8 Peb: 7ffdf000 SessionId: 00000000 DirBase: 03639000 ObjectTable: 81353508 TableSize: 371. Image: csrss.exe PROCESS 81180c80 Cid: 0364 Peb: 7ffdf000 SessionId: 00000001 DirBase: 00fcc000 ObjectTable: 812c7288 TableSize: 115. Image: csrss.exe PROCESS 811358e0 Cid: 0618 Peb: 7ffdf000 SessionId: 00000002 DirBase: 040f0000 ObjectTable: 8114bcc8 TableSize: 111. Image: csrss.exe
This process performs some initialization of the user environment (such as restoring mapped drive letters, running the login script, and applying group policies) and then looks in the registry at the Shell value (under the same Winlogon key referred to previously) and creates a process to run the system-defined shell (by default, Explorer.exe). Then Userinit exits. This is the reason Explorer.exe is shown with no parent—its parent has exited, and as explained earlier, tlist left-justifies processes whose parent isn't running. (Another way of looking at it is that Explorer is the grandchild of Winlogon.)
The identification and authentication aspects of the logon process are implemented in a replaceable DLL named GINA (Graphical Identification and Authentication). The standard Windows 2000 GINA, Msgina.dll, implements the default Windows 2000 logon interface. However, developers can provide their own GINA DLL to implement other identification and authentication mechanisms in place of the standard Windows 2000 username/password method (such as one based on a voice print). In addition, Winlogon can load additional network provider DLLs that need to perform secondary authentication. This capability allows multiple network providers to gather identification and authentication information all at one time during normal logon.
Winlogon is active not only during user logon and logoff but also whenever it intercepts the SAS from the keyboard. For example, when you press Ctrl+Alt+Delete while logged in, the Windows Security dialog box comes up, providing the options to log off, start the Task Manager, lock the workstation, shut down the system, and so forth. Winlogon is the process that handles this interaction.
For more details on Winlogon, see Chapter 8.
The local security authentication server process (\Winnt\System32\Lsass.exe) receives authentication requests from Winlogon and calls the appropriate authentication package (implemented as a DLL) to perform the actual verification, such as checking whether a password matches what is stored in the active directory or the SAM (the part of the registry that contains the definition of the users and groups).
Upon a successful authentication, Lsass generates an access token object that contains the user's security profile. Winlogon then uses this access token to create the initial shell process. Processes launched from the shell then by default inherit this access token.
For more details about Lsass and security authentication, see Chapter 8. For details on the callable functions that interface with Lsass (the functions that start with Lsa), see the documentation in the Platform SDK.
Recall from earlier in the chapter that "services" on Windows 2000 can refer either to a server process or to a device driver. This section deals with services that are user-mode processes. Services are like UNIX "daemon processes" or VMS "detached processes" in that they can be configured to start automatically at system boot time without requiring an interactive logon. They can also be started manually (such as by running the Services administrative tool or by calling the Win32 StartService function). Typically, services do not interact with the logged on user, though there are special conditions when this is possible. (See Chapter 5.)
The service control manager is a special system process running the image \Winnt\System32\Services.exe that is responsible for starting, stopping, and interacting with service processes. Service programs are really just Win32 images that call special Win32 functions to interact with the service control manager to perform such actions as registering the service's successful startup, responding to status requests, or pausing or shutting down the service. Services are defined in the registry under HKLM\SYSTEM\CurrentControlSet \Services. The resource kit Registry Entries help file (Regentry.chm) documents the subkeys and values for services.
Keep in mind that services have three names: the process name you see running on the system, the internal name in the registry, and the display name shown in the Services administrative tool. (Not all services have a display name—if a service doesn't have a display name, the internal name is shown.) With Windows 2000, services can also have a description field that can contain up to 1024 characters that further detail what the service does.
To map a service process to the services contained in that process, use the tlist /s command. Note that there isn't always one-to-one mapping between service process and running services, however, because some services share a process with other services. In the registry, the type code indicates whether the service runs in its own process or shares a process with other services in the image.
A number of Windows 2000 components are implemented as services, such as the spooler, Event Log, Task Scheduler, and various other networking components.
Listing Installed Services
To list the installed services, select Administrative Tools from Control Panel, and then select Services. You should see output like this:
To see the detailed properties about a service, right-click on a service and select Properties. For example, here are the properties for the Print Spooler service (highlighted in the previous figure):
Notice that the Path To Executable field identifies the program that contains this service. Remember that some services share a process with other services—mapping isn't always one to one.
For more details on services, see Chapter 5.