As the name suggests, interrupt signals provide a way to divert the processor to code outside the normal flow of control. When an interrupt signal arrives, the CPU must stop what it's currently doing and switch to a new activity; it does this by saving the current value of the program counter (i.e., the content of the eip and cs registers) in the Kernel Mode stack and by placing an address related to the interrupt type into the program counter.
There is a key difference between interrupt handling and process switching: the code executed by an interrupt or by an exception handler is not a process. Rather, it is a kernel control path that runs on behalf of the same process that was running when the interrupt occurred. As a kernel control path, the interrupt handler is lighter than a process (it has less context and requires less time to set up or tear down).
Each interrupt or exception is identified by a numberranging from to 255; for some unknown reason, Intel calls this 8-bit unsignednumber a vector. The vectorsof nonmaskable interrupts and exceptions are fixed, while those of maskableinterrupts can be altered by programming the Interrupt Controller.
Linux uses the following vectors:
• Vectors ranging from to 31 correspond toexceptions and nonmaskable interrupts.
• Vectors ranging from 32 to 47 are assigned tomaskable interrupts, that is, to interrupts caused by IRQs.
• The remaining vectors ranging from 48 to 255 may be used to identify software interrupts. Linux uses only one of them, namely the 128 or 0x80 vector,which it uses to implement system calls. When an int 0x80 Assembly instruction is executed by a process in User Mode, the CPU switches into Kernel Mode and starts executing the system_call( ) kernel function.
A system table called Interrupt Descriptor Table (IDT) associates each interrupt or exception vector with the address of the corresponding interrupt or exception handler. The IDT must be properly initialized before the kernel enables interrupts.
#define NR_IRQS 128
struct irqdesc {
unsigned int nomask : 1; /* IRQ does not mask in IRQ */
unsigned int enabled : 1; /* IRQ is currently enabled */
unsigned int triggered: 1; /* IRQ has occurred */
unsigned int probing : 1; /* IRQ in use for a probe */
unsigned int probe_ok : 1; /* IRQ can be used for probe */
unsigned int valid : 1; /* IRQ claimable */
unsigned int unused :26;
void (*mask_ack)(unsigned int irq); /* Mask and acknowledge IRQ */
void (*mask)(unsigned int irq); /* Mask IRQ */
void (*unmask)(unsigned int irq); /* Unmask IRQ */
struct irqaction *action;
unsigned int unused2[3];
};
static struct irqdesc irq_desc[NR_IRQS];
/*
* do_IRQ handles all normal device IRQ's
*/
asmlinkage void do_IRQ(int irq, struct pt_regs * regs)
{
struct irqdesc * desc = irq_desc + irq;
struct irqaction * action;
int status, cpu;
spin_lock(&irq_controller_lock);
desc->mask_ack(irq);
spin_unlock(&irq_controller_lock);
cpu = smp_processor_id();
irq_enter(cpu, irq);
kstat.irqs[cpu][irq]++;
desc->triggered = 1;
/* Return with this interrupt masked if no action */
status = 0;
action = desc->action;
if (action) {
if (desc->nomask) {
spin_lock(&irq_controller_lock);
desc->unmask(irq);
spin_unlock(&irq_controller_lock);
}
if (!(action->flags & SA_INTERRUPT))
__sti();
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli();
if (!desc->nomask && desc->enabled) {
spin_lock(&irq_controller_lock);
desc->unmask(irq);
spin_unlock(&irq_controller_lock);
}
}
irq_exit(cpu, irq);
/*
* This should be conditional: we should really get
* a return code from the irq handler to tell us
* whether the handler wants us to do software bottom
* half handling or not..
*
* ** IMPORTANT NOTE: do_bottom_half() ENABLES IRQS!!! **
* ** WE MUST DISABLE THEM AGAIN, ELSE IDE DISKS GO **
* ** AWOL **
*/
if (1) {
if (bh_active & bh_mask)
do_bottom_half();
__cli();
}
}
A bottom half is a low-priority function, usually related to interrupt handling, that is waiting for the kernel to find a convenient moment to run it. Bottom halves that arewaiting will be executed only when one of the following events occurs:
• The kernel finisheshandling a system call.
• The kernel finisheshandling an exception.
• The kernelterminates the do_IRQ( ) function—thatis, it finishes handling an interrupt.
• The kernel executes the schedule( ) function to select anew process to run on the CPU.