|
|
 |

 |
|
Inside Microsoft® Windows® 2000, Third Edition
|
|
 |
Author |
 |
David A. Solomon and Mark E. Russinovich
|
 |
|
Pages |
944
|
|
Disk |
1 Companion CD(s)
|
|
Level |
Intermediate
|
|
Published |
08/16/2000
|
|
ISBN |
9780735610217
|
|
ISBN-10 |
0-7356-1021-5
|
|
Price(USD) |
$49.99
To see this book's discounted price, select a reseller below.
|
|
|
|
|
 |
|
|
Chapter 6: Processes, Threads, and Jobs (continued)
Thread Internals
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.

Click to view graphic
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.

Click to view graphic
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
(three more wait blocks)
+0cc void *LegoData = 00000000
+0d0 uint32 KernelApcDisable = 00000000
+0d4 uint32 UserAffinity = 00000001
+0d8 byte SystemAffinityActive = 00
+0d9 byte PowerState = 00
+0da byte NpxIrql = 00
+0db byte Pad[1] = 00
+0dc void *ServiceTable = 8046AB80
+0e0 struct _KQUEUE *Queue = 00000000
+0e4 uint32 ApcQueueLock = 00000000
+0e8 struct _KTIMER Timer
+0e8 struct _DISPATCHER_HEADER Header
+0e8 byte Type = 08
+0e9 byte Absolute = 00
+0ea byte Size = 0a
+0eb byte Inserted = 00
+0ec int32 SignalState = 00000001
+0f0 struct _LIST_ENTRY WaitListHead
+0f0 struct _LIST_ENTRY *Flink = 8112F800
+0f4 struct _LIST_ENTRY *Blink = 8112F800
+0f8 union _ULARGE_INTEGER DueTime
+0f8 uint32 LowPart = 9925c310
+0fc uint32 HighPart = 00000009
+0f8 struct __unnamed12 u
+0f8 uint32 LowPart = 9925c310
+0fc uint32 HighPart = 00000009
+0f8 uint64 QuadPart = 000000099925c310
+100 struct _LIST_ENTRY TimerListEntry
+100 struct _LIST_ENTRY *Flink = 81BDBEB0
+104 struct _LIST_ENTRY *Blink = 8046FD70
+108 struct _KDPC *Dpc = 00000000
+10c int32 Period = 00000000
+110 struct _LIST_ENTRY QueueListEntry
+110 struct _LIST_ENTRY *Flink = 00000000
+114 struct _LIST_ENTRY *Blink = 00000000
+118 uint32 Affinity = 00000001
+11c byte Preempted = 00
+11d byte ProcessReadyQueue = 00
+11e byte KernelStackResident = 00
+11f byte NextProcessor = 00
+120 void *CallbackStack = 00000000
+124 void *Win32Thread = 00000000
+128 struct _KTRAP_FRAME *TrapFrame = BDEBCD64
+12c struct _KAPC_STATE *ApcStatePointer[2] = 8112F744
8112F850
+134 char PreviousMode = 01
+135 byte EnableStackSwap = 01
+136 byte LargeStack = 00
+137 byte ResourceIndex = 00
+138 uint32 KernelTime = 00000000
+13c uint32 UserTime = 00000000
+140 struct _KAPC_STATE SavedApcState
+140 struct _LIST_ENTRY ApcListHead[2]
+140 ApcListHead[0]
+140 struct _LIST_ENTRY *Flink = 00000000
+144 struct _LIST_ENTRY *Blink = 00000000
+148 ApcListHead[1]
+148 struct _LIST_ENTRY *Flink = 00000000
+14c struct _LIST_ENTRY *Blink = 00000000
+150 struct _KPROCESS *Process = 00000000
+154 byte KernelApcInProgress = 00
+155 byte KernelApcPending = 00
+156 byte UserApcPending = 00
+158 byte Alertable = 00
+159 byte ApcStateIndex = 00
+15a byte ApcQueueable = 01
+15b byte AutoAlignment = 00
+15c void *StackBase = BDEBD000
+160 struct _KAPC SuspendApc
+160 int16 Type = 0012
+162 int16 Size = 0030
+164 uint32 Spare0 = 00000000
+168 struct _KTHREAD *Thread = 8112F710
+16c struct _LIST_ENTRY ApcListEntry
+16c struct _LIST_ENTRY *Flink = 8112F744
+170 struct _LIST_ENTRY *Blink = 8112F744
+174 function *KernelRoutine = 80430B27
+178 function *RundownRoutine = 00000000
+17c function *NormalRoutine = 80430E2B
+180 void *NormalContext = 00000000
+184 void *SystemArgument1 = 00000000
+188 void *SystemArgument2 = 00000000
+18c char ApcStateIndex = 00
+18d char ApcMode = 00
+18e byte Inserted = 00
+190 struct _KSEMAPHORE SuspendSemaphore
+190 struct _DISPATCHER_HEADER Header
+190 byte Type = 05
+191 byte Absolute = 00
+192 byte Size = 05
+193 byte Inserted = 00
+194 int32 SignalState = 00000000
+198 struct _LIST_ENTRY WaitListHead
+198 struct _LIST_ENTRY *Flink = 8112F8A8
+19c struct _LIST_ENTRY *Blink = 8112F8A8
+1a0 int32 Limit = 00000002
+1a4 struct _LIST_ENTRY ThreadListEntry
+1a4 struct _LIST_ENTRY *Flink = 810BADC0
+1a8 struct _LIST_ENTRY *Blink = 810A44B4
+1ac char FreezeCount = 00
+1ad char SuspendCount = 00
+1ae byte IdealProcessor = 00
+1af byte DisableBoost = 00
+1b0 union _LARGE_INTEGER CreateTime
+1b0 uint32 LowPart = ade71268
+1b4 int32 HighPart = 0dfd0747
+1b0 struct __unnamed3 u
+1b0 uint32 LowPart = ade71268
+1b4 int32 HighPart = 0dfd0747
+1b0 int64 QuadPart = 0dfd0747ade71268
+1b0 bits0-1 NestedFaultCount = 0
+1b0 bits2-2 ApcNeeded = 0
+1b8 union _LARGE_INTEGER ExitTime
+1b8 uint32 LowPart = 8112f8c8
+1bc int32 HighPart = 8112f8c8
+1b8 struct __unnamed3 u
+1b8 uint32 LowPart = 8112f8c8
+1bc int32 HighPart = 8112f8c8
+1b8 int64 QuadPart = 8112f8c88112f8c8
+1b8 struct _LIST_ENTRY LpcReplyChain
+1b8 struct _LIST_ENTRY *Flink = 8112F8C8
+1bc struct _LIST_ENTRY *Blink = 8112F8C8
+1c0 int32 ExitStatus = 00000000
+1c0 void *OfsChain = 00000000
+1c4 struct _LIST_ENTRY PostBlockList
+1c4 struct _LIST_ENTRY *Flink = 8112F8D4
+1c8 struct _LIST_ENTRY *Blink = 8112F8D4
+1cc struct _LIST_ENTRY TerminationPortList
+1cc struct _LIST_ENTRY *Flink = E252C508
+1d0 struct _LIST_ENTRY *Blink = E252C508
+1d4 uint32 ActiveTimerListLock = 00000000
+1d8 struct _LIST_ENTRY ActiveTimerListHead
+1d8 struct _LIST_ENTRY *Flink = 8112F8E8
+1dc struct _LIST_ENTRY *Blink = 8112F8E8
+1e0 struct _CLIENT_ID Cid
+1e0 void *UniqueProcess = 000002C8
+1e4 void *UniqueThread = 000005BC
+1e8 struct _KSEMAPHORE LpcReplySemaphore
+1e8 struct _DISPATCHER_HEADER Header
+1e8 byte Type = 05
+1e9 byte Absolute = 00
+1ea byte Size = 05
+1eb byte Inserted = 00
+1ec int32 SignalState = 00000000
+1f0 struct _LIST_ENTRY WaitListHead
+1f0 struct _LIST_ENTRY *Flink = 8112F900
+1f4 struct _LIST_ENTRY *Blink = 8112F900
+1f8 int32 Limit = 00000001
+1fc void *LpcReplyMessage = 00000000
+200 uint32 LpcReplyMessageId = 00000000
+204 uint32 PerformanceCountLow = 00000000
+208 struct _PS_IMPERSONATION_INFORMATION *ImpersonationInfo = 00000000
+20c struct _LIST_ENTRY IrpList
+20c struct _LIST_ENTRY *Flink = 8112F91C
+210 struct _LIST_ENTRY *Blink = 8112F91C
+214 uint32 TopLevelIrp = 00000000
+218 struct _DEVICE_OBJECT *DeviceToVerify = 00000000
+21c uint32 ReadClusterSize = 00000007
+220 byte ForwardClusterOnly = 00
+221 byte DisablePageFaultClustering = 00
+222 byte DeadThread = 00
+223 byte HideFromDebugger = 00
+224 uint32 HasTerminated = 00000000
+228 uint32 GrantedAccess = 001f03ff
+22c struct _EPROCESS *ThreadsProcess = 810BAD70
+230 void *StartAddress = 77E92C50
+234 void *Win32StartAddress = 77D4B759
+234 uint32 LpcReceivedMessageId = 77d4b759
+238 byte LpcExitThreadCalled = 00
+239 byte HardErrorsAreDisabled = 00
+23a byte LpcReceivedMsgIdValid = 00
+23b byte ActiveImpersonationInfo = 00
+23c int32 PerformanceCountHigh = 00000000
+240 struct _LIST_ENTRY ThreadListEntry
+240 struct _LIST_ENTRY *Flink = 810BAFE0
+244 struct _LIST_ENTRY *Blink = 810A4550
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).

