Friday, October 3, 2014 at 12:33 am
Real time is all about providing a result in a bounded amount of time. It is about juggling multiple inputs from the outside world and supplying outputs back to it exactly when needed. An example of a real-time application is the antilock brakes on your car (they must be accurately pulsed tens of times per second). To satisfy the needs of real-time systems, APIs must be available to support accurate timing, fast communications and I/O, and precise, priority-driven scheduling. Conventional wisdom held that without proprietary APIs and OSes, it was impossible to achieve the level of performance required to solve real-time problems. However, there are significant costs associated with coding solutions to a proprietary product.
Recognizing this, OS vendors, researchers, and users participated in an IEEE working group known as Posix.4. The group’s goal was to refine existing Posix APIs and develop new APIs to address the needs of the real-time environment. The result of this effort was the Posix 1003.1b-1993 standard, or Posix.4. To address the need for efficient communications, the group added APIs that support memory mapping, message queues, semaphores, signals, and asynchronous I/O, or it extended existing calls. It also added timers, memory locking, and programmable scheduling capabilities to support the accurate timing and scheduling necessary for time-critical tasks. Many legacy OSes offered one or more of these features, but often as a clumsy, heavyweight, kernel-based implementation, rather than the simple and fast implementation necessary for real-time applications.
Posix specifies an OS environment where multiple processes operate independently, each with its own protected address space. While the protected address space ensures that processes do not affect a system’s integrity or that of other processes, this environment is confining for real-time communications. This is because programs must perform time-consuming OS calls to communicate with each other and with the outside world. The fastest form of communication is through memory itself. Therefore, for processes to interoperate at the highest possible speed with each other or with devices, they must be able to share physical memory. Posix.4 defines a sophisticated yet elegant function called mmap() that uses a file descriptor to establish shared-memory mapping. Here’s how it works.
First, the shm_open() call provides an easy means to obtain a file descriptor corresponding to a supplied path. If multiple processes call shm_open() with the same path argument, and each process supplies this returned file descriptor to mmap(), this effects a mapping to the same physical memory. That is, all the processes access the same block of memory, as shown in the figure “Client/Server Application of Shared Memory.” Mmap() is extremely powerful. It maps memory among processes. Also, you can use it to map a disk file into memory. You can read or alter data in the file through fast memory operations, rather than through the traditional set of open/read/write/close calls. To ensure safe operation, mmap() provides control over which processes are allowed to read or write given shared-memory areas. Traditional synchronous I/O (e.g., writing to a disk file) puts an application to sleep while the I/O operation is pending (an unbounded time). Clearly, this action is not suitable for a real-time application that must always be ready to handle an event. To satisfy this requirement, Posix.4 provides asynchronous I/O (e.g., aio_read()), so that the application can continue executing and be notified (via a signal) when the operation completes. A list I/O (lio_listio()) function can execute many synchronous or asynchronous I/O operations via a single command.
When a real-time process executes on a demand-paged OS, it must ensure that any memory it uses stays locked in physical RAM. If it does not, the OS might page that memory out to disk. When the process next accesses this memory, the system’s memory management unit (MMU) must schedule a synchronous I/O operation to reload the memory page into RAM. Meanwhile, the process is put to sleep, leaving it vulnerable to missing time-critical events. Posix.4 provides the mlock() and mlockall() calls to accomplish the desired memory locking.
Control and Access Shared memory is great for fast interprocess communications (IPC), but some means must be provided to manage access to it. Confusion will result if a process attempts to read or write to shared memory that is being updated by another process. The Posix.4 solution to this problem is the semaphore. Both named and unnamed semaphores are provided. You create and access named semaphores via a path name. Access to named semaphores is through the sem_open() call. Unnamed semaphores are created directly in shared memory and managed by the user. Through the use of shared memory and unnamed semaphores, it is possible to build elaborate shared data structures with fine-grained locking. Event notification is vital to real-time applications, because processes must react quickly to outside events–such as releasing the brake on a wheel just about to lock up. Posix.4 provides an extension to traditional Posix signals called real-time signals. Posix signals simply set a bit, so it is impossible to know how many signals were actually sent to a process, or why. In contrast, real-time signals are queued so that none are lost. They have also been extended to contain additional information. If a signal is sent by sigqueue(), an integer or pointer value can be passed to the recipient. This result provides some indication of the actual event to be processed.
Messaging is a staple of real-time applications. Posix.4 provides an elegant set of APIs to implement message queues. To address real-time requirements, Posix.4 message queues support at least 32 levels of priority and can use real-time signals to notify a recipient of delivery. Message queues are efficient: Tests show that they are two to four times faster than traditional communications mechanisms such as sockets. Message queues are accessed via mq_open(), which uses a path name to identify the message queue to access. Real-time applications must ensure that operations occur on schedule. To meet these requirements, Posix.4 provides real-time clocks (clock_gettime()) with up to nanosecond resolution and real-time timers (timer_create()). Unlike traditional Unix timers, many real-time timers can coexist in one process. Posix.4 timers use real-time signals to notify the process when an interval has expired. If all that is needed is a simple time delay, the nanosleep() call can delay the current thread of execution for a precise amount of time. In addition, Posix.4 provides a set of scheduling APIs that let a process define, query, and alter the scheduling policies and characteristics that apply to that process.