Real-Time and Embedded Linux, Part II
In the previous article, I touched upon embedded systems, their uses, and the importance of developed them in an open manner. Now I'd like to take a closer look at the technology involved, looking into the specifics of some of the approaches on the market right now.
Real-time systems have been on the market for years because there has long been a need for systems to control industrial systems, robotics, and similar systems. When it comes to controlling hardware on a machine floor, where a scheduling latency could result in injury or death to those on the floor, guaranteed scheduling is extremely important. Nearly all systems that interact with the rest of the world can benefit in some way from some level of real-time scheduling of operations, and real-time operating systems have been solving problems in the field for years. Many of these are special-purpose systems that require developers to work within their specific framework.
These days, Linux has been making large inroads into this market. Whereas you used to have to write specific code for a specific operating system, today you can write the code for a general-purpose operating system such as Linux. In many cases, special real-time systems focused on doing one thingreal-time servicesand didn't do much else. This means that while you could perform real-time services, you couldn't easily interact with database systems, special networking layers, and so on. For many vendors that supplied these services, it was rarely worth the development effort needed to support these systems.
With Linux, though, you have a general-purpose operating system with all the applications and services from a normal Linux environment, plus extensions that allow you to perform real-time services. Not only does this make application developers' lives simpler by supporting a smaller group of operating systems, but it also allows the real-time developer to stay within the normal UNIX-like environment.
Enough about the bonuses, though. Let's look into how it works. As mentioned in the previous article, soft and hard real-time status is achieved through a variety of mechanisms. Some approaches modify the standard Linux kernel to allow it to be pre-empted. While this approach does allow code to be run in a more deterministic manner, it depends on direct modifications to kernel code, albeit restricted to the SMP spinlocks. While this is contained and relatively simple to keep up-to-date with new releases, it is a moving target. It is also not perfect with regard to driver bugs, bad locking controls, and other shifting targets in the Linux development model.
Instead, a dual-kernel approach is being taken up. RTLinux and RTAI both use this mechanism. A real-time system runs on top of the rest of the Linux system, and the real-time system runs Linux as its lowest-priority thread. This way, real-time code can be scheduled into the real-time system, and nonreal-time code can easily be written as a normal userspace application within Linux itself. This segmentation of logic serves to simplify the problem into of the real-time code, which runs as a small kernel module, and the rest of the code, which can be run in userspace. The userspace code is free to use anything that any other Linux application could use. The kernel modules can access the real-time API calls and can use Linux kernel calls that don't depend on locking mechanisms. Communication between the two sides can be done in a variety of ways, from FIFOs to shared-memory mechanisms.
This dual-kernel approach has been used before with other operating systems, but applying this concept to non-UNIX systems opens up the door to unneeded complexities, while at the same time losing the stability and flexibility that UNIX environments have been providing for years. And because the real-time component exists as a standalone component, the thread that it runs as a general-purpose OS doesn't have to be Linux. In the case of RTLinux, the real-time kernel can run a Linux as a thread or a BSD kernel (or neither, if the real-time code is all that is needed). Because the real-time component is only heavy enough to handle a minimal set of operations, overhead is generally found to be negligible. Also, even worst-case situations are still often near the hardware limit.
In general, the simplicity of the dual system is difficult to beat. The last thing that you need when coding for a very fine timing granularity on an embedded system is the ambiguity of complex infrastructure code. One complaint is that writing so much kernel code requires more work by the engineer, but now there exist means for writing real-time code in userspace. Unless this is needed, however, the division of code between real-time portions and userspace management is a good idea because it keeps the real-time problems simple and abstracted out of the rest of the code base.
I'll leave the explicit details of programming within these systems as an exercise because most of the work involved is a matter of writing the specific code to control your device. The interfaces for real-time thread control revolve around the POSIX standard and, as a result, are simple to understand and use. Keep these systems in mind when you're developing an embedded system: These days, you can go from a normal, nondeterministic system to a deterministic, hard real-time OS with very little difficulty, while still remaining in the comfortable UNIX environment.