IPC@CHIP® RTOS-LNX – API Documentation
1. Multitasking introduction
1.1 Reasons for using Multitasking
1.1.1 Different Priority Work
1.1.2 Event Triggered Actions
1.2 Reasons not to use Multitasking
1.2.1 Resources Expended per Task
1.2.2 Critical Sections
2. Primary Task Attributes
2.1 Task Priority
2.2 Task State
3. Application (DOS) Program Tasks
4. System Timing
4.1 Periodic Tasks
4.1.1 Roughly Periodic
4.1.2 Precisely Periodic (with RTOS timer)
5. Critical Sections in Programs
5.1 Example Critical Section
5.2 Protecting Critical Sections
5.2.1 Semaphore Protection
5.2.2 RTOS Task Switch Lock
5.2.3 Critical Section Protection Methods Summary
6. Control and Communication between Tasks
The @CHIP-RTOS-LNX provides for a multitasking operation. A task provides a thread of execution. Each task has its own context, including an instruction pointer and program stack. Each task in the @CHIP-RTOS-LNX has a unique priority of execution, providing a preemptive form of multitasking. By executing multiple tasks at the same time, various activities can be performed concurrently.
|API||Application Programming Interface|
|ISR||Interrupt Service Routine|
|RTOS||Real-Time Operating System|
|TCP/IP||Transport Control Protocol / Internet Protocol|
Here are some situations where multitasking can be helpful.
Probably the most compelling reason for using multitasking is when required activities have different priorities. For example, an application with a user interface may need to be responsive to keyboard entry from a user console while at the same time the program is conducting a time consuming calculation or data base search. A low priority task could perform the calculation/data base search while a high priority task periodically polls (or sleeps waiting) for keyboard input. Typically there would be some communication between these two tasks, such as having the keyboard task cancel the background data base search in the event that the user presses the escape key.
In more involved applications, there might be a complete spectrum of concurrent activity at different priorities. Just for an example, the set of tasks within a real-time application might be as follows. Listed in priority order:
Sometimes an activity is required only upon an event such as some data becoming available. For these situations a task could be made to block, awaiting the event or data arrival. For example a task could make a blocking call on a TCP/IP port with the recv() library function. The task sleeps until some data arrives at the respective socket, or until a prescribed time-out period expires.
Note that polling can usually be an alternative software design approach, as opposed to applying a dedicated task to each event or data source.
While multitasking can be an ideal solution for some applications, it also has its disadvantages. Some of these disadvantages are noted here. The point is that you should not complicate an application with multiple tasks unless to do so truly makes your application simpler.
Each task requires its own stack space. A minimum stack space of 32768 bytes is enforced by the RTX_NewTask() API, which fails to create a task if the user provided stack space is less than this amount. Note that inside API calls (system calls) and interrupt service routines, the system will switch to a different stack space. So the user stacks do not need to provide space for these purposes.
Objects (memory or devices) shared between tasks can lead to critical sections. A critical section is a section of code in a task which is sensitive to order of execution relative to code in some other task, such that there is a possible (prior to adding the necessary protection) firing order of the two instruction streams which leads to incorrect results.
When you design your application with only a single task, it is safe to say you have no critical sections. When more than one task is used, you must beware. Due to the importance of understanding and recognizing critical sections in multitasking systems, this topic is covered in more detail below.
Two important attributes of each task are task priority and state.
Each task executing under the @CHIP-RTOS-LNX has a task priority. This task priority is set at task creation time or using the RTX_Task_Priority() API (and is visible through the RTX_Get_Task_State() API).
Application program tasks can range in user priority from 0 to 90 (inclusive), where 0 is higher priority.
In Table 2 below, the possible states and sub-states for @CHIP-RTOS-LNX tasks are summarized. There are three primary states: Active, Blocked and Suspended.
|State||Sub-State||Notes and State Transitions|
|Active||Executing||Highest priority non-waiting task|
|Pending||In queue ordered by task priority|
|Blocked||Trigger Wait1||RTX_Trigger_Task() –> Active|
|Semaphore Wait2||RTX_Signal_Sem(), RTX_Release_Sem() –> Active|
|Event Group Wait2||RTX_Signal_Events() –> Active|
|Message Exchange Wait2||RTX_Send_Msg() –> Active|
|Asleep2||RTX_Wakeup() –> Active|
|Suspended||Free to run||RTX_Resume_Task() –> Active|
|Trigger Wait1||RTX_Resume_Task() –> Blocked|
RTX_Trigger_Task() –> Free to run3
|Semaphore Wait2||RTX_Resume_Task() –> Blocked|
Granted semaphore –> Free to run3
|Event Group Wait2||RTX_Resume_Task() –> Blocked|
RTX_Signal_Events() –> Free to run3
|Message Exchange Wait2||RTX_Resume_Task() –> Blocked|
RTX_Send_Msg() –> Free to run3
|Asleep2||RTX_Resume_Task() –> Blocked|
RTX_Wakeup() –> Free to run3
The set of active tasks we speak of as executing concurrently. However, only a single task is executing at any given time since the IPC@CHIP® contains only a single CPU. The task selected for execution by the @CHIP-RTOS-LNX will always be the highest priority of the tasks that are in the active state.
The C-library routines which force a task to exit the Blocked and Suspended states when called by some other task or Interrupt Service Procedure (ISP) are stated in the table. The two inactive states, Blocked and Suspended, differ in their exit state transitions. The RTX_Suspend_Task() API transitions a task into the Suspended state.
Each application (DOS) program is launched as a task under @CHIP-RTOS-LNX. These tasks are created with initial priority 50 and time-slicing disabled. Within these programs, users can create additional tasks with the RTX_NewTask() API.
The @CHIP-RTOS-LNX uses at default a 1000 Hz Real-Time Interrupt (RTI) for its time base. Therefore one millisecond is the lower resolution available for task timing. (The Real-Time Interrupt frequency and the time base can be changed with a CHIP.INI entry.)
Users can install Timer Callback procedures with the RTX_NewTimerCB() API. Your callback procedure is invoked within a high priority system task at a specified interval.
Periodic tasks can be created in either of two ways, depending on how accurate the execution period is required to be for the respective application.
The simplest form for a periodic task uses the RTX_Sleep_Time() API within a loop as shown below in Figure 1. The period of this loop will not be exact, but would be close enough for many applications.
SLEEP_10HZ constant used in this example is adjusted based on the expected system loading, including CPU dwell within the
Activity_10Hz() procedure. This would require some timing measurements to be made during the program's development, if this extra accuracy is of any concern.
A precisely periodic loop can be controlled with an RTOS timer. This will result in a periodic loop. A RTOS timer periodically wakes up the periodic task loop as illustrated below in Figure 2.
When multitasking is used, the programmer must beware of critical sections which may occur between threads.
Critical sections can be protected with proper design. The important step at program design time is to identify these code sections, which are not always obvious. Most programmers are well aware of what critical sections are. However, due to their importance when multitasking, a simple example is provided here for emphasis.
Data sharing between tasks leads to critical sections when the shared data object can not be read or written atomically, within a single non-interruptible machine instruction.
After a brief review of the C code in the above example, the C programmer might suspect a hardware problem if the Surprised_to_be_Here() function was to ever execute. However, with a closer examination of the resulting machine assembly code and multitasking consideration, we will see that execution of the Surprised_to_be_Here() function is possible. And as the rule goes, "if something bad can happen, it will happen".
All tasks in the @CHIP-RTOS-LNX system have a unique task priority. So in the above example either Task_A can potentially interrupt execution of Task_B, or visa-versa, depending on the assigned priorities. Consider the case where priority of Task_A is lower (higher numerically) than priority of Task_B, such that Task_B can preempt Task_A. This case can lead to execution of the Surprised_to_be_Here() function under the following circumstances.
Let us say that Task_A on its first execution is preempted by Task_B at the indicated "Sensitive" point immediately after executing the "STW r11" opcode which will be writing a zero into the MSH of the
My_Ticker 64 bit integer. At this exact point, the
My_Ticker value will read zero due to that the pending write of 0xFFFFFFFF from r12 into the LSH of
My_Ticker is still pending. And thus Task_B lands in the Surprised_to_be_Here() function when it tests the partially updated
Two methods for protecting critical sections are presented here.
Each method has its advantages and limitations, which are summarized at the end of this discussion. The choice of which method to use will depend on the design situation.
A common way to protect critical sections is with the use of semaphores. The @CHIP-RTOS-LNX provides resource semaphores which provide a mutually exclusive access to a resource or data object.
The example defective code from Figure 3 above can be corrected with the use of a resource semaphore as shown below.
Now the Surprised_to_be_Here() function will never be executed.
A potential disadvantage to using semaphores is a possible task priority inversion, where a high priority task is blocked by a lower priority task as it awaits the semaphore. To illustrate this point, consider an example where task priorities are designed as follows:
If Task_A is suspended while it has possession of the semaphore, Task_B will have to wait if it then tries to access the same semaphore at that moment. This wait is effectively at the very low priority 90, which would mean that Task_B (priority 4) must sit waiting behind time consuming system activities such as FTP transfer (priority 90). In applications where this potential priority inversion is not acceptable, the task lock methods of protecting critical sections discussed below can be considered as an alternative to using semaphores.
The priority inversion problem can also be avoided in some cases by using the priority inheritance type of resource semaphore. The simple critical section in the above example would be such a case, since the work done within the critical section is very limited (no blocking on other objects or sleep).
A alternative to using semaphores to protect critical sections is to prevent task switching within the critical section. This method is shown in the example below.
Hardware interrupts continue to be serviced during the task lock. However, the task lock period should still be keep to some reasonable small amount of time. Note that task locks also inhibit all system timer activity.
The design trade-offs for the two methods presented above for protecting critical sections are summarized in Table 3.
|Semaphore||A long duration of critical section does not adversely affect portions of system not accessing the semaphore.||Can result in a priority inversion.|
|Task Lock||No priority inversion.||Impact on system operation becomes questionable if task lock duration exceeds around 400 us.|
The @CHIP-RTOS-LNX provides the following mechanisms for tasks to control one another and to communicate. These interactions can either be between tasks within the same application, or across applications.