Threads, or threads of execution, are the active, code-executing objects in a system.
Threads typically respond to external interrupts or internal events. External interrupts include events such as a keystroke, a system clock tick, or any other hardware-based event. Internal events include events such as the arrival of a message at a mailbox. Threads have both a priority and an execution state, whether the thread is running or not.
There are system calls to create and delete threads, view and manipulate a thread's priority, control thread readiness, and obtain thread handles.
A thread runs a sequence of instructions that manipulate data and objects. Threads typically do the system's work. Threads are the active portion of a process.
Each process starts with an initial thread. Usually a process contains multiple threads. The initial thread, created by the RT Application Loader, initializes the process.
Place threads in the same process when they:
No hierarchy exists among RT threads: all threads in a process belong to the process, even if one thread created the others. All objects in a process belong to the process, not to the threads that created them.
The executable part of a thread is a procedure with a single parameter. A thread makes system calls and can call other procedures. You can write a procedure specifically for one thread, or share it among several threads.
The INtime kernel schedules threads so that each thread sees itself as having its code executed continuously. Depending on the needs of the application, a thread can execute in these or other ways:
Note: Since RT threads preempt Windows thread and interrupt operation, threads of this type are not recommended.
Basic thread types include:
Type | RT thread function |
---|---|
Initial | Initializes the process environment and creates one or more threads for the process. It is created by the RT Application Loader and is the first thread to run in a new process. It can exist for the life of the process, performing housekeeping and other functions, or execute once. |
Ordinary | Typically responds to internal events. Does the work of the application. |
Interrupt | Services interrupts. |
A thread inherits some attributes from its process, such as its exception handler and exception mode. It also has these attributes of its own:
Once you create a thread, the INtime kernel tracks these attributes.
When you create a thread using CreateRtThread, the INtime kernel takes resources that it needs (such as memory for a stack) from the parent process. These are the resources you specify when you create a thread:
These system calls create the three thread types:
Thread type | System call |
---|---|
Initial | RT Application Loader |
Ordinary | CreateRtThread |
Interrupt | CreateRtThread followed by SetRtInterruptHandler called from within the new thread, causes it to become an interrupt thread. |
When you delete a thread using DeleteRtThread, the thread is disassociated from its process, and any stack space created for it is reclaimed for allocation to new threads. The thread's resources are returned to the parent process. These system calls delete the three thread types:
Thread type | System call |
---|---|
Initial | DeleteRtThread |
Ordinary | DeleteRtThread |
Interrupt | ResetRtInterruptHandler; when an interrupt thread is reset, DeleteRtThread is automatically called. |
A thread exists in one of these execution states:
As an application runs, a thread often transitions from one execution state to another. A thread in any state except ready cannot run, even if it has the highest priority. You can delete a thread from any state. Creating a thread instantly makes it ready.
Transition | Reason |
---|---|
Ready to running | The thread becomes ready, has the highest priority of all ready threads and one of these characteristics:
|
Running to ready | One of these:
|
Running to asleep | One of these:
|
Asleep to ready, or asleep-suspended to suspended | One of these:
|
Running to suspended | The thread suspends itself. |
Ready to suspended or asleep to asleep suspended. | The thread is suspended by another thread. The suspension depth increases by one each time the thread suspends. |
These system calls may cause execution state transitions:
To avoid problems, ensure that threads use only SuspendRtThread to suspend themselves. Unpredictable results may occur when a thread uses SuspendRtThread to suspend another thread for synchronization. Whenever possible, use a semaphore or mailbox to synchronize threads instead.
Each time you call SuspendRtThread, the suspension depth increases by one. The INtime kernel keeps track of the thread's suspension depth, up to 255. The larger the number of calls made, the greater the depth. When the suspension depth is greater than 0, you must make a corresponding number of ResumeRtThread calls to bring the thread out of suspension. You cannot obtain the suspension depth of a thread from the INtime kernel.
You must make multiple calls to ResumeRtThread from another thread to make a thread ready when the suspension depth is greater than 1. Each time you call ResumeRtThread, the suspension depth decreases by one. You do not need to make the ResumeRtThread calls from the thread that suspended the thread.
The INtime kernel handles thread scheduling based on priority. What process a thread belongs to has no effect on scheduling. The INtime kernel always executes the highest priority ready thread until it is interrupted, preempted by a higher priority ready thread, puts itself to sleep, suspends itself, or completes and deletes itself.
The priority of a thread determines its importance in relation to other threads and interrupts. You specify a thread's static priority when you create it or later, if you need to, using SetRtThreadPriority. The thread's priority can be adjusted by the INtime kernel when using a region (described later); this is called dynamic priority. The priority is an integer value from 0 through 254, with 0 as the highest priority.
Range | Used for |
---|---|
0-130 | Used by the INtime kernel for servicing external interrupts.
Let the INtime kernel assign these levels to interrupt threads, based on the order you attached your external interrupt sources to the PICs. In general, don't create threads in this range. A thread that runs in this range masks one or more interrupt, meaning response time to external interrupts is slower and interrupts may be lost. |
131-253 | Use for threads that handle internal events, like message passing and computation. Typically, you don't assign a thread to every level in this range.
You might put important threads, such as mailbox managers, in the range 140-159. Leave some gaps if you plan to add features and threads later on. Round-robin scheduling begins, by default, at 141. |
You create interrupt threads using CreateRtThread, assign any priority acceptable for the thread's process, then associate the thread to an interrupt handler using SetRtInterruptHandler with a byMaxInt
value other than 0 (zero).
You may need to use SetRtProcessMaxPriority if the priority of the interrupt thread (as set by the RT kernel) will be higher (numerically larger) than the process' current maximum priority.
See also: About Interrupts
If you assign the same priority to more than one thread, the first thread that runs may continue running indefinitely. Use round-robin scheduling to ensure that each thread gets an opportunity to run.
To set up round-robin scheduling, set these parameters using the INtime Configuration utility:
By default, the threshold priority below which round-robin scheduling occurs, is set at 140 and the amount of time each thread gets to run is 50 milliseconds.
This illustrates round-robin scheduling and the priority threshold:
Note: the minimum thread priority is 254 on INtime for Windows systems
This shows how round-robin scheduling works with priority-based scheduling.
The priority threshold in the figure is 200. The standard clock "tick" is 10ms and the default round-robin interval is 50ms. There are threads A, B, C, and D with these priorities in the figure:
Priority | RT threads |
---|---|
130 | Thread C |
140 | Thread D |
200 | Threads A and B |
Several system calls allow you to check thread state, the CPU, and other high-level system information. These calls can be useful at any time but are particularly useful after exceptions occur. You can use these calls to:
RT threads communicate with each other to exchange data and synchronize execution. The INtime kernel provides these exchange objects used to exchange data and synchronize threads:
See also: Developing INtime applications
For threads to share exchange objects, you must create them, then catalog them from the creating thread using CatalogRtHandle. Then other threads can use LookupRtHandle to get the object's RT Handle so they can access the object.
RT threads commonly use mailboxes to request a service from another thread. The client thread sends a message that specifies parameters for the server call; the service thread receives the message and provides the specified server. The server thread can return results of the server, if any, to the client using a mailbox.
Two types of mailboxes are available for communications between threads: object mailboxes and data mailboxes. Object mailboxes are used to pass 2 byte object handles from thread to thread. Data mailboxes are used to pass up to 128 bytes of data from thread to thread. The data mailbox, however, has some disadvantages. A message arriving at a mailbox where no thread waits is copied by the RT kernel into buffer space in the mailbox message queue; this is the first copy. When a thread arrives to receive the message, the message is copied by the INtime kernel from the mailbox queue into the thread's message buffer; this is the second copy. There is no copying with object mailboxes, which pass only RT handles.
Both semaphores and regions provide mutual exclusion to shared resources.
You can use a semaphore with more than one unit as a general purpose counter to synchronize the actions of multiple threads. A semaphore with one unit can also provide mutual exclusion of threads, but without the dynamic priority adjustment and deletion protection provided by regions. Semaphores do not enforce synchronization or mutual exclusion. Semaphores provide more flexibility in waiting for access to resources than regions because you can specify a time limit for waiting in the thread queue. A thread using a region cannot set a time limit.
RT threads use regions to enforce mutual exclusion to a specific resource or data. Only one thread at a time can control a region. The thread that controls the region cannot be deleted or suspended. If the region has a priority-based thread queue, the thread in the region has its priority dynamically adjusted making its priority always at least as high as the highest priority thread waiting in the queue. When a thread gains control of several regions, then gives up control of those regions one at a time, the thread's dynamic priority does not readjust to its static priority until the thread gives up control of the last region; this improves performance.
If a thread makes a request that cannot be filled immediately and the thread can wait, the thread stops executing, goes into a thread queue, and goes to sleep. More than one thread can wait in a queue. Using one of these queue methods when you create the exchange object, you specify how the RT kernel places threads in the queue.
You specify the maximum length of time the thread can wait in the queue for an event.
Besides the thread queues maintained by all exchange objects, mailboxes also have message queues to hold incoming messages for threads. Message queues are always FIFO-based.
This lists common thread tasks and the system calls that perform the operations.
To . . . | Use this system call . . . |
---|---|
Create a thread | CreateRtThread |
Delete a thread | DeleteRtThread |
Put a thread to sleep | RtSleep RtSleepEx |
Suspend thread | SuspendRtThread |
Resume a thread | ResumeRtThread |
Return information about the specified thread's stack use | GetRtStackInfo |
Modify a thread's priority | GetRtThreadPriority SetRtThreadPriority |
Obtain a specific handle | GetRtThreadHandles |
Dynamically change the maximum priority of threads in a process | SetRtProcessMaxPriority |
Manage and retrieve thread accounting information | GetRtThreadAccounting SetRtSystemAccountingMode |
Get high-level thread information | GetRtThreadInfo |
Get CPU information | GetRtThreadState |
This shows the order to make thread system calls and lists calls that threads frequently use: