darlingserver needs to be involved in signal processing for two main reasons. The first is that there is some Mach functionality that relies on having access to signal information such as what signal occurred and the register state when it occured so that we can pass this on to debuggers when they ask for it. The second reason is that signals can occur while a thread is waiting for a server call to complete. When this occurs, there is a possible race between the server replying to thread's interrupted call and the thread making a new server call as part of signal processing/handling.

Informing the Server about Signals

When a thread is interrupted/signaled, it informs the server using the interrupt_enter server call (more on that in Handling Interrupted Calls). This method only informs the server that the thread has been interrupted and allows the client to wait for the server to sort things out on its end (in case it was in the middle of processing the client's interrupted call). A separate call, sigprocess, is required to inform the server about the signal info and register state.

On the server side, this call reads the signal information from the client and hands it over to some duct-taped XNU signal processing code. This code notifies anyone that's interested (e.g. debuggers) and allows them to manipulate the thread state if necessary. Once that's done, the server writes out the new thread state to the client and sends the reply to allow the client to continue. The client then processes the signal as appropriate (e.g. calling signal handlers installed by program code), and once it's done, it informs the server using interrupt_exit.

Handling Interrupted Calls

One issue with handling signals is that signal handlers need to make server calls of their own, but signals can occur at any time, even in the middle of another server call. If this is not handled correctly, a race condition is almost sure to occur, with the server sending a reply for the interrupted call at the same time that the client makes a new call in the signal handler.

That's why the interrupt_enter call also synchronizes the call state with the server. What this means is that it informs the server that the client received a signal and waits for the server to give it the green light to continue.

On the server side, if the server was processing a call for the client, the call is aborted and any reply it generates is saved to be sent once the client finishes processing the signal (i.e. when it makes the interrupt_exit call).

However, there's still another possible source for a race condition: what if the server had just finished processing the call and had already queued the reply (or had just finished sending it) when the client received a signal and made the interrupt_enter call? In this case, the client would immediately receive a reply, but for the wrong call (the one that was just interrupted). That's why another job of interrupt_enter on the client side is to handle unexpected replies and push them back to the server.

It allocates a buffer (on the stack) large enough to accomodate all possible replies and file descriptors. When it receives a reply it did not expect (i.e. a reply that is not for the interrupt_enter call), it sends it back to the server using a special push_reply message, which the server uses to save the message so it can send it once it receives an interrupt_exit call.