Bob continues his article series about the open-source FreeRTOS. One of the essential elements of an RTOS is a rich set of inter-process communication (IPC) APIs. In Part 4, Bob looks at the IPCs available in FreeRTOS and how they stack up against Linux.
One of the most revolutionary concepts that shaped my professional development as a software designer of embedded systems was an article published in 1972 by David Parnas entitled “On the Criteria to Be Used in Decomposing Systems into Modules” . The principles encapsulated in that little paper became known as Information Hiding. The article was intended to help us break our systems into modules with a coherent encapsulation that would enable us to make designs that were more flexible to change and more easily understood. The practice was not widely understood for a number of years.
As late as 1981, it was not understood by our client when I created a Software Requirements Specification for a large avionics system. As part of the development requirements, I specified that the system should be decomposed in such a way so as to reflect the good industry practices of information hiding. This set off a flurry of activity on the part of our client who didn’t want any information hidden in the system design! It got so hot, I eventually removed it as a requirement and used it as an internal guideline with our team of developers.
Modern operating systems enable us to take information hiding to another level. In Parnas’ example, all of the data is stored in the same “core memory.” The term core memory refers to the hardware that stored the ones and zeros consisting of little magnetic beads that could be magnetized as either a north or a south pole to represent a one or a zero (Figure 1). All of the data stored in core memory was not hidden from all of the modules. Errant code in one module could destroy data from another module with ease. All modern operating systems provide mechanisms to protect data from one module from being harmed by another module. Most of this protection comes from the use of memory management and inter-process communications (IPCs).
With a modern real-time operating system like FreeRTOS, you don’t start your design by breaking your embedded systems (piece-wise refinement) into modules as Parnas describes. First you must identify the concurrent operations and then break the design into a number of concurrent processes or tasks. I covered how to do this in my series of articles on “Concurrency in Embedded Systems” (Circuit Cellar 263-275, June 2012 through June 2013) .
By the way, using IPCs doesn’t guarantee that you will be decomposing your system into processes that will be flexible to change and easily understood. You still need to implement the principles laid out by David Parnas as you create your embedded system both in creating your processes and when you modularize each process. I cannot emphasize enough the value of the principles in this paper. I highly recommend that you study it carefully. He provides two example module architectures for a given problem. He thoroughly examines these designs for their maintainability in the face of change and for ease in understanding. One of the main concepts he introduces is the principle of making our interfaces more abstract. IPCs definitely help with that. This month we will look at the IPCs that FreeRTOS provides and see how they stack up to the IPCs provided by Linux.
Table 1 provides a list of the IPCs available in these two operating systems. As I mentioned last time, my purpose is not to make FreeRTOS another Linux but rather to demonstrate the power of FreeRTOS. In the following description, I am going to use the word “process” interchangeably with “thread” and “task.” I understand that there are differences—but, since we are looking at inter-process communications, we will call all of them processes. Let’s first look at shared file IPCs versus shared memory IPCs.
|Stream and Message Buffers||X|
This is perhaps the most basic IPC. For example, one process reads data from an A/D and writes it to a file. To do this, it must open the file, write the data and close the file. Another process wants to use the data—for analysis or display, for example. It might want to only read the data. In this way IPC takes place using the shared file. In one system we designed, we used files like semaphores. We did this because a lot of the functionality was done with Linux scripting, which did not have semaphore capability.
One challenge with using shared files as a means of IPC is that the file system must be thread-safe and provide for file sharing. This includes locking out other processes from modifying the data when it is being modified by another process. Each RTOS can provide ways for shared files to be thread-safe or, in other words, be able to be used as an IPC.
A simple RTOS might just prevent other processes from opening the file when it is already opened. It would be thread-safe but this is too restrictive. What if I just want to read it while you are writing it? Linux file systems (and there are multiple ones) provide a rich set of options—even the concept of record locking—allowing two processes to open a file for writing and only locking out certain records in the file while they are being written. Clearly if you have two processes that need to write a file at the same time, things can get dicey. Another challenge in using shared files is the real-time overhead of constantly opening and closing a file.
FreeRTOS does support a thread-aware FAT (file allocation table) file system when you incorporate the FreeRTOS+FAT library. As we mentioned in the last article, a thread local errno is necessary and provided. The FreeRTOS community freely admits that the documentation is quite sparse (much less than Linux) so a lot of the nuances of how file sharing works and what exactly “thread-aware” means will have to be discovered as you go—either by reading the source code or by testing. Notice that they don’t say “thread safe.” Using shared files in any complicated way is not trivial. Thankfully there are other IPCs available in FreeRTOS.
Another basic IPC is shared memory. This is the fastest means of IPC. David Parnas’ design had shared memory but did not address how to protect it. With many of the FreeRTOS ports that do not have memory management (where the software in one process cannot access the memory in another process), shared memory is simple since all memory is shared. However, it is up to the designer to protect against problems that come from unprotected shared memory. Using our example with an analog-to-digital converter (ADC) process writing its data to memory and the Display Process displaying the data on a screen from the same memory, the following kinds of problems can happen:
• The Display Process displays the current 24-bit ADC value and uses two instructions to read the value from the shared memory (first the lower 16 bits and then the upper 8 bits).
• The higher priority ADC Process interrupts the Display Process between the reading of the lower 16 bits and the upper 8 bits. It writes the current 24-bit ADC value.
• The Display Process reads the upper 8 bits—but then does not have coherent data because all 24 bits are not from the same read of the ADC.
In a worst-case scenario, a one bit increase in the ADC data could result in the Display Process displaying a value almost twice as great as what it actually is! If the existing ADC value was 0xFFFF in the lower sixteen bits and 0x00 in the upper eight bits, the Display Process could display 0x01FFFF for the ADC value instead of 0x010000! This can, of course, be mitigated by using Semaphores or Mutexes around the shared data. More on that later.
A more important question for FreeRTOS is: “Does FreeRTOS allow shared memory when a memory management is used?” Linux does provide such a mechanism. And the answer is yes. It does it more simply than Linux but that means it is more limited. The details go beyond the scope of this article and is also port-dependent.
Now let’s go through of the key definitions relevant to IPCs.
Pipes, FIFOs and Message Queues:
Die.net provides a helpful definition of pipes and FIFOs, as follows: “Pipes and FIFOs (FIFOs are also known as named pipes) provide a unidirectional inter-process communication channel. A pipe has a read end and a write end. Data written to the write end of a pipe can be read from the read end of the pipe” . Figure 2 shows a Linux pipe.
FreeRTOS doesn’t support pipes like Linux. Linux FIFOs are similar to Message Queues in FreeRTOS although they do not rely on an underlying file system as they do in Linux. Message Queues  are the primary mechanism to send data from process to process, process to interrupt and from interrupt to process. Data can be sent to either the front or back of the queue. Queues can send data directly or indirectly through reference pointers (for large data assuming that the memory is shared). Processes can be placed in a blocked state when they attempt to read an empty queue and can be awakened when data arrives or when a timer expires.
Semaphores and mutexes: A semaphore is a means to control access to a shared resource. A mutex is a means to prevent mutual access to a shared resource . FreeRTOS provides three different kinds of semaphores: binary semaphores which are like a mutex except that they do not have a mechanism to inherit the priority of the current process; counting semaphores which keep track of the number of times they are invoked; and recursive semaphores, which are also called recursive mutexes that allow the same process to be called recursively.
Sockets: Sockets  are the means for providing network connectivity in an embedded system. When integrated with the TCP library, FreeRTOS provides the Berkley socket API. Socket software written in Linux should be easily ported to FreeRTOS.
Signals and Task Notification: Linux provides an extensive way of IPC and synchronization through signals. In FreeRTOS, this can be accomplished with Task Notifications . Just as a process in Linux can block and wait on a signal, a process in FreeRTOS can block on a Task Notification. Waiting on a Task Notification can be faster than implementing with queues, semaphores or event groups. Task Notifications can only be pending or not pending along with a 32-bit notification value. Streams and Message Buffers use Task Notifications for their blocking mechanism. Unlike signals, only one process can be the recipient of a FreeRTOS Task Notification.
Event Groups: As an embedded systems designer with FreeRTOS, you can specify a number of events that can be used to provide IPC . These events can be grouped together. Individual processes can set and clear events as well as wait on the setting or clearing of the event or events in a group. Using Event Groups can allow the design to create an Ada-like rendezvous between processes (Figure 3). For example: The Client can proceed to a certain point in its design flow and then wait for the Server to reach the same point and vice versa. This IPC can be used between interrupt service routines and processes.
Streams and Message Buffers: Stream and Message Buffers  are used to pass a variable length stream of data from one process to another or from an interrupt to a process. Unique to Stream and Message Buffers is that they are only intended for use with a single reader and single writer.
FreeRTOS, when coupled with some of the provided libraries, offers a rich array of IPCs, which provides more than enough flexibility for most embedded system designs. As with Linux, the problem for the designer is not there are not enough IPCs, but there is so much overlap it is hard to know which IPC to choose for your given implementation. It is much like standing in front of a shelf of salad dressings at a supermarket. This has been a quick tour of the IPCs available in FreeRTOS—as always, only in thin slices. Next time we will look at the FreeRTOS+POSIX library.
 “On the Criteria To Be Used in Decomposing Systems into Modules” Communications of the ACM. Volume 15, Issue 12 from December 1972; pages 1021–1089. I highly recommend this paper. He provides an example simple project and decomposes it into five modules. Then he provides an alternative decomposition which practices information hiding. Take time with it until you understand the principle. The article is available here: http://sunnyday.mit.edu/16.355/parnas-criteria.html
 See issue 263 (June 2012) through issue 275 (June 2013) in Circuit Cellar
(Circuit Cellar 2012 articles , Circuit Cellar 2013 articles)
 See https://linux.die.net/man/7/pipe
 https://www.freertos.org/Embedded-RTOS-Queues.html See page of 129 of the FreeRTOS Tutorial https://www.freertos.org/fr-content-src/uploads/2018/07/161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
 https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html and https://www.freertos.org/Real-time-embedded-RTOS-Counting-Semaphores.html
 https://www.freertos.org/Real-time-embedded-RTOS-mutexes.html and https://www.freertos.org/RTOS-Recursive-Mutexes.html
FreeRTOS | www.freertos.org
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JUNE 2021 #371 – Get a PDF of the issue