Friday, 22 November 2013

Thread Scheduling and Priority Levels

The next few posts, or maybe this post if it doesn't get too large, will concern the matter of thread scheduling and priority levels. It will support perfectly the topic of interrupts and synchronization mechanisms which I've explained in the past.

Priority Levels

Each thread has it's own priority level and thread state. There are currently 32 different thread priority levels, which range from 0 to 32. The higher the priority, the more likely it is to run before any lower priority threads. You will need to also consider the concept of a Quantum, a Quantum is the amount of time a thread is allowed to run, before a thread of the same priority is scheduled to run. This system prevents errors like hangs and deadlocks. Remember higher priority threads can still interrupt lower priority threads, they take no consideration of a thread's quantum.

The priority levels we are most interested in, are the variable or dynamic levels (1-15), since these are mapped for the use by the Windows API.  The Windows API has 5 different priority classes: High (11 to 15), Above Normal (8 to 12), Normal (6 to 10), Below Normal (4 to 8) and Idle (2 to 6).

A process has a base priority level, which sets the starting priority of a thread, which in turn has base and current priority. The current priority decides if it can preempt another thread of a lower priority. Remember that when a thread is interrupted, a context switch is formed.


If we open Process Explorer, and then view the Threads tab of the Properties form of a process, then we can see the Base Priority, Dynamic Priority and Ideal Processor for a thread. Here, the Base Priority is 8, the Base Priority for non-system processes is always the middle (median) of a Win32 API priority class. The Dynamic Priority is the current priority of the thread, and the Ideal Processor is the processor number in which the thread would like to run on.

Although I haven't marked in it in the screenshot, we can see the Thread State which is Waiting.

IRQL Levels and Thread Priorities are also mapped together, typically the thread priority levels are between IRQL Level 0 and Level 1. This is stop threads from having higher priority over device interrupts and thread dispatching mechanisms. 

Thread States

There are 9 different thread states. The thread state of a thread can be found in Process Explorer, like in the above example, or it can also be found in WinDbg with the !thread extension.

 We can see that the thread is currently Running, this means the thread is currently executing code. The kind of function calls are evident within the call stack of the thread. Let's explain the other thread states, and how we can investigate these thread states in the WinDbg.

Ready: These threads are waiting and ready to execute, only these waiting threads are considered suitable to run by the dispatcher. We can view all the threads in the Ready state with the !ready extension.


Currently, there are no threads in the Ready state, however, if there were threads in the Ready state, they would be organized by processor number and then decreasing thread priority level. There are a few flags you can use with this extension, but I will not mention those here. Check the WinDbg documentation.

Deferred Ready:

Similar to ready, but the threads have been scheduled to run on a specific processor.

Standby:

A thread has been scheduled to run next on a specific processor. Only one thread per processor can exist in this state, and these types of threads can be preempted by higher priority threads.

Running:

As explained before, these are threads which are currently executing code. We can view the currently Running threads with the !running extension. The -ti flags have been added, since i adds idle processors and t adds a call stack for each thread.

The 16 characters highlighted in blue, indicate if the thread currently has any queued spinlocks (explained previously). O indicates that the processor is currently holding a queued spinlock, whereas, W indicates the processor is waiting to obtain a queued spinlock. This same information can be found in the _KPRCB data structure in the LockQueue field.



Waiting: A thread is in a wait state, which can be for a few different reasons: waiting upon a object for synchronization, I/O and paging or a subsystem has placed the thread into a wait state.

Gate Waiting: A thread is waiting upon a Gate dispatcher object (explained previously).

Transition: The thread is in the ready state, but it's kernel stack isn't currently in memory (it's paged out). A kernel stack is essentially the same as a user stack, but it has access to privileged areas of memory. There is a few pages of a stack in the Intel Manual in Chapter 6 Section 2.

Terminated: A thread has finished executing, the ETHREAD data structure from this point (non-paged pool), may or may not be deallocated depending upon the Object Manager's policy for this.

Initialised: A thread is being created (internal use only).



1 comment: