How RTOS Divides Tasks

1. Definition of Tasks

In embedded development, we face situations with a single CPU, and during this development process, we may involve bare-metal development or development with an operating system. In bare-metal development, the entire system is viewed from a modular perspective, meaning that after the system finishes running one module, it then runs another module.

However, in the presence of an operating system, we divide the system’s processing into tasks, which are executed concurrently. The execution of each task appears independent; from a macroscopic perspective, multiple tasks simultaneously occupy the CPU, much like a multi-CPU system. In a true multi-CPU system, each CPU has its own set of registers. To achieve such a multi-CPU operation mechanism, the operating system constructs a ‘virtual CPU’ for each task using a dedicated memory space to store the information of each CPU’s registers. This dedicated memory space is known as the ‘task stack’; the number of task stacks corresponds to the number of tasks.

The operating system must possess detailed dynamic information about each task to efficiently switch between them, meaning that task scheduling requires detailed information about each task. Therefore, the operating system establishes records for each task to document these details, which is known as the ‘Task Control Block’. Additionally, each task has its own content, which is the task function written by the developer to implement the necessary functionalities for that task.

In summary, we know that each task will have the following three characteristics:

  • Task Function

  • Task Stack

  • Task Control Block

2. Characteristics of Tasks

The basic characteristics of tasks are independence, concurrency, and dynamism. This is also the essential difference between tasks and program modules; program modules are typically used in bare-metal development without an operating system.

1. Independence

In traditional program modules, one module can call another. However, in an operating system, each task has its own CPU, meaning that a task cannot call another task like it would a subroutine. This illustrates the concept of independence.

Another aspect of independence is that when tasks pass information, the main module passes information to the submodule’s parameters as actual parameters, and the submodule returns results to the main module as return values. However, information transfer between tasks requires the assistance of a third party, such as operating system-related semaphores, mailboxes, and message queues. This reliance on a third party for information transfer results in asynchronous communication, which is another manifestation of task independence.

2. Concurrency

Task A completes between times t1 and t4, while Task B completes between times t2 and t3, with overlapping execution periods. This execution style is referred to as ‘concurrent’ execution. In a multi-CPU system, concurrently executing tasks indeed have their own CPUs, and their execution states reflect true concurrent execution, as illustrated below:

How RTOS Divides Tasks
Concurrent Execution of Tasks

In a single CPU system, the task scheduling function of the operating system addresses this issue. The kernels of embedded real-time operating systems typically use ‘preemptive’ task scheduling algorithms, meaning that a ready high-priority task can preempt a running low-priority task’s execution rights and enter a running state, as illustrated below:

How RTOS Divides Tasks
Concurrent Execution of Tasks in a Single CPU

3. Dynamism

The state of a task is dynamically changing, meaning that these tasks are not always ready to run. Tasks can exist in one of five different states, represented in the state diagram below.

How RTOS Divides Tasks
Task State Diagram

3. Division of Tasks

Dividing tasks for a specific embedded application system is crucial for designing real-time operating system application software. The rationality of task division directly affects the quality of software design. When tasks are divided rationally, software design becomes comparatively simple and efficient; otherwise, it may become complicated or even fail. When dividing tasks, the following principles should be adhered to:

  • The primary goal is to meet real-time performance indicators.

  • Even in the worst-case scenario, functionalities requiring real-time performance must be achievable.

  • The number of tasks must be reasonable.

  • When the number of tasks is high, each task can implement simpler functionalities, making task design easier. However, increased task scheduling operations and communication activities between tasks can decrease system efficiency and increase resource overhead. Conversely, when the number of tasks is low, each task must implement more complex functionalities, but it can reduce the amount of inter-task communication and lessen the burden on the system, thereby reducing resource overhead. Thus, the design of the number of tasks is critical.

  • Simplify the software system.

  • A system must implement functionalities, and in addition to designing its core functions, it also requires time management, task synchronization, task communication, memory management, and other functionalities. Rationally dividing tasks can lower the service requirements for the operating system, simplify system software design, and reduce the scale of software code.

  • Reduce resource requirements.

  • By reducing or simplifying synchronization and communication functions between tasks, the corresponding data structures’ memory models can be reduced, thus lowering the demand for system resources.

