Linux异常处理结构

ARM 裸机程序里对异常的处理过程:

  1. 按键按下。
  2. CPU 发生中断。程序会强制的跳到异常向量处执行(中断是异常的一种)。
  3. “入口函数”是一条跳转指令。跳到某个函数:
    1. 保存被中断处的现场(各种寄存器的值)。
    2. 执行中断处理函数。
    3. 恢复被中断的现场。

LINUX 内核中断的处理过程分析:

程序要先设置异常入口。在head.S中可以看到,异常入口是0x18

@ 0x18:中断模式的向量地址
    b    HandleIRQ

中断后跳到0x18处开始执行。再跳转(b)到函数HandleIRQ处执行。

ARM 架构的 CPU 的异常向量基址可以是 0x0000 0000,也可以是 0xffff0000,LINUX 内核使用后者。这个地址并不代表实际的内存,是虚拟地址。当建立了虚拟地址与物理地址间的映射后,得将那些异常向量,即相当于把 head.S 中那些跳转指令(如:HandleSWI 等)复制拷贝到这个 0xffff0000 这个地址处去。这个过程是在 trap_init 这个函数里做:

arch/arm/kernel/traps.c

void __init trap_init(void)
{
    unsigned long vectors = CONFIG_VECTORS_BASE;//在 linux 源码顶层目录下:less .config, 搜索 "CONFIG_VECTORS_BASE" , 可看到其值为0xffff0000
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    extern char __kuser_helper_start[], __kuser_helper_end[];
    int kuser_sz = __kuser_helper_end - __kuser_helper_start;

    /*
     * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
     * into the vector page, mapped at 0xffff0000, and ensure these
     * are visible to the instruction stream.
     */
    //将异常向量复制到 0xffff0000 处,将 __vectors_start, __vectors_end - __vectors_start 这段代码拷贝到 vectors 来。vectors 是“CONFIG_VECTORS_BASE” 是个配置项(内核的配置选项)。
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

    /*
     * Copy signal return handlers into the vector page, and
     * set sigreturn to be a pointer to these.
     */
    memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
           sizeof(sigreturn_codes));

    flush_icache_range(vectors, vectors + PAGE_SIZE);
    modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

__vectors_start在arch/arm/kernel/entry-armv.s里面,它也是一些跳转指令。跟单片机的跳转类似。

__vectors_start:
    swi SYS_ERROR0
    b   vector_und + stubs_offset
    ldr pc, .LCvswi + stubs_offset
    b   vector_pabt + stubs_offset
    b   vector_dabt + stubs_offset
    b   vector_addrexcptn + stubs_offset
    b   vector_irq + stubs_offset
    b   vector_fiq + stubs_offset

    .globl  __vectors_end
__vectors_end:

    .data

    .globl  cr_alignment
    .globl  cr_no_alignment

当发生未定义指令异常vector_und的话,会跳转到 vector_und + stubs_offset 处。

这个 vector_und 地址标号是一个宏:

.macro vector_stub, name, mode, correction=0

/*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
    vector_stub und, UND_MODE

//宏:
/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
    .macro  vector_stub, name, mode, correction=0
    .align  5

vector_\name:
    .if \correction
    sub lr, lr, #\correction
    .endif

    @
    @ Save r0, lr_ (parent PC) and spsr_
    @ (parent CPSR)
    @
    stmia   sp, {r0, lr}        @ save r0, lr
    mrs lr, spsr
    str lr, [sp, #8]        @ save spsr

    @
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs r0, cpsr
    eor r0, r0, #(\mode ^ SVC_MODE)
    msr spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @
    and lr, lr, #0x0f
    mov r0, sp
    ldr lr, [pc, lr, lsl #2]
    movs    pc, lr          @ branch to handler in SVC mode
    .endm

将宏展开:

/*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
    vector_stub und, UND_MODE


//宏:
/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
    .macro  vector_stub, name, mode, correction=0
    .align  5

vector_und://展开看到,就是定义了这么一个标号vector_und
    
    @//保存现场
    @ Save r0, lr_ (parent PC) and spsr_
    @ (parent CPSR)
    @
    stmia   sp, {r0, lr}        @ save r0, lr
    mrs lr, spsr
    str lr, [sp, #8]        @ save spsr

    @//转换到管理模式
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs r0, cpsr
    eor r0, r0, #(\mode ^ SVC_MODE)
    msr spsr_cxsf, r0

    @//下面又是下一级的跳转,根据跳转表跳转。
    @ the branch table must immediately follow this code
    @
    and lr, lr, #0x0f
    mov r0, sp
    ldr lr, [pc, lr, lsl #2]
    movs    pc, lr          @ branch to handler in SVC mode
    .endm
    
    
    //跳转表:
    /*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
    vector_stub und, UND_MODE

    .long   __und_usr           @  0 (USR_26 / USR_32)
    .long   __und_invalid           @  1 (FIQ_26 / FIQ_32)
    .long   __und_invalid           @  2 (IRQ_26 / IRQ_32)
    .long   __und_svc           @  3 (SVC_26 / SVC_32)
    .long   __und_invalid           @  4
    .long   __und_invalid           @  5
    .long   __und_invalid           @  6
    .long   __und_invalid           @  7
    .long   __und_invalid           @  8
    .long   __und_invalid           @  9
    .long   __und_invalid           @  a
    .long   __und_invalid           @  b
    .long   __und_invalid           @  c
    .long   __und_invalid           @  d
    .long   __und_invalid           @  e
    .long   __und_invalid           @  f

    .align  5
        
  //在这些标号里面,又去保存那些寄存器,做一些处理,处理完后恢复那些寄存器。就是恢复那些被中断的程序。
    

中断的跳转:

__vectors_start:
    swi SYS_ERROR0
    b   vector_und + stubs_offset
    ldr pc, .LCvswi + stubs_offset
    b   vector_pabt + stubs_offset
    b   vector_dabt + stubs_offset
    b   vector_addrexcptn + stubs_offset
    b   vector_irq + stubs_offset   //发生了中断后就会跳到这里来
    b   vector_fiq + stubs_offset

vector_irq也是一个宏来定义的:

vector_stub irq, IRQ_MODE, 4

//name = irq  condition = 4
//展开:   
.macro  vector_stub, name, mode, correction=0
    .align  5

vector_irq:
    
    sub lr, lr, #4 //计算返回地址
    
    @
    @ Save r0, lr_ (parent PC) and spsr_
    @ (parent CPSR)
    @
    stmia   sp, {r0, lr}        @ save r0, lr
    mrs lr, spsr
    str lr, [sp, #8]        @ save spsr

    @//转换到管理模式
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs r0, cpsr
    eor r0, r0, #(\mode ^ SVC_MODE)
    msr spsr_cxsf, r0

    @//继续跳转
    @ the branch table must immediately follow this code
    @
    and lr, lr, #0x0f
    mov r0, sp
    ldr lr, [pc, lr, lsl #2]
    movs    pc, lr          @ branch to handler in SVC mode
    .endm
    
    
    //跳转表:
    /*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4

    .long   __irq_usr           @  0  (USR_26 / USR_32)  //用户态发生中断,跳到这里
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc           @  3  (SVC_26 / SVC_32)   //管理模式发生中断,跳到这里
    .long   __irq_invalid           @  4
    .long   __irq_invalid           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

__irq_usr:

__irq_usr:
    usr_entry   //宏,保存寄存器

#ifdef CONFIG_TRACE_IRQFLAGS
    bl  trace_hardirqs_off
#endif
    get_thread_info tsk
#ifdef CONFIG_PREEMPT
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    add r7, r8, #1          @ increment it
    str r7, [tsk, #TI_PREEMPT]
#endif

    irq_handler //宏,最终会调用asm_do_IRQ
#ifdef CONFIG_PREEMPT
    ldr r0, [tsk, #TI_PREEMPT]
    str r8, [tsk, #TI_PREEMPT]
    teq r0, r7
    strne   r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
    bl  trace_hardirqs_on
#endif

    mov why, #0
    b   ret_to_user

    .ltorg

    .align  5

irq_handler:

/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
    .macro  irq_handler
    get_irqnr_preamble r5, lr
1:  get_irqnr_and_base r0, r6, r5, lr
    movne   r1, sp
    @
    @ routine called with r0 = irq number, r1 = struct pt_regs *
    @
    adrne   lr, 1b
    bne asm_do_IRQ

asm_do_IRQ:

/arch/arm/kernel/irq.c

/*
 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not
 * come via this function.  Instead, they should provide their
 * own 'handler'
 */
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc = irq_desc + irq;

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();

    desc_handle_irq(irq, desc);

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}

linux 内核中处理异常的流程,最后调用到 "asm_do_IRQ"

//异常向量:
__vectors_start:
    swi SYS_ERROR0
    b   vector_und + stubs_offset
    ldr pc, .LCvswi + stubs_offset
    b   vector_pabt + stubs_offset
    b   vector_dabt + stubs_offset
    b   vector_addrexcptn + stubs_offset
    b   vector_irq + stubs_offset   //发生了中断后就会跳到这里来
    b   vector_fiq + stubs_offset
    
//vector_irq  ---> __irq_usr  ---> irq_handler  ---> asm_do_IRQ

ARM架构Linux内核的异常处理体系结构

