IPC@CHIP® RTOS-LNX – API Documentation

Header image

Main page


RTOS Multitasking introduction

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

1. Multitasking introduction

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.

Abbreviations Used

AcronymStands For
APIApplication Programming Interface
ISRInterrupt Service Routine
RTIReal-Time Interrupt
RTOSReal-Time Operating System
TCP/IPTransport Control Protocol / Internet Protocol
Table 1 ) Abbreviations

1.1 Reasons for using Multitasking

Here are some situations where multitasking can be helpful.

1.1.1 Different Priority Work

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:

1.1.2 Event Triggered Actions

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.

1.2 Reasons not to use Multitasking

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.

1.2.1 Resources Expended per Task

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.

1.2.2 Critical Sections

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.


2. Primary Task Attributes

Two important attributes of each task are task priority and state.

2.1 Task Priority

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.

2.2 Task State

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
Table 2 ) @CHIP-RTOS-LNX Task States

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.


3. Application (DOS) Program Tasks

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.


4. System Timing

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.

4.1 Periodic Tasks

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.

4.1.1 Roughly Periodic

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.

Example:
#define SLEEP_10HZ (90) // Assuming 10 ms CPU load per 100 ms
void Task_Roughly_10Hz(void)
{
while (1)
{
Activity_10Hz() ; // Get here about each 100 ms
RTX_Sleep_Time(SLEEP_10HZ) ;
}
}
Figure 1 ) Sleep Based Periodic Loop

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

4.1.2 Precisely Periodic (with RTOS Timer)

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.

Example:
static int TaskID_10Hz ;
void huge Task_10Hz(void)
{
while (1) // 10 Hz loop
{
Activity_10Hz() ; // Get here each 100 ms.
}
}
static void huge Timer_Callback(void) // Clean 10 Hz
{
RTX_Wakeup (TaskID_10Hz) ;
}
static TimerID ;
static TimerProc_Structure Timer_Spec =
{
&TimerID,
Timer_Callback,
0,
{ '1', '0', 'H', 'z'},
100 // .interval = 100 milliseconds
} ;
void Initialize(void)
{
extern TaskDefBlock Task_10Hz_Def ;
RTX_Create_Task (&TaskID_10Hz, &Task_10Hz_Def) ;
RTX_Install_Timer (&Timer_Spec) ;
RTX_Start_Timer (TimerID) ;
}
Figure 2 ) Timer Based Periodic Loop

5. Critical Sections in Programs

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.

5.1 Example Critical Section

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.

Example:
static QINT My_Ticker = (0x1 << 32) ;
void Task_A(void)
{
if (My_Ticker > 1)
{
My_Ticker-- ;
// Compiler's machine code:
// LWZ r11,My_Ticker(r13)
// LWZ r12,My_Ticker+0x4(r13)
// ADDIC r12,r12,-1
// ADDME r11,r11
// STW r11,My_Ticker(r13)
// --> Sensitive to task switch here!
// STW r12,My_Ticker+0x4(r13)
}
}
void Task_B(void)
{
if (My_Ticker == 0)
{
Surprised_to_be_Here() ; // How did this happen?
}
}
Figure 3 ) Critical Section Example

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

5.2 Protecting Critical Sections

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.

5.2.1 Semaphore Protection

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.

Example:
static QINT My_Ticker = (0x1 << 32) ;
void Task_A(void)
{
if (My_Ticker > 1)
{
RTX_Sem_Wait(semID) ;
My_Ticker-- ;
RTX_Release_Sem(semID) ;
}
}
void Task_B(void)
{
QINT ticker ;
RTX_Sem_Wait(semID) ;
ticker = My_Ticker;
RTX_Release_Sem(semID) ;
if (ticker == 0)
{
Surprised_to_be_Here() ; // Not going to happen.
}
}
Figure 4 ) Protected Critical Section with Semaphore

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

5.2.2 RTOS Task Switch Lock

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.

Example:
static QINT My_Ticker = (0x1 << 32) ;
void Task_A(void)
{
if (My_Ticker > 1)
{
// Needed if Task_A is lower priority
My_Ticker-- ;
}
}
void Task_B(void)
{
QINT ticker ;
// Needed if Task_B is lower priority
ticker = My_Ticker ;
if (ticker == 0)
{
Surprised_to_be_Here() ; // Not going to happen.
}
}
Figure 5 ) Protected Critical Section with Task Lock

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.

5.2.3 Critical Section Protection Methods Summary

The design trade-offs for the two methods presented above for protecting critical sections are summarized in Table 3.

Method Advantage Limitations
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.
Table 3 ) Critical Section Protection Methods

6. Control and Communication between Tasks

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.






Top of page | Main page

Copyright © 2017 Beck IPC GmbH
Generated on Tue Dec 12 2017 09:23:36 by Doxygen 1.8.13