To make task division more rational, the following methods are typically employed:

4. Device-Dependent Task Division

Assuming we have a system with input and output capabilities:

How RTOS Divides Tasks
Input/Output System

From the block diagram above, we can understand the overall workflow of the system, where data is input through the keyboard, and image information is collected by the camera, sent to the CPU for processing, and then output through devices like LCD screens and speakers. Centered around the CPU, we can thus divide tasks into keyboard tasks, display tasks, data acquisition tasks, control output tasks, and communication tasks.

5. Division of Critical Tasks

‘Criticality’ refers to the importance of a certain functionality within an application system. If such functionality cannot be implemented normally, it could lead to significant impacts or even catastrophic consequences. Tasks containing critical functionalities are referred to as ‘critical tasks’, and critical tasks must be given the opportunity to run, as even a single omission is unacceptable.

So how can we ensure that critical tasks are executed accurately? The first thought is to elevate the priority of critical tasks to the highest level. However, this is not sufficient. Let’s consider a fire alarm system, which generally performs several tasks: detecting fire signals, dialing fire emergency numbers, activating sprinkler systems, generating and saving fire alarm records, and printing fire alarm records.

If we package these tasks into one task and set its priority to the highest, during system operation, if an issue arises with the printer while generating and saving fire alarm records and printing them, the current task will be suspended. Once suspended, the fire signal detection task will also fail to function, leading to system paralysis.

Therefore, for critical functionalities: they must be as independent as possible from other functionalities, forming a separate task, and then triggering other tasks through communication mechanisms to complete subsequent operations.

In addition to separating critical tasks from other functionalities and setting their priority to the highest, another method to ensure accurate execution of critical tasks is to use interrupts. For example, in a fire alarm system, we can have the fire signal from the sensor trigger an external interrupt. Once the interrupt occurs, it completes the signal detection function, and the interrupt service function then uses a communication mechanism to notify other tasks. The diagram below illustrates this:

How RTOS Divides Tasks
Fire Alarm System

To enhance system real-time performance, the interrupt service function should be as brief as possible. Thus, we can further divide the program related to task communication into a separate task specifically for communication, reducing the burden on the ISR, as shown in the diagram below:

How RTOS Divides Tasks
Using ISR to Complete Critical Functions

From the diagram above, we can see that a new task, the message dispatch task, has been added. The existence of this message task should not interfere with the operation of critical tasks while also ensuring timely notifications to other tasks. Therefore, the priority of the message dispatch task is determined to be lower than all critical tasks and higher than all operational tasks.

Another situation is when a critical task cannot be started by an interrupt; in this case, that critical functionality can be implemented by a separate task, as illustrated below:

How RTOS Divides Tasks
Critical Task Completing Critical Functions

In this case, we cannot use interrupts to detect alarm signals, so we need to continuously poll the smoke detector’s status to prevent missing important information. Once an alarm signal is detected, we notify other tasks to complete the corresponding operations through communication mechanisms.

Finally, it is important to note that if a critical task has strict real-time requirements, it must be assigned a sufficiently high priority to ensure timely access to execution rights. If there are no real-time requirements, high priority is not mandatory; the key is to separate other non-critical operations to avoid being hindered by them.

6. Division of Urgent Tasks

‘Urgency’ refers to a functionality that must be granted execution rights within a specified time (timely execution) and must be completed before a specified deadline (on-time completion). Thus, these functionalities have strict real-time requirements. Most urgent tasks are triggered by asynchronous events, which typically can cause certain interrupts.

In such cases, arranging urgent tasks within the corresponding ISR is the most effective method. If urgent tasks cannot be arranged within an ISR, assigning them the highest possible priority is an effective solution for achieving ‘timeliness’.

To achieve timely completion, urgent tasks must be executed as briefly as possible. This can be accomplished by streamlining urgent tasks, separating less urgent tasks, and retaining only the operations that must be performed immediately. The less urgent operations can then be packaged into a separate task.

