Multitasking with @Chip-RTOS
CONTROL AND COMMUNICATION BETWEEN TASKS Multitasking IntroductionThe @Chip-RTOS 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 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
Reasons for using MultitaskingHere are some situations where multitasking can be helpful. Different Priority WorkProbably 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: Event Triggered ActionsSometimes 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. Reasons not to use MultitaskingWhile 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. Resources Expended per TaskEach task requires its own stack space. This stack must be made large enough to support the system's interrupt handlers, some of which operate without a switch to a system stack. A minimum stack space of 1024 bytes is recommended. Critical SectionsObjects (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 on chapter Critical Sections in Programs. Top of this document IPC@CHIP Documentation Index Primary Task AttributesTwo important attributes of each task are task priority and state. Task PriorityEach task executing under the @Chip-RTOS has a unique internal task priority. This internal task priority is represented with a 16 bit value, the most significant byte of which is the user task priority which is set at task creation time or using the RTX_Change_Task_Prio() API (and is visible through the RTX_Get_Task_State() API). The hidden least significant byte of the task priority is used internally by the @Chip-RTOS to assign each task at a given user priority a unique priority by appending a sequence number to the upper byte. A task is assigned the lowest internal priority of all tasks with the same user priority whenever that task is appended to the list of tasks at that given user priority. This occurs when:
b) the task's priority is changed c) the task's time-slice period times out (see chapter Time-Slicing). Application program tasks can range in user priority from 3 to 127 (inclusive), where 3 is higher priority. Generally, user task priorities between 20 and 30 are recommended. This recommendation is based on the priority assignments of the built-in system tasks. Too high a priority for an application task may block urgent system tasks: e.g. the Ethernet receiver task. Task StateIn Table 2 below, the possible states and sub-states for @Chip-RTOS tasks are summarized. There are three primary states: Active, Blocked and Suspended.
1) - Trigger Wait sub-state is entered after RTX_Create_Task_Without_Start() or after a task has terminated.
2) - A specified time-out period in milliseconds can be applied to these states. 3) - Only the sub-state has changed here. The set of active tasks we speak of as executing concurrently. However, only a single task (at most1) is executing at any given time since the IPC@Chip contains only a single CPU. The task selected for execution by the @Chip-RTOS 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 Routine (ISR) 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.
1 Hardware interrupt service routines can momentarily suspend the executing task.
Top of this document IPC@CHIP Documentation Index DOS Program TasksEach DOS program is launched as a task under @Chip-RTOS. These tasks are created with initial priority 25 and time-slicing disabled. Within these DOS programs, users can create additional tasks with the RTX_Task_Create() or RTX_Task_Create_Without_Run() API System TimingThe @Chip-RTOS uses a 1000 Hz Real-Time Interrupt (RTI) for its time base. Therefore one millisecond is the lower resolution available for task timing. Users can install Timer Callback procedures with the RTX_Install_Timer() API. Your callback procedure is invoked within the top priority kernel task at a specified interval Time-SlicingFor the tasks created within DOS programs by the user, a time-slicing feature is available. This feature is enabled for a specific task by specifying a non-zero number of milliseconds in the time_slice member of the TaskDefBlock structure passed to the RTX_Task_Create() or RTX_Task_Create_Without_Run() functions. A time-sliced task will be permitted to execute for time_slice milliseconds (RTI ticks) after which time it will be cycled to the end of the list of tasks at this task's user priority. (The task is not charged for ticks during which it was blocked, suspended or active-pending preempted by some higher priority task.) In the special case where it is the only active task at that user priority, it would then on time-out immediately be given another time_slice milliseconds execution time budget and allowed to continue execution. Otherwise one of the other active tasks pending execution at this same user priority will begin execution and the previously executing task whose time-slice expired will be cycled to the end of the priority queue in a round-robin fashion. Note that the next task to execute may or may not be configured for time_slice operation. The time-slice operation would apply primarily to fully independent tasks which do not pass any data between each other. Time-slicing can introduce chaos into a program which could execute more orderly using explicit yields (e.g. RTX_Sleep_Time API). The extra task switching due to time-slice operation can cause critical sections to appear where they otherwise would not if the task was permitted to execute up to where the program yields voluntarily. There may be times where time-slicing is the graceful design solution, but reliance on this technique raises the suspicion that the software design was not thought out thoroughly. Also keep in mind that any task with lower user priority than the time-sliced tasks will never be executed so long as any of the time-sliced tasks are active. During execution of a time-sliced task, there is a very slight additional load placed on the system's 1000 Hz RTI. Periodic TasksPeriodic tasks can be created in either of two ways, depending on how accurate the execution period is required to be for the respective application. Roughly PeriodicThe simplest form for a periodic task uses a RTX_Sleep_Time call 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.
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. Precisely PeriodicA precisely periodic loop can be controlled with an RTOS timer. This will result in a periodic loop which on average tracks the CPU quartz clock. A RTOS timer periodically wakes up the periodic task loop as illustrated below in Figure 2.
An alternative method here would be to use RTX_Suspend_Task in Task_10Hz and RTX_Resume_Task in Timer_Callback. However, the RTX_Wakeup has the advantage of a wakeup pending flag used in the implementation which covers for the case where, due to CPU loading, the Task_10Hz may not yet have reached the RTX_Sleep_Request call before the RTX_Wakeup is executed in the Timer_Callback. In this case when the RTX_Sleep_Request is later called in Task_10Hz, the API will return immediately after clearing the task's internal wakeup pending flag, which had been set when Timer_Callback called RTX_Wakeup before Task_10Hz reached its sleep. Top of this document IPC@CHIP Documentation Index Critical Sections in ProgramsWhen 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. Example Critical SectionData 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 possible2. 2 And "if something bad can happen, it will happen".
All tasks in the @Chip-RTOS 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 has already executed 0xFFFE times and on its 0xFFFF'th execution it is preempted by Task_B at the indicated "Sensitive" point immediately after executing the ADD opcode which increments the lower half of the 32 bit up counter. At this exact point, the My_Ticker value will read zero due to the carry from the lower 16 bits not yet being applied to the upper half word. And thus Task_B lands in the Surprised_to_be_Here() function when it encounters the half updated My_Ticker reading. Protecting Critical SectionsThree methods for protecting critical sections are presented here.
2) Interrupt Masking 3) RTOS Task Switch Lock 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. Semaphore ProtectionA common way to protect critical sections is with the use of semaphores. The @Chip-RTOS 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:
Task_B - Priority 4 (very high priority) 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 60, which would mean that Task_B (priority 4) must sit waiting behind time consuming system activities such as FTP transfer (priority 41). In applications where this potential priority inversion is not acceptable, either the interrupt masking or task lock methods of protecting critical sections discussed below can be considered as an alternative to using semaphores. Interrupt MaskingInterrupt masking can in some cases be a safe alternative to using semaphores to protect critical sections. This fast method places a minimum load on the system, so is most suitable where performance is a concern. The interrupt masking method is used in the example below.
This method of protection is safe to use when the section being protected executes in very few machine cycles, as is the case in this example. The concern is the hardware interrupt latency created by this interrupt mask period. Masking interrupts for as long as 50 microseconds should be tolerable on most systems. Caution must be used to assure that interrupts are always quickly re-enabled when ever they are disabled! Note that when the nature of the two tasks competing for access to the resource (Task_A and Task_B in this example) dictates that one is higher priority than the other, only the lower priority task requires the interrupt masking. It is not possible that the lower priority task could preempt the higher priority task (unless the program design was to change task priorities dynamically somewhere). RTOS Task Switch LockA further 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, so you can include more work now within the critical section than was possible with the interrupt masking method. 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. Critical Section Protection Methods SummaryThe design trade-offs for the three methods presented above for protecting critical sections are summarized in Table 3.
Top of this document IPC@CHIP Documentation Index Control and Communication between TasksThe @Chip-RTOS provides the following mechanisms for tasks to control one another and to communicate. These interactions can either be between tasks within the same DOS application, or across applications.
The usage of these @Chip-RTOS resources is covered in depth within the on-line HTML help. Top of this document IPC@CHIP Documentation Index |