Programming languages for multicore systems
Editor’s Note: In this article on programming languages for multicore software development excerpted from Real world multicore embedded systems, Gitu Jain of Synopsys compares and contrasts the use of C, assembly, C++, Java, Python and Ada.
When writing a software application for an embedded system, the choice of programming language must produce not only an application that executes correctly, but one that does so under the resource and timing constraints imposed by the device on which it runs. This device can be limited in terms of memory, battery power, data transfer bandwidth, or input/output capabilities such as a keyboard or display screen. It can have safety or portability considerations. The application may be limited by the development environment as well.
An embedded system may come with an operating system as elaborate as a regular desktop or it may not have an operating system at all. If the device has no operating system, code must be written to deal with all the low-level details of the device, usually in assembly language. Most embedded systems today come with an operating system that has limited or reduced functionality as compared to a desktop or laptop. If an operating system and a suitable development environment are available on the embedded system, you can use a mid- or high-level language such as C or C11. For developing web applications, Java or Python is suitable. For real-time safety-critical applications such as air traffic control systems, consider Ada. A hybrid of two or more languages can also be used: a high-level language for most of the complex code and assembly language for timing-critical portions and for instructions not supported by the high-level language.
In this article, we will look at the most popular programming languages used for development of multicore embedded systems today. The languages will be presented in order of popularity. The features of the languages that support programming for embedded as well as those that support multi-processing or multi-threading will be illustrated with suitable example code throughout the sections.
The two most common programming languages used in embedded systems are C and assembly language. Assembly language lets programmers squeeze out the maximum performance from their applications in terms of speed and memory. But recent advancements in compiler technology for languages such as C have enabled compilers to generate code that is comparable to hand-written assembly language code. There is a distinct advantage in using a mid-level programming language such as C in terms of ease of development and maintenance, shorter debug cycles, testability, and portability.
C has emerged as the language of choice for many embedded system programmers because of its ability to access, modify, or update the hardware directly through language features such as pointers (Figure 1) and bit manipulation (Figure 2).
C has the ability to declare members of a data structure at the bit level, as shown in Figure 3. Members of a data structure declared in this manner, such as flagA and flagB, can be used and addressed in exactly the same manner as other members of the data structure, such as varA and varB.
The C language provides a limited set of language features. An embedded device may need certain features that the language does not support (for example bit-wise rotation), which you may need to program in assembly. You can do that in the form of in-line assembly embedded in the C program (see Figure 8 in the Assembly section).
Dynamic memory allocation is a property of C, and other high-level programming languages, where a program can determine, at run-time, whether it needs a certain amount of memory to store a variable, and gets that memory via a system call, malloc(). The memory allocated is placed in the heap. Many embedded systems have limited heap or no heap at all, in which case it may be necessary to disable the dynamic memory management feature of C and do the memory allocation statically in your program. For example, you can change dynamic allocation to static allocation for a linked list as shown in Figure 4. The program prints ‘0 1 2’.
Another programming trick for embedded systems is to replace recursion by iteration, as recursion is very inefficient in terms of space and time due to the added cost of a function call for each recursion. See Figure 5 to see how recursion can be replaced with iteration in some cases.
Another rule of thumb when programming for embedded systems is to declare your variable or function argument as a const if it is not going to be modified. This is because these values can then be stored in ROM (read-only memory) rather than RAM (random-access memory), ROM being cheaper and more plentiful in embedded systems; see Figure 6.
Certain read/write compiler optimizations can cause caching that does not work if the embedded device needs to communicate with I/O peripherals. Use the volatile keyword in such cases. For example, in Figure 7, suppose a variable cvar is not being used anywhere other than the two lines where it is being set to 1 and 2 in the function ControlFunc(). The lines *cvar = 1; and *cvar = 2 are optimized away by the “smart” compiler because it thinks those values are never used and can be removed.
But suppose this variable was the control line to an external I/O device and setting the memory location pointed by *cvar told that device to start some operation and writing 2 told it to stop. Optimizing these two lines will cause this operation to be completely lost. This can be prevented by using the volatile keyword as shown in Figure 7. Note, however, that the volatile keyword, when used, may also turn off other compiler optimizations such as SIMD, loop unrolling, parallelizing and pipelining. Use it with care for embedded multicore processors.
Multi-threading support in C. C11 is the new C language standard published in 2011, and it has added multi-threading support directly in the language in the form of a library, <threads.h>. It defines macros, declares types, enumeration constants, and functions that support multiple threads of execution. For more information, refer to the published standard .
If you are using an older version of C, you can add multi-threading to your programs by using a standard threading library such as POSIX Threads, Win32 Threads, or Boost Threads (for portability).