Linux异常处理结构_第1张图片
039 ARM架构Linux内核的异常处理体系结构.png

分析“asm_do_IRQ”

单片机下的中断处理:

  1. 分辨是哪个中断。
  2. 调用处理函数(哪个中断就调用哪个处理函数)。
  3. 清中断。

Linux内核的中断处理与单片机的也差不多。以上单片机的 3 个过程,都是在 asm_do_IRQ 中实现的。

/*
 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not
 * come via this function.  Instead, they should provide their
 * own 'handler'
 */
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc *desc = irq_desc + irq;//中断号作为中断描述数组的索引。irq_desc是一个数组,在handle.c里定义

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();

    desc_handle_irq(irq, desc);//处理

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}
//irq_desc数组以中断号为索引
//这是一个“中断描述”数组。以中断号“NR_IRQS”为下标。
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .status = IRQ_DISABLED,
        .chip = &no_irq_chip,
        .handle_irq = handle_bad_irq,
        .depth = 1,
        .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
#ifdef CONFIG_SMP
        .affinity = CPU_MASK_ALL
#endif
    }
};
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

裸机程序中的 3 过程是在这个“handle_irq”中实现的。

handle_irq 的初始化

desc->handle_irq 是在/kernel/irq/chip.c里__set_irq_handler函数里被赋值的:

void
__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    struct irq_desc *desc;
    desc = irq_desc + irq;
    ...
    desc->handle_irq = handle;
    desc->name = name;
    ...
}

__set_irq_handler在下面函数中被调用

//include/linux/irq.h
static inline void
set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __set_irq_handler(irq, handle, 0, NULL);
}

set_irq_handler 又在下面函数中被调用

//arch/arm/plat-s3c24xx/irq.c
//从这里跟踪set_irq_handler的执行过程,可跟到__set_irq_handler,是以irq为索引,在desc数组里找到一项,把那一项的handle_irq指向传进来那个handle。显然,在s3c24xx_init_irq里面,就构造了这个数组里面的很多项。
/* s3c24xx_init_irq
 *
 * Initialise S3C2410 IRQ system
*/

