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