The hardware device that generated the interrupt will keep the interrupt line asserted until it's sure the software handled the interrupt. Since the hardware can't read minds, the software must tell it when it has responded to the cause of the interrupt. Generally, this is done by reading a status register from a specific hardware port or a block of data from a specific memory location.
In any event, there's usually some form of positive acknowledgment between the hardware and the software to de-assert the interrupt line. (Sometimes there isn't an acknowledgment; for example, a piece of hardware may generate an interrupt and assume that the software will handle it.)
Because the interrupt runs at a higher priority than any software thread, we should spend as little time as possible in the ISR itself to minimize the impact on scheduling. If we clear the source of the interrupt simply by reading a register, and perhaps stuffing that value into a global variable, then our job is simple.
This is the kind of processing done by the ISR for the serial port. The serial port hardware generates an interrupt when a character has arrived. The ISR handler reads a status register containing the character, and stuffs that character into a circular buffer. Done. Total processing time: a few microseconds. And, it must be fast. Consider what would happen if you were receiving characters at 115 Kbaud (a character about every 100 µs); if you spent anywhere near 100 µs handling the interrupt, you wouldn't have time to do anything else!
Clearly, minimizing the amount of time spent in the interrupt can be perceived as Good customer service in our analogy—by keeping the amount of time that we're on the phone to a minimum, we avoid giving other customers a busy signal.
What if the handler needs to do a significant amount of work? Here are a couple of possibilities:
In the first case, we'd like to clear the source of the interrupt as fast as possible and then tell the kernel to have a thread do the actual work of talking to the slow hardware. The advantage here is that the ISR spends just a tiny amount of time at the super-high priority, and then the rest of the work is done based on regular thread priorities. This is similar to your answering the phone (the super-high priority), and delegating the real work to one of your assistants. We'll look at how the ISR tells the kernel to schedule someone else later in this chapter.
In the second case, things get ugly. If an ISR doesn't clear the source of the interrupt when it exits, the kernel will immediately be re-interrupted by the Programmable Interrupt Controller (PIC—on the x86, this is the 8259 or equivalent) chip.
We'll continuously be running the ISR, without ever getting a chance to run the thread-level code we need to properly handle the interrupt.
What kind of brain-damaged hardware requires a long time to clear the source of the interrupt? Your basic PC floppy disk controller keeps the interrupt asserted until you've read a number of status register values. Unfortunately, the data in the registers isn't available immediately, and you have to poll for this status data. This could take milliseconds (a long time in computer terms)!
The solution to this is to temporarily mask interrupts—literally tell the PIC to ignore interrupts from this particular source until you tell it otherwise. In this case, even though the interrupt line is asserted from the hardware, the PIC ignores it and doesn't tell the processor about it. This lets your ISR schedule a thread to handle this hardware outside the ISR. When your thread is finished transferring data from the hardware, it can tell the PIC to unmask that interrupt. This lets interrupts from that piece of hardware be recognized again. In our analogy, this is like transferring the VIC's call to your assistant.