Hopman and the select() system call

Reactive applications

When an application must essentially react to external events like user inputs or kernel events, it is called reactive. An example is the shell, which remains sleeping in the absence of inputs, where inputs can be user keyboard and child termination. So are text editors. Similarly, programs with a graphical interface are permanently waiting for mouse events and/or keyboard input. All these applications must react without delay to various inputs, which means that they use very little CPU resource and essentially remain sleeping until some input requires action. When such an application needs to perform resource intensive action, it delegates it either to a dedicated thread or to another application.

Asynchronous events

There are two sorts of asynchronous events an application can receive:

The select() system call

At the heart of the art of asynchronous programming in Unix is the select() system call. This system call has variants such as pselect(), poll(), and more recently introduced, epoll(). Here we will talk of them all under the generic name of select(), since they all do the same thing with different syntaxes and different limitations and options.

When an application invokes select(), it passes it a list of I/O events it is waiting for, and (unless otherwise specified) enters a sleeping state until at least one of the expected events happens. select() also returns when a signal is caught.

In Linux, thanks to the signalfd API, the signals can be routed to a pseudo-file descriptor, which can be awaited for, like any other file descriptor thanks to select() - in this case, the signals must be masked. This allows to serialise the processing of signals and to treat all asynchronous events in the same way.

Kernel events

In addition to ordinary I/O paths, there are several kinds of events which can be produced by the kernel upon request of an application. Such are

ALl these events are presented to the application as data to be read from pseudo-file descriptors, making them eligible for use in select().

Callback management

As we have seen, all events in Unix can be awaited in one single place by invoking select() with all related I/O paths in the same time. It is an error to wait for a set of I/O path in one place and for others in another place. In the best case it would lead to inefficiency.

In some applications, the I/O paths which must be monitored are known from the beginning and never change. In some others they may be created on the fly - eg when a new client connects to a server.

The classical way to implement the use of select() is through a well organized registration of callbacks. A callback-declarator function takes as argument

The callback-function is to be called when the given event is detected. This kind of function is named a callback function, because of the method in which it is used. It receives in argument the file descriptor and the event type (input or output).

When the initial set of callback functions and I/O paths have been declared and all other initialization steps have been done, the program enters its reactive phase, which normally lasts until the end. The reactive phase consists in an indefinite loop made of select(), followed by the invocation of the callback functions for all detected events.

Interfacing with a graphics library

The graphics libraries make use of select() to handle mouse-events (movements and clicks) and keyboard inputs. They have, therefore their own calback-declarator. Since it is well known that having another invocation of select() in the application would lead to a total mess, the graphics libraries use to make their callback-declarator available to the application, although with their particular datatype for the file descriptors and argument list for the callback function. For example, in the GTK+-2 library, the callback declarator is g_io_add_watch(). GTK+-2 is an object-oriented library written in C. The file descriptors are represented by objects of type GIOChannel, which must be created by gio_channel_unix_new(filedescriptor). Similarly, a particular type is declared for callback functions.

Hopman and graphics libraries

Hopman was designed independantly of any particular graphics library. It comprises an initialization phase and a reactive phase (reactions to device insertion/removal through inotify, reaction to signals (SIGWAIT, SIGTERM, SIGHUP), and reactions to changes in /proc/self/mountinfo. it therefore provides three callback functions, one for the file descriptor associated to Inotify, one for the file descriptor associated to signalfd, and one for the condition "urgent data" of /proc/self/mountinfo. The library watch.a provides six functions to initialize and to read inotify, signalfd and mountinfo events. It is up to the main program "hopman" associated to the graphics library to invoke the initialization functions and convert the file descriptors to objects matching the types defined by the graphics library and declare callback functions apropriate to invoke the functions which read the associated paths.