Linux kernel Source-code scenario analysis-Interrupt top half

Source: Internet
Author: User

First, interrupt initialization

1. Initialization of the interrupt vector table IDT

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 as follows:

void set_intr_gate (unsigned int n, void *addr) {_set_gate (IDT_TABLE+N,14,0,ADDR);}
A door descriptor is set in IDT, such as:


Selector for _kernel_cs. P for 1;DPL for 00;DT for 0;type for 14, interrupt gate. Offset is the deviation of interrupt[i].


So what is the function of interrupt[i]? Expand as defined by several macros, as follows:

void (*interrupt[nr_irqs]) (void) = {Irq0x00_interrupt,irqx01_interrupt,..... Irqx0f_interrupt};
Irq0x00_interrupt, expanded as defined by several macros, as follows:

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, as follows:

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", enabling and disable are used to turn on and off their owning channel, the ACK is used to respond to the interrupt controller, and end is used on the eve of each interrupt service return.


These data structures are defined as follows:

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);        ......}
where irq0, as follows:

static struct Irqaction irq0  = {timer_interrupt, sa_interrupt, 0, "timer", NULL, NULL};
Combine the above struct irqaction to understand.

SETUP_IRQ, as follows:

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) {//If the interrupt request queue already has elements */* 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, representing the 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;//If the interrupt request queue has no elements, the irq0 is directly chained 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 executing interrupt handler function

If the interrupt occurs in the user state, it will form as follows:



(1), the CPU from the interrupt controller to take the interrupt vector, and then according to the specific interrupt vector (in this case, 0x20), from the interrupt vector table IDT to find the corresponding table entry, and the table entry should be an interrupt gate.

First, the SS of the user-state stack, the esp,eflags of the user stack, and the cs,eip of the user space are deposited into the system stack (obtained from 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 the completion of the Save_all, the same stack is formed. 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 executes the DO_IRQ.


2. Execute Interrupt handler 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 corresponding 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, execute the following code action = desc->action;//Find the 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)//If action is null, 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, &regs, 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))//If this flag position is 0, then the __sti () must be executed in the case of an interrupt;//interrupt do {status |= action->flags;action- >handler (IRQ, action->dev_id, regs);//execute 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 executed at the shut-off interrupt. However, Action->flags's Sa_interrupt 0 is executed in the case of an interruption.


If the interrupt handler is executed, it is in an open interrupt condition, and it happens to be the same channel interrupt, which is the IRQ interrupt number (assumed to be 0). Since the last interrupt has not exited, the Desc->status is irq_inprogress at this time. 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 execute 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 execute:
        for (;;) {Spin_unlock (&desc->lock); Handle_irq_event (IRQ, &regs, action); Spin_lock (&desc->lock); Desc->status & irq_pending)//Desc->status is irq_pending| due to new interrupt execution Irq_inprogress, so continue execution 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, with the following code:

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 executing the interrupt handler function, returns DO_IRQ, checks if there is an interrupt to be executed, and if necessary, calls DO_SOFTIRQ, and the lower half starts execution in the case of an open interrupt.


3. After executing interrupt handler function

After execution of the DO_IRQ, the RET is called and returned to Ret_from_intr execution with the following code:

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 interrupt is in User state jne Ret_with_reschedule   //If in user state, then execute 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

Linux kernel Source-code scenario analysis-Interrupt top half

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.