Hello. Today we’d like to share the first part of an ongoing review, by MVP David Morton, of the new edition of Windows Internals, by Mark Russinovich and David Solomon, with Alex Ionescu (Microsoft Press, 2009; ISBN: 9780735625303). If you’re new to the world that is Windows internals and you’re not already familiar with the guide book to that world, this is a review for you. Enjoy!
David Morton here. I'm a C# Developer and an MVP in Visual C#. Though I'm chiefly a .NET developer, I like to stretch myself by learning about how things work and am never satisfied with simply "that it works." In that vein, I read Jeffrey Richter's CLR via C# (Microsoft Press, 2006; ISBN: 9780735621633) and loved it. But, of course, that wasn't enough for my curiosity, so I've starting reading the 5th edition of Windows Internals.
Windows Internals, Fifth Edition – Review, Part One
As an experienced programmer living day to day in the world of managed code, I’ve always been somewhat aware of own ignorance concerning the inner workings of Windows, but until I started reading Windows Internals, I had absolutely no idea how much I didn’t know. Not only have I never written in assembly, but I’ve rarely written more than “Hello World” in C++ (although I have occasional moments of masochistic inspiration where I want to learn more of it). I write C#, plain and simple. My world is filled with words like “object,” “class,” “reference,” “namespace,” and “LINQ.” Words like “EAX register,” “pointer,” “virtual address space,” “kernel,” and “symmetric multiprocessing” rarely, if ever, come into my consciousness. In fact, my visceral reaction to the word pointer is “you don’t need it in C#, so don’t use it.” I have it easy. Perhaps I’ve had it too easy, because it took me 2 weeks to read the first 250 pages of this lengthy 1200-page volume. It’s on those first three chapters I’m writing today. Driver developers, you can stop mocking me now. Given my “wet-behind-the-ears” experience in this subject matter, you may refer to this post as “A managed developer’s terror-filled journey into understanding Windows, the most frightening thing a C# developer will ever face.” You may refer to the book as “A Geek’s Paradise of Deep Information.”
This book is about Windows Vista and Windows Server 2008. Previous editions cover previous versions of the OS. Windows 7 is, from most accounts, a more finely tuned version of Vista, so many of the ideas that are discussed within this book should apply to Windows 7, although there’s no guarantee. Unfortunately, with a book this size, covering the scope it covers, it’s most certainly a tremendous task to update it for each operating system, and this book is already on the fifth edition.
Chapter 1: Concepts and Tools (“If you’re not paying attention, you might miss something.”)
The first chapter sets the stage for the rest of the book. It’s worth reading twice. Things are mentioned, sometimes in passing, that if you miss, you’ll be confused later. One sentence in particular struck me while I was rereading the first chapter for this review: “For example, the word service can refer to a callable routine in the operating system, a device driver, or a server process.” The typical .NET definition of “service” only covers one of those definitions, so that was certainly a change. From what I could tell of the first three chapters, the definition actually tilts more towards the “callable routines” definition than any other. C# developer, consider yourself warned. I missed this the first time around, and was extremely confused for a while. Pay attention. There’s too much information crammed into this book to glaze over any one sentence. You’ll probably need a pen or highlighter if you’re going to make it. I got both.
After a brief discussion on the terminology of services, functions, and routines, the topic turns to processes, threads and jobs. The short story here is that a job contains one or more processes, each of which in turn contain one or more threads. A differentiation is also made between a program and a process, the former being a sequence of instructions, while the latter is a container for resources while executing the program.
It was there that the experiments began.
As a way of getting some hands-on observation, the authors have included, throughout the text, a series of experiments, using various tools, some which come with Windows, some which are available with the Windows SDK, and some that were written by Mark Russinovich, one of the co-authors of the book. These experiments are designed to give you direct experience viewing Windows debugging information, and helping to solidify the concepts that are being presented in the text. I’d strongly recommend doing these exercises, as the very act of exploring the software used to perform them will give you a more concrete and fully fleshed out understanding of the concepts involved.
Shortly after the discussion on threads, Virtual Memory is discussed. This is where things start to get a little confusing for those not familiar with memory layout. Again, as for everything in this chapter, read it slowly, letting everything sink in. The first chapter presents a slightly more than cursory overview of the topic, even going into the differences between the various hardware architectures as it concerns address space layout for processes. On X86 systems, 2 GB is virtually mapped, with up to 64 GB of mapping provided via Windows’ Address Windowing Extensions (AWE). On 64-bit systems, the mapped memory can make it up to 8 TB.
One of the other new concepts I learned about from the book was the difference between kernel mode and user mode in threads, the former being a privileged mode of operation that gives greater access to system memory and processor instructions, while the latter is a mode of operation in which the user’s custom code can operate. Applications frequently switch back and forth between these two modes, while device drivers operate in kernel mode. In fact, it’s interesting to note that many of the applications used in the experiments consist of a kernel-mode portion as well as a user-mode portion, in order to allow access to viewing and manipulating kernel objects.
After a brief aside on Terminal Services, the book moves to a discussion of objects and handles, the registry and a quick section on Windows dependence on Unicode before moving into another discussion on the tools used to debug and observe Windows. The following tools are discussed in depth: Reliability and Performance Monitor, LiveKD and the symbols for kernel debugging, the Debugging tools for Windows, the Windows Driver Kit (WDK), the Windows Software Development Kit (SDK), and a very humble and understated section about the tools provided for free from Sysinternals, which are mostly written by Russinovich.
Chapter 2: System Architecture
After leaving the introduction, which was mainly an overview of terms, we move to System Architecture. This chapter uses a broad paintbrush to paint a picture of the general framework upon which Windows rests.
The start of this chapter is a description of the requirements and design goals for the operating system. You may or may not be surprised to know that much of Windows is designed to comply to POSIX (Portable Operating System Interface [for Unix]) standards. Complying to this standard ensures compatibility with many UNIX tools and utilities. It’s interesting to me that Microsoft followed this standard primarily to cater to the U.S. Government. That being said, the original POSIX subsystem has been replaced in Vista by the SUI, or the Windows Subsystem for Unix-based Applications. This is discussed more in detail later in the chapter.
The primary design goals in Windows were extensibility, portability, reliability and robustness, compatibility and performance. Everything in this chapter is designed to point at how Microsoft successfully achieved these goals with Windows.
Next, we begin looking at the more technical aspects of Windows. This is where the model of the operating system is discussed, and the terms “user mode” and “kernel mode,” which are used throughout the book, are fleshed out and discussed. User mode processes include fixed system support processes (logon, session manager, etc.), service processes (the background programs you see running in services.msc, or in the services node of computer management), user applications, and the environment subsystem server processes (which supports the actual UI of Windows). Kernel mode components include the executive, the kernel, device drivers, the hardware abstraction layer (HAL) and the windowing and graphics system. In fact, user mode applications that make heavy use of the UI often spend more of their time running in kernel mode than they do running in user mode as a result.
Portability, which is available thanks to the hardware abstraction layer (HAL), allows Windows to run on multiple architectures including x86, x64 and Itanium, as well as a number of variants therein, including multi-processor computers. Speaking of multi-processor, multi-core and hyper-threaded processors, Windows achieves scalability via symmetric multiprocessing, which means it balances the work load evenly between the multiple processors on the system. This is in contrast to asymmetric processing, which forces one processor to carry loads that the other will never carry. This symmetric multiprocessing allows the computer to make the most of its CPU cycles and avoid bottlenecking. Symmetric multiprocessing is available through the thread scheduling that occurs in the Kernel in conjunction with the HAL.
The client and server versions of Windows are then contrasted with one another. Other than the differences in the various limitations between the versions, some supporting more physical memory and processors than others, the chief difference between the operating systems is in priorities. The server versions place a higher priority on throughput, or “getting the data out there”, while the client versions are more concerned with keeping the UI responsive and snappy for better desktop interaction.
Did you know there’s a completely separate build of Windows called the “checked build”? I had no idea, but found out about it on page 47. The checked build is essentially a recompilation of the Windows source code in “debug mode,” allowing for greater control and visibility while writing device drivers. Even though this section of the book discussing checked builds is only about 2 pages long, there’s a great deal of information packed into it.
Next on the docket is a more detailed run-down of the key system components. This 33-page section is the latter half of this chapter.
First up, are the Environment Subsystems and Subsystem DLLs, which includes the Windows subsystem and the POSIX subsystem. The Windows subsystem is used to run applications that are compiled to use Windows service calls, while the POSIX subsystem is provided to ease the task of porting UNIX applications to Windows. Both of these subsystems, at their root, depend on Ntdll.dll, which provides the interaction layer to the Windows executive.
Next up is the Executive, which includes the configuration manager, the process and threading manager, security reference monitor, I/O manager, Plug and Play manager, power manager, WDM WMI, cache manager, memory manager, and the logical prefetcher. It also includes the object manager, the ALPC, runtime-library functions and support routines. Each of these above components has a small paragraph describing their use, and is the subject of a lengthy discussion later in the text.
The kernel follows the executive. The kernel is the main “brain” of Windows. It’s where the low-level thread scheduling functions occur, and it also provides an abstraction layer between the HAL and the device drivers and executive. The kernel provides the ability to store processor specific data, and contains services responsible for saving and restoring CPU registers during a context switch. Much of the code within the kernel is architecture specific.
Following this is the hardware abstraction layer, or the HAL, which has unfortunately been the brunt of many Arthur C. Clarke jokes in the past (“Dave. What are you doing, Dave?”). There are, in fact, several versions of the HAL, each tailored to a specific architecture, and each designed to abstract the processor’s architecture from the executive. It’s important to note that the abstracted hardware is the base system hardware here. Device drivers are the subject of the next section.
Device drivers include several types. The most well-known is probably the hardware device driver, but there’s also the file system driver, the file system filter drivers, network redirectors, protocol drivers and kernel-streaming filter drivers, each of which provide a separate function in the system. These are described in detail in Chapter 7.
Next is WDM, or Windows Driver Model, which is closely tied to Plug and Play. WDM categorizes drivers either as bus drivers (yes, I know), function drivers and filter drivers. Each of these has a specific reason for existing, which is described in the text.
Finally, the chapter closes out with a discussion of system processes, which includes the Idle process (did you know the idle process isn’t always simply “idle”? It’s actually doing stuff… weird), interrupts, system threads, the session manager, Winlogon, and the service control manager.
Chapter 3: System Mechanisms (“Oh no! What have I gotten myself into?”)
System Mechanics is described as the “base mechanisms” of Windows. I’m anticipating a much easier read after this chapter. Glancing through the book, it seems as though this chapter is the most removed from my typical realm of understanding, and as such, is probably the hardest chapter I’ll have to read. Many of the concepts later in the book are based in this section, so it was imperative to me that I understand what was being read, if for no other reason than for easier reading later on. You might also recognize some of the same topics in this chapter from some of the topics in Chapter 1. These topics, while repeated, are expounded slightly more in this section, though most of the topics are fully fleshed out in later chapters (with the exception, perhaps of the object manager and objects, which are very thoroughly described in this chapter).
The chapter starts with a term I’ve never heard before in my life: “trap dispatching.” The basic idea is that at boot time, Windows loads up the addresses of specific functions into the processor itself, in order to handle interrupts, which are asynchronous events that could happen at any time during the execution of code. These functions are known as “trap handlers,” and they’re called by the processor whenever a specific interrupt is raised. This is what is meant by trap dispatching. The interrupts are “trapped” and then “dispatched” to the “handlers.”
Much of this chapter discusses the details around this structure, and much of that conversation includes specific information that has to do with processor interrupt controllers (PICs) and advanced processor interrupt controllers (APICs). These controllers prioritize requests from hardware to interrupt the processor. Windows also has a prioritization level, known as the software interrupt request level. Implemented in the kernel, this is what allows Windows to preferentially ignore specific interrupts from the processor, while still acknowledging the hardware device requesting an interrupt. All of the details of this are covered in the first several pages of this chapter.
Exception dispatching is provided by structured exception handling. Structured exception handling is the facility that “bubbles up” exceptions through the call stack looking for a handler. This should be familiar to any managed code developer, as the unwinding of the call stack is something very familiar to managed developers that have had to debug issues that bring their applications down. This is closely tied in Windows to the idea of the Windows Error Reporting system, which is new in Vista, and prevents the situation where a failing application simply “disappears” without a trace. After reading this section, I now know why computer optimizers that tell you to disable “windows error reporting” are simply, ahem, in error. This is not a service you want to turn off, especially when running beta applications or other apps that could simply die at a moment’s notice.
All of the items in the preceding two paragraphs are described in full detail during the first 50 or so pages of this exhaustive chapter. Reading these, you really get an appreciation for the attention to detail that was incorporated into the operating system, and it’s optimization when it comes to handling threading within the OS.
After that, we move up one small level, to the Windows object manager. Resources in Windows are accounted for and secured via the object manager. The object manager is a generic, uniform mechanism that manages the system’s resources. The objects managed by the object manager include the kernel objects, the executive objects (that are built upon the kernel objects in nice OOP fashion), and the GDI/User objects. There are several kinds of objects, (a couple include the “process object,” “thread object,” “mutex object,” and “file object.”) Many of these objects can be created and acquired a handle to via the WinAPI functions that are exposed through various subsystem DLLs such as User32.dll. The structure of these objects are described in detail, including properties and methods of these objects, their security structure, how they’re accounted for, and how they’re linked together and found using namespaces an filters.
Next, we move to the discussion on synchronization to object access. Much of this discussion intertwines with the discussion on IRQL levels. The interactions between object access and levels of operation are fascinating, and are explained in “problem-solution” form, where a specific programming dilemma is presented, followed by the solution that was used in Windows to solve the dilemma. Some of the things presented in this fashion include spinlocks, queued spinlocks, mutexes (which is most certainly short for “mutual exclusion” for those of you who don’t know), keyed events, pushlocks, critical operations, reader-writer locks and interlocked operations, as well as a couple others. How threads wait for objects is also described in detail. While this chapter doesn’t discuss threading heavily (the authors wait for Chapter 5 for this), everything in Windows is couched in multithreading and scheduling, so access to objects from multiple threads is described in detail here as an agent to understand the object system.
After the discussion on objects, which is the majority of the chapter, the authors move on to a discussion on worker threads, followed by windows global flags, advanced local procedure calls (and their relation to RPC and logon processes), kernel event tracing, Windows on Windows 64 (Wow64) emulation, and the changes that occur to memory layout, system calls, exception dispatching, callbacks, filesystem and registry redirection, I/O requests and printing that results from running a 32-bit application on a 64-bit system. Following this are discussions on user-mode debugging, and then a very interesting discussion on the image loader (ever wonder how Windows loads that executable you’re about to run?).
One of the newer concepts in Windows is the concept of virtualization, which is the ability for a single physical machine to run multiple operating systems at the same time. This is allowed in Windows via the Hypervisor, which is discussed next. It’s important when reading this chapter to look for the differences between a truly virtualized OS and one that is run in an application such as Virtual PC. It’s not completely the same, and the differences between the two are discussed in this section. Also discussed here are the different ways of emulating and providing access to device drivers from within a virtualized OS, as well as the differences with memory access that have to be enforced for the whole system to work properly.
The chapter closes with a discussion on the Kernel Transaction Manager, which provides automatic error recovery. Also discussed is the support in Windows for hot patching, and various protections against malicious patches that may make the kernel vulnerable to hacking.
I’m only three chapters through this book, but as I’m sure you have been able to tell, the first three chapters contain an enormous amount of information. This is not a book you can read quickly, and it’s not easy reading. It’s not meant to be. It’s meant to give more detail than you might ever have to use. It’s good to know it’s there when you need it. It’s a dirty job, but someone has to do it. A special thanks goes out to the authors for compiling this extremely detailed volume. I’m looking forward to the rest of the book.