• Linux 中断处理


    HardIRQ 和 softirq : http://www.it165.net/os/html/201211/3766.html

    博文:http://blog.csdn.net/yin262/article/details/53994699

    中断:http://blog.csdn.net/skyflying2012/article/details/7850674

    1: 通过调用 request_irq api来注册指定中断号上的 irq_handler,flags可选是否是共享或者其他
    static inline int __must_check
    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);
    }

    2:requst_irq 进一步调用 request_threaded_irq
    desc = irq_to_desc(irq); // 获取该irq号对应的 irq_desc结构,并check相关的字段,注意到desc结构和irq号是一一对应的(采用基数树结构实现irq_desc_tree)
    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 创建一个irqaction对象
    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
    return -ENOMEM;

    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    retval = __setup_irq(irq, desc, action); // 调用__setup_irq 安装action

    3:__setup_irq函数负责安装一个irq,应该是代码的核心所在
    /*
    * Internal function to register an irqaction - typically used to
    * allocate special interrupts that are part of the architecture.
    */
    static int
    __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)

    Step1: 如果触发类型没有指定,那么使用默认的配置
    Step2:检查中断是否被嵌套到其他的中断线程中
    Step3:如果该中断提供了一个线程函数并且中断不被其他线程嵌套,那么就为该中断创建一个线程
    /*
    * Create a handler thread when a thread function is supplied
    * and the interrupt does not nest into another interrupt
    * thread.
    */
    if (new->thread_fn && !nested) {
    ret = setup_irq_thread(new, irq, false);
    if (ret)
    goto out_mput;
    if (new->secondary) {
    ret = setup_irq_thread(new->secondary, irq, true);
    if (ret)
    goto out_thread;
    }
    }
    Step4:如果该中断号被设置为共享,那么必须满足下面的条件
    (1)同一个IRQ上被注册的其他irq_handler都设置了共享,即IRQF_SHARED
    (2)中断触发类型必须相同(level, edge, polarity)
    (3)是否是ONESHOT类型也必须保持一致
    (4)all handlers must agree on per-cpuness

    /* add new interrupt at end of irq queue */
    do {
    /*
    * Or all existing action->thread_mask bits,
    * so we can find the next zero bit for this
    * new action.
    */
    thread_mask |= old->thread_mask;
    old_ptr = &old->next;
    old = *old_ptr;
    } while (old);
    shared = 1;
    Step5:
    Step6:设置和更新/proc/interrupts的显示
    register_irq_proc(irq, desc);
    new->dir = NULL;
    register_handler_proc(irq, new);

    4:内核提供了配置开关,可以开启强制irq线程
    注意:部分中断是不能被线程化的,例如IPIs中断等,这些中断被标志为:IRQF_NO_THREAD,All interrupts marked IRQF_TIMER or IRQF_PER_CPU are automatically excluded from threading
    #ifdef CONFIG_IRQ_FORCE_THREADING
    __readmostly bool fore_threads


    5:早期的中断和异常处理
    https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-2.html
    arch/x86/kernel/head64.c,中断向量共256个(前32个为异常,后面的为中断编号:32~255)
    #define NUM_EXCEPTION_VECTORS 32
    在64位模式下我们IDT(中断描述符表)初始化

    set_intr_gate(n, addr)
    set_system_gate(n, addr)
    set_system_intr_gate(n, addr)
    set_trap_gate(n, addr)
    set_task_gate(n, addr)


    #ifdef CONFIG_X86_64
    struct desc_ptr idt_descr __ro_after_init = {
    .size = NR_VECTORS * 16 - 1,
    .address = (unsigned long) idt_table,
    };

    asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data)
    {

    for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)
    set_intr_gate(i, early_idt_handler_array[i]); // 设置中断门,填充到idt_table里
    load_idt((const struct desc_ptr *)&idt_descr);
    }

    static inline void _set_gate(int gate, unsigned type, void *addr,
    unsigned dpl, unsigned ist, unsigned seg)
    {
    gate_desc s;

    pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
    /*
    * does not need to be atomic because it is only done once at
    * setup time
    */
    write_idt_entry(idt_table, gate, &s);
    write_trace_idt_entry(gate, &s);
    }

    static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
    {
    memcpy(&idt[entry], gate, sizeof(*gate));
    }


    如果是由中断门进入中断处理程序的,CPU会清除IF标志位,这样当当前中断处理程序执行时,CPU不会对其他的中断进行处理;
    只有当当前的中断处理程序返回时,CPU才在iret指令执行时重新设置IF标志位。

    默认情况下,内核栈(线程栈大小为)2个Page大小(32位体系结构上8KB,64位体系结构上16KB)
    中断上下文的栈空间大小在禁用KASAN时大小是2个Page大小(和线程栈类似)
    #define IRQ_STACK_ORDER (2 + KASAN_STACK_ORDER)
    #define IRQ_STACK_SIZE (PAGE_SIZE << IRQ_STACK_ORDER)

    #define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
    #define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

    PerCPU的中断栈
    #ifdef CONFIG_X86_64
    per_cpu(irq_stack_ptr, cpu) =
    per_cpu(irq_stack_union.irq_stack, cpu) +
    IRQ_STACK_SIZE;
    #endif


    arch/x86/include/asm/irq.h:44:extern __visible unsigned int do_IRQ(struct pt_regs *regs);
    arch/x86/kernel/irq_64.c:59: WARN_ONCE(1, "do_IRQ(): %s has overflown the kernel stack (cur:%Lx,sp:%lx,irq stk top-bottom:%Lx-%Lx,exception stk top-bottom:%Lx-%Lx) ",
    arch/x86/kernel/irq.c:208: * do_IRQ handles all normal device IRQ's (the special
    arch/x86/kernel/irq.c:212:__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
    arch/x86/entry/entry_64.S:518: interrupt do_IRQ
    arch/x86/entry/entry_32.S:655: call do_IRQ


    do_IRQ函数是所有普通设备中断的入口
    /*
    * do_IRQ handles all normal device IRQ's (the special
    * SMP cross-CPU interrupts have their own specific
    * handlers).
    */
    __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
    {
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc * desc;
    /* high bit used in ret_from_ code */
    unsigned vector = ~regs->orig_ax;

    /*
    * NB: Unlike exception entries, IRQ entries do not reliably
    * handle context tracking in the low-level entry code. This is
    * because syscall entries execute briefly with IRQs on before
    * updating context tracking state, so we can take an IRQ from
    * kernel mode with CONTEXT_USER. The low-level entry code only
    * updates the context if we came from user mode, so we won't
    * switch to CONTEXT_KERNEL. We'll fix that once the syscall
    * code is cleaned up enough that we can cleanly defer enabling
    * IRQs.
    */

    entering_irq();

    /* entering_irq() tells RCU that we're not quiescent. Check it. */
    RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

    desc = __this_cpu_read(vector_irq[vector]);

    if (!handle_irq(desc, regs)) {
    ack_APIC_irq();

    if (desc != VECTOR_RETRIGGERED) {
    pr_emerg_ratelimited("%s: %d.%d No irq handler for vector ",
    __func__, smp_processor_id(),
    vector);
    } else {
    __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
    }
    }

    exiting_irq();

    set_irq_regs(old_regs);
    return 1;
    }

    调用流程 do_IRQ --> handle_irq --> generic_handle_irq_desc --> desc->handle_irq(desc);

    作业:
    (1)irq_stack_union的含义是什么?
    (2)desc数据结构是何时创建的?
    答:
    256个中断向量对应的irq_desc不是一次性初始化完的。
    其中一条路径是:start_kernel --> early_irq_init --> alloc_descs --> irq_insert_desc
    另外一条路径是: arch_setup_msi_irq --> irq_alloc_hwirq --> irq_alloc_hwirqs --> __irq_alloc_descs --> alloc_descs

  • 相关阅读:
    一些问题
    为什么Python在列表,元组和字典的末尾允许使用逗号?
    #!/bin/bash
    gitbook 入门教程之小白都能看懂的 Gitbook 插件开发全流程
    go 学习笔记之10 分钟简要理解 go 语言闭包技术
    gitbook 入门教程之还在搞公众号互推涨粉?gitbook 集成导流工具,轻轻松松躺增粉丝!
    go 学习笔记之仅仅需要一个示例就能讲清楚什么闭包
    go 学习笔记之学习函数式编程前不要忘了函数基础
    go 学习笔记之无心插柳柳成荫的接口和无为而治的空接口
    go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?
  • 原文地址:https://www.cnblogs.com/fangying7/p/6979446.html
Copyright © 2020-2023  润新知