Signals are often described as software interrupts. The kernel of a multitasking Operating System such as UNIX switches between running applications when either a running application goes to sleep (calls a blocking system call or enters select(2) or poll(2)) or after a certain period of time (ie. 10 ms). Signals are handled by the kernel at the moment it switches between running processes, hence the word software interrupts. Interrupting an application that doesn't go to sleep within 10 ms is performed by a hardware interrupt though and the program execution can be at any arbitrary point. When such an application has pending signals, then the signal handler function is called while the application is in a totally undefined state. For instance, the application could be in the middle of a call to malloc(3) or free(3), which makes it dangerous to allocate memory from within a signal handler.
The result of how signals work is that signal handlers can only be allowed to set a flag of a very simple type: sig_atomic_t (often an int). This fact gives signals their real meaning: Signals are a way of communication between processes or between the kernel and a process. Signals fall in the category of IPC (Inter Process Communication), just like pipes and UNIX sockets. For a correct understanding of signals one has to see them as messages, not as interrupts.
And like with messages received via a socket, there is no accurate timing possible - not without a Real Time kernel.
SignalServer
Libcw treats signals as events. The event server for signals is a singleton, its instance can be accessed through the static method SignalServer::instance(). The signal event server accepts request data of type SignalRequestData.
The signal event server uses sigaction(2) to turn on and off handling of signals. Handling is turned on when events are requested and turned off when the last request is removed. Note that before any event request a signal is handled by its default handler (SIG_DFL), while after the last event request is removed, the signal is ignored (SIG_IGN).
A single handler is used for all signals, counting the total number of signals occurred as well as the number of occurrances per signal. Signal events are dispatched from the main loop. The number of times a specific signal occurred in between two such main loop checks is passed as part of the SignalData to the event client.
As with all events, a busy interface and/or a cookie can be passed to the signalRequest call as needed (see also chapter Events §2.2.6).
SignalRequestData::SignalRequestData (int sig, int flags = 0)
Constructs a signal request object.
sig is the requested signal.
flags specifies a set of flags which modify the behavior of the signal handling process. It is formed by the bit-wise OR of zero or more of the following:
sigOneShot | : | Remove the request after it is being handled. The callback function will be called only once. | |
sigReplace | : | For each signal type sig, there can be only one request with this flag set. Each new request with sigReplace set causes the previous request to be removed. |
The event interface uses operator() to register event requests. Because the signal server is a singleton, one must use the rather obnoxious looking code (where Foo::foo is an event client and Foo::gotSIGHUP(SignalType const&) the callback function)
(SignalServer::instance())(SignalRequestData(SIGHUP), foo, &Foo::gotSIGHUP);
In order to make such expressions more readable, libcw defines the macro signalRequest
#define signalRequest (SignalServer::instance())
allowing the developer to request signal notifications with better looking code
signalRequest(SignalRequestData(SIGHUP), foo, &Foo::gotSIGHUP);
For example, suppose you want your application class MyApplication to handle the signals SIGINT and SIGHUP. Then you could request the signal server to notify you by calling:
signalRequest(SignalRequestData(SIGINT, sigOneShot|sigReplace), MyApplication::instance(), &MyApplication::handle_signal); signalRequest(SignalRequestData(SIGHUP), MyApplication::instance(), &MyApplication::handle_signal);
where the callback function must be declared as
void MyApplication::handle_signal(SignalType const&);
Note that instead of the macro we could have created a global reference for the server with
SignalServer& signalRequest(SignalServer::instantiate());
However, this solution has a pitfall: You don't know in what order global objects are initialized (click here for more info). Using this global reference, the compiler assumes that it is initialized and uses its value without checking it. That means that this would coredump when a signal is requested from the constructor of a global object that is initialized first. Therefore you shouldn't use such a reference from global constructors, but instead (SignalServer::instantiate()).
In this sense, the reference is equivalent with SignalServer::instance() (also this function returns a reference without checking if it is initialized, see chapter Global Objects Management) except for the order of initialization: it is possible that using the reference does work on one operating system, but doesn't work on another and vica versa. The relevant difference however is that the macro DEBUGGLOBAL enables checks for «static initialization order fiasco» errors when SignalServer::instance() is used, but would not catch such an error when the reference signalRequest is used in a global constructor (and is initialized first by coincidence). This is why using the reference is bad.
So now we have to choose between the following solutions:
Duplicating code and changing (proto)types as function of debugging macros are absolutely evil. Therefore we have only one solution left: use a macro for signalRequest.
The disadvantages of this macro are considered less serious than the disadvantages of the other solutions.
The event type reference that is passed as a parameter to the callback function (the «Foo::gotSIGHUP» above), is derived from class SignalData.
int SignalData::get_signal(void) const
Returns the signal that needs handling. This is only interesting when using the same callback function for more than one signal, of course.
sig_atomic_t SignalData::get_count(void) const
Returns the number of times the signal occurred since the last time a callback function for this signal was called (or since any signal event request was received). This value will usually be 1 unless the application is being flooded with the signal faster then it could return to the main loop.
This is the unique event type of SignalServer which is passed to the callback function. It is derived from SignalData and doesn't have any extra data or methods.
For example, the callback function handle_signal from the previous example could be defined like:
void MyApplication::handle_signal(SignalType const& signal_type) { switch(signal_type.get_signal()) { case SIGHUP: Dout( dc::notice, "Received SIGHUP " << signal_type.get_count() << " times." ); break; case SIGINT: fd_dct::terminate_when_done(); break; } }
Note that the usage of sigOneShot in the request for SIGINT guarantees that terminate_when_done() will only be called once. While the usage of the flag sigReplace means that if later a new request for SIGINT is made by any other object, this one will be removed.
The application base class libcw_app_tct initializes a few default signal handlers with the following effects: