This article is part of the Embedded Software series: Ada for the Embedded C Developer
Like C, Ada is a compiled language. This means that the compiler will parse the source code and emit machine code native to the target hardware. The Ada compiler we’ll be discussing in this course is the GNAT compiler. The compiler is based on the GCC technology like many available C and C++ compilers.
When the GNAT compiler is invoked on Ada code, the GNAT front end expands and translates the Ada code into an intermediate language that’s passed to GCC, where the code is optimized and translated to machine code. A C compiler based on GCC performs the same steps and uses the same intermediate GCC representation. This means that the optimizations we’re accustomed to seeing with a GCC-based C compiler also can be applied to Ada code.
The main difference between the two compilers is that the Ada compiler is expanding high-level constructs into intermediate code. After expansion, the Ada code will be very similar to the equivalent C code.
It’s possible to do a line-by-line translation of C code to Ada. This feels like a natural step for a developer familiar with C paradigms. However, there may be very little benefit to going this route. For this course, we're going to assume that the choice of Ada over C is guided by considerations linked to reliability, safety, or security.
To improve on the reliability, safety, and security of our application, Ada paradigms should be applied in replacement of those usually applied in C. Constructs such as pointers, preprocessor macros, bitwise operations, and defensive code typically get expressed in Ada in very different ways, improving the overall reliability and readability of the applications. Oftentimes, learning these new ways of coding requires effort by the developer at first, but proves more efficient once the paradigms are understood.
Concurrency and Real-Time
Concurrent and real-time programming are standard parts of the Ada language. As such, they have the same semantics, whether executing on a native target with an OS such as Linux, on a real-time operating system (RTOS) like VxWorks, or on a bare metal target with no OS or RTOS at all.
Understanding the Various Options
For resource-constrained systems, two subsets of the Ada concurrency facilities are defined, known as the Ravenscar and Jorvik profiles. Though restricted, these subsets have highly desirable properties, including efficiency, predictability, analyzability, absence of deadlock, bounded blocking, absence of priority inversion, a real-time scheduler, and a small memory footprint. On bare-metal systems, this means in effect that Ada comes with its own real-time kernel.
Enhanced portability and expressive power are the primary advantages of using the standard concurrency facilities, potentially resulting in considerable cost savings. For example, with little effort, it’s possible to migrate from Windows to Linux to a bare machine without requiring any changes to the code. Thread management and synchronization is all done by the implementation, transparently.
However, in some situations, it’s critical to be able to directly access the services provided by the platform. In this case, it’s always possible to make direct system calls from Ada code. Several targets of the GNAT compiler provide this sort of API by default—for example, win32ada for Windows and Florist for POSIX systems.
On native and RTOS-based platforms, GNAT typically provides the full concurrency facilities. In contrast, on bare-metal platforms GNAT typically provides the two standard subsets: Ravenscar and Jorvik.
Ravenscar
The Ravenscar profile, a subset of the Ada concurrency facilities, supports determinism, schedulability analysis, constrained memory utilization, and certification to the highest integrity levels. Four distinct application domains are intended:
- Hard real-time applications requiring predictability.
- Safety-critical systems requiring formal, stringent certification.
- High-integrity applications requiring formal static analysis and verification.
- Embedded applications requiring both a small memory footprint and low execution overhead.
Tasking constructs that preclude analysis, either technically or economically, are disallowed. You can use the pragma Profile (Ravenscar) to indicate that the Ravenscar restrictions must be observed in your program.
Ravenscar offers many additional restrictions. Covering those would exceed the scope of this chapter. You can find more examples using the Ravenscar profile on this blog post.
Read more from the Embedded Software Series: Ada for the Embedded C Developer