This naturally brings us to the question, Why would I use one over the other?
The most obvious advantage of InterruptAttachEvent() is that it's simpler to use than InterruptAttach()—there's no ISR routine (hence no need to debug it). Another advantage is that since there's nothing running in kernel space (as an ISR routine would be) there's no danger of crashing the entire system. If you do encounter a programming error, then the process will crash, rather than the whole system. However, it may be more or less efficient than InterruptAttach() depending on what you're trying to achieve. This issue is complex enough that reducing it to a few words (like faster or better) probably won't suffice. We'll need to look at a few pictures and scenarios.
Here's what happens when we use InterruptAttach():
The thread that's currently running (thread1) gets interrupted, and we go into the kernel. The kernel saves the context of thread1. The kernel then does a lookup to see who's responsible for handling the interrupt and decides that ISR1 is responsible. At this point, the kernel sets up the context for ISR1 and transfers control. ISR1 looks at the hardware and decides to return a struct sigevent. The kernel notices the return value, figures out who needs to handle it, and makes them READY. This may cause the kernel to schedule a different thread to run, thread2.
Now, let's contrast that with what happens when we use InterruptAttachEvent():
In this case, the servicing path is much shorter. We made one context switch from the currently running thread (thread1) into the kernel. Instead of doing another context switch into the ISR, the kernel simply pretended that the ISR returned a struct sigevent and acted on it, rescheduling thread2 to run.
Now you're thinking, Great! I'm going to forget all about InterruptAttach() and just use the easier InterruptAttachEvent().
That's not such a great idea, because you may not need to wake up for every interrupt that the hardware generates! Go back and look at the source example above—it returned an event only when the modem status register on the serial port changed state, not when a character arrived, not when a line status register changed, and not when the transmit holding buffer was empty.
In that case, especially if the serial port was receiving characters (that you wanted to ignore), you'd be wasting a lot of time rescheduling your thread to run, only to have it look at the serial port and decide that it didn't want to do anything about it anyway. In that case, things would look like this:
All that happens is that you incur a thread-to-thread context switch to get into thread2 which looks at the hardware and decides that it doesn't need to do anything about it, costing you another thread-to-thread context switch to get back to thread1.
Here's how things would look if you used InterruptAttach() but didn't want to schedule a different thread (i.e., you returned):
The kernel knows that thread1 was running, and the ISR didn't tell it to do anything, so it can just go right ahead and let thread1 continue after the interrupt.
Just for reference, here's what the InterruptAttachEvent() function call does (note that this isn't the real source, because InterruptAttachEvent() actually binds a data structure to the kernel—it isn't implemented as a discrete function that gets called!):
// the "internal" handler static const struct sigevent * internalHandler (void *arg, int id) { struct sigevent *event = arg; InterruptMask (intr, id); return (arg); } int InterruptAttachEvent (int intr, const struct sigevent *event, unsigned flags) { static struct sigevent static_event; memcpy (&static_event, event, sizeof (static_event)); return (InterruptAttach (intr, internalHandler, &static_event, sizeof (*event), flags)); }