When it's time to resume, the GC unprotects the page then writes a byte (or enough bytes if read() is used), which wakes all the blocked mutator threads.
Those threads also need to synchronise acks with the GC. After the GC protects the guard page, the mutators don't stop mutating immediately, so they must send an ack to the GC thread from their SIGSEGV handler to say when they have stopped. A pipe can be used for this too. Use of a non-mutex-using atomic counter, if available, can speed this by ensuring the GC only wakes once. Similarly the mutator threads must send an ack after they resume before returning from their SIGSEGV handler, to ensure the next GC run does not race with a mutator thread still slowly handling the current GC run
On Linux, futex or eventfd are async-signal-safe but non-portable alternatives to pipes. Futex has the advantage of not using a file descriptor and may be faster.