void __init s3c24xx_init_irq(void)
{
    unsigned long pend;
    unsigned long last;
    int irqno;
    int i;

    irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

    /* first, clear all interrupts pending... */

    last = 0;
    for (i = 0; i < 4; i++) {
        pend = __raw_readl(S3C24XX_EINTPEND);

        if (pend == 0 || pend == last)
            break;

        __raw_writel(pend, S3C24XX_EINTPEND);
        printk("irq: clearing pending ext status %08x\n", (int)pend);
        last = pend;
    }

    last = 0;
    for (i = 0; i < 4; i++) {
        pend = __raw_readl(S3C2410_INTPND);

        if (pend == 0 || pend == last)
            break;

        __raw_writel(pend, S3C2410_SRCPND);
        __raw_writel(pend, S3C2410_INTPND);
        printk("irq: clearing pending status %08x\n", (int)pend);
        last = pend;
    }

    last = 0;
    for (i = 0; i < 4; i++) {
        pend = __raw_readl(S3C2410_SUBSRCPND);

        if (pend == 0 || pend == last)
            break;

        printk("irq: clearing subpending status %08x\n", (int)pend);
        __raw_writel(pend, S3C2410_SUBSRCPND);
        last = pend;
    }

    /* register the main interrupts */

    irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");

    for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
        /* set all the s3c2410 internal irqs */

        switch (irqno) {
            /* deal with the special IRQs (cascaded) */

        case IRQ_EINT4t7:
        case IRQ_EINT8t23:
        case IRQ_UART0:
        case IRQ_UART1:
        case IRQ_UART2:
        case IRQ_ADCPARENT:
            set_irq_chip(irqno, &s3c_irq_level_chip);
            set_irq_handler(irqno, handle_level_irq);
            break;

        case IRQ_RESERVED6:
        case IRQ_RESERVED24:
            /* no IRQ here */
            break;

        default:
            //irqdbf("registering irq %d (s3c irq)\n", irqno);
            set_irq_chip(irqno, &s3c_irq_chip);
            set_irq_handler(irqno, handle_edge_irq);
            set_irq_flags(irqno, IRQF_VALID);
        }
    }

    /* setup the cascade irq handlers */

    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

    set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
    set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
    set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
    set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

    /* external interrupts */

    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
        irqdbf("registering irq %d (extended s3c irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irqext_chip);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    /* register the uart interrupts */

    irqdbf("s3c2410: registering external interrupts\n");

    for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
        irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_uart0);
        set_irq_handler(irqno, handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
        irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_uart1);
        set_irq_handler(irqno, handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
        irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_uart2);
        set_irq_handler(irqno, handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
        irqdbf("registering irq %d (s3c adc irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_adc);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

    irqdbf("s3c2410: registered interrupt handlers\n");
}

Irq_desc[外部中断 0 ~ 3 ]:
{
    Handle_irq = handle_edge_irq;
    Chip = s3c_irq_eint0t4;
}
Irq_desc[外部中断 4 到 外部中断 23]:
 {
    Handle_irq = handle_edge_irq;
    Chip = s3c_irqext_chip;
}

以上就是初始化。是在“void __init s3c24xx_init_irq(void)”中实现的。

中断处理 C 函数入口 “asm_do_IRQ”会调用到事先初始的“handle_irq”函数。

handle_edge_irq : 处理边缘触发的中断

void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    const unsigned int cpu = smp_processor_id();

    spin_lock(&desc->lock);

    desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

    /*
     * If we're currently running this IRQ, or its disabled,
     * we shouldn't process the IRQ. Mark it pending, handle
     * the necessary masking and go out
     */
    if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || //对错误状态的处理
            !desc->action)) {
        desc->status |= (IRQ_PENDING | IRQ_MASKED);
        mask_ack_irq(desc, irq);
        goto out_unlock;
    }

    kstat_cpu(cpu).irqs[irq]++;  //中断次数计数

    /* Start handling the irq */
    desc->chip->ack(irq);  //清中断

    /* Mark the IRQ currently in progress.*/
    desc->status |= IRQ_INPROGRESS;

    do {
        struct irqaction *action = desc->action;
        irqreturn_t action_ret;

        if (unlikely(!action)) { //action 是链表。若此链表为空
            desc->chip->mask(irq); //则屏蔽中断
            goto out_unlock;
        }

        /*
         * When another irq arrived while we were handling
         * one, we could have masked the irq.
         * Renable it, if it was not disabled in meantime.
         */
        if (unlikely((desc->status &
                   (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
                  (IRQ_PENDING | IRQ_MASKED))) {
            desc->chip->unmask(irq);
            desc->status &= ~IRQ_MASKED;
        }

        desc->status &= ~IRQ_PENDING;
        spin_unlock(&desc->lock);
        action_ret = handle_IRQ_event(irq, action);//处理中断
        if (!noirqdebug)
            note_interrupt(irq, desc, action_ret);
        spin_lock(&desc->lock);

    } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

    desc->status &= ~IRQ_INPROGRESS;
out_unlock:
    spin_unlock(&desc->lock);
}

handle_edge_irq
-->desc->chip->ack(irq). 清中断
-->handle_IRQ_event(irq, action). 处理中断

/**
 * handle_IRQ_event - irq action chain handler
 * @irq:    the interrupt number
 * @action: the interrupt action chain for this irq
 *
 * Handles the action chain of an irq event
 */
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    irqreturn_t ret, retval = IRQ_NONE;
    unsigned int status = 0;

    handle_dynamic_tick(action);

    if (!(action->flags & IRQF_DISABLED))
        local_irq_enable_in_hardirq();

    do { //取出action链表中的成员,执行action->handler
        ret = action->handler(irq, action->dev_id);
        if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
        action = action->next;
    } while (action);

    if (status & IRQF_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
    local_irq_disable();

    return retval;
}

中断处理框架总结

按下按键后

  1. CPU 自动进入“异常模式”。调用“异常处理函数”。

    //异常向量:
    __vectors_start:
     swi SYS_ERROR0
     b   vector_und + stubs_offset
     ldr pc, .LCvswi + stubs_offset
     b   vector_pabt + stubs_offset
     b   vector_dabt + stubs_offset
     b   vector_addrexcptn + stubs_offset
     b   vector_irq + stubs_offset   //发生了中断后就会跳到这里来
     b   vector_fiq + stubs_offset
     
    //vector_irq  ---> __irq_usr  ---> irq_handler  ---> asm_do_IRQ
    
  2. 在“异常处理函数”中如果跳到 “b vector_irq + stubs_offset” 。 vector_irq是一个宏定义。

  3. 调用到列表中的“__irq_usr”,再调用“irq_handler”,这也是一个宏。最终会调用到“asm_do_IRQ”这个 C 函数。

  4. “asm_do_IRQ” 调用 irq_desc[IRQ 中断下标] 取出里面的一项 “handle_irq”

在:linux-2.6.22.6\arch\arm\plat-s3c24xx\Irq.c 中,s3c24xx_init_irq(void)初始化中断时,若是外部中断 0-3,或外部中断 4-23 时,desc->handle_irq(irq, desc)指向“handle_edge_irq 函数

for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
     irqdbf("registering irq %d (ext int)\n", irqno);
     set_irq_chip(irqno, &s3c_irq_eint0t4);
     set_irq_handler(irqno, handle_edge_irq);
     set_irq_flags(irqno, IRQF_VALID);
 }

 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
     irqdbf("registering irq %d (extended s3c irq)\n", irqno);
     set_irq_chip(irqno, &s3c_irqext_chip);
     set_irq_handler(irqno, handle_edge_irq);
     set_irq_flags(irqno, IRQF_VALID);
 }
  1. “handle_edge_irq”做的事:
    ① desc->chip->ack() 清中断
    ② handle_IRQ_event() 处理中断
    ③ 取出“action”链表中的成员,执行“action->handler”。

  2. 核心在“irq_desc” 结构数组:

    分析“irq_desc[]”结构数组:里面有“action 链表”,有“chip”等。

