When I needed something similar to that for older Linux kernels, I have used primitives based on file descriptors (eventfd for manually reset events, pidfd_open to wait for completion of processes, mq_open for sending messages), and poll() to wait for multiple things with one system call.