中断处理流程

来源:互联网 发布:淘宝客的原理 编辑:程序博客网 时间:2024/05/11 04:25
大家都说在中断处理函数中不能调度,或者说睡眠。这到底为什么?下面看中断处理的过程,从中是否能找到原因。

中断发生后会调到__irq_svc:

    .align    5
__irq_svc:
    svc_entry
    irq_handler
    ----
    svc_exit r5     @ return from exception
 UNWIND(.fnend        )
ENDPROC(__irq_svc)

看配置文件中定义了CONFIG_MULTI_IRQ_HANDLER:
    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif

因使用了多个macro,看汇编会清楚些。

crash> dis -l __irq_svc

../arch/arm/kernel/entry-armv.S
0xc000df00 <__irq_svc>: sub     sp, sp, #68     ; 0x44
0xc000df04 <__irq_svc+4>:       tst     sp, #4
0xc000df08 <__irq_svc+8>:       subeq   sp, sp, #4
0xc000df0c <__irq_svc+12>:      stm     sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}
0xc000df10 <__irq_svc+16>:      ldm     r0, {r3, r4, r5}
0xc000df14 <__irq_svc+20>:      add     r7, sp, #48     ; 0x30
0xc000df18 <__irq_svc+24>:      mvn     r6, #0
0xc000df1c <__irq_svc+28>:      add     r2, sp, #68     ; 0x44
0xc000df20 <__irq_svc+32>:      addeq   r2, r2, #4
0xc000df24 <__irq_svc+36>:      push    {r3}            ; (str r3, [sp, #-4]!)
0xc000df28 <__irq_svc+40>:      mov     r3, lr
0xc000df2c <__irq_svc+44>:      stm     r7, {r2, r3, r4, r5, r6}
0xc000df30 <__irq_svc+48>:      ldr     r1, [pc, #52]   ; 0xc000df6c
0xc000df34 <__irq_svc+52>:      mov     r0, sp
0xc000df38 <__irq_svc+56>:      add     lr, pc, #0
0xc000df3c <__irq_svc+60>:      ldr     pc, [r1]
0xc000df40 <__irq_svc+64>:      lsr     r9, sp, #13
0xc000df44 <__irq_svc+68>:      lsl     r9, r9, #13
0xc000df48 <__irq_svc+72>:      ldr     r8, [r9, #4]
0xc000df4c <__irq_svc+76>:      ldr     r0, [r9]
0xc000df50 <__irq_svc+80>:      teq     r8, #0
0xc000df54 <__irq_svc+84>:      movne   r0, #0
0xc000df58 <__irq_svc+88>:      tst     r0, #2
0xc000df5c <__irq_svc+92>:      blne    0xc000df70 <svc_preempt>
0xc000df60 <__irq_svc+96>:      msr     SPSR_fsxc, r5
0xc000df64 <__irq_svc+100>:     clrex
0xc000df68 <__irq_svc+104>:     ldm     sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr, pc}^
0xc000df6c <__irq_svc+108>:     ldrshtgt        sp, [r3], #-236 ; 0xffffff14

看其中的ldr     pc, [r1],可以看到最终PC为c0008458
0xc000df30 <__irq_svc+48>:      ldr     r1, [pc, #52]   ; 0xc000df6c
crash> rd 0xc000df6c
c000df6c:  c073defc
r1: c073defc

0xc000df3c <__irq_svc+60>:      ldr     pc, [r1]
crash> rd c073defc
c073defc:  c0008458

找到调用的函数gic_handle_irq

crash> dis -l c0008458
/home/wenshuai/code/kernel3.4/linux_kernel/arch/arm/common/gic.c: 288
0xc0008458 <gic_handle_irq>:    mov     r12, sp

asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & ~0x1c00;

        if (likely(irqnr > 15 && irqnr < 1021)) {
            irqnr = irq_find_mapping(gic->domain, irqnr);
            handle_IRQ(irqnr, regs);
            continue;
        }
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}


void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    irq_enter();

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(irq >= nr_irqs)) {
        if (printk_ratelimit())
            printk(KERN_WARNING "Bad IRQ%u\n", irq);
        ack_bad_irq(irq);
    } else {
        generic_handle_irq(irq);
    }

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

    irq_exit();
    set_irq_regs(old_regs);
}

从中断号到irq_desc

以中断号为索引,从全局变量数组struct irq_desc   irq_desc[NR_IRQS]中得到对应的 irq_desc,

这个全局变量是哪里赋值的?

int generic_handle_irq(unsigned int irq)

{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}


struct irq_desc *irq_to_desc(unsigned int irq)
{
    return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq    = handle_bad_irq,
        .depth        = 1,
        .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

crash> irq_desc | grep "handle_irq"
    handle_irq = 0xc007cbd8 <handle_bad_irq>,
 
    handle_irq = 0xc007cbd8 <handle_bad_irq>,
    handle_irq = 0xc007f608 <handle_percpu_devid_irq>,

    handle_irq = 0xc007fb20 <handle_level_irq>,

    handle_irq = 0xc0026244 <tl7689_gpio_irq_dispatch>,
    handle_irq = 0xc007f9fc <handle_fasteoi_irq>,
 
    handle_irq = 0xc007fb20 <handle_level_irq>,
    handle_irq = 0xc0026244 <tl7689_gpio_irq_dispatch>,
    handle_irq = 0xc007fb20 <handle_level_irq>,

    handle_irq = 0xc007fb20 <handle_level_irq>,
    handle_irq = 0xc007f884 <handle_edge_irq>,
    handle_irq = 0xc007f884 <handle_edge_irq>,
    handle_irq = 0xc007fb20 <handle_level_irq>,

    handle_irq = 0xc007fb20 <handle_level_irq>,
    handle_irq = 0xc007fb20 <handle_level_irq>,
    handle_irq = 0xc007fb20 <handle_level_irq>,
    handle_irq = 0xc0026244 <tl7689_gpio_irq_dispatch>,
    handle_irq = 0xc007f9fc <handle_fasteoi_irq>,
    
    handle_irq = 0xc007fc30 <handle_simple_irq>.

从irq_desc到irqaction

一个硬件中断上可以挂载多个action,当然要通过参数把各个action区别开,每个irq_desc 有一个指向irqaction的单向链表。


handle_level_irq(unsigned int irq, struct irq_desc *desc)
    -> handle_irq_event(desc);
handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    handle_irq_event_percpu(desc, action);
}
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
    -> action->handler(irq, action->dev_id);

中断共享

handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int random = 0, irq = desc->irq_data.irq;

    do {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);

        action = action->next;
    } while (action);
}

