In these series of posts i would like to walk you through any thing you need to know about Threads in windows and .NET. There are a few essential concepts and vocabularies that should be understood in order to get a better understanding about threading:
- Processes and threads introduction.
- Multi threading in .net.
- Thread Pool.
- OS Multitasking.
- OS Scheduling.
In part one of these series i'll try to cover Processes:
Back when i started programming I was always thinking about how operating system manage application, I remember at first days of programming I was trying write an app in C++ that locates the life variable of players's health (Counter strike 1.6) in memory and increases it :ِD (I'm actually a pro in counter strike).
As you know I've never succeeded but that mistake forced me to investigate very much more about memory management. The question was still living in my mind: why this thing dose not work? I'll explain why it did not.
A process is an object created by operating system and its job is to provide all resources an app needs in order to run properly.
A new process is created for every app that is going to run using CreateProcess (there are other functions although) function documented by Microsoft, this function returns a bool value which shows weather it succeeded or no.
When a process is terminated?
A process is terminated when:
- All threads inside the process terminate.
- Any of its threads make a call to ExitProcess function.
- A call be made to TerminateProcess function from the outside of the process.
Now, what a process contain or provide? here is a small list:
- Private virtual address space.
- Private process handles table.
- Access token. (determines the security context in which that code is being executed in that process)
After exploring these topic about Processes, we'll jump right into the code and examine them.
1- Virtual address space
Each process will see a flat linear memory which then gets mapped physical memory. This is in fact an abstraction layer on top of physical memory, (that's why my app was not working :D) because the process accesses the data in memory by its pointers regardless of where it actually lives. It might be RAM or some thing called a page file. You can fine more information about page files on msdn in here. The memory management is done by windows memory manager.
(for more about information about windows memory management function click here)
You can see the processes and virtual memory being used by each process in task manager:
Processes currently running are shown in windows task manager along with its details
The memory mapping
The size of this virtual memory is 2 GB on 32 bit and 8 TB on 64 bit windows.
As you might know, a Thread is inside the Process and is the one who is executing code. In a very simple word a thread is some sort of code stream where one side is the code and other side is the processor of the machine.
A process may contain a hundred of threads, it can have several reasons which we will discuss in up coming parts. each thread will run on a different processor or at least different cores of a processor.
Let's go back to windows task manager and see what threads are currently running.
A very brief calculation show 152 threads running currently on my machine. I'm sure that my computer dose not have 152 cores, so what's going on?!!
Well, when there is enough processor each thread will run on its own core but when there is not enough cores whats happen is that windows thread scheduler lets every thread to run for a little bit of time and after saving its state then another thread will take its place on the processor.
For windows this time is approximately 20 ms but it varies between different Operating systems, this process is called context switching.
Context switches are usually computationally intensive, and much of the design of operating systems is to optimize the use of context switches. Switching from one process to another requires a certain amount of time for doing the administration - saving and loading registers and memory maps, updating various tables and lists etc.
A context switch can mean a register context switch, a task context switch, a stack frame switch, a thread context switch, or a process context switch.
Now, what a thread contain or actually has?
- Thread context
- Security token
- Scheduling state
1- Thread Context
The thread context includes the thread's set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread's process. Threads can also have their own security context, which can be used for impersonating clients temporarily.
As we have seen a thread context consists of a set of machine registers, user stack and a kernel stack.
- Machine register set: determines the processor register that the thread is currently using.
- Kernel mode stack and user mode stack:
When a context switching Occurs the information the information about the thread will be saving in its stack. In order to isolate the kernel code from faults, when executing in user mode, kernel memory is not accessible even though if it is mapped. Eventually whats the difference between the two stacks in simple words?
Basically user mode stack is used to contain local variable and argument passing to methods plus next execution target after the current method returns. on the other hand the kernel mode stack is used when application code (user mode) calls kernel code, at this point windows copies arguments from user mode stack to kernel mode stack so it can verify them and make sure they are not changed any more since application does not have access code to kernel mode stack.
Thread environment block:
Or for short TEB, this is a block of memory (4 KB) initialized in user mode which holds some information about the thread.
There is a really cool debugger command in here !teb that shows information that a particular TEB holds. Some of the most important of them are:
- Thread local storage
- Exception List
- Exception code
- ... .
2- thread Security token
This item is explained in downer parts of this post because its same as Process access token.
3- Scheduling state
Every thread can have one of these following states:
| ||state |
|Aborted ||The thread state includes AbortRequested and the thread is now dead, but its state has not yet changed to Stopped. |
|AbortRequested ||The Thread.Abort method has been invoked on the thread, but the thread has not yet received the pending System.Threading.ThreadAbortException that will attempt to terminate it. |
|Background ||The thread is being executed as a background thread, as opposed to a foreground thread. This state is controlled by setting the Thread.IsBackground property. |
|Running ||The thread has been started, it is not blocked, and there is no pending ThreadAbortException. |
|Stopped ||The thread has stopped. |
|StopRequested ||The thread is being requested to stop. This is for internal use only. |
|Suspended ||The thread has been suspended. |
|SuspendRequested ||The thread is being requested to suspend. |
|Unstarted ||The Thread.Start method has not been invoked on the thread. |
|WaitSleepJoin ||The thread is blocked. This could be the result of calling Thread.Sleep or Thread.Join, of requesting a lock for example, by calling Monitor.Enter or Monitor.Wait or of waiting on a thread synchronization object such as ManualResetEvent. |
So in what order threads are running in our machines?
Thread Priority: threads are scheduled according to their priority property, this property can have a value starting from 0 and ending to 31.
thread's base priority level and Process Priority class:
Process priority class includes a range of thread priority levels. This value together with each thread's priority will form the each thread's base priority which eventually determines execution priority of the thread.
Here I want to show a sample combination of these two:
|Process priority class ||Thread priority level ||Base priority |
|IDLE_PRIORITY_CLASS ||THREAD_PRIORITY_IDLE ||1 |
|THREAD_PRIORITY_LOWEST ||2 |
|THREAD_PRIORITY_BELOW_NORMAL ||3 |
|THREAD_PRIORITY_NORMAL ||4 |
|THREAD_PRIORITY_ABOVE_NORMAL ||5 |
|THREAD_PRIORITY_HIGHEST ||6 |
|THREAD_PRIORITY_TIME_CRITICAL ||15 |
There is on more thing that i would like to discuss about threads in here and that is Thread Message queue:
In computer science, message queues are software-engineering components used for inter-process communication (IPC), or for inter-thread communication within the same process.
So first, lets see what is a message queue:
By default when a thread is created it is assumed to be a worker thread but, if a thread make a call to any GDI32.dll function windows will create a message queue for that particular thread.
Since windows-based apps are event driven they way they obtain input from the user is different than MS-DOS apps where the apps where making calls to C run-time library functions in order to get inputs. instead they for the system to pass input to them.
Every window in windows operating system is associated with a window procedure which is a function that processes all messages sent or posted to all windows and pass control to system.
Once a window stops responding to posted message form system, it will be considered as not responding which is bad for us as developers because here comes that bad part, after window has been considered as not responding, system will replace it with a ghost windows with same x and y positions and lets the user to actually close that.
What we got from this paragraph is that if a thread is blocked and the UI is on that thread it means that our app is now not responding and is in under the risk of being killed by the user, in most worse case get "End Task" from task manager.
This situation is one of the most important use cases of multiple threads in an application.
3- Process Handle table
It holds all the kernel objects that have been opened by the process (actually running code in the process). For those dose not know about handles:
An object is a data structure that represents a system resource, such as a file, thread, or graphic image. An application cannot directly access object data or the system resource that an object represents. Instead, an application must obtain an object handle, which can be used to examine or modify the system resource.
Each handle has an entry in an internally maintained table. These entries contain the addresses of the resources and the means to identify the resource type.
Object - handle mechanism has two huge benefit:
First, it makes sure that the original object interface is maintained while Microsoft update system functionality as newer versions are coming.
Second, it provides a protection layer since it has its own ACL(access-control list). the ACL specifies actions that a particular process can perform on the object. every time a handle is created to a specific object the system will check the ACL.
After the event object has been created, the application can use the event handle to set or wait on the event. This handle remains valid until the application closes the handle is terminated.
The process handle table is used only for kernel object not for user objects or GDI objects.
Kernel objects are meant to perform system operation such as memory management, Process execution and IPC (inter-process communications).
Handles can be created to existing kernel objects by any process (even ones created by another process), only in case that particular process knows the name of the object and has security access to the object.
Oh, I said security access. Every object has its own set of access rights. For example file handles might have read or write access or even both.
|Kernel object ||Creator function ||Destroyer function |
|Access token ||CreateRestrictedToken, DuplicateToken, DuplicateTokenEx,OpenProcessToken, OpenThreadToken ||CloseHandle |
|Change notification ||FindFirstChangeNotification ||FindCloseChangeNotification |
|Communications device ||CreateFile ||CloseHandle |
|Console input ||CreateFile ||CloseHandle |
|Console screen buffer ||CreateFile ||CloseHandle |
|Desktop ||GetThreadDesktop ||Applications cannot delete this object. |
|Event ||CreateEvent, CreateEventEx, OpenEvent ||CloseHandle |
|Event log ||OpenEventLog, RegisterEventSource, OpenBackupEventLog ||CloseEventLog |
|File ||CreateFile ||CloseHandle, DeleteFile |
3- Access tokens
An access token is supposed to describe the security context of a particular process or a thread because often the security token of the process is used by its threads.
Once the user logs into system, this token is produced and then every process executed after has a copy of this access token.
So when does it act?
The system use this token for identification when a thread tries to interact with a securable object.
The information an access token contains is listed blow as Microsoft documented:
- The security identifier (SID) for the user's account
- SIDs for the groups of which the user is a member
- A logon SID that identifies the current logon session
- A list of the privileges held by either the user or the user's groups
- An owner SID
- The SID for the primary group
- The default DACL that the system uses when the user creates a securable object without specifying a security descriptor
- The source of the access token
- Whether the token is a primary or impersonation token
- An optional list of restricting SIDs
- Current impersonation levels
- Other statistics
Types of Access Tokens
There are two types of access tokens, one is Primary Token and the other one is called Impersonation token.
Primary token is set to each process by default and contains information of the currently logged on user. the impersonation token is used when a thread needs the identity of a user other than the one currently logged on to the system. this is useful on server application when they need client identity to perform a specific task.
The impersonation token can be set by a call to SetThreadToken windows function.
Can a thread have both access tokens? Yes of course.
Following functions can be used in order to manipulate access tokens:
|Function ||Description |
|AdjustTokenGroups ||Changes the group information in an access token. |
|AdjustTokenPrivileges ||Enables or disables the privileges in an access token. It does not grant new privileges or revoke existing ones. |
|CheckTokenMembership ||Determines whether a specified SID is enabled in a specified access token. |
|CreateRestrictedToken ||Creates a new token that is a restricted version of an existing token. The restricted token can have disabled SIDs, deleted privileges, and a list of restricted SIDs. |
|DuplicateToken ||Creates a new impersonation token that duplicates an existing token. |
|DuplicateTokenEx ||Creates a new primary token or impersonation token that duplicates an existing token. |
|GetTokenInformation ||Retrieves information about a token. |
|IsTokenRestricted ||Determines whether a token has a list of restricting SIDs. |
|OpenProcessToken ||Retrieves a handle to the primary access token for a process. |
|OpenThreadToken ||Retrieves a handle to the impersonation access token for a thread. |
|SetThreadToken ||Assigns or removes an impersonation token for a thread. |
|SetTokenInformation ||Changes a token's owner, primary group, or default DACL. |
OK, we are done with part 1 in next post we'll start to talk about threading in .NET framework.