/**
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:     highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:       low level interrupt hardware access
 * @msi_desc:       MSI descriptor
 * @handler_data:   per-IRQ data for the irq_chip methods
 * @chip_data:      platform-specific per-chip private data for the chip
 *          methods, to allow shared chip implementations
 * @action:     the irq action chain
 * @status:     status information
 * @depth:      disable-depth, for nested irq_disable() calls
 * @wake_depth:     enable depth, for multiple set_irq_wake() callers
 * @irq_count:      stats field to detect stalled irqs
 * @irqs_unhandled: stats field for spurious unhandled interrupts
 * @lock:       locking for SMP
 * @affinity:       IRQ affinity on SMP
 * @cpu:        cpu index useful for balancing
 * @pending_mask:   pending rebalanced interrupts
 * @dir:        /proc/irq/ procfs entry
 * @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP
 * @name:       flow handler name for /proc/interrupts output
 */
struct irq_desc {
    irq_flow_handler_t  handle_irq; //函数指针,在前面例子中指向handle_edge_irq()
    struct irq_chip     *chip; //芯片相关底层处理函数。如指向s3c_irqext_chip
    struct msi_desc     *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;     /* IRQ status */

    unsigned int        depth;      /* nested irq disables */
    unsigned int        wake_depth; /* nested wake enables */
    unsigned int        irq_count;  /* For detecting broken IRQs */
    unsigned int        irqs_unhandled;
    spinlock_t      lock;
#ifdef CONFIG_SMP
    cpumask_t       affinity;
    unsigned int        cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    cpumask_t       pending_mask;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
    const char      *name;
} ____cacheline_internodealigned_in_smp;

handle_irq:是函数指针。在上面的例子中这个函数指针指向了“handle_edge_irq”。
首先是清中断,然后把链表“action”中的成员取出,一一执行里面的“action->handler”

#endif

struct proc_dir_entry;
struct msi_desc;

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:       name for /proc/interrupts
 * @startup:        start up the interrupt (defaults to ->enable if NULL)
 * @shutdown:       shut down the interrupt (defaults to ->disable if NULL)
 * @enable:     enable the interrupt (defaults to chip->unmask if NULL)
 * @disable:        disable the interrupt (defaults to chip->mask if NULL)
 * @ack:        start of a new interrupt
 * @mask:       mask an interrupt source
 * @mask_ack:       ack and mask an interrupt source
 * @unmask:     unmask an interrupt source
 * @eoi:        end of interrupt - chip level
 * @end:        end of interrupt - flow level
 * @set_affinity:   set the CPU affinity on SMP machines
 * @retrigger:      resend an IRQ to the CPU
 * @set_type:       set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @set_wake:       enable/disable power-management wake-on of an IRQ
 *
 * @release:        release function solely used by UML
 * @typename:       obsoleted by name, kept as migration helper
 */
struct irq_chip {
    const char  *name;
    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        (*mask)(unsigned int irq);
    void        (*mask_ack)(unsigned int irq);
    void        (*unmask)(unsigned int irq);
    void        (*eoi)(unsigned int irq);

    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq, cpumask_t dest);
    int     (*retrigger)(unsigned int irq);
    int     (*set_type)(unsigned int irq, unsigned int flow_type);
    int     (*set_wake)(unsigned int irq, unsigned int on);

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */
    const char  *typename;
};
struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;
    const char *name;
    void *dev_id;
    struct irqaction *next;
    int irq;
    struct proc_dir_entry *dir;
};

写中断处理时,是想要执行自已的中断代码。代码就应该放在“action->handler”这里。用“request_irq()”告诉内核处理函数是什么。

Linux内核的中断处理体系结构

Linux异常处理结构_第2张图片
040 Linux内核的中断处理体系结构.png

首先内核中有个数组“irq_desc[]”IRQ 的描述结构数组,这个结构数组是以“中断号”为下标。