Click to view graphic
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. |
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 prioritiesthose 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

Click to view graphic
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:

Click to view graphic
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
list of DLLs loaded in process
Flow of CreateThread
A thread's life cycle starts when a program creates a new
thread. The request filters down to the Windows 2000 executive, where
the process manager allocates space for a thread object and calls the
kernel to initialize the kernel thread block. The steps in the
following list are taken inside the Win32 CreateThread function
in Kernel32.dll to create a Win32 thread. The work that occurs inside
the Windows 2000 executive are substeps of step 3, and the work that
occurs in the context of the new thread are substeps of step 7. Because
process creation includes creating a thread, some of the information
here is repeated from the earlier description of the flow of
CreateProcess.
- CreateThread creates a user-mode stack for the thread in
the process's address space.
- CreateThread initializes the thread's hardware
context (CPU architecture-specific). (For further information on the thread context block, see the Win32 API reference documentation on the
CONTEXT structure.)
- NtCreateThread is called to create the executive thread
object in the suspended state. The following steps execute in kernel
mode inside the Windows 2000 executive and kernel:
- The thread count in the process object is incremented.
- An executive thread block (ETHREAD) is created and
initialized.
- A thread ID is generated for the new thread.
- The thread's kernel stack is allocated from the nonpaged
pool.
- The TEB is set up in the user-mode address space of the
process.
- The thread start address (KiThreadStartup) is stored on the
kernel stack. (The kernel stack address is stored in the KTHREAD.) The
user's specified Win32 start address is stored in the ETHREAD
block.
- KeInitializeThread is called to set up the KTHREAD block.
The thread's initial and current base priorities are set to the
process's base priority, and its affinity and quantum are set to
that of the process. This function also sets the initial thread ideal
processor based on the process thread seed (a random number set during
execution of CreateProcess). The seed is then incremented so
that each thread in the process will have a different ideal processor,
assuming the system has more than one. KeInitializeThread next
sets the thread's state to Initialized and initializes the
machine-dependent hardware context for the thread, including the
context, trap, and exception frames. The thread's context is set up
so that the thread will start in kernel mode in SwapContext, the
context switch code. SwapContext loads the thread's context
from the thread's kernel stack, which results in the thread starting its
execution in the systemwide startup routine KiThreadStartup
(described in step 6a), the function that was stored in the stack by
step 3f.
- Any registered systemwide thread creation notification routines
are called.
- The thread's access token is set to point to the process
access token, and an access check is made to determine whether the
caller has the right to create the thread. This check will always
succeed if you're creating a thread in the local process but might
fail if you're using CreateRemoteThread to create a thread
in another process and the process creating the thread doesn't have
the debug privilege enabled.
- CreateThread notifies the Win32 subsystem about the new
thread, and the subsystem does some setup work for the new thread.
- The thread handle and the thread ID (generated during step 3)
are returned to the caller.
- Unless the caller created the thread with the CREATE_SUSPENDED
flag set, the thread is now resumed so that it can be scheduled for
execution. When the thread starts running, it executes the following
additional steps (in the context of the new thread) before calling the
actual user's specified start address. (A flowchart of this final
part of thread creation is shown in Figure 6-10.)
- KiThreadStartup lowers the thread's IRQL level from
DPC/dispatch level to APC level and then calls the system initial
thread routine, PspUserThreadStartup. The user-specified thread
start address is passed as a parameter to this routine.
- The system initial thread routine enables working set expansion
and then queues a user-mode APC to run the image loader initialization
routine (LdrInitializeThunk in Ntdll.dll). The IRQL is lowered
to 0, thus causing the pending APC to fire.
- The loader initialization routine then performs a number of additional thread-specific initialization steps, such as calling
loaded DLLs to notify them of the new thread. (The detailed steps of
the initialization of the Win32 subsystem DLLs, such as USER32,
KERNEL32, and GDI32, are beyond the scope of this book.)
- If the process has a debugger attached, the thread startup
routine suspends all other active threads in the process and notifies
the Win32 subsystem so that it can deliver the thread startup debug
event (CREATE_THREAD_DEBUG_INFO) to the appropriate debugger process.
The startup routine then waits for the Win32 subsystem to get the reply
from the debugger (via the ContinueDebugEvent function). When
the Win32 subsystem receives a reply from the debugger, it in turn
replies to the thread startup routine and all the threads are
resumed.
- Finally, the main thread begins execution in user mode at the
entry point to the image being run. Execution begins when the trap that
started the thread execution, using a trap frame (built earlier when
the kernel thread block was being initialized) that specifies previous
mode as user and the PC as the start address of the thread, is
dismissed.

Click to view graphic
Figure 6-10 In-context thread initialization
Previous | Table of Contents | Next
Last Updated: Friday, July 6, 2001 |