Real-time operating systems (RTOS) are now included in many embedded applications. They can dramatically simplify time and task management in a system and help improve scalability and maintainability (if the application is designed correctly). I’ve found though that there are several common challenges that face RTOS application developers. Let’s examine these challenges and discuss a few potential solutions.
Challenge #1 – Selecting Task Priorities
Strangely enough, the most common challenge I see and am asked about is a fairly fundamental one; how to select the right priorities for tasks. It turns out that there are several different ways to select task priority. First, there is shortest response time. In this method, a developer examines the response time requirements for each task and assigns the highest priority to the task with the shortest response time requirement. Next, there is the shortest job first method. In this method, a developer examines the execution times for the tasks and the task that has the shortest execution time is the highest priority (obviously followed by the next highest and so forth). Finally, there is the method that is used most often in real-time embedded systems, shortest period first or more commonly rate monotonic scheduling (RMS). In this method, the task with the shortest period has the highest priority.
Following RMS will get you 95% of the way there and then there are usually an oddball task or so that is aperiodic that needs a priority assignment. These aperiodic tasks can either be assigned a worst-case period or they can be assigned based on their importance, execution time, or whether it needs to run prior to another task that may need its data. (Remember, there is no right or wrong answer to task priorities, just systems that may run better or be more efficient than others).
Challenge #2 – Seeing the Big Picture with a Dataflow Diagram
While consulting and mentoring development teams, I often encounter developers who are implementing their RTOS application without really understanding where data is being produced, where it is going and how it is getting there. As you can image, this results in software that is a bit spaghetti code like and often requires constant rework as more application pieces are put in place. The way to minimize this rework and understand the whole application is to develop a simple data flow diagram. This diagram contains several key components:
- The data producers
- The data consumers
- Data transfer mechanisms
- Storage mechanisms
- Task coordination mechanisms
For example, below is a simple data flow diagram for a smart thermostat:
This diagram provides a developer will an understanding of the data flow but also the major RTOS components that are required for the application:
- Tasks (in gray)
- Shared memory resources (green)
- RTOS mechanisms (in blue: queues (q), mutexes (m), semaphores (s), etc)
- Input and output devices (dark blue)
Having this dataflow diagram can answer many questions about the application design and prevent a lot of time being wasted reworking the implementation or debugging.
Challenge #3 – Properly Protecting Shared Memory
In the previous section, I pointed out that the section in green is a shared memory region. This memory region is accessed by both the Process Inputs task and the Application task. A mutex is used to protect the shared memory resource but in the implementation, I often see developers create the mutex separately from the data that is being protected. While this may seem okay at first glance, the problem is that if the mutex is created separate from the data structure, and someone goes to use the data structure, they may not realize that it is a shared and protected resource. (Yes documentation, design and many other things should make this obvious, but if it is declared separately it is so easy to overlook).
The solution is to look at shared memory as an object and include the mutex as part of the data structure for the shared memory. For example, the shared memory maybe has data from a humidity, temperature, and current sensor. We might normally declare the structure for the data as follows:
Again, the mutex declared separately may make it less obvious that the data would be shared. Instead, we can define the structure as follows:
Now every time a developer looks at the data structure, tries to do an autocomplete and so forth, they are reminded that this is protected data. When they see it is protected, it should remind them that before they access the data, they need to acquire the mutex. Developers often forget that just because a mutex is created to protect data, there are no assurances that the mutex will be used to access the data. (This is also why it can be useful to view the data structure as an object and create functions that limit access and control to the data resource that abstracts out the mutex at the application level).
Real-time operating systems can simplify time and resource management of embedded systems. Unfortunately, the RTOS does add complexity to the system that can create unexpected challenges that effect development schedules and code quality. In today’s post, we have examined several common challenges that developers often encounter and that I often see when working with development teams. As we have seen, these challenges can be overcome quite easily by following a few best practices.