结构中有:

  • handle_irq:中断入口函数。这个入口函数中,是做清中断,再调用 action 链表中的各种处理函数。
  • chip: 指向底层硬件的访问函数,它做屏蔽中断、使能中断,响应中断等。
  • action:链表头,它指向“irqaction 结构”,这个结构中存放“handler 用户注册的中断处理函数”等。

我们想使用自已的中断处理函数时,就是在内核的这个中断框架里在其中的“action 链表”中添加进我们的“中断函数”。

分析request_irq:

//  kernel\irq\Manage.c
/**
 *  request_irq - allocate an interrupt line
 *  @irq: Interrupt line to allocate
 *  @handler: Function to be called when the IRQ occurs
 *  @irqflags: Interrupt type flags
 *  @devname: An ascii name for the claiming device
 *  @dev_id: A cookie passed back to the handler function
 *
 *  This call allocates interrupt resources and enables the
 *  interrupt line and IRQ handling. From the point this
 *  call is made your handler function may be invoked. Since
 *  your handler function must clear any interrupt the board
 *  raises, you must take care both to initialise your hardware
 *  and to set up the interrupt handler in the right order.
 *
 *  Dev_id must be globally unique. Normally the address of the
 *  device data structure is used as the cookie. Since the handler
 *  receives this value it makes sense to use it.
 *
 *  If your interrupt is shared you must pass a non NULL dev_id
 *  as this is required when freeing the interrupt.
 *
 *  Flags:
 *
 *  IRQF_SHARED     Interrupt is shared
 *  IRQF_DISABLED   Disable local interrupts while processing
 *  IRQF_SAMPLE_RANDOM  The interrupt can be used for entropy
 *
 */
/*
irq:中断号。
handler:处理函数。
irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
*devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟) 文件上,或内核发生中断错误时使用。
第 5 个参数 dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址
*/
int request_irq(unsigned int irq, irq_handler_t handler,
        unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
    int retval;

#ifdef CONFIG_LOCKDEP
    /*
     * Lockdep wants atomic interrupt handlers:
     */
    irqflags |= IRQF_DISABLED;
#endif
    /*
     * Sanity-check: shared interrupts must pass in a real dev-ID,
     * otherwise we'll have trouble later trying to figure out
     * which interrupt is which (messes up the interrupt freeing
     * logic etc).
     */
    if ((irqflags & IRQF_SHARED) && !dev_id)
        return -EINVAL;
    if (irq >= NR_IRQS)
        return -EINVAL;
    if (irq_desc[irq].status & IRQ_NOREQUEST)
        return -EINVAL;
    if (!handler)
        return -EINVAL;

    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); //分配一个“irqaction”结构。
    if (!action)
        return -ENOMEM;
    //这个申请的结构将传来的 handler 处理函数等都记录下来。
    action->handler = handler;
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    select_smp_affinity(irq);

#ifdef CONFIG_DEBUG_SHIRQ
    if (irqflags & IRQF_SHARED) {
        /*
         * It's a shared IRQ -- the driver ought to be prepared for it
         * to happen immediately, so let's make sure....
         * We do this before actually registering it, to make sure that
         * a 'real' IRQ doesn't run in parallel with our fake
         */
        if (irqflags & IRQF_DISABLED) {
            unsigned long flags;

            local_irq_save(flags);
            handler(irq, dev_id);
            local_irq_restore(flags);
        } else
            handler(irq, dev_id);
    }
#endif

    retval = setup_irq(irq, action); ///* 设置中断 */
    //参 1:要安装中断处理程序的中断号。
    //参 2:irq 号中断的中断操作行为描述结构指针。
    
    if (retval)
        kfree(action);

    return retval;
}
EXPORT_SYMBOL(request_irq);

