I think the reason is mainly historical. In the original BSD socket API, it was only possible register to receive a SIGIO
(or SIGURG
) signal, which did not include information identifying the file descriptor.
The select()
API was designed to multiplex among multiple ready sockets, and I venture that the SIGIO
interface was intended to indicate that an application's main loop should call select()
(which would not block when triggered by SIGIO
).
Because of limitations of select()
when dealing with processes that support a large number of file descriptors (as file descriptors are held in a bitmap), Sys V introduced the poll()
interface, which replaces the bitmap with an array of descriptors.
poll()
itself suffers when the number of descriptors becomes large, as each call needs to copy the entire array to the kernel. Consequently, Linux introduced the epoll()
interface, which allows file descriptors to be registered once, reducing per-blocking call overhead.
However, the POSIX real-time working group, building on the improved signal handling facilities of SysV, introduced real-time signals, which are queued and carry additional information. For example, both POSIX timers and POSIX asynchronous I/O indicate events (timing resp. file operation completion) via real-time signals. The additional information of the signal identifies some user-defined data or the file descriptor, which allows for efficient de-multiplexing of the event stream.
And in fact, Linux has extended the SIGIO
mechanism to allow arbitrary signals and to associate additional information with queued signals.
Signal handling is tricky in POSIX though, primarily because it is not possible to direct signal delivery to a given thread. To do this, you will need to block reception of that signal to all threads except the one(s) you designate for handling that I/O. A somewhat easy way to do this is to block the signal during initialization, while the process is still single-threaded, and to unblock in exactly those threads that can handle it.
An easy way of working-around the async-signal safety issue is to receive and process signals synchronously via sigwaitinfo()
or sigtimedwait()
.