这些中断处理函数处理完后,会回到
0xc000df68 <__irq_svc+104>:     ldm     sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr, pc}^
也就是中断前的位置;

如果在中断处理过程中,发生了调度,处理完回到这里即可,这里中断使用的是当前进程的栈【这和编译选项中的8K的内核栈有关】。

原理上是可以的,只是中断处理函数的处理时间过长,影响了I/O的性能而已

中断处理相关数据结构

有两个数据结构和中断处理相关:irq_desc and irqaction.两者的关系是 irq_desc通过struct irqaction *action找到对应的 irqaction list

 struct irq_desc {
    struct irq_data irq_data;
    struct timer_rand_state *timer_rand_state;
    unsigned int *kstat_irqs;
    irq_flow_handler_t handle_irq;
    struct irqaction *action;
    unsigned int status_use_accessors;
    unsigned int core_internal_state__do_not_mess_with_it;
    unsigned int depth;
    unsigned int wake_depth;
    unsigned int irq_count;
    unsigned long last_unhandled;
    unsigned int irqs_unhandled;
    raw_spinlock_t lock;
    struct cpumask *percpu_enabled;
    const struct cpumask *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
    unsigned long threads_oneshot;
    atomic_t threads_active;
    wait_queue_head_t wait_for_threads;
    struct proc_dir_entry *dir;
    struct module *owner;
    const char *name;
}

struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    void *dev_id;
    void *percpu_dev_id;
    struct irqaction *next;
    int irq;
    irq_handler_t thread_fn;
    struct task_struct *thread;
    unsigned long thread_flags;
    unsigned long thread_mask;
    const char *name;
    struct proc_dir_entry *dir;
}

irq_desc相关的操作

通过函数irq_set_chip_and_handler对irq_desc的 chip and handle赋值

arch/arm/common/gic.c
gic_irq_domain_map ->
    irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);
    irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);

kernel/irq/chip.c
static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *chip,
                        irq_flow_handler_t handle)
{
    irq_set_chip_and_handler_name(irq, chip, handle, NULL);
}

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                  irq_flow_handler_t handle, const char *name)
{
    irq_set_chip(irq, chip);
    __irq_set_handler(irq, handle, 0, name);
}

int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);
    if (!chip)
        chip = &no_irq_chip;
    desc->irq_data.chip = chip;
}

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
    desc->handle_irq = handle;
    desc->name = name;
}

irqaction相关的操作

对应的函数是request_irq and request_threaded_irq. request_irq 是 request_threaded_irq的特例。

irqaction的创建和赋值在函数request_threaded_irq中实现,另外request_threaded_irq还创建了一个 调度属性为FIFO[RT]的内核thread,

去处理中断的后半部。

static inline int __must_checkre
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

kernel/irq/manager.c
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    desc = irq_to_desc(irq);

    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    __setup_irq(irq, desc, action);
}

static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
    struct irqaction *old, **old_ptr;

    if (new->thread_fn && !nested) {
        struct task_struct *t;

        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);
        /*
         * We keep the reference to the task struct even if
         * the thread dies to avoid that the interrupt code
         * references an already freed task_struct.
         */
        get_task_struct(t);
        new->thread = t;
    }
    
    ---

    if (new->thread)
       wake_up_process(new->thread);
}



原创粉丝点击