setup_irq:

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 */
int setup_irq(unsigned int irq, struct irqaction *new)
{
    struct irq_desc *desc = irq_desc + irq;
    struct irqaction *old, **p;
    const char *old_name = NULL;
    unsigned long flags;
    int shared = 0;

    if (irq >= NR_IRQS)
        return -EINVAL;

    if (desc->chip == &no_irq_chip)
        return -ENOSYS;
    /*
     * Some drivers like serial.c use request_irq() heavily,
     * so we have to be careful not to interfere with a
     * running system.
     */
    if (new->flags & IRQF_SAMPLE_RANDOM) {
        /*
         * This function might sleep, we want to call it first,
         * outside of the atomic block.
         * Yes, this might clear the entropy pool if the wrong
         * driver is attempted to be loaded, without actually
         * installing a new handler, but is this really a problem,
         * only the sysadmin is able to do this.
         */
        rand_initialize_irq(irq);
    }

    /*
     * The following block of code has to be executed atomically
     */
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;  //以中断号为下标,找到“irq_desc[]”数组中的一项。
    old = *p;
    if (old) {//action 是链表头,判断这个链表开始有没有结构。
        /*
         * Can't share interrupts unless both agree to and are
         * the same type (level, edge, polarity). So both flag
         * fields must have IRQF_SHARED set and the bits which
         * set the trigger type must match.
         */
        if (!((old->flags & new->flags) & IRQF_SHARED) || /* 是否是共享中断。若action链表头里挂接了多个项就表明此中断为共享中断 */
            //“共享中断”:表示中断来源有很多种,但它们共享同一个引脚。
            ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
            old_name = old->name;
            goto mismatch;//如果之前那个老的 action 结构不是作“共享中断”时,我们就不能将申请的新结构放进这个老的“action”链表中去。就不能再次链接新的结构进去了。就表示出“goto mismatch;”出错
        }

#if defined(CONFIG_IRQ_PER_CPU)
        /* All handlers must agree on per-cpuness */
        if ((old->flags & IRQF_PERCPU) !=
            (new->flags & IRQF_PERCPU))
            goto mismatch;
#endif

        /* add new interrupt at end of irq queue */
        do {//这是将新的结构放进去。
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;
    }

    *p = new;

    /* Exclude IRQ from balancing */
    if (new->flags & IRQF_NOBALANCING)
        desc->status |= IRQ_NO_BALANCING;

    if (!shared) {
        irq_chip_set_defaults(desc->chip);

#if defined(CONFIG_IRQ_PER_CPU)
        if (new->flags & IRQF_PERCPU)
            desc->status |= IRQ_PER_CPU;
#endif

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            if (desc->chip && desc->chip->set_type)
                desc->chip->set_type(irq,//将对应的引脚设置为“中断引脚”(外部中断,中断触发方式)。
                        new->flags & IRQF_TRIGGER_MASK);
            else
                /*
                 * IRQF_TRIGGER_* but the PIC does not support
                 * multiple flow-types?
                 */
                printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                       "function for IRQ %d (%s)\n", irq,
                       desc->chip ? desc->chip->name :
                       "unknown");
        } else
            compat_irq_chip_set_default_handler(desc);

        desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
                  IRQ_INPROGRESS);

        if (!(desc->status & IRQ_NOAUTOEN)) {
            desc->depth = 0;
            desc->status &= ~IRQ_DISABLED;
            if (desc->chip->startup)
                desc->chip->startup(irq); //“使能中断
            else
                desc->chip->enable(irq); //“使能中断
        } else
            /* Undo nested disables: */
            desc->depth = 1;
    }
    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;
    spin_unlock_irqrestore(&desc->lock, flags);

    new->irq = irq;
    register_irq_proc(irq);
    new->dir = NULL;
    register_handler_proc(irq, new);

    return 0;

mismatch:
#ifdef CONFIG_DEBUG_SHIRQ
    if (!(new->flags & IRQF_PROBE_SHARED)) {
        printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);
        if (old_name)
            printk(KERN_ERR "current handler: %s\n", old_name);
        dump_stack();
    }
#endif
    spin_unlock_irqrestore(&desc->lock, flags);
    return -EBUSY;
}


//chip 结构:
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
}

static struct irq_chip s3c_irq_eint0t4 = {
    .name       = "s3c-ext0",
    .ack        = s3c_irq_ack,
    .mask       = s3c_irq_mask,
    .unmask     = s3c_irq_unmask,
    .set_wake   = s3c_irq_wake,
    .set_type   = s3c_irqext_type,
};

