As embedded systems become more eclectic, they are likely to include third-party C++ code. This is especially true since the third-party code may not have been written for embedded systems. C++ poses a problem for partitioned embedded systems (Ref. 1). As discussed in Ref. 2, it is best to use mutexes for heap accesses in multitasking systems rather than implementing heap services as RTOS services. This permits a higher-priority task, which does not need access to the heap, to preempt and run during a long heap operation initiated by a lower priority task. Otherwise, the higher priority task would need to wait for the long heap operation to complete, possibly missing the task’s deadline. When the higher-priority task finishes, the lower-priority heap operation resumes.
However, in partitioned systems, heap accesses from umode partitions must operate like other system services — i.e. via a software interrupt API (svc n) , as shown in Figure 1.
Figure 1: Heap Operations in umode and pmode
In the above figure, the pmode barrier separates umode from pmode via hardware. Note that the blocks and ellipses, in this figure, are color-coded to indicate if they are code, data, or partition. In order to understand the discussion that follows, it is important to distinguish what is code from what is data. Heaps 1, 2, and 3 are shown as separate blocks for clarity, but they are actually part of their respective partitions. If the following discussion is still confusing, reading references 1 and 2 should help.
In the umode part, the App1 partition is written in C and has its own heap, heap1. heap1 is a separate data region in App1, or it is in an App1 data region. ucom_code is a common code region shared between umode partitions. The clib part of ucom_code contains C library functions. The SVC Sh section consists of SVC shell functions that convert RTOS, system, and heap service calls into SVC exceptions, which can penetrate the pmode barrier, as shown. Below the pmode barrier the exceptions are converted back to service calls by the SVC Handler, SVCH.
The App2 partition is written in C++ and has its own heap, heap2. Note that it also uses the SVC shells and SVCH to access xheap code. The latter provides the mutex protection for eheap™, for which heap services are not task safe. This path places a large overhead on App2 C++ object creation and deletion. Note that App3 isalso written in C++, but since it is in pmode it can interface directly with eheap and thus it does not have the SVC plus xheap overhead. App4, written in C and also in pmode, interfaces with the main heap, mheap, via xheap. It has the heap mutex overhead, but not the SVC overhead. In Figure 1, App4 is shown as also accessing mheap. It might do so initially to get the data block for heap3.
In the foregoing, mheap is task-safe, but heap3 is not. We are assuming here that App2 has only one task, all of its tasks have the same priority, or that it is acceptable for a higher-priority task to wait for a lower-priority task to complete a heap operation. Since each heap has its own mutex, heap3 operations do not affect other heap operations.
Naked Heap Access
Whereas the foregoing allows direct eheap accesses for pmode C++ partitions like App3, this is not really what we want. pmode code is most likely to be written in C, if not in assembly. Code written in C++ is unlikely to be mission-critical, RTOS, or security code. Hence it is the kind of code that we want to put into umode. So what we really want is efficient operation of C++ code in umode. Figure 2 shows the solution for this.
Figure 2: Improved Heap Operations in umode
Here, eheap has been moved into the shared ucom_code region, where it can be directly accessed by the App2 C++ partition. Note that the xheap and App3 to eheap connections (red lines) skip over the pmode barrier. You probably think that this is cheating! It is not. Whereas umode code cannot skip over the pmode barrier, pmode code can. This slight-of-hand is accomplished in the linker command file: eheap is moved to ucom_code, and sys_code already contains ucom_code (not shown). Mission accomplished!
If you are getting confused, bear in mind that eheap and xheap are just code. The actual partition heaps are in data regions of their respective partitions. Code running in one partition cannot access the heap in another partition. Hence a C++ partition can share eheap functions with another partition without damaging that partition’s heap. (If you still have questions, please see Ref. 1.) The downside, of this solution, is that there is no multitasking protection for eheap accesses. Consequently, as noted above, a C++ partition must have only one C++ task, all C++ tasks in the partition must have the same priority, or higher-priority tasks may need to wait on heap accesses by lower-priority tasks — a small price to pay for good performance!
Integrated Block Pools
Even a naked, bin-heap may not be fast enough for some C++ programs. For this reason, eheap incorporates small-block, block pools to handle small objects. These are the ones most frequently created and deleted in C++ code. The basic concept is that for blocks smaller than the smallest heap bin, malloc() is directed to the integrated block pool having the desired size blocks. This is very fast, even compared to a small bin access. When the block is freed, it is added to the linked list of free blocks in the block pool.
The interesting thing about building small-block, block pools into a heap is that if a pool becomes empty, the requested block can be allocated from the heap, itself. Thus there might be a hiccup, but not an allocation failure. When such a block is freed it goes back to the heap, not to the block pool.
- Ralph Moore, Achieving Device Security, Micro Digital, January 2022.
- Ralph Moore, Using Heaps in Embedded Systems, Micro Digital, January 2022.
Copyright © 2022 Micro Digital, Inc. All rights reserved.