Supervisor: Michael Zapp
Undergraduate Honours Project:
Provably Safe Real-Time Programming
This paper demonstrates the feasibility of using a type-safe language, Standard ML, in the realm of real-time systems. Using an SML compiler which does not use garbage collection, the ML Kit, and the RTAI real-time Linux environment, a framework for real-time systems is outlined.
1. LITERATURE REVIEW
Embedded and real-time systems are areas in which it is often very important that the software that runs these systems not fail. Typically, these systems also have very strict constraints on the resources available to them. Because of this, it is often felt by developers that the only feasible way to create the software to drive these systems is to use a lower-level language like C or assembler. Unfortunately, it is quite easy to introduce bugs into C programs which will cause catastrophic failure.
SML is a strongly typed, functional, and safe language. An SML program which passes through the compiler's type-checking system and successfully compiles is guaranteed not to crash. Buffer Thanks to SML's formal definition , many of these desirable language properties have been mathematically proven [2,3]. As well, many SML compilers have very good optimizing capabilities, outputting extremely efficient machine code.
The only thing stopping SML from being beneficial and useful in the embedded domain is that most SML compilers use garbage collection. Garbage collection schemes and the requirements of hard real-time are largely incompatible. While there is some very promising research being conducted about real-time garbage collection [4,5], there are still a number of factors preventing it from being useful in this domain.
There currently exists one SML compiler that does not use garbage collection. It is called ML Kit, and it uses a scheme called region-based memory management [6,7]. This scheme makes it a promising candidate to be used in real-time systems.
There has been other research into provably correct real-time programming, most notably within the various ProCoS projects . The ProCoS initiatives, however, are geared towards basic research in the field of provably correct systems, and are meant more to inspire further research and implementations rather than provide them.
The Giotto project  shows a great deal of promise in creating systems whose timing characteristics can be proven. However, it still requires a large amount of outside low-level code to actually perform the work of the system, which may or may not be safely written.
There has also been some interesting research being done into taking the Z formal specification language, and combining it with the real-time CSP language . However, as these are specification languages, more work still has to be done to use this work to actually create provably correct real-time systems.
The goal of this project is to investigate the possibility of using SML in a low-level, real-time setting, using tools that are currently available. To accomplish this, a real-time Linux environment, RTAI, was chosen, based on its low-level driver interface.
An SML driver framework was created to bridge the gap between the ML Kit compiler and RTAI's interface. Using this framework, a simple hard-real-time program was created in SML, which attached to the PC's timer interrupt.
An investigation into the ML Kit's runtime code was done in order to determine what changes were necessary to ensure hard real-time performance. Several changes were made to the runtime to facilitate this.
Timing measurements were not taken due to the fact that user-space interrupts were necessary. Therefore, if minimal interrupt latency was required, SML would be unsuitable due to this limitation.
3. CHOOSING A REAL-TIME LINUX ENVIRONMENT
The ML Kit’s only officially supported platform is Linux on an x86 architecture. As such, we need a way to make Linux real-time. There are two major real-time extensions for Linux, RTLinux  and RTAI . Both work in roughly the same way; they run the Linux kernel as a low-priority process, and allow the user to create real-time tasks which can pre-empt it. RTAI, in fact, is a community-driven fork of RTLinux, which is controlled by a commercial company.
RTAI contained numerous features, such as the LXRT system, which allowed for easier integration with ML Kit-compiled programs, and thus made it a much more attractive platform to work with. The LXRT system allowed user-space Linux tasks to be "stolen" from the Linux kernel by RTAI's scheduler, thus making them hard real-time. RTAI 3.0r3 was chosen for the project.
4. PROBLEMS WITH THE ML KIT
The ML Kit allows an SML program to call out to specially-designed C functions. It does not, however, allow specially-designed C functions to call SML functions. This has serious ramifications.
For one, it makes it impossible to hook SML code directly into the Linux kernel without serious changes. A framework would have to be structured in such a way that an SML module would call in to the Linux kernel, and the kernel return back to the SML code when it was ready to run. While one could conceivably create a C function to do this, it would basically require the SML module to be treated as a seperate lightweight task, context-switched into when a callback is needed. This is hardly a clean solution, and it requires the developer of the framework to basically become an OS developer.
As well, most real-time APIs for Linux will require the user to create a seperate real-time task, seperate from Linux tasks, for the custom real-time scheduler to schedule. Typically, this requires the user to pass in a pointer to a C function which performs the real-time task. Since we can't call SML code from C code, we again find ourselves in a position where we cannot use these APIs without a lot of work.
Thankfully, RTAI contains a useful API called LXRT. LXRT allows userspace Linux processes to convert themselves into real-time processes, stealing them from the Linux scheduler and scheduling them natively. LXRT then creates a lightweight real-time process in kernel space through which real-time requests of the task are proxied. Creating a real-time task with this API requires no callback functions.
The ML Kit's memory management scheme bears closer inspection. Understanding the region inference algorithm is crucial to writing safe, correct, and memory-efficient code. Programs written with the ML Kit require recursive function calls to be carefully constructed in order to prevent memory leaks . ML Kit's documentation specifies a useful pattern for creating iterative functions which will prevent such memory leaks. However, the developer must always be vigilant; very small changes which otherwise would not change the meaning of the program can alter its memory characteristics significantly.
5. REAL-TIME CRITERIA
The main problems with creating an ML Kit framework using LXRT are as follows:
Minimizing system calls in the compiler’s generated code. System calls which require the assistance of the Linux kernel – file I/O, memory allocation, etc. – force a task to be scheduled by the Linux scheduler, breaking hard real-time. For the most part, overcoming this is straightforward – the compiler is relatively platform agnostic and the only system calls are made within the runtime libraries. Indeed, the only calls we need to worry about at all are memory allocation, and the ML Kit’s memory allocation needs are quite simple.
Minimizing system calls in SML code. Because we are forced to use LXRT for everything but a very minimal framework, we must take care that it is possible to do everything we want to do within that framework without making Linux calls. I/O is especially important – if we cannot read and write directly to a piece of hardware’s memory without dropping to soft real-time, we have failed.
Working with IPC. RTAI’s various messaging APIs all make the relatively reasonable assumption that you are able to send a message as a string of bytes. However, there is no standard way to serialize an arbitrary data structure in SML. Papers have been written on the topic . Some form of compromise needs to be reached.
Dealing with interrupts. As the the ML Kit does not allow C code to call SML code, we cannot directly install an interrupt handler. Instead, we must, in our framework, allow for a default interrupt handler to send a message to a real-time SML process.
If we can overcome all of these problems, then we can use SML to create real-time systems.
6. MODIFYING THE ML KIT'S RUNTIME
In RTAI 3.0, LXRT can give user space threads hard real-time capabilities by "stealing" them from the Linux scheduler. RTAI then runs these tasks at a higher priority than the kernel itself. This has the unfortunate side effect of not being able to make normal system calls without returning the thread back to the Linux scheduler, temporarily lowering it to soft real-time status. In most cases, this is gracefully handled by mixing hard and soft real-time threads which communicate through RTAI's standard IPC mechanisms. The hard real-time tasks talk to the hardware and perform all of the really time-critical functions, and the soft real-time tasks display output, do file I/O, etc. While the ML Kit does not directly support threading, this is easily overcome by simply writing seperate programs instead.
For the most part, this works well with C programs because they have absolute control over when system calls are made. However, when using the ML Kit, the programmer has absolutely no control over memory allocation; it is handled for them by the ML Kit's runtime. This means that anytime new memory must be allocated for a new region, the task will have to be downgraded to soft real-time. We would like to avoid this if at all possible.
Looking closely at how the ML Kit performs memory allocation, we find that it is thankfully quite simple. A region is defined by linking together a variable number of "region pages". These are allocated 30 at a time. If any objects must be created in the region which are larger than the size of a region page (1016 bytes), they are allocated seperately.
In most cases, it is not really possible to predict exactly when a new region allocation might occur. This non-determinism must be avoided. It would make sense for a hard real-time program to be able to pre-grow its memory on startup. For region pages, this is easy; just create a bunch of them and add them to the free list. For large objects, however, the problem becomes more difficult. Since we want to avoid actually calling malloc unless absolutely necessary, and because large object allocation will always call malloc, it is necessary to write some memory management code in user space to emulate malloc without calling out to the Linux kernel to guarantee hard real-time. As contiguous objects bigger than 1kb are probably quite rare (one would need to create a tuple with at least 255 entries), and memory management is somewhat outside the scope of this paper, we will simply note that it is relatively straightforward to extend the runtime in this way, should the need arise.
7. INTERFACING THE ML KIT WITH RTAI
Accessing hardware memory is straightforward; using mmap on /dev/mem causes the Linux kernel  to directly map the memory into the process' memory space. The kernel sources were examined to ensure that it didn't simply trap the segfault that would result, which would break hard real-time. As well, the ioperm() call allows programs to directly use the inp/outp instructions for access to hardware which uses the x86's port interface.
We can use RTAI's user-space interrupt module to service interrupts in SML code. The USI module installs an interrupt handler in kernel space which simply signals a given semaphore when an interrupt occurs. It is then up to the SML program to service the interrupt, clear the source of it, and unmask it when finished.
IPC was not implemented for this project due to time constraints. A simple implementation which restricts messages to lists of integers was originally considered. A proper serialization system would be preferable, as messages could then be type-safe.
8. PROBLEMS WITH RTAI
The first, and surprisingly, by far the largest, major struggle in my research was getting a real-time Linux environment up and running. The system I used, RTAI, is an ever-evolving piece of open-source software. It has a plethora of how-to style documentation available [15, 16, 17]; unfortunately, it is scattered all over the internet, and most of it is either thoroughly out of date or too bleeding-edge, referring to the latest unstable version. The official site  is drastically out of date, suggesting that the user wanting the absolute latest updates check out a branch from CVS which hasn't been updated for nearly a year, and offering a beginner's guide many major versions old.
In the CVS repository itself was a file describing the two actively updated branches. "magma" was the unstable version where current development was taking place. "kilauea" was the stable version, with patches merged in from magma when necessary. I attempted to use "kilauea." (Over the course of my work, it was mentioned on the RTAI mailing list  that mutexes in RTAI were broken -- the lock and unlock procedures had exactly the same code. Hence my desire for the latest patches, if not the latest features.)
However, this particular version of RTAI had a tendency to hang my system at random. Contained were two versions of the real-time Linux kernel patch -- one derived from the RTLinux patch, and therefore having patent issues, and a new version based on Adeos, a high-level system for sharing resources between OSes, which allows for more portability and is also free of patent issues. I noticed in a posting to the RTAI mailing list that the Adeos patch had issues with LXRT, which (as detailed above) was the system which I needed to use. In desperation, I tried the legacy RTLinux-based patch. The freezes stopped, and the test programs seemed to work. However, mine did not. The simplest RTAI program I wrote would hang the system.
Eventually, it occurred to me that the patches thrown into the "kilauea" branch might not actually be tested; rather, they were being committed to the tree by developers working on "magma" simply so that any bugfixes wouldn't be lost when it came time to do a release. My theory appears to contain some truth -- downloading the latest release instead of working from CVS finally seemed to fix the problems.
SML is not a cure-all. RTAI is extremely picky; making an RTAI call with an invalid parameter, or in the wrong order, was likely to hang the system. (In retrospect, some error-checking should have been included in the framework, making the RTAI calls throw an exception instead of returning a null pointer.) At best, the loaded program would freeze, in hard real-time mode, completely unkillable from Linux. When creating the demo, it became much easier to write it in C, ensure that it worked, and then transcribe it into SML and fix the new problems that appeared.
If you look at the source code to the demo, you see that it's not very SML-like at all (Fig. 1). It mostly consists of imperative RTAI calls to set up the task. An equivilant program in C would look extremely similar. However, it is important to keep in mind that the demo does not actually do any processing.
fun catchInterrupts(sem, irq) =
fun catchNumIRQs(_, _, 0, l) = l
| catchNumIRQs(sem, irq, num, l) =
val ovr = rt_sem_wait_timed(sem, nano2count(ms2nano(1000)))
catchNumIRQs(sem, irq, num - 1, ovr::l)
catchNumIRQs(sem, irq, 30, )
fun printOvrList(list) =
fun printOvrList'() = print "\n"
| printOvrList'(h::t) =
val _ = print (Int.toString h);
val _ = print " "
fun go() =
val task = rt_task_init(nam2num("MYTASK"), 0, 0, 0);
val irqsem = rt_typed_sem_init(nam2num("IRQSEM"), 0, CNT_SEM)
val ovrList = catchInterrupts(irqsem, 0)
val _ = go()
FIGURE 1 - A simple real-time SML program which reacts to IRQ 0.
Writing low-level code in SML is just as dangerous as writing it in C. SML has the added burden of being a functional language, and thus is somewhat ill-suited for the imperative style of programming which low-level code demands.
As well, the ML Kit is unique among SML compilers in that, while you can prove that your program does not access memory that it isn't supposed to, you can't necessarily prove that your program won't just keep allocating memory. As the memory allocation is implicit, yet care must be taken to ensure that the compiler infers the proper memory management, the ML Kit can actually be fairly dangerous.
However, unlike C, the ML Kit will provide you with an intermediate representation which can be used to easily check for memory leaks. As long as no new regions are created in recursive calls, code generated by the ML Kit will be free from memory leaks. The compiler can't do this automatically, as sometimes this may be desirable (for example, tree traversal).
In this paper, we have presented a framework which allows the construction of real-time systems in SML. However, there are still some areas in which this framework loses out to C, most notably in the area of interrupt handling. As writing an ISR directly in SML is not currently possible, user-space interrupts were used, which introduced serious latency which would be unnecessary in C. As well, low-level programming is just as unsafe in SML as it is in C.
The most advantageous configuration, then, is to have low-level tasks written in a low-level language, such as C, and have them send the information to a higher-level real-time task, written in SML. The high-level tasks gain SML's safety and expressiveness, while the low-level tasks gain C's speed and debugging tools.
The best candidate for future work is the construction of an IPC framework which implements some kind of data serialization interface. Currently, the only method of IPC implemented is semaphores, which is quite limiting.
 R. Milner, editor. The Definition of Standard ML: Revised. MIT Press, June 1997.
 M. VanInwegen. The Machine-Assisted Proof of Programming Language Properties. PhD thesis, University of Pennsylvania, Department of Computer and Information Science, 1996.
 M. VanInwegen. Towards Type Preservation for Core SML. http://www.myra-simon.com/myra/papers/JAR.html, 1997.
 S. Nettles and J. O'Toole. Real-Time Replication Garbage Collection. In ACM SIGPLAN 1993 conference on Programming language design and implementation, pages 217-226, Albuquerque, NM, 1993.
 D. F. Bacon, P. Cheng, and V. T. Rajan. A real-time garbage collector with low overhead and consistent utilization. In Principles of Programming Languages (POPL), January 2003.
 M. Tofte and J. Talpin. Region-based memory management. Information and Computation, 132(2):109-176, February 1997.
 M. Tofte, L. Birkedal, M. Elsman, N. Hallenberg, T. Højfeld Olesen, and P. Sestoft. Programming with Regions in the ML Kit (for Version 4). IT University of Copenhagen. April 2002.
 Martin Franzle and Markus Muller-Olm. Towards provably correct code generation for a hard real-time programming language. Compiler Construction '94, 5th International Conference Edinburgh U.K., volume 786 of LNCS, pages 294-308, April 1994.
 T. A. Henzinger, B. Horowitz, and C. M. Kirsch. Giotto: A Time-triggered Language for Embedded Programming. Proceedings of the IEEE 91:84-99, 2003.
 M. Heisel and C. Suhl. Formal specification of safety-critical software with Z and real-time CSP. In E. Schoitsch, editor, Proceedings 15th International Conference on Computer Safety, Reliability and Security, pages 31-45, 1996.
 RTLinuxFree. http://www.fsmlabs.com/products/openrtlinux/
 DIAPM RTAI - Realtime Application Interface. http://www.aero.polimi.it/~rtai/
 M. Elsman. Type-Specialized Serialization with Sharing. IT University Technical Report Series, TR-2004-43, 2004.
 L. Torvalds, S. Ananian, K. Sarcar. linux/drivers/char/mem.c. Linux Kernel, v2.4.24.
 Porting Your C++ GNU/Linux Application to RTAI/LXRT. http://people.mech.kuleuven.ac.be/~psoetens/portingtolxrt.html
 RTAI 3.0 Documentation Project. http://www.fdn.fr/~brouchou/rtai/rtai-doc-prj/
 RTAI.dk Wiki. http://www.rtai.dk/
 RTAI Mailing List. https://mail.rtai.org/cgi-bin/mailman/listinfo/rtai