Special Offers

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.

More Information

About the Book
Table of Contents
Sample Chapter
Related Series
Related Books
About the Author

Support: Book & CD

Rate this book
Barnes Noble Amazon Quantum Books


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
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
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.

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
+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
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.

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 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.

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

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

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.

  1. CreateThread creates a user-mode stack for the thread in the process's address space.
  2. 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.)
  3. 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:
    1. The thread count in the process object is incremented.
    2. An executive thread block (ETHREAD) is created and initialized.
    3. A thread ID is generated for the new thread.
    4. The thread's kernel stack is allocated from the nonpaged pool.
    5. The TEB is set up in the user-mode address space of the process.
    6. 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.
    7. 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.
    8. Any registered systemwide thread creation notification routines are called.
    9. 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.

  4. CreateThread notifies the Win32 subsystem about the new thread, and the subsystem does some setup work for the new thread.
  5. The thread handle and the thread ID (generated during step 3) are returned to the caller.
  6. 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.)
    1. 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.
    2. 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.
    3. 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.)
    4. 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.
    5. 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
Click to view graphic

Figure 6-10 In-context thread initialization

Previous  |  Table of Contents   |  Next

Top of Page

Last Updated: Friday, July 6, 2001