A Real-Time Data Plotting Program
In order to provide an acceptable user interface, rtp must quickly respond to GUI events (i.e., mouse events, etc.) at all times. This requirement would be easily met if all program activity were directed by GUI events. For example, an interactive drawing program is entirely GUI-driven, so its only responsibility is to execute relatively short sequences of code in response to GUI events.
rtp's architecture is complicated by two additional requirements, beyond the snappy GUI response. It must quickly update when new data points are available on STDIN. This feature is what differentiates rtp from other plot programs, such as gnuplot. It must also deal with the fact that rendering the data set often takes more time than is acceptable for a GUI delay. This precludes the use of a simple function call to render the whole plot.
Fundamentally, there are three “tasks” that rtp must multiplex, listed below from highest to lowest priority:
Respond quickly to GUI events. These events come as data from the X server on a socket.
Read data points from STDIN as they become available.
Render the data set into the plot window when it needs updating.
The Qt library provides mechanisms that support this processing structure. The first mechanism is the QSocketNotifier class. When we create a QSocketNotifier object, we pass it the file descriptor of interest. (The fancy name QSocketNotifier made me think the class was all tied in with network sockets, when in reality it can work with most any file descriptor.) In the case of rtp, this is the STDIN file descriptor (STDIN_FILENO). We then connect QSocketNotifier's activated signal to a particular slot that deals with new data.
The second mechanism is the QTimer class. This class is provided to support regularly scheduled background processing, as well as single-shot timed events. The Qt documentation tells us that by setting up a QTimer object with zero timeout, we can cause a function to be called whenever there are no events to process. Again, the mechanisms for connecting the QTimer to the actual function that does the background processing are signals and slots.
Figure 2 illustrates rtp's control flow and data processing scheme. The Qt event loop is the control center for the application. It calls functions in the rtp application as events occur. Each arrow in the diagram represents a call into a function or library. Note that only the functions which have names starting with PlotWindow or RtpRender are actual rtp code. The rtp functions consist of X event callbacks (such as PlotWindow::paintEvent and friends), the QSocketNotifier callback (PlotWindow::slotStdinAwake) and the QTimer callback (RtpRender::slotWorkAwhile).
XLib is the lowest-level C library provided as an interface to an X server across a byte-stream socket. It manages both the input side of the socket, which provides events, and the output side which sends requests to the server. (Note that Figure 2 shows only the input side.) XLib also provides certain performance optimizations, such as filtering redundant events and delaying requests in an internal queue in order to group requests in large data blocks for efficient socket usage. For details, see Adrian Nye's classic XLib book in Resources.
The POSIX select system call is commonly used by applications that service more than one file descriptor (socket) in a single thread. select is used by applications, such as rtp, that must respond to data on any of several file descriptors and do not wish to waste CPU time by polling. Additionally, Qt uses select's timeout function to start QTimer-scheduled functions.
The select call in the Qt library is the only place (to my knowledge) where the rtp process can block. For newcomers to systems programming, I should explain what “blocking” means. A multitasking operating system such as Linux must be able to multiplex the execution of a large number of programs on a smaller number of processors. By trapping interrupts, Linux switches the processor(s) among running programs in a certain order. This makes it impossible for a single program to lock the system by entering an infinite loop.
Because Linux has pre-emptive multitasking, rtp could enter an infinite loop waiting for either an X event or a data point on STDIN without locking the system. However, the CPU time spent in this loop would unnecessarily degrade the performance of the rest of the executing programs. Therefore, most calls into the kernel for I/O will “block” the executing program until the I/O is complete. The program will be removed from the set of programs run by the CPU. Once the I/O is done, the program is marked as “runnable” and will re-enter the kernel's run queue to be switched in and out of the CPU along with other runnable programs.
select is Qt's way of saying, “I have nothing to do until an X event is available, an I/O event occurs on one of the QSocketNotifier objects, or a timeout from one of the QTimer objects expires.” From Qt's point of view, select waits for one of these events to occur, then returns.
The Qt documentation clearly describes how to use QSocketNotifier and QTimer to hook into select. However, it does not fully describe the priority levels of X events vs. other socket events vs. timer events. In writing a program such as rtp, it is important to understand these details, because the program's performance greatly depends on them.