The diagram below illustrates a data acquisition task. Data acquisition is an urgent task triggered by peak detection circuits, completed within an interrupt. The diagram below shows the entire data acquisition system:

How RTOS Divides Tasks
Data Acquisition System Framework

7. Division of Data Processing Tasks

The user application program that consumes the most time consists of various data processing units, which usually serve different functionalities. These units should be separated and packaged into different tasks. Since these tasks require considerable time, their priority must be arranged relatively low.

Additionally, if the operating system supports mechanisms that allow multiple tasks to be scheduled with the same priority, when multiple data processing tasks exist, they can be assigned the same priority and run in a time-slicing manner. If the operating system does not support multiple tasks with the same priority, multiple parallel data processing tasks can be divided into several data processing tasks, as illustrated in the diagram below:

How RTOS Divides Tasks
Simulated Time-Slicing

8. Division of Functional Aggregation Tasks

As the title suggests, functional aggregation task division means combining closely related tasks into one task to achieve functional aggregation. What qualifies as closely related tasks generally satisfies the following two requirements:

  • Tight data correlation

  • Tight temporal correlation

The reason for combining them into one task is straightforward: if closely related functionalities are implemented using different tasks, it would necessitate extensive data and synchronization communication, which would be a significant burden on the system.

9. Division of Tasks Triggered by the Same Conditions

If several functionalities are triggered by the same event, they can be combined into one task, thereby eliminating the workload of distributing events to multiple tasks.

However, there is a condition: when these functionalities are executed in a certain sequential order, the real-time requirements of each functionality must still be met. This is critical.

Moreover, the tasks triggered by the same conditions are typically critical or urgent tasks, which need to be divided according to the methods described earlier. The conditions for triggering tasks of this type are usually internal events, such as a clock event, which means that a task is triggered when a specified time is reached, or when a task produces a conclusion that triggers another task. The diagram below illustrates this:

How RTOS Divides Tasks
Tasks Triggered by the Same Conditions

Additionally, it is important to arrange the execution order of functionalities within the task as rationally as possible:

  • If there are causal relationships between functionalities, the execution order should be arranged according to causality, such as calculating first and then outputting results.

  • If functionalities are completely independent, the execution order should be arranged according to the strength of real-time requirements.

10. Division of Tasks with the Same Execution Period

Combining functionalities with the same period into one task can avoid triggering multiple tasks at one time, eliminating event distribution operations and inter-task communication, thereby reducing the burden on the system.

11. Division of Sequential Operation Tasks

If several functionalities operate in a fixed sequence without any concurrency, they should be combined into one task.

12. Conclusion

Through the above discussion, we understand how to divide tasks in an RTOS. Finally, we summarize the key points as follows:

  • Centering around the CPU, divide functionalities related to various input/output into independent tasks.

  • Separate critical functionalities into an independent task or ISR, and implement the remaining parts with another task, communicating between the two through mechanisms.

  • Separate urgent functionalities into a high-priority task or ISR, and implement the remaining parts with another task, communicating between the two through mechanisms.

  • For functionalities that are both critical and urgent, handle them according to the processing methods for urgent functionalities.

  • Separate data processing functionalities that consume significant CPU time, packaging them into low-priority tasks.

  • Combine closely related tasks into one task to achieve functional aggregation.

  • Combine functionalities triggered by the same event into one task, thereby eliminating the event distribution mechanism.

  • Combine tasks with the same execution period into one task to eliminate the time event distribution mechanism.

  • Combine functionalities that execute in a fixed sequence into one functionality to avoid the hassle of synchronous relay communication.

References: ‘Program Design Techniques Based on Embedded Real-Time Operating Systems’

END
Author: wenzid
Source: wenzi Embedded Software
Copyright belongs to the original author. If there is any infringement, please contact for deletion.
Recommended Reading
Illustrating the Differences Between Firmware, Drivers, and Software
Can a 3-Year MCU Engineer Write Such Code?
Building a Server with Raspberry Pi for Permanent Website Operation
→ Follow to avoid getting lost ←

Leave a Comment

Your email address will not be published. Required fields are marked *