First, interrupt initialization
1. Interrupt Vector Table IDT initialization
void __init Init_irq (void) {int i; #ifndef Config_x86_visws_apicinit_isa_irqs (); #elseinit_VISWS_APIC_irqs (); #endif/* * Cover the whole vector space, no vector can escape * us. (Some of these would be overridden and become * ' special ' SMP interrupts) */for (i = 0; i < Nr_irqs; i++) {//nr_irqs 224 int vector = First_external_vector + i;//first_external_vector for 0x20if (vector! = syscall_vector)//syscall_vector for 0x80 Set_intr_gate (vector, interrupt[i]);} ......}
For interrupt vectors from 0x20 to 224, set the interrupt handler, Set_intr_gate for example:
void set_intr_gate (unsigned int n, void *addr) {_set_gate (IDT_TABLE+N,14,0,ADDR);}
A portal descriptor is set up in IDT. For example, with:
Selector for _kernel_cs. P is 1;dpl to 00. DT is 0. Type is 14, which interrupts the gate.
Offset is the deviation of interrupt[i].
So what is the function of interrupt[i]? A deployment that has been defined by several macros. For example, the following:
void (*interrupt[nr_irqs]) (void) = {Irq0x00_interrupt. Irqx01_interrupt,..... Irqx0f_interrupt};
Irq0x00_interrupt. An expansion that has been defined by several macros, such as the following:
asmlinkage void Irq0x00_interrupt (); __asm__ ("\ n" "Irq0x00_interrupt: \n\t" "Pushl $0x00-256 \n\t" "JMP common_interrupt ");
2. Initialization of Interrupt request queue
In Init_irq call Init_isa_irqs, for example the following:
void __init Init_isa_irqs (void) {int i;init_8259a (0); for (i = 0; i < Nr_irqs; i++) {//nr_irqs = 224irq_desc[i].status = i Rq_disabled;irq_desc[i].action = 0;irq_desc[i].depth = 1;if (i <) {/* * Old-style inta-cycle interrupts: */irq_de Sc[i].handler = &i8259a_irq_type;//Sets the handler pointer at the beginning of the 16 interrupt request queue to point to the data structure} else {/* * ' high ' PCI IRQs filled in on demand */i Rq_desc[i].handler = &no_irq_type;}}}
Irq_dec[i] The member variable action is the header of this interrupt request queue. I have altogether 16 interrupt request queues from 0~15. Its member variable handler controls the shared "Interrupt channel". The Enable and disable are used to turn on and off their owning channel, and the ACK is used to respond to the interrupt controller. The end is used on the eve of each interrupt service return.
These data structures are defined such as the following:
struct hw_ interrupt_type {const char * typename;unsigned int (*startup) (unsigned int IRQ); void (*shutdown) (unsigned int IRQ); void (* Enable) (unsigned int IRQ), void (*disable) (unsigned int IRQ), void (*ack) (unsigned int IRQ), void (*end) (unsigned int IRQ), V OID (*set_affinity) (unsigned int IRQ, unsigned long mask);}; typedef struct HW_INTERRUPT_TYPE hw_irq_controller;/* * This is the "IRQ descriptor", which contains various information * About the IRQ, including "What kind of hardware handling it have, * whether it is disabled etc etc. * * Pad this off to 32 Bytes for cache and indexing reasons. */typedef struct {unsigned int status;/* IRQ status */hw_irq_controller *handler;struct irqaction *action;/* IRQ Action Li St */unsigned int depth;/* nested IRQ disables */spinlock_t lock;} ____cacheline_aligned irq_desc_t;extern irq_desc_t Irq_desc [Nr_irqs];
static struct Hw_interrupt_type I8259a_irq_type = {"Xt-pic", STARTUP_8259A_IRQ,SHUTDOWN_8259A_IRQ,ENABLE_8259A_IRQ, Disable_8259a_irq,mask_and_ack_8259a,end_8259a_irq,null};
struct Irqaction {void (*handler) (int, void *, struct pt_regs *); unsigned long flags;unsigned long mask;const char *name;v OID *dev_id;struct irqaction *next;};
Second, the irq0 of the irqaction data structure is linked into the corresponding interrupt request queue
void __init time_init (void) {... setup_irq (0, &irq0); ......}
among the irq0, such as the following:
static struct Irqaction irq0 = {timer_interrupt, sa_interrupt, 0, "timer", NULL, NULL};
Combine the above struct irqaction to understand.
Setup_irq. For example, the following:
int SETUP_IRQ (unsigned int IRQ, struct irqaction * new)//IRQ as interrupt request number {int shared = 0;unsigned long flags;struct irqaction *ol D, **p;irq_desc_t *desc = Irq_desc + irq;//find the corresponding channel */* Some drivers like serial.c use REQUEST_IRQ () heavily, * so we have To is careful not to interfere with a * running system. */if (New->flags & Sa_sample_random) {/* * This function might sleep, we want to call it first, * outside of the Omic block. * Yes, this might clear the entropy pool if the wrong * driver are attempted to be loaded, without actually * installing a New handler, but was this really a problem, * only the sysadmin is able to does this. */RAND_INITIALIZE_IRQ (IRQ);} /* * The following block of code have to is executed atomically */spin_lock_irqsave (&desc->lock,flags);p = &desc ->action;//find the channel corresponding interrupt processing queue if ((old = *p)! = NULL) {//Assume that there are already elements in the interrupt request queue */* Can ' t share interrupts unless both agree to */if ( ! (Old->flags & New->flags & SA_SHIRQ)) {//The flags that require both the original element and the new element are SA_SHIRQ, indicating that otherInterrupt source public This interrupt request channel Spin_unlock_irqrestore (&desc->lock,flags); return-ebusy;} /* Add new interrupt at end of the IRQ queue */do {p = &old->next;old = *p;} while (old);//link into the corresponding location shared = 1;} *p = new;//assumes that the interrupt request queue has no elements, then directly link the irq0 to the interrupt request queue if (!shared) {///The first element is linked into desc->depth = 0;desc->status &= ~ (irq_ DISABLED | Irq_autodetect | irq_waiting);//status is 0desc->handler->startup (IRQ);} Spin_unlock_irqrestore (&desc->lock,flags); Register_irq_proc (IRQ); return 0;}
Third, interrupt response
We interrupt the clock, for example.
Suppose a clock break has occurred.
1. Before running interrupt handling function
Assuming that the interrupt occurs in the user state, it will form for example by:
(1), the CPU obtains the interrupt vector from the interrupt controller, and then according to the detailed interrupt vector (in this case, 0x20). Find the corresponding table entry from the interrupt vector table IDT, and the table entry should be an interrupt gate.
First put the user-state stack of SS, the user stack of ESP. EFlags. The Cs,eip of the user space is deposited into the system stack (obtained from the TSS).
(2), the CPU according to the setting of the interrupt gate reached the entrance of the total service program of the channel.
asmlinkage void Irq0x00_interrupt (); __asm__ ("\ n" "Irq0x00_interrupt: \n\t" "Pushl $0x00-256 \n\t" "JMP common_interrupt ");
Press the interrupt number-256 onto the stack.
#define BUILD_COMMON_IRQ () asmlinkage void Call_do_irq (void); __asm__ ("\ n" __align_str "\ n" "common_interrupt:\n\t" Save_all "PUSHL $ret _from_intr\n\t" Symbol_name_str (Call_do_IRQ ) ": \n\t", "jmp" Symbol_name_str (DO_IRQ));
#define Save_all "cld\n\t" "Pushl%es\n\t" "Pushl%ds\n\t" "Pushl%eax\n\t" "Pushl%ebp\n\t" "Pushl%edi\n\t" "Pushl%esi\ N\t "" Pushl%edx\n\t "" Pushl%ecx\n\t "" Pushl%ebx\n\t "" Movl $ "STR (__kernel_ds)",%edx\n\t "" Movl%edx,%ds\n\t "" Movl%e Dx,%es\n\t "
after running the Save_all, the stack is formed as the same. At this point CS is already _kernel_cs, DS and es for _kernel_ds.
It then presses the ret_from_intr into the stack and runs the DO_IRQ.
2. Run Interrupt handling function
asmlinkage unsigned int do_irq (struct pt_regs regs)//is the contents of the above stack {/* * We ack quickly, we don ' t want the IRQ controller * t Hinking we ' re snobs just because some other CPUs have * disabled global interrupts (we had already done the * Int_ack cycle S, it's too late to try to pretend to the * controller so we aren ' t taking the interrupt). * * 0 return value means that this IRQ was already being * handled by some other CPU. (or is disabled) */int IRQ = Regs.orig_eax & 0xFF; The interrupt number was obtained for 0int CPU = smp_processor_id (); irq_desc_t *desc = Irq_desc + irq;//Find the appropriate channel struct irqaction * action;unsigned int Status;kstat.irqs[cpu][irq]++;spin_lock (&desc->lock);d esc->handler->ack (IRQ);//I have processed *//REPLAY is When Linux resends a IRQ that was dropped earlier waiting be used by probe to mark IRQs This is being tested */statu s = desc->status & ~ (Irq_replay | irq_waiting); status |= irq_pending;//status to irq_pending/* * If the IRQ is disabled for whatever reason, we cannot * use th e ACtion we have. */action = Null;if (!) ( Status & (Irq_disabled | irq_inprogress)) {//status is irq_pending. Run the following code, ACTION = desc->action;//Find the appropriate interrupt processing queue for the channel status &= ~irq_pending; Status is 0, the irq_pending position cleared the status |= irq_inprogress; Status Irq_inprocess}desc->status = Status;//desc->status for irq_inprocess/* * If There is no IRQ handler or it was Disabled, exit early. Since we set PENDING, if another processor is handling a different instance of this same IRQ, the other processor would Take care of it. */if (!action)//Assume that action is null. Direct Exit Goto out;/* * Edge triggered interrupts need to remember * pending events. * This applies-to-any HW interrupts-allow a second * instance of the same IRQ-to-arrive while we is in DO_IRQ * or I n the handler. But the code here is handles the _second_ * instance of the IRQ, not the third or fourth. So it was mostly * useful for IRQ hardware that does not mask cleanly in an * SMP environment. */for (;;) {Spin_unlock (&desc->lock); HandlE_irq_event (IRQ, ®s, action),//action is the head pointer of the interrupt request queue, the IRQ is 0,spin_lock (&desc->lock); Desc->status & irq_pending)) Break;desc->status &= ~irq_pending;} Desc->status &= ~irq_inprogress;//finish processing, put irq_inprocess position 0out:/* * The->end () handler have to deal with interrupts which got * disabled while the handler is running. */desc->handler->end (IRQ);//Interrupt Spin_unlock (&desc->lock), if (Softirq_active (CPU) & Softirq_mask ( CPU)//processing interrupts the lower half of Do_softirq (); return 1;}
struct Pt_regs {long ebx;long ecx;long edx;long esi;long edi;long ebp;long eax;int xds;int xes;long orig_eax ; Long eip;int xcs;long eflags;long esp;int XSS;};
Handle_irq_event. The code is as follows:
int handle_irq_event (unsigned int IRQ, struct pt_regs * regs, struct irqaction * action) {int Status;int CPU = Smp_processo R_ID (); Irq_enter (CPU, IRQ), status = 1;/* Force the ' do bottom halves ' bit */if (! ( Action->flags & Sa_interrupt))//Assuming this flag position 0, then to run in the case of an interrupt __sti ();//Open interrupt do {status |= action->flags;action- >handler (IRQ, action->dev_id, regs);//Run interrupt handler function on interrupt request queue in sequence action = Action->next;} while (action), if (Status & Sa_sample_random) add_interrupt_randomness (IRQ), __CLI ();//Off Interrupt Irq_exit (CPU, IRQ); return status;}
In this example, the interrupt handler function is Timer_interrupt (Action->handler).
We see that most of the interrupt handling functions are running under shut-off interrupts.
But Action->flags's Sa_interrupt 0, is running in the case of interruption.
Assume that you run an interrupt handler function. In the case of an open interrupt. And this happens to be the same channel interrupt, which is the IRQ interrupt number (assuming all 0). Because the last interrupt has not yet exited. At this point desc->status is irq_inprogress. Let's look at this code:
Status = Desc->status & ~ (Irq_replay | irq_waiting); Status |= irq_pending;//this status is Irq_pending| irq_inprogress/* * If The IRQ is disabled for whatever reason, we cannot * use the action we have. */action = Null;if (!) ( Status & (Irq_disabled | irq_inprogress)) {//Do not run the following program action = Desc->action;status &= ~irq_pending; status |= irq_inprogress;} Desc->status = Status;//desc->status to Irq_pending| Irq_inprogressif (!action)//action is null and exits Goto out;
Assuming that the new interrupt exits, the original interrupt continues to run:
for (;;) {Spin_unlock (&desc->lock); Handle_irq_event (IRQ, ®s, action); Spin_lock (&desc->lock); Desc->status & irq_pending)//Because the new interrupt is running Desc->status irq_pending| Irq_inprogress. So continue running for Loop break;desc->status &= ~irq_pending;//desc->status for irq_inprogress}
This makes it a loop to dissolve the interrupt nesting that occurs on the same channel.
We continue to analyze interrupt handling functions. Timer_interrupt, code such as the following:
static void Timer_interrupt (int irq, void *dev_id, struct pt_regs *regs) {int count; .... do_timer_interrupt (IRQ, NULL, re GS); Write_unlock (&xtime_lock);}
static inline void do_timer_interrupt (int irq, void *dev_id, struct pt_regs *regs) { ... do_timer (regs); ......}
void Do_timer (struct pt_regs *regs) {(* (unsigned long *) &jiffies) + +; #ifndef config_smp/* SMP process accounting uses t He local APIC timer */update_process_times (User_mode (regs));//related to process scheduling #endifmark_bh (TIMER_BH);//interrupt the lower half of the relevant if (tq_active (Tq_timer)) MARK_BH (TQUEUE_BH);}
After the interrupt handler function is run. Returns the DO_IRQ and checks if there is an interruption in the lower part that needs to be run. If you need to run, call Do_softirq, and the lower half starts running in the case of an open interrupt.
3. After running interrupt processing function
When the DO_IRQ is finished running, RET is called. Return to Ret_from_intr run. The code is as follows:
ENTRY (RET_FROM_INTR) get_current (%EBX) //Place a pointer to the TASK_STRUCT structure of the current process into the Register EBXMOVL eflags (%ESP),%eax# mix eflags and Csmovb CS (%ESP),%altestl $ (vm_mask | 3),%eax//see if the interrupt is in User state jne Ret_with_reschedule //assumed to be in user state, then run Ret_with_ RESCHEDULEJMP Restore_all
Ret_with_reschedule:cmpl $0,need_resched (%EBX)//View the contents of the TASK_STRUCT structure at need_resched jne Sigpending (%EBX)//View the contents of the TASK_STRUCT structure that are shifted to sigpending jne Signal_returnrestore_all:restore_all
signal_return:sti# we can get here from an interrupt Handlertestl $ (vm_mask), EFlags (%ESP) movl%esp,%eaxjne V86_signal_ret Urnxorl%edx,%edxcall symbol_name (do_signal)//processing signal JMP Restore_all
Restore_all:restore_all
#define Restore_all\//return interrupt before POPL%ebx;popl%ecx;popl%edx;popl%esi;popl%edi;popl%ebp;popl%eax;1:popl%ds;2:popl%es;a DDL $4,%esp;\//Skip Orig_eax3:iret;
state= 0flags= 4sigpending= 8addr_limit= 12exec_domain= 16need_resched= 20tsk_ptrace= 24processor= 52
Copyright notice: This article Bo Master original article. Blog, not reproduced without consent.
Linux Kernel source code scenario analysis-Interrupt Half