int
s3c_irqext_type(unsigned int irq, unsigned int type)
{
    void __iomem *extint_reg;
    void __iomem *gpcon_reg;
    unsigned long gpcon_offset, extint_offset;
    unsigned long newvalue = 0, value;

    //若为外部中断 0-3,则将上面的相关引脚配置成中断引脚
    if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))
    {
        gpcon_reg = S3C2410_GPFCON;
        extint_reg = S3C24XX_EXTINT0;
        gpcon_offset = (irq - IRQ_EINT0) * 2;
        extint_offset = (irq - IRQ_EINT0) * 4;
    }
    else if ((irq >= IRQ_EINT4) && (irq <= IRQ_EINT7))
    {
        gpcon_reg = S3C2410_GPFCON;
        extint_reg = S3C24XX_EXTINT0;
        gpcon_offset = (irq - (EXTINT_OFF)) * 2;
        extint_offset = (irq - (EXTINT_OFF)) * 4;
    }
    else if ((irq >= IRQ_EINT8) && (irq <= IRQ_EINT15))
    {
        gpcon_reg = S3C2410_GPGCON;
        extint_reg = S3C24XX_EXTINT1;
        gpcon_offset = (irq - IRQ_EINT8) * 2;
        extint_offset = (irq - IRQ_EINT8) * 4;
    }
    else if ((irq >= IRQ_EINT16) && (irq <= IRQ_EINT23))
    {
        gpcon_reg = S3C2410_GPGCON;
        extint_reg = S3C24XX_EXTINT2;
        gpcon_offset = (irq - IRQ_EINT8) * 2;
        extint_offset = (irq - IRQ_EINT16) * 4;
    } else
        return -1;

    /* Set the GPIO to external interrupt mode */
    value = __raw_readl(gpcon_reg);
    value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);
    __raw_writel(value, gpcon_reg);

    /* Set the external interrupt to pointed trigger type */
    switch (type)//根据request_irq()参3的irqflags标志来判断中断触发方式
    {
        case IRQT_NOEDGE:
            printk(KERN_WARNING "No edge setting!\n");
            break;

        case IRQT_RISING: //上升沿触发
            newvalue = S3C2410_EXTINT_RISEEDGE;
            break;

        case IRQT_FALLING: //下降沿触发
            newvalue = S3C2410_EXTINT_FALLEDGE;
            break;

        case IRQT_BOTHEDGE: //双边沿触发
            newvalue = S3C2410_EXTINT_BOTHEDGE;
            break;

        case IRQT_LOW: //低电平触发
            newvalue = S3C2410_EXTINT_LOWLEV;
            break;

        case IRQT_HIGH://高电平触发
            newvalue = S3C2410_EXTINT_HILEV;
            break;

        default:
            printk(KERN_ERR "No such irq type %d", type);
            return -1;
    }

    value = __raw_readl(extint_reg);
    value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);
    __raw_writel(value, extint_reg);

    return 0;
}

注册用户中断处理函数:

request_irq(中断号,处理函数,硬件触发试,中断名字,dev_id).
{
​ 分配一个 irqaction 结构。结构指向 request_irq 中的参数。
​ 将上面的 irqaction 结构放到 irq_desc[irq]数组项中的 action 链表中。
​ 设置引脚成为中断引脚。
​ 使能中断。
}

分析卸载中断free_irq:

/**
 *  free_irq - free an interrupt
 *  @irq: Interrupt line to free
 *  @dev_id: Device identity to free
 *
 *  Remove an interrupt handler. The handler is removed and if the
 *  interrupt line is no longer in use by any driver it is disabled.
 *  On a shared IRQ the caller must ensure the interrupt is disabled
 *  on the card it drives before calling this function. The function
 *  does not return until any executing interrupts for this IRQ
 *  have completed.
 *
 *  This function must not be called from interrupt context.
 */
void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc;
    struct irqaction **p;
    unsigned long flags;
    irqreturn_t (*handler)(int, void *) = NULL;

    WARN_ON(in_interrupt());
    if (irq >= NR_IRQS)
        return;

    desc = irq_desc + irq; /* 找到数组项 */
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;
    for (;;) {
        struct irqaction *action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            if (action->dev_id != dev_id) /* 判断dev_id是否为free_irq()传来的参2 */

                continue;

            /* Found it - now remove it from the list of entries */
            *pp = action->next;

            /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
            if (desc->chip->release)
                desc->chip->release(irq, dev_id);
#endif

            if (!desc->action) {///* 若链表空了,就禁止或屏蔽这个chip */
                desc->status |= IRQ_DISABLED;
                if (desc->chip->shutdown)
                    desc->chip->shutdown(irq);
                else
                    desc->chip->disable(irq);
            }
            spin_unlock_irqrestore(&desc->lock, flags);
            unregister_handler_proc(irq, action);

            /* Make sure it's not being used on another CPU */
            synchronize_irq(irq);
            if (action->flags & IRQF_SHARED)
                handler = action->handler;
            kfree(action);
            return;
        }
        printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
        spin_unlock_irqrestore(&desc->lock, flags);
        return;
    }
#ifdef CONFIG_DEBUG_SHIRQ
    if (handler) {
        /*
         * It's a shared IRQ -- the driver ought to be prepared for it
         * to happen even now it's being freed, so let's make sure....
         * We do this after actually deregistering it, to make sure that
         * a 'real' IRQ doesn't run in parallel with our fake
         */
        handler(irq, dev_id);
    }
#endif
}
EXPORT_SYMBOL(free_irq);


卸载中断处理函数:
void free_irq(unsigned int irq, void *dev_id)
{

​ 将其从 action 链表中删除结构。
​ 禁止中断(在 action 链表中没有成员结构 irqacton 时了,要是共享中断中删除一个结构还有其他结构时,中断也不会禁止)。
}

你可能感兴趣的:(Linux异常处理结构)