InterruptAttach() versus InterruptAttachEvent()

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():

Figure 1. Control flow with 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():

Figure 2. Control flow with 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:

Figure 3. Control flow with InterruptAttachEvent() and unnecessary rescheduling.

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):

Figure 4. Control flow with InterruptAttach() with no thread rescheduling.

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));
}