Inside Microsoft Windows 2000, Third Edition (Microsoft Programming Series)
Now that we've dissected processes, let's turn our attention to the structure of a thread. Unless explicitly stated otherwise, you can assume that anything in this section applies to both normal user-mode threads and kernel-mode system threads (described in Chapter 3).
Data Structures
At the operating system level, a Windows 2000 thread is represented by an executive thread (ETHREAD) block, which is illustrated in Figure 6-7. The ETHREAD block and the structures it points to exist in the system address space, with the exception of the thread environment block (TEB), which exists in the process address space. In addition, the Win32 subsystem process (Csrss) maintains a parallel structure for each thread created in a Win32 process. Also, for threads that have called a Win32 subsystem USER or GDI function, the kernel-mode portion of the Win32 subsystem (Win32k.sys) maintains a per-thread data structure (called the W32THREAD structure) that the ETHREAD block points to.
Figure 6-7 Structure of the executive thread block
Fibers vs. Threads
Fibers allow an application to schedule its own "threads" of execution rather than rely on the priority-based scheduling mechanism built into Windows 2000. Fibers are often called "lightweight" threads, and in terms of scheduling, they're invisible to the kernel because they're implemented in user mode in Kernel32.dll. To use fibers, a call is first made to the Win32 ConvertThreadToFiber function. This function converts the thread to a running fiber. Afterward, the newly converted fiber can create additional fibers with the CreateFiber function. (Each fiber can have its own set of fibers.) Unlike a thread, however, a fiber doesn't begin execution until it's manually selected through a call to the SwitchToFiber function. The new fiber runs until it exits or until it calls SwitchToFiber, again selecting another fiber to run. For more information, see the Platform SDK documentation on fiber functions.
Most of the fields illustrated in Figure 6-7 are self-explanatory. The first field is the kernel thread (KTHREAD) block. Following that are the thread identification information, the process identification information (including a pointer to the owning process so that its environment information can be accessed), security information in the form of a pointer to the access token and impersonation information, and finally, fields relating to LPC messages and pending I/O requests. As you can see in Table 6-9, some of these key fields are covered in more detail elsewhere in this book.
Table 6-9 Key Contents of the Executive Thread Block
Element | Description | Additional Reference |
---|---|---|
KTHREAD block | See Table 6-10 | |
Thread time information | Thread create and exit time | |
Process identification | Process ID and pointer to EPROCESS block of the process that the thread belongs to | |
Start address | Address of thread start routine | |
Impersonation information | Access token and impersonation level (if the thread is impersonating a client) | Security (Chapter 8) |
LPC information | Message ID that the thread is waiting for and address of message | Local procedure calls (Chapter 3) |
I/O information | List of pending I/O request packets (IRPs) | I/O system (Chapter 9) |
For more details on the internal structure of an ETHREAD block, you can use the kernel debugger !threadfields or !kdex2x86.strct ethread command to display the offsets in hexadecimal for almost every field in the structure. Although many of the field names are self-explanatory, the output doesn't give the data type of the fields, nor does it show the format of the structures that are included within or pointed to by the ETHREAD block.
Let's take a closer look at two of the key thread data structures referred to above: the KTHREAD block and the TEB. The KTHREAD block contains the information that the Windows 2000 kernel needs to access to perform thread scheduling and synchronization on behalf of running threads. Its layout is illustrated in Figure 6-8.
Figure 6-8 Structure of the kernel thread block
The key fields of the KTHREAD block are described briefly in Table 6-10.
Table 6-10 Key Contents of the KTHREAD Block
Element | Description | Additional Reference |
---|---|---|
Dispatcher header | Because the thread is an object that can be waited on, it starts with a standard kernel dispatcher object header. | Dispatcher objects (Chapter 3) |
Execution time | Total user and kernel CPU time. | |
Pointer to kernel stack information | Base and upper address of the kernel stack. | Memory management (Chapter 7) |
Pointer to system service table | Each thread starts out with this field pointing to the main system service table (KeServiceDescriptorTable). When a thread first calls a Win32 GUI service, its system service table is changed to one that includes the GDI and USER services in Win32k.sys. | System service dispatching (Chapter 3) |
Scheduling information | Base and current priority, quantum, affinity mask, ideal processor, scheduling state, freeze count, and suspend count. | Thread scheduling |
Wait blocks | The thread block contains four built-in wait blocks so that wait blocks don't have to be allocated and initialized each time the thread waits on something. (One wait block is dedicated to timers.) | "Synchronization" (Chapter 3) |
Wait information | List of objects the thread is waiting on, wait reason, and time at which the thread entered the wait state. | "Synchronization" |
Mutant list | List of mutant objects the thread owns. | "Synchronization" |
APC queues | List of pending user-mode and kernel-mode APCs, and alertable flag. | APC queues (Chapter 3) |
Timer block | Built-in timer block (also a corresponding wait block). | |
Queue list | Pointer to queue object that the thread is associated with. | "Synchronization" |
Pointer to TEB | Thread ID, TLS information, PEB pointer, and GDI and OpenGL information. |
EXPERIMENT
Displaying ETHREAD and KTHREAD Structures
Although you can use the kernel debugger command !threadfields to see information similar to that in the output of !processfields shown in the experiment below, you'll get more detailed output of the fields in ETHREAD and KTHREAD blocks if you use !ethread. The !ethread command is one of the commands provided in Kdex2x86.dll, the secondary kernel debugger extension DLL. To use these extension commands, you must first load this DLL as shown in the following output listing. The !ethread command takes the address of an ETHREAD block and displays both the KTHREAD and the ETHREAD blocks that follow. In the following example, we used !process to dump a process and find the address of the ETHREAD blocks for the threads in the process. The output looks like this:
kd> .load kdex2x86 kd> !process 2c8 2 Searching for Process with Cid == 2c8 PROCESS 810bad70 SessionId: 0 Cid: 02c8 Peb: 7ffdf000 ParentCid: 0430 DirBase: 04e4c000 ObjectTable: 81114628 TableSize: 42. Image: notepad.exe THREAD 810a4310 Cid 2c8.5a4 Teb: 7ffde000 Win32Thread: e267f908 WAIT: (WrUserRequest) UserMode Non-Alertable 842f75b0 SynchronizationEvent THREAD 8112f710 Cid 2c8.5bc Teb: 7ffdd000 Win32Thread: 00000000 WAIT: (WrLpcReceive) UserMode Non-Alertable 8109a0a8 Semaphore Limit 0x7fffffff kd> !ethread 8112f710 struct _ETHREAD (sizeof=584) +000 struct _KTHREAD Tcb +000 struct _DISPATCHER_HEADER Header +000 byte Type = 06 +001 byte Absolute = 00 +002 byte Size = 6c +003 byte Inserted = 00 +004 int32 SignalState = 00000000 +008 struct _LIST_ENTRY WaitListHead +008 struct _LIST_ENTRY *Flink = 8112F718 +00c struct _LIST_ENTRY *Blink = 8112F718 +010 struct _LIST_ENTRY MutantListHead +010 struct _LIST_ENTRY *Flink = 8112F720 +014 struct _LIST_ENTRY *Blink = 8112F720 +018 void *InitialStack = BDEBD000 +01c void *StackLimit = BDEBA000 +020 void *Teb = 7FFDD000 +024 void *TlsArray = 00000000 +028 void *KernelStack = BDEBCC48 +02c byte DebugActive = 00 +02d byte State = 05 +02e byte Alerted[2] = 00 00 +030 byte Iopl = 00 +031 byte NpxState = 0a +032 char Saturation = 00 +033 char Priority = 08 +034 struct _KAPC_STATE ApcState +034 struct _LIST_ENTRY ApcListHead[2] +034 ApcListHead[0] +034 struct _LIST_ENTRY *Flink = 8112F744 +038 struct _LIST_ENTRY *Blink = 8112F744 +03c ApcListHead[1] +03c struct _LIST_ENTRY *Flink = 8112F74C +040 struct _LIST_ENTRY *Blink = 8112F74C +044 struct _KPROCESS *Process = 810BAD70 +048 byte KernelApcInProgress = 00 +049 byte KernelApcPending = 00 +04a byte UserApcPending = 00 +04c uint32 ContextSwitches = 00000003 +050 int32 WaitStatus = 00000000 +054 byte WaitIrql = 00 +055 char WaitMode = 01 +056 byte WaitNext = 00 +057 byte WaitReason = 10 +058 struct _KWAIT_BLOCK *WaitBlockList = 8112F77C +05c struct _LIST_ENTRY WaitListEntry +05c struct _LIST_ENTRY *Flink = 84F478AC +060 struct _LIST_ENTRY *Blink = 8114458C +064 uint32 WaitTime = 00064800 +068 char BasePriority = 08 +069 byte DecrementCount = 00 +06a char PriorityDecrement = 00 +06b char Quantum = 05 +06c struct _KWAIT_BLOCK WaitBlock[4] +06c WaitBlock[0] +06c struct _LIST_ENTRY WaitListEntry +06c struct _LIST_ENTRY *Flink = 8109A0B0 +070 struct _LIST_ENTRY *Blink = 8109A0B0 +074 struct _KTHREAD *Thread = 8112F710 +078 void *Object = 8109A0A8 +07c struct _KWAIT_BLOCK *NextWaitBlock = 8112F77C +080 uint16 WaitKey = 0000 +082 uint16 WaitType = 0001 |
The TEB, illustrated in Figure 6-9, is the only data structure explained in this section that exists in the process address space (as opposed to the system space).
Figure 6-9 Fields of the thread environment block
The TEB stores context information for the image loader and various Win32 DLLs. Because these components run in user mode, they need a data structure writable from user mode. That's why this structure exists in the process address space instead of in the system space, where it would be writable only from kernel mode. You can find the address of the TEB with the kernel debugger !thread command.
EXPERIMENT
Examining the TEB
You can dump the TEB structure with the !teb command in the kernel debugger. The output looks like this:
kd> !teb TEB at 7FFDE000 ExceptionList: 12ffb0 Stack Base: 130000 Stack Limit: 12d000 SubSystemTib: 0 FiberData: 1e00 ArbitraryUser: 0 Self: 7ffde000 EnvironmentPtr: 0 ClientId: 490.458 Real ClientId: 490.458 RpcHandle: 0 Tls Storage: 0 PEB Address: 7ffdf000 LastErrorValue: 0 LastStatusValue: 0 Count Owned Locks:0 HardErrorsMode: 0 |
Kernel Variables
As with processes, a number of Windows 2000 kernel variables control how threads run. Table 6-11 shows the kernel-mode kernel variables that relate to threads.
Table 6-11 Thread-Related Kernel Variables
Variable | Type | Description |
---|---|---|
PspCreateThreadNotifyRoutine | Array of pointers | Array of pointers to routines to be called on during thread creation and deletion (maximum of eight). |
PspCreateThreadNotifyRoutineCount | DWORD | Count of registered thread-notification routines. |
PspCreateProcessNotifyRoutine | Array of pointers | Array of pointers to routines to be called on during process creation and deletion (maximum of eight). |
PspCreateProcessNotifyRoutineCount | DWORD | Count of registered process-notification routines. |
PspCidTable | Handle table | Handle table that stores process and thread objects. Using a thread's or process's handle value as its thread or process ID guarantees uniqueness. |
Performance Counters
Most of the key information in the thread data structures is exported as performance counters, which are listed in Table 6-12. You can extract much information about the internals of a thread just by using the Performance tool in Windows 2000.
Table 6-12 Thread-Related Performance Counters
Object: Counter | Function |
---|---|
Process: Priority Base | Returns the current base priority of the process. This is the starting priority for threads created within this process. |
Thread: % Privileged Time | Describes the percentage of time that the thread has run in kernel mode during a specified interval. |
Thread: % Processor Time | Describes the percentage of CPU time that the thread has used during a specified interval. This count is the sum of % Privileged Time and % User Time. |
Thread: % User Time | Describes the percentage of time that the thread has run in user mode during a specified interval. |
Thread: Context Switches/Sec | Returns the number of context switches per second that the system is executing. The higher this number, the more threads of an equal priority are attempting to execute. |
Thread: Elapsed Time | Returns the amount of CPU time (in seconds) that the thread has consumed. |
Thread: ID Process | Returns the process ID of the thread's process. This ID is valid only during the process's lifetime because process IDs are reused. |
Thread: ID Thread | Returns the thread's thread ID. This ID is valid only during the thread's lifetime because thread IDs are reused. |
Thread: Priority Base | Returns the thread's current base priority. This number might be different from the thread's starting base priority. |
Thread: Priority Current | Returns the thread's current dynamic priority. |
Thread: Start Address | Returns the thread's starting virtual address (Note: This address will be the same for most threads.) |
Thread: Thread State | Returns a value from 0 through 7 relating to the current state of the thread. |
Thread: Thread Wait Reason | Returns a value from 0 through 19 relating to the reason why the thread is in a wait state. |
Relevant Functions
Table 6-13 shows the Win32 functions for creating and manipulating threads. This table doesn't include functions that have to do with thread scheduling and priorities—those are included in the section "Thread Scheduling" later in this chapter.
Table 6-13 Win32 Thread Functions
Function | Description |
---|---|
CreateThread | Creates a new thread |
CreateRemoteThread | Creates a thread in another process |
ExitThread | Ends execution of a thread normally |
TerminateThread | Terminates a thread |
GetExitCodeThread | Gets another thread's exit code |
GetThreadTimes | Returns another thread's timing information |
Get/SetThreadContext | Returns or changes a thread's CPU registers |
GetThreadSelectorEntry | Returns another thread's descriptor table entry (applies only to x86 systems) |
Relevant Tools
Besides the Performance tool, several other tools expose various elements of the state of Windows 2000 threads. (The tools that show thread-scheduling information are listed in the section "Thread Scheduling.") These tools are itemized in Table 6-14.
NOTE
To display thread details with Tlist, you must type tlist xxx, where xxx is a process image name or window title. (Wildcards are supported.)
Table 6-14 Thread-Related Tools and Their Functions
EXPERIMENT
Using the Kernel Debugger !thread Command
The kernel debugger !thread command dumps a subset of the information in the thread data structures. Some key elements of the information the kernel debugger displays can't be displayed by any Windows 2000 utility: internal structure addresses; priority details; stack information; the pending I/O request list; and, for threads in a wait state, the list of objects the thread is waiting on. (Refer to Table 6-14.)
To display thread information, use either the !process command (which displays all the thread blocks after displaying the process block) or the !thread command to dump a specific thread. The output of the thread information, along with some annotations of key fields, is shown here:
EXPERIMENT
Viewing Thread Information
The following output is the detailed display of a process produced by using the Tlist utility in the Windows 2000 Support Tools. Notice that the thread list shows the Win32 start address. (All the other utilities that show the thread start address show the actual start address, not the Win32 start address.)
C:\> tlist winword 155 WINWORD.EXE Document1 - Microsoft Word CWD: C:\book\ CmdLine: "C:\Program Files\Microsoft Office\Office\WINWORD.EXE" VirtualSize: 64448 KB PeakVirtualSize: 106748 KB WorkingSetSize: 1104 KB PeakWorkingSetSize: 6776 KB NumberOfThreads: 2 156 Win32StartAddr:0x5032cfdb LastErr:0x00000000 State:Waiting 167 Win32StartAddr:0x00022982 LastErr:0x00000000 State:Waiting 0x50000000 WINWORD.EXE 5.0.2163.1 shp 0x77f60000 ntdll.dll 5.0.2191.1 shp 0x77f00000 KERNEL32.dll |