You want signalfd, which may optionally fed to epoll or any of the other multiplexing syscalls.
Signalfd can mostly be implemented on any platform using a pipe (if you don't have to mix 32-bit and 64-bit processes, or if you don't need the siginfo payload, or if you read your kernel's documentation enough to figure out which "layout" of the union members is active - this is really hairy). Note however the major caveat of running out of pipe buffer.
A more-reliable alternative is to use an async-signal-safe allocator (e.g. an `mmap` wrapper) to atomically store the payloads, and only use a pipe as a flag for whether there's something to look for.
Of course, none of these mechanisms are useful for naturally synchronous signals, such as the `SIGSEGV` from dereferencing an invalid pointer, so the function-coloring approach still needs to be used.