• Linux内核中断 一、中断概述 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再...
    日期 内核版本 架构 作者 内容
    2018-8-23 Linux-2.6.32

    X86

    Bystander Linux内核中断
    • 一、中断概述

    中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

    1.1中断类型

    同步中断由CPU本身产生,又称为内部中断。这里同步是指中断请求信号与代码指令之间的同步执行,在一条指令执行完毕后,CPU才能进行中断,不能在执行期间。所以也称为异常(exception)。

    异步中断是由外部硬件设备产生,又称为外部中断,与同步中断相反,异步中断可在任何时间产生,包括指令执行期间,所以也被称为中断(interrupt)。

    异常又可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。而中断可分为故障(fault)、陷阱(trap)、终止(abort)三类。

    从广义上讲,中断又可分为四类:中断故障陷阱终止。这些类别之间的异同点请参考 表 1。

    表 1:中断类别及其行为

    类别

    原因

    异步/同步

    返回行为

    中断

    来自I/O设备的信号

    异步

    总是返回到下一条指令

    陷阱

    有意的异常

    同步

    总是返回到下一条指令

    故障

    潜在可恢复的错误

    同步

    返回到当前指令

    终止

    不可恢复的错误

    同步

    不会返回

     

    有些参考资料中按照中断来源进行分类,如下图所示(个人建议不采用这种方式):

                                                                         图1-1

    1.2区分中断号与中断向

    I/O设备把中断信号发送给中断控制器(8259A)时与之相关联的是一个中断号,当中断控制器把中断信号发送给CPU时与之关联的是一个中断向量。换个角度分析就是中断号是从中断控制器层面划分,中断向量是从CPU层面划分,所以中断号与中断向量之间存在一对一映射关系。在Intel X86中最大支持256种中断,从0到255开始编号,这个8位的编号就是中断向量。其中将0到31保留用于异常处理和不可屏蔽中断。

    • 二、中断数据处理结构

    Linux内核中处理中断主要有三个数据结构,irq_desc,irq_chip和irqaction。

    在\include\linux\ irq.h中定义了

    1)irq_desc用于描述IRQ线的属性与状态,被称为中断描述符。

    /**
    
     * struct irq_desc - interrupt descriptor
    
     * @irq:           interrupt number for this descriptor
    
     * @timer_rand_state:    pointer to timer rand state struct
    
     * @kstat_irqs:        irq stats per cpu
    
     * @irq_2_iommu:  iommu with this irq
    
     * @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
    
     * @last_unhandled:      aging timer for unhandled count
    
     * @irqs_unhandled:      stats field for spurious unhandled interrupts
    
     * @lock:         locking for SMP
    
     * @affinity:            IRQ affinity on SMP
    
     * @node:       node index useful for balancing
    
     * @pending_mask: pending rebalanced interrupts
    
     * @threads_active: number of irqaction threads currently running
    
     * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers
    
     * @dir:           /proc/irq/ procfs entry
    
     * @name:             flow handler name for /proc/interrupts output
    
     */
    
    struct irq_desc{
    
      unsigned int          irq;
    
      struct timer_rand_state *timer_rand_state;
    
      unsigned int            *kstat_irqs;
    
    #ifdef CONFIG_INTR_REMAP
    
      struct irq_2_iommu      *irq_2_iommu;
    
    #endif
    
      irq_flow_handler_t handle_irq;
    
      struct irq_chip              *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 long              last_unhandled;     /* Aging timer for unhandled count */
    
      unsigned int          irqs_unhandled;
    
      spinlock_t             lock;
    
    #ifdef CONFIG_SMP
    
      cpumask_var_t             affinity;
    
      unsigned int          node;
    
    #ifdef CONFIG_GENERIC_PENDING_IRQ
    
      cpumask_var_t             pending_mask;
    
    #endif
    
    #endif
    
      atomic_t         threads_active;
    
      wait_queue_head_t       wait_for_threads;
    
    #ifdef CONFIG_PROC_FS
    
      struct proc_dir_entry    *dir;
    
    #endif
    
      const char             *name;
    
    }

    2)irq_chip用于描述不同类型的中断控制器。

    /**
    
     * 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
    
     *
    
     * @bus_lock:         function to lock access to slow bus (i2c) chips
    
     * @bus_sync_unlock:    function to sync and unlock slow bus (i2c) chips
    
     *
    
     * @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);
    
      int          (*set_affinity)(unsigned int irq,
    
                                  const struct cpumask *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);
    
    
      void        (*bus_lock)(unsigned int irq);
    
      void        (*bus_sync_unlock)(unsigned int irq);
    
    
      /* 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;
    
    }

    在\include\linux\ interrupt.h中定义了 irqaction用来描述特定设备所产生的中断描述符。

    /**
    
     * struct irqaction - per interrupt action descriptor
    
     * @handler:   interrupt handler function
    
     * @flags: flags (see IRQF_* above)
    
     * @name:      name of the device
    
     * @dev_id:     cookie to identify the device
    
     * @next:  pointer to the next irqaction for shared interrupts
    
     * @irq:    interrupt number
    
     * @dir:    pointer to the proc/irq/NN/name entry
    
     * @thread_fn: interupt handler function for threaded interrupts
    
     * @thread:     thread pointer for threaded interrupts
    
     * @thread_flags:    flags related to @thread
    
     */
    
    struct irqaction {
    
      irq_handler_t handler;
    
      unsigned long flags;
    
      const char *name;
    
      void *dev_id;
    
      struct irqaction *next;
    
      int irq;
    
      struct proc_dir_entry *dir;
    
      irq_handler_t thread_fn;
    
      struct task_struct *thread;
    
      unsigned long thread_flags;
    
    };
    • 三、Linux中断机制

    Linux中断机制由三部分组成:

    1. 中断子系统初始化:内核自身初始化过程中对中断处理机制初始化,例如中断的数据结构以及中断请求等。
    2. 中断或异常处理:中断整体处理过程。
    3. 中断API:为设备驱动提供API,例如注册,释放和激活等。

    3.1中断子系统初始化

    3.1.1中断描述符表(IDT)初始化

    中断描述符表初始化需要经过两个过程:

    1. 第一个过程在内核引导过程。由两个步骤组成,首先给分配IDT分配2KB空间(256中断向量,每个向量由8bit组成)并初始化;然后把IDT起始地址存储到IDTR寄存器中。
    2. 第二个过程内核在初始化自身的start_kernal函数中使用trap_init初始化系统保留中断向量,使用init_IRQ完成其余中断向量初始化。

    3.1.2中断请求队列初始化

    init_IRQ调用pre_intr_init_hook,进而最终调用init_ISA_irqs初始化中断控制器以及每个IRQ线的中断请求队列。

    3.2中断或异常处理

    中断处理过程:设备产生中断,并通过中断线将中断信号送往中断控制器,如果中断没有被屏蔽则会到达CPU的INTR引脚,CPU立即停止当前工作,根据获得中断向量号从IDT中找出门描述符,并执行相关中断程序。

    异常处理过程:异常是由CPU内部发生所以不会通过中断控制器,CPU直接根据中断向量号从IDT中找出门描述符,并执行相关中断程序。

                                                                                          图3-1

    中断控制器处理主要有5个步骤:1.中断请求 2.中断相应 3.优先级比较 4.提交中断向量 5.中断结束。这里不再赘述5个步骤的具体流程。

    CPU处理流程主要有6个步骤:1.确定中断或异常的中断向量 2.通过IDTR寄存器找到IDT 3.特权检查 4.特权级发生变化,进行堆栈切换 5.如果是异常将异常代码压入堆栈,如果是中断则关闭可屏蔽中断 6.进入中断或异常服务程序执行。这里不再赘述6个步骤的具体流程。

    3.3中断API

    内核提供的API主要用于驱动的开发。

    注册IRQ:

    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

    释放IRQ:

    void free_irq(unsigned int, void *);

    注:IRQ线资源非常宝贵,我们在使用时必须先注册,不使用时必须释放IRQ资源。

    激活当前CPU中断:

    local_irq_enable();

    禁止当前CPU中断:

    local_irq_disable();

    激活指定中断线:

    void enable_irq(unsigned int irq);

    禁止指定中断线:

    void disable_irq(unsigned int irq);

    禁止指定中断线:

    void disable_irq_nosync(unsigned int irq);

    注:此函数调用irq_chip中disable禁止指定中断线,所以不会保证中断线上执行的中断服务程序已经退出。

    3.4中断机制划分

     由于中断会打断内核中进程的正常调度运行,所以要求中断服务程序尽可能的短小精悍;但是在实际系统中,当中断到来时,要完成工作往往进行大量的耗时处理。因此期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,诞生——顶/底半部机制。

      中断处理程序是顶半部——接受中断,它就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。顶半部简单快速,执行时禁止一些或者全部中断。

     底半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。顶半部只有中断处理程序机制,而底半部的实现有软中断,tasklet和工作队列实现。

                                                 图3-2 注:登记中断,将底半部处理程序挂到该设备的低半部执行队列中。

    3.4.1顶/底半部划分原则:

     1) 如果一个任务对时间非常敏感,将其放在顶半部中执行;

     2) 如果一个任务和硬件有关,将其放在顶半部中执行;

     3) 如果一个任务要保证不被其他中断打断,将其放在顶半部中执行;

     4) 其他所有任务,考虑放置在底半部执行。

     

    3.4.2底半部实现机制

                                                                                                图3-3

    软中断:

    软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:

    a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。

    b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

    内核中定义了几种软中断的用途:

    enum
    
    {
       HI_SOFTIRQ=0,
    
       TIMER_SOFTIRQ,
    
       NET_TX_SOFTIRQ,
    
       NET_RX_SOFTIRQ,
    
       BLOCK_SOFTIRQ,
    
       BLOCK_IOPOLL_SOFTIRQ,
    
       TASKLET_SOFTIRQ,
    
       SCHED_SOFTIRQ,
    
       HRTIMER_SOFTIRQ,
    
       RCU_SOFTIRQ,  /* Preferable RCU should always be the last softirq */
    
       NR_SOFTIRQS
    
    };

     

    Tasklet

      tasklet是通过软中断实现的,所以它本身也是软中断。

      软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

      为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

      Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。

      总结下tasklet的优点:

      (1)无类型数量限制;

      (2)效率高,无需循环查表;

      (3)支持SMP机制;

      它的特性如下:

      1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。

      2)多个不同类型的tasklet可以并行在多个CPU上。

    3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

    工作队列:

    上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。

    工作队列能运行在进程上下文,它将工作给一个内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

    如何选择下半部机制:

    1. 软中断和tasklet运行在中断上下文,工作队列运行在进程上下文。如果需要休眠则选择工作队列,否则选择tasklet;如果对性能要求较高则选择软中断。
    2. 从易用性考虑,首选工作队列,然后tasklet,最后是软中断,因为软中断需要静态创建。
    3. 从代码安全考虑,如果对底半部代码保护不够安全,则选择tasklet,因为相对软中断,tasklet对锁要求低,上面也简述它们工作方式以及运用场景。

    四、多处理器系统中断相关概念

    4.1处理器间中断

     在多处理器系统中,操作系统需要在多个处理器中协调操作,所以需要处理器中断(Inter-Processor-Interrupt,IPI)实现,IPI是一种特殊硬件中断,由CPU送出,其他CPU接收,处理CPU之间通信和同步操作。以下是x86中SMP定义的IPI,中断向量号用十六进制表示:

    SPURIOUS_APIC_VECTOR		0xff
    ERROR_APIC_VECTOR		0xfe
    RESCHEDULE_VECTOR		0xfd
    CALL_FUNCTION_VECTOR		0xfc
    CALL_FUNCTION_SINGLE_VECTOR	0xfb
    THERMAL_APIC_VECTOR		0xfa
    THRESHOLD_APIC_VECTOR		0xf9
    REBOOT_VECTOR			0xf8
    INVALIDATE_TLB_VECTOR_END	0xf7
    INVALIDATE_TLB_VECTOR_START	0xf0
    

     

    4.2中断亲和力

         将一个或多个中断服务程序绑定到特定的CPU上处理,这就是中断亲和力(SMP IRQ affinity)。我们可以使用中断亲和力来均衡各个CPU的负载,提高系统处理能力。

    以上便是本人对Linux中断的理解,若有纰漏欢迎指正!

    展开全文
  • 如果你要禁止所有的中断该怎么办? 在2.6内核中,可以通过下面两个函数中的其中任何一个关闭当前处理器上的所有中断处理,这两个函数定义在 中:  void local_irq_save(unsigned long flags);  void local_irq_...

    如果你要禁止所有的中断该怎么办? 在2.6内核中,可以通过下面两个函数中的其中任何一个关闭当前处理器上的所有中断处理,这两个函数定义在 <asm/system.h>中:

        void local_irq_save(unsigned long flags);
        void local_irq_disable(void);
        对 local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。注意, flags 被直接传递, 而不是通过指针来传递。 local_irq_disable不保存状态而关闭本地处理器上的中断发送; 只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个版本。
        可通过如下函数打开中断:
        void local_irq_restore(unsigned long flags);
        void local_irq_enable(void);
        第一个版本将local_irq_save保存的flags状态值恢复, 而local_irq_enable无条件打开中断. 与 disable_irq不同, local_irq_disable不会维护对多次的调用的跟踪。 如果调用链中有多个函数需要禁止中断, 应该使用local_irq_save.
        在2.6内核, 没有方法全局禁用整个系统的所有中断。 内核开发者认为关闭所有中断的代价太高,因此没有必要提供这个能力。如果读者使用的老驱动程序调用了类似cli和sti这样的函数,为了该驱动程序能够在2.6下使用,则需要进行修改而使用正确的锁。

        in_interrupt()是判断当前进程是否处于中断上下文,这个中断上下文包括底半部和硬件中断处理过程,

    函数实现:

         #define in_interrupt() ({ const int __cpu = smp_processor_id(); /
              (local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })

    判断中断计数和底半部计数是否〉0,如果只希望判断是否在硬件中断上下文,则可以使用:in_irq()。

    展开全文
  • 在代码里想写一个这样的中断static irqreturn_t xxx_interrupt(int irq, void *arg) { disable_irq(irq); xxxxx enable_irq(irq); }

    在代码里想写一个这样的中断

    static irqreturn_t xxx_interrupt(int irq, void *arg)
    {
       disable_irq(irq);
       xxxxx
       enable_irq(irq);
    }

    使用的时候,发现系统会卡死在中断处理函数中.

    追踪代码发现和使用disable_irq导致(linux3.2)

    void disable_irq(unsigned int irq)
    {
        if (!__disable_irq_nosync(irq))
    	synchronize_irq(irq);
    }
    
    static int __disable_irq_nosync(unsigned int irq)
    {
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
    
        if (!desc)
            return -EINVAL;
        __disable_irq(desc, irq, false);
        irq_put_desc_busunlock(desc, flags);
        return 0;
    }
    
    从disable_irq的源码中我们可以看出
    __disable_irq_nosync 
    -> __disable_irq(desc, irq, false);
    这里已经关闭了中断,而后面继续执行synchronize_irq函数

    void synchronize_irq(unsigned int irq)
    {
        struct irq_desc *desc = irq_to_desc(irq);
        bool inprogress;
    
        if (!desc)
            return;
    
        do {
            unsigned long flags;
    
            /*
             * Wait until we're out of the critical section.  This might
             * give the wrong answer due to the lack of memory barriers.
             */  
            /* 根据 IRQD_IRQ_INPROGRESS 标志位判断当前中断是不是还在处理 
             * 如果还在处理,则等待处理结束
             */
            while (irqd_irq_inprogress(&desc->irq_data))
                cpu_relax();
    
            /* Ok, that indicated we're done: double-check carefully. */  
            raw_spin_lock_irqsave(&desc->lock, flags);
            inprogress = irqd_irq_inprogress(&desc->irq_data);
            raw_spin_unlock_irqrestore(&desc->lock, flags);
    
            /* Oops, that failed? */
        } while (inprogress);
    
        /*
         * We made sure that no hardirq handler is running. Now verify
         * that no threaded handlers are active.
         */
        wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));
    }
    
    static inline bool irqd_irq_inprogress(struct irq_data *d)
    {
        return d->state_use_accessors & IRQD_IRQ_INPROGRESS;
    }

    也就是说disable_irq

    一方面关闭中断 ( __disable_irq_nosync )

    另一方面等待当前的中断结束( synchronize_irq )

    这是好事,很严禁,但是放在中断函数中就不好了,代码会卡死在

    while (irqd_irq_inprogress(&desc->irq_data))

    卡死的原因说起来还是和中断的执行流程有关系,摘取部分中断执行流程如下:

    handle_irq_event
        -> irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
            -> handle_irq_event_percpu
              -> xxx_interrupt
        -> irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    ...

    这样一贴流程很明显就看到原因了 IRQD_IRQ_INPROGRESS标志位的清除是在退出中断函数进行的,

    而代码一直在中断函数中等待IRQD_IRQ_INPROGRESS标志位清空,不卡死才怪.

    到了这里,问题原因就找到了!

    那么,如果中断函数里关中断用什么api?

    __disable_irq_nosync就可以,但是这个函数前面有下划线,看起来不是个api,查找一下调用

    void disable_irq_nosync(unsigned int irq)
    {
        __disable_irq_nosync(irq);
    }

    ok,以后就用disable_irq_nosync了.

    static irqreturn_t xxx_interrupt(int irq, void *arg)
    {
       disable_irq_nosync(irq);
       xxxxx
       enable_irq(irq);
    }






    展开全文
  • Linux中断相关函数

    2019-10-16 15:36:38
    目录 request_irq 函数介绍 注意事项 request_irq 函数介绍 函数原型 ...request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) //对应free...

    目录

    request_irq

    函数介绍

    注意事项


    request_irq

    函数介绍

    函数原型

    最好用devm_request_irq代替

    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,   const char *name, void *dev)
             //对应free_irq   (此函数必须要调用)

    参数

    irq:     要申请的硬件中断号。
                 对于不支持设备树的内核:在内核si项目里搜“irqs.h",找对应平台,即可得到。
                 对于支持设备树的内核:不能直接写中断号,要通过设备树映射中断号,见:request_irq注意事项
    handler: 向系统注册的中断处理函数,是回调函数,中断发生时,系统调用这函数,dev_id参数传递给它。
                 本函数最后要return IRQ_HANDLED;
    irqflags:中断处理的属性,  //以下老版本意思是linux2.6.24之前
              IRQF_DISABLED (老版本中的SA_INTERRUPT)表示中断处理程序是快速处理程序,
                                               快速处理程序被调用时禁止所有中断。慢速处理程序不禁止
              IRQF_SHARED (老版本中的SA_SHIRQ)多个设备共享中断,
              IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),对系统获取随机数有好处。
              ...
    name:  中断名称,通常是设备驱动程序的名称. 
              在/proc/interrupts  里可以看到。cat /proc/interrupts
    dev:   作为中断服务函数(handler)的第二个参数。若中断服务函数用不到更多参数,则设置为NULL。
              若此项不是NULL,free_irq的第二个参数也要写上对应的值。

    返回值
    0:     成功
    -EINVAL:表示中断号无效或处理函数指针为NULL
    -EBUSY:表示中断已经被占用且不能共享

    注意事项

            在设备树存在以后,不能直接把中断号作为第1个参数,得从设备树获取,获取时核心层函数做了映射将中断号数组中对应的索引作为此函数的参数。
    示例:
    i2c0的中断号是89
    设备树中:
    interrupt-parent = <&intc>;
    interrupt = <0 57 1>;  //共享中断,57+32=89, 触发方式:上升沿。设备树中共享中断要减32,私有中断要减16 

    驱动代码
    static int i2c_probe(struct platform_device *pdev)
    {
        struct device_node *node;
        int irq;

       irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
            dev_err(&pdev->dev, "failed to get IRQ number\n");
           return irq;
        }

        if(request_irq(irq, i2c0_int_irq_handler, IRQF_SHARED, "i2c0-test" , &(dev->dev)))
        {
            printk(KERN_DEBUG "requst irq failled \n");
        }
        return 0;
    }

    展开全文
  • 来源: CSDN | phenix_lord的专栏硬件处理最近解决一个关于Linux中断的问题,把相关机制整理了一遍,记录在此。不同的外部设备、不同的体系结构、不同的OS...
        

    来源: CSDN | phenix_lord的专栏

    硬件处理

    最近解决一个关于Linux中断的问题,把相关机制整理了一遍,记录在此。

    不同的外部设备、不同的体系结构、不同的OS其中断实现机制都有差别,本文对应的OS为linux3.4版本,外部设备为PCI设备、系统为X86。

    概览

    中断让外设能够通知CPU他需要获得服务(让CPU执行指定的中断服务例程ISR)。为了达到这个目的,首先要为中断执行做好准备,完成初始化相关的操作。包括: 
    1、 初始化中断控制器等相关器件(OS初始化过程中完成); 
    2、 配置并使能外部设备(比如使用pci_enable_msix),得到irq号;在这个操作过程中,内核需要完成的大致操作是:

        1、  确定该中断的执行CPU,并在对应CPU上建立vector和irq号的对应关系(利用全局per-cpu变量vector_irq),配置中断控制器(I/OAPIC、PIR等),可能还需要设置外部设备(比如设置MSI

        Capacity registers); 

        2、  为对应的irq_desc初始化正确的handle_irq接口(通用逻辑接口);

        3、  为对应的irq_desc初始化正确的底层chip操作接口。

    3、 使用request_irq号为该中断号指定一个服务例程;

    完成了以上的初始化操作,在外设中断到来的时候,为该中断指定的ISR(Interrupt Service Routines)就能得到执行,这个执行过程大致如下:

    1、 外设根据各自的配置,产生中断信号或者中断消息(MSI,INT# message)。 
    2、 中断控制器从外设获取中断电信号或者中断消息,把它翻译为vector(CPU使用这个参数来决定是谁发生了中断,要如何处理)并提交到CPU。 
    3、 对X86系统,CPU利用从中断控制器获取到的vector为索引,查询IDT (interrupt descriptor table)得到该中断的处理接口(对linux,是在entry_64.s中定义的函数common_interrupt接口)并执行。 
    4、 在linux定义的common_interrupt接口中,执行完中断执行环境建立后,会进入generic interrupt layer执行,其首先通过vector查找到irq和对应的irq_desc结构,并执行该结构的handle_irq接口,这个接口就是generic interrupt layer的通用逻辑接口,比如handle_edge_irq/handle_level_irq等;在中断执行的通用逻辑接口中,会通过irq_desc::action调用外设指定的ISR。 
    在linux中可以通过/proc/interrupts查看当前系统中所有中断的统计信息,在/proc/irq/xxx(中断号)下面,可以看到该中断的详细信息。

    中断相关硬件

    这里的描述很多来自INTEL的文档《Intel Software developer’s Manual, system programming guide》和《PCI Express System Architecture》

    中断控制器

    中断控制器的功能是:把外设的中断信号,转换成CPU能够明白的vector,并完成中断执行控制,确保在合适的时机把中断提交给CPU执行。对这部分内容,《interrupt in linux》有详细的描述。 
    1、 8259A: 
    每个8259A有8个管脚,每个管脚对应其连接的CPU的IDT中的一个vector,单独使用8259A,其硬件连线就决定了对设备vector的使用。典型的场景是使用两个8259A级联,理论最多16个中断号(就是ISA IRQs),实际能提供对15个中断线的处理(master的IRQ2用于连接slave),其具体的分配见下图。 
    2、 PIR: 
    用于完成输入的信号到输出信号的映射。在下图中PIR被用于完成多个PCI设备的INT#信号到8259A对应引脚的路由。对应这种连接方式,在PCI设备初始化的时候,OS会根据BISO提供的信息设置PIR,把INT#路由到O0-O3中正确的管脚,从而体现到8259A的正确管脚(对应了vector),这样INT#信号就被转换为vector并提交到CPU。由于可能有较多的PCI设备,而PIR的输入/出错管脚有限,所以连接到相同输入关键的INT#会共享一个中断。 

    640?wx_fmt=png

    3、 I/O APIC 
    每个I/O APIC提供24个管脚,能够和外部设备的中断线连接,每个管脚都可以通过配RTE(Redirection table entry)配置对应的vector。其功能是:把外部设备的中断请求,翻译为local APIC的interrupt message,并按照配置的vector,发送给指定的local APIC处理(在SMP系统,存在多个CPU,也就有多个local APIC)。通常的配置方式是:第一个I/O APIC的前16个管脚,配置来处理之前的ISA IRQs,其它外设比如PCI设备,则直接使用其他管脚连接。 
    4、 local APIC 
    其负责处理IPI(inter-process interrupt)、直接连接的中断处理、接收和处理interrupt message,每个CPU有自己的local APIC。 
    对应I/O APIC和local APIC的组合,其连接方式见下图 

    640?wx_fmt=png

    针对X86中断控制器硬件和linux对这些硬件的初始化,在《interrupt in linux》中有很详细的描述。

    X86对中断的处理

    Local APIC的处理过程 
    每个local APIC对应了一个CPU。其处理interrupt message的过程如下: 
    1、 判断该中断的destination是否为当前APIC,如果不是则忽略,否则继续处理 
    2、 如果是SMI/NMI/INIT/ExtINT, or SIPI(这些中断都负责特殊的系统管理任务,外设一般不会使用)被直接送到CPU执行,否则执行下一步。 
    3、 设置Local APIC 的IRR寄存器的对应bit位。 
    4、 如果该中断优先级高于当前CPU正在执行的中断,且当前CPU没有屏蔽中断(按照X86和LINUX的实现,这时是屏蔽了中断的),则该高优先级中断会中断当前正在执行的中断(置ISR位,并开始执行),低优先级中断会在高优先级中断完成后继续执行,否则只有等到当前中断执行完成(写了EOI寄存器)后才能开始执行下一个中断。 
    5、 在CPU可以处理下一个中断的时候,从IRR中选取最高优先级的中断,清0 IRR中的对应位,并设置ISR中的对应位,然后ISR中最高优先级的中断被发送到CPU执行(如果其它优先级和屏蔽检查通过)。 
    6、 CPU执行中断处理例程,在合适的时机(在IRET指令前)通过写EOI寄存器来确认中断处理已经完成,写EOI寄存器会导致local APIC清理ISR的对应bit,对于level trigged中断,还会向所有的I/O APIC发送EOI message,通告中断处理已经完成。

    说明: 
    1、 关于Local APIC的IRR和ISR 
    寄存器interrupt request register (IRR) 和 in-service register (ISR),都是256bit寄存器,每个bit对应一个中断(其中[0-15]不能使用,SMI/NMI/INIT/ExtINT/SIPI的发送和执行不经过ISR和IRR) 。IRR中保存的是已经被local APIC接纳但是还没有开始执行的中断;ISR中保持的是当前正在执行但是还没有完成的中断。 
    2、 中断优先级 
    对应通过local APIC发送到CPU的中断,按照其vector进行优先级排序: 
    优先级=vector/16 
    数值越大,优先级越高。由于local APIC允许的vector范围为[16,255],而X86系统预留了[0,31]作为系统保留使用的vector,实际的用户定义中断的优先级的取值范围为[2,15],在每个优先级内部,vector的值越大,优先级越高。 
    Local APIC中还有一个关于中断优先级的寄存器TPR(task priority register)寄存器:用于确定打断线程执行需要的中断优先级级别,只有优先级高于设置值的中断才会被CPU执行 (SMI/NMI/INIT/ExtINT, or SIPI不受限制),也就是除了特殊中断外,优先级低于TPR指定值的中断将被忽略。 
    3、 中断的pending 
    对于同一个vector,如果有多次中断请求,可能IRR和ISR对应的bit位都被置位,也就是对同一个vector,local APIC可以pending两个中断,其后的即使有多处,也会被合并为一个执行。 
    4、 中断执行时机 
    中断的执行总是在指令边界开始(只有一个特殊的exception:abort在外,出现了这个中断,系统基本上也就完蛋了),也就是中断不可能打断指令的执行。

    CPU对中断和异常的处理 
    相关概念 
    1、 vector(中断向量) 
    vector是一个整数,在X86CPU上,使用vector对中断(interrupt,外部设备产生)和异常(exception,CPU在程序执行中产生)统一编号,每个CPU核心内部,中断/异常和vector所以一一对应的;但是在各个不同的CPU核心上,相同的vector可以对应不同的中断(至少对于linux的设置,异常还是使用相同的vector)。 
    vector的取值范围为[0,255],其中[0,31]被系统保留使用(多数作为异常的vector),其余的可供外设中断使用(系统设备比如local APIC也占用了部分[32,255]这个范围的vector)。 
    2、 IDT(interrupt descriptor table) 
    X86 CPU采用一个有256个元素的数组来描述中断/异常,该数组的index为vector;其内容包括了三种gate descriptor,用于描述一个中断/异常的处理接口;这个数组就是IDT,CPU在收到中断请求的时候,就利用vector获取到对应的中断处理接口描述并执行。 
    3、 可屏蔽中断 
    通过CPU INTR管脚/local APIC接收到的中断是可屏蔽中断,这些中断能够通过清零EFLAGS的IF来屏蔽(CLI指令)。通过INT n指令生成的中断即使使用了和外部中断一样的vector,也是不可屏蔽的;同样CPU运行过程中同步产生的trap、fault、abort等异常也是不可屏蔽的。 
    4、 NMI 
    NMI是不可屏蔽中断(不可通过IF标志屏蔽),是通过CPU的NMI管脚发出的中断或者通过delivery mode为NMI的方式提交的中断。NMI中断在执行前,CPU不仅会屏蔽其它中断,也会屏蔽NMI中断,直到NMI中断处理执行完成(IRET指令被执行)。使用INT 2指令虽然能执行NMI中断处理函数,但是相关硬件不会介入,也就是没有相关的屏蔽NMI中断的操作。

    CPU执行中断的过程 
    1、 利用vector,查IDT得到中断描述符; 
    2、 如果中断发生在用户态,会首先执行stack switch切换到内核态执行; 
    3、 依次保存EFLAGS CS IP到当前栈,如果需要(有error code的异常),把error code PUSH到当前栈。并把IF/TF位清零屏蔽可屏蔽中断;至此,CPU完成了中断处理程序执行环境的建立。 
    4、 执行中断描述符定义的中断处理入口(IDT中指定地址的代码); 
    5、 根据环境执行不同的中断退出方式,比如执行现场调度操作(retint_careful和retint_kernel),最终都会执行IRET指令;至此,中断执行完成。 
    异常的执行过程类似,只不过异常在执行前不会把IF位清零,只清零TF位。

    PCI设备的中断

    本部分的很多内容来自《PCI Interrupts for x86 Machines under FreeBSD》和《PCI Express® 
    Base Specification Revision 3.0》和《PCI Express System Architecture》。 
    PCI设备的中断有两种模式:一种是INT#模式,一种是MSI模式。

    INT#模式 
    每个PCI设备用四个中断信号,对应INTA#、INTB# INTC#、INTD#,这些中断信号采用level trigger 的方式并且为低电平有效,PCI设备通过拉低对应的信号来assert对应的中断,并在ISR访问PCI设备的指定寄存器deassert该中断。

    中断线和X86系统的连接 
    这里存在两种常见连接模式,一种是使用老的8259A+PIR的系统,一种是使用新的I/O APIC的系统。 
    对于使用8259A的系统:PCI的中断线连接到一个可编程的PIR设备,再通过该设备连接到8259A(见X86中断控制器一章的图);对于采用I/OAPIC的系统,可以使用以下的连接方式,同样这里只画出了一个中断线,同时根据不同的系统配置可能存在多个I/OAPIC。除了采用直接的中断引脚连接,PCI还支持virtual INT#,使用INT# message(Assert INT# message和deassert INT# message)的方式来使用INT#信号。 

    640?wx_fmt=png

    NT#模式的局限 
    1、 中断数量有限且不方便扩展:每个物理的PCI设备,最多只有4个中断但是至少能支持8个function,且系统中可能存在多个PCI设备,不得不使用中断共享的模式,影响使用性能。 
    2、 同步问题:由于INT#中断采用的是side channel,中断信号和数据本身存在不同步的问题:可能在中断到达的时候,对应的数据没有达到,为了处理这个问题,一般采用“读刷新”的做法,也就是在使用该设备写入到X86的数据之前,ISR先对这个设备进行一次读操作来确保相关数据已经写入完成,比如读PCI设备的中断状态寄存器等。

    MSI/MSI-X模式 
    在这种模式下,PCI设备通过和数据DMA一样的通道来完成中断处理,通过向特定地址空间(系统FSB Interrupt存储器空间)发起一个写操作来发起中断。该写操作的地址和数据信息在PCI设备初始化MSI功能的时候已经填写到MSI Capacity registers(MSI模式)/MSI-X table(MSI-X)中(对X86,这个地址空间是FEE00000H开始的地址空间,其实就是local APIC寄存器映射的地址空间),地址信息保存在Message address register,其中包含了目标CPU信息和FSB Interrupt存储器空间;数据中包含了该MSI中断对应的vector,保存在Message data register中。 MCH(memory control hub)截获这个写操作,转换为FSB interrupt message并向各个CPU核心广播,local APIC接收并处理这个消息,最终触发CPU的中断处理过程。使用这种机制,中断的数量不受PIR/ IOAPIC等各种器件管脚数量的限制,MSI可以支持32个中断,而MSI-X可以达到2048个;中断的传递相当直接,省略了中断路由的过程;并且能直接从interrupt message中获取vector信息,减少了交互过程。

    初始化

    相关概念和关键数据结构

    1、 irq号:在当前系统中全局唯一,对应内核数据结构struct irq_desc,每个外设的中断有一个irq号(体系结构预留的中断,是没有对应的irq_desc结构和irq号的),该irq在该中断的生命周期内都不会改变,且和该中断的中断处理函数关联;内核使用一个bitmap allocated_irqs来标识当前系统已经分配的irq;irq号的管理与底层中断设备和配置无关,属于Generic Interrupt Layer;对于irq号分布集中的情况,不配置CONFIG_SPARSE_IRQ,内核采用数组直接管理,数组下标就是irq号;而对于irq号比较分散的,设置CONFIG_SPARSE_IRQ,内核采用radix tree来管理所有的irq号。 
    2、 vector号:内核使用全局bitmap used_vectors来标识那些vector被系统预留,不能被外设分配使用。 
    3、 irq号和vector号的关联:内核中使用per-cpu变量vector_irq来描述irq号和vector号的关联,对每个CPU,vector_irq是一个数组,在X86架构下成员数量为256,其数组的index为vector,值为irq,如果为-1则表示该CPU上的这个vector尚未分配。 
    4、 struct irq_desc结构,用来描述一个中断,是内核generic interrupt layer的关键数据结构,其包含了中断的大部分信息,并连接了driver层和物理中断设备层,每个irq号对应一个该结构,共享相同irq号的中断共享该结构。它的关键成员包括:

        a)  irq_data  :为该中断对应的物理中断设备层相关的数据。     

        b)  handle_irq:为该该中断使用的通用逻辑接口。

        c)  action:为driver层提供的ISR信息,其为一个单向链表结构,所有共享该中断的设备的ISR都链接在这里。

    内核关键数据结构和相关初始化

    对X86 CPU,Linux内核使用全局idt_table来表达当前的IDT,该变量定义在traps.c

    gate_desc  idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化为全0。


    对中断相关的初始化,内核主要有以下工作: 
    1、 设置used_vectors,确保外设不能分配到X86保留使用的vector(预留的vector范围为[0,31],另外还有其他通过apic_intr_init等接口预留的系统使用的vector); 
    2、 设置X86CPU保留使用的vector对应的IDT entry;这些entry使用特定的中断处理接口; 
    3、 设置外设 (包括ISA中断)使用的中断处理接口,这些中断处理接口都一样。 
    4、 设置ISA IRQ使用的irq_desc; 
    5、 把IDT的首地址加载到CPU的IDTR(Interrupt Descriptor Table Register); 
    6、 初始化中断控制器(下一章描述) 
    以上工作主要在以下函数中完成:

    640?wx_fmt=png

    可以看到,这个过程会完成每个中断vector对应的idt entry的初始化,系统把这些中断vector分成以下几种: 
    1、X86保留vector,这些vector包括[0,0x1f]和APIC等系统部件占用的vector,对这些vector,会记录在bitmap used_vectors中,确保不会被外设分配使用;同时这些vector都使用各自的中断处理接口,其中断处理过程相对简单(没有generic interrupt layer的参与,CPU直接调用到各自的ISR)。 
    2、ISA irqs,对这些中断,在初始化过程中已经完成了irq_desc、vector_irq、以及IDT中对应entry的分配和设置,同时可以发现ISA中断,在初始化的时候都被设置为运行在0号CPU。 
    3、其它外设的中断,对这些中断,在初始化过程中仅设置了对应的IDT,和ISA中断一样,其中断处理接口都来自interrupt数组。

    中断处理接口interrupt数组 
    interrupt数组是内核中外设中断对应的IDT entry,其在entry_64.S中定义,定义如下:

    640?wx_fmt=png

    这段汇编的效果是:在代码段,生成了一个符号irq_entries_start,该符号对应的内容是一组可执行代码,一共(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7组,每组为7个中断入口,为:

    640?wx_fmt=png

    每组的最后一个中断入口不需要jmp 2f是因为其pushq_cfi(就是pushq咯)下面就 
    是2f这个标号的地址了。(不明白的是:为什么不在jmp 2f的地方直接写上jmp common_interrupt?非要jmp 2f,2f的地方再次jmp common_interrupt? )

    而interrupt是一个数组,该数组在初始化完成后释放,其每个数组项都是一个地址,是对应的“pushq_cfi”代码的地址(每个代表中断入口的标号)。系统在初始化的时候,对外设使用interrupt数组作中断处理接口,就是在中断发生时,执行代码段:

    640?wx_fmt=png

    初始化中断控制器

    对中断控制器的使用基本上有三种机制: 
    1、 中断路由表 $PIR 
    struct irq_routing_table,该结构用于使用PIR和8259A的系统,在微软的文献《PCI IRQ Routing Table Specification》中描述了该结构详细信息。其描述了一个PCI设备的INT#是如何连接到PIR设备的输入端口的。其关键数据是一个可变长的struct irq_info数组,每个struct irq_info描述了一个PCI物理设备的4个INT#相关的中断路由信息和对应可用的ISA IRQs的bitmap。BIOS根据相关设备的物理连接填写该数据结构,OS在设备初始化过程中使用这些信息为使用INT#的设备分配对应的vector和irq。 
    2、 MP table 
    struct mpc_intsrc,该数据结构用于使用I/O APIC的系统中,描述系统中所有PCI设备4个INT#信号和I/O APIC输入引脚的对应关系。该数据结构的srcbus成员为对应PCI设备的bus id;srcbusirq描述了一个INT#信号,其bit0-bit1用于描述是INTA#–INTD#中的哪一个(对应值为0-3),bit2-bit6描述该PCI设备的slot id。dstapic为该描述对应的I/O APIC的ID。dstirq描述srcbus和srcbusirq确定的INT#对应的irq号信息(具体的解析有多种情况)。在系统中有一个以该数据结构为成员的全局数组mp_irqs,用于管理系统中所有的硬件中断信号和irq之间的关联。对MP table及其使用的更加详细的描述,见《Multiprocessor Specification v1.4》 
    3、 ACPI(Advanced Configuration and PowerInterface)机制 
    这种机制为I/O APIC机制和中PIR机制提供统一的管理界面,该机制使用struct acpi_prt_entry描述INT#和GSI(能和vector、irq对应)的关系,系统中所有的struct acpi_prt_entry由OS从BIOS提供的信息中获取,并保存在链表acpi_prt_list中。 
    注:对GSI的说明,GSI(global system interrupt)表示的是系统中中断控制器的每个输入管脚的唯一编号,在使用ACPI模式管理中断控制器的时候使用。对使用8259A的系统,GSI和ISA IRQ是一一对应的。对于使用APIC的,每个I/O APIC会由BISO分配一个基址,这个base+对应管脚的编号(从0开始)就是对应的GSI。通常是基址为0的I/O APIC的前16个管脚用于ISA IRQS,对GSI更加详细的描述,见《Advanced Configuration and Power Interface Revision 2.0》

    除了中断路由表,其它两种机制的初始化(包括相关中断路由信息的初始化)的在《interrupt in linux》中有很详细的描述。这些初始化操作都在内核初始化的时候完成。

    为PCI设备配置中断

    为PCI设备配置中断,分为两个步骤, 
    步骤一:为设备分配irq号(对MSIX,会有多个),为该中断分配执行CPU和它使用的vector,并通过对中断控制器的设置,确保对应的中断信号和vector匹配。对于使用INT#类型的中断,通常通过pci_enable_device/pci_enable_device_mem/pci_enable_device_io中对函数pcibios_enable_device的调用来完成(只有在没有开启MSI/MSIX的时候才会为INT#做配置),而要配置MSI/MSIX中断要使用的是pci_enable_msix。 
    步骤二:request_irq为该设备的irq指定对应的中断处理例程,把irq号和驱动定义ISR关联。

    pcibios_enable_device

    该接口用于使能PCI设备INT#模式的中断。其主要功能由pcibios_enable_irq(dev)完成,pcibios_enable_irq是一个函数指针,对于ACPI模式,其在上电过程中被设置为acpi_pci_irq_enable,其它情况被设置为pirq_enable_irq。

    对ACPI模式,其执行过程为: 
    1、 acpi_pci_irq_enable:其先根据设备的管脚信息获取一个GSI(可以认为有了GSI,就有了irq号,gsi_to_irq可以完成其转换),有了gsi/irq,要完成设置还必须有vector并且把它们关联起来,因此如果GSI获取成功,会使用acpi_register_gsi来完成后续操作。 
    2、 acpi_register_gsi:其主要功能由__acpi_register_gsi来完成,该函数指针在ACPI模式下被设置为acpi_register_gsi_ioapic,acpi_register_gsi_ioapic的执行过程如下: 
    mp_register_gsi===>io_apic_set_pci_routing===>io_apic_set_pci_routing===>io_apic_setup_irq_pin_once===>io_apic_setup_irq_pin===>setup_ioapic_irq,在setup_ioapic_irq中,就会利用assign_irq_vector为该irq选择对应的执行CPU,并分配该CPU上的vector,同时还把该vector等配置写入到I/O APIC对应管脚的RTE,从而完成整个中断的配置。这样在该INT#信号到来的时候,I/O APIC就能根据对应管脚的RTE,把该信号翻译为一个vector,并通过中断消息发送到local APIC。同时在setup_ioapic_irq中,还通过ioapic_register_intr===>irq_set_chip_and_handler_name为得到的irq号对应的irq_desc设置了->irq_data.chip和handle_irq函数指针(对level触发的,为handle_fasteoi_irq,否则为handle_edge_irq)

    对其它模式,其通过pcibios_lookup_irq完成执行: 
    在配置了I/O APIC的场景,pirq_enable_irq通过IO_APIC_get_PCI_irq_vector获取到irq号,然后和ACPI模式一样,通过io_apic_set_pci_routing完成对I/O APIC的配置。而对没有配置I/O APIC的场景,主要通过pcibios_lookup_irq来完成相关操作: 
    1、 pcibios_lookup_irq通过读取BIOS提供的中断路由表 ($PIR表,irq_routing_table)信息和当前irq分配情况(pirq_penalty数组),在考虑均衡的前提下为当前设备分配一个可用的irq。 
    2、 根据当前PIR的相关信息,决定最终的irq号选择,相关代码行如下

    640?wx_fmt=png

    也就是:如果是硬链接(INT#直接连接到了8259A,没有经过PIR),直接获取irq号,如果PIR中已经有该输入线的配置,使用已有的值,否则利用刚刚分配的可用irq,并写入到PIR,以便能够完成中断信号到irq号的转换。 
    注意: 
    1、这里的r,也就是pirq_router,代表一种PIR硬件,全局配置pirq_routers中描述了当前支持的PIR,并在初始化的时候通过pirq_find_router获取了对应当前配置的PIR对应的描述。 
    2、这里没有分配vector,是因为这里使用的irq号范围为0-16,是ISA IRQs,其与vector的对应关系简单:vector = IRQ0_VECTOR + irq,并在系统初始化过程中,已经通过early_irq_init中分配了irq_desc结构,通过init_IRQ设置了vector_irq(只运行于CPU0上),然后通过x86_init.irqs.intr_init(native_init_IRQ)===> x86_init.irqs.pre_vector_init(init_ISA_irqs)设置了->irq_data.chip(i8259A_chip)和handle_irq函数指针(handle_level_irq)。

    Pci_enable_msix

    该函数完成MSIX中断相关的设置。

    640?wx_fmt=png

    msix_capability_init中实现中断初始化的是arch_setup_msi_irqs,对于X86系统,其为x86_setup_msi_irqs,x86_setup_msi_irqs中直接调用了native_setup_msi_irqs,该函数是X86系统中实现MSIX中断初始化的关键函数,对于没有启用interrupt remap的系统,其实现如下:

    640?wx_fmt=png

    该函数中有两个关键函数,分别是create_irq_nr和setup_msi_irq,其中create_irq_nr是分配一个vector给当前的中断,分配vector的同时,也为该中断指定了执行CPU。setup_msi_irq则负责把相关配置信息写入到PCIE配置区,并设置irq_desc的数据,其中关键的是irq_desc的handle_irq被设置为handle_edge_irq。 
    create_irq_nr的实现如下:

    640?wx_fmt=png

    其中__assign_irq_vector负责分配vector,并和中断在CPU上的调度相关,其实现如下

    640?wx_fmt=png

    640?wx_fmt=png

    640?wx_fmt=png

    从实现中可以看到,该函数从FIRST_EXTERNAL_VECTOR(外设中断的起始vector号,通常是0x20) 到first_system_vector(外部中断结束vector号,通常是254,255被系统作为保留的SPURIOUS_APIC_VECTOR使用)的范围中,为当前中断分配一个vector,要求该vector在对应的cpu上均可用,该vector按照系统配置的要求和对应的cpu核心绑定,并在要求的cpu中没有被其它中断使用。需要说明的是,在setup_msi_irq中会再次通过msi_compose_msg再次调用__assign_irq_vector,但是由于这时已经存在满足CPU绑定要求的vector,不会多次分配。

    从以上分析可以得到MSI-X中断的一个绑定特征:根据当前APIC配置,每个中断都有对应的可以运行的cpu,pci_enable_msix在这些要求的cpu核心上建立了vector (APIC的配置由数据结构struct apic来抽象,其vector_allocation_domain用于决定需要在那些cpu核心上为该中断建立vector),当前我的系统使用的是apic_physflat,对每个MSI中断,其只在一个cpu核心上建立vector,对应的MSI-X中断事实上被绑定到该cpu核心上。在用户通过echo xxx > /proc/irq/xxx/affinity来调整中断的绑定属性时,内核会重新为该中断分配一个新的在对应核心上可用的vector,但是irq号不会改变。绑定属性调整的调用路径大致为irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>__irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最终通过msi_set_affinity来实现,在该函数中首先通过 __ioapic_set_affinity在绑定属性要求的cpu中选择空闲vector,然后通过__write_msi_msg把配置写入PCIE配置区。需要说明的是:该irq最终可以运行的cpu数量并不完全由用户指定,还与apic的模式相关,对于apic_physflat,实际上只为该irq分配了一个cpu核心,该irq只能运行在用户指定的cpu中的一个,而不是全部。

    附:关于全局变量apic

    该全局变量为local apic的抽象,在不同的系统配置下,有不同的选择,其最终的选择结果,由内核的config(反应在/arch/x86/kernel/apic/Makefile)和硬件配置等来决定。 
    1、 定义各种apic driver 
    首先,每种apic配置都会使用apic_driver/ apic_drivers来定义,apic_driver的定义如下

    640?wx_fmt=png

    这个定义的目的是把sym的地址写入到名为” .apicdrivers”的段中。 
    2、 定义全局符号__apicdrivers和__apicdrivers_end 
    在linker script vmlinux.lds.S中,定义了__apicdrivers为” .apicdrivers”段的开始地址,而__apicdrivers_end为结束地址。” .apicdrivers”段中是各个不同的apic配置对应的struct apic。

    640?wx_fmt=png

    3、 apic的probe 
    在初始化过程(start_kernel)中,会调用default_setup_apic_routing(probe_64.c中定义)来完成apic的probe,该函数会按照各个struct apic结构在.apicdrivers中的顺序,依次调用其probe接口,第一个调用返回非0的struct apic结构就被初始化到全局变量apic。也就是:如果有多个apic结构可用,最终会选择在.apicdrivers段中出现的第一个;所以makefile文件中各个.o出现的顺序也会觉得最终的apic probe结果。

    request_irq

    该函数把irq和用户指定的中断处理函数关联。用户指定的每个处理函数对应于一个struct irqaction结构,这些处理函数构成一个链表,保存在struct irq_desc::action成员中。详细见request_irq===>request_threaded_irq中的处理。

    中断的执行

    在内核代码中,对X86平台中断执行的基本过程是: 
    1、 通过IDT中的中断描述符,调用common_interrupt; 
    2、 通过common_interrupt,调用do_IRQ,完成vector到irq_desc的转换,进入Generic interrupt layer(调用处理函数generic_handle_irq_desc); 
    3、 调用在中断初始化的时候,按照中断特性(level触发,edge触发等、simple等)初始化的irq_desc:: handle_irq,执行不同的通用处理接口,比如handle_simple_irq; 
    4、 这些通用处理接口会调用中断初始化的时候注册的外部中断处理函数;完成EOI等硬件相关操作;并完成中断处理的相关控制。

    common_interrupt

    按照之前CPU执行中断过程的描述,X86 CPU在准备好了中断执行环境后,会调用中断描述符定义的中断处理入口;根据中断相关初始化过程我们知道,对于用户自定义中断,中断处理入口都是(对系统预留的,就直接执行定义的接口了):

    640?wx_fmt=png

    就是在把vector入栈后,执行common_interrupt,common_interrupt在entry_64.S中定义,其中关键步骤为:调用do_IRQ,完成后会根据环境判断是否需要执行调度,最后执行iretq指令完成中断处理,iret指令的重要功能就是回复中断函数前的EFLAGS(执行中断入口前被入栈保存,并清零IF位关中断),并恢复执行被中断的程序(这里不一定会恢复到之前的执行环境,可能执行软中断处理,或者执行调度)。

    do_IRQ

    do_IRQ的基本处理过程如下,其负责中断执行环境建立、vector到irq的转换等

    640?wx_fmt=png

    Generic interrupt layer

    该层负责的是平台无关/设备无关的中断通用逻辑,对这部分,在《Linux generic IRQ handling》中有详细描述。其负责完成中断处理的接口是generic_handle_irq_desc,该接口会执行irq_desc::handle_irq; Generic interrupt layer根据中断特性的不同,把中断分成几类,包括:level type(handle_level_irq)、edge type(handle_edge_irq)、simple type(handle_simple_irq)等,这些中断类型对应的处理函数是都在kernel/irq/chip.c中定义,并入前面的描述,在相关中断初始化的时候,被赋值给irq_desc::handle_irq;对于PCI设备,只用了两种,level type(INT#模式)、edge type(MSI/MSI-X模式)。

    edge 触发中断的基本处理过程:

    电压跳变触发中断===>中断控制器接收中断,记IRR寄存器===>中断控制器置ISR寄存器===>CPU屏蔽本CPU中断===>CPU处理中断,发出EOI===>中断控制器确认可以处理下一次中断===>ISR清中断源,电压归位===>中断源可以发起下一次中断===>CPU中断处理完成,执行完现场处理后执行IRET,不再屏蔽本CPU中断。 
    edge触发的特点: 
    a) 中断不会丢 
    如果中断触发时中断被屏蔽,那么中断控制器会记录下该中断,在屏蔽取消的时候会再执行。 
    b) edge触发的缺点是完成共享不方便: 
    比如A和B两个中断源共享一个中断,每次ISR先检查A再检查B,如果B先发生中断,在ISR检查完A,检查B的过程中,A发生中断。那么在ISR处理开始的时候,A会告诉ISR,不是它干的,然后ISR处理B的中断,完成后通过清理中断源把B的电压归位,但是由于A的中断没有得到处理,电压没有归位,这个共享的中断就不能得到再次触发了。 
    edge触发对应的通用逻辑接口

    640?wx_fmt=png

    level 触发:

    这种模式下,外设通过把电压保持到某个门限值来完成触发中断,在处理完成(EOI)后,如果电压还在门限值,就会再次触发中断的执行。 
    level触发的特点: 
    a) 方便中断共享 
    b) 对中断触发时中断被屏蔽的情况,如果中断屏蔽解除后仍然引脚电压仍然在门限值,就执行该中断的ISR,否则不执行。 
    需要说明的是:对于使用local APIC的系统,level触发和edge触发需要配置local APIC的Local Vector Table。 
    4、 level触发对应的通用逻辑接口

    640?wx_fmt=png

    level触发和edge触发在通用逻辑层最大的不同就是当其他CPU正在处理该中断的时候,系统的行为,对edge触发,会把该中断记录下来,当前处理结束后再次执行,而level直接退出。产生这种差异的原因是:level触发不怕丢?

    无论是那种触发方式,都会调用handle_irq_event处理中断,该函数中会遍历irq_desc::action链表,执行action->handler,也就是驱动在中断初始化的时候,通过request_irq注册的中断处理接口。

    总结

    中断的使能状态

    1、 在local APIC层次(当前CPU),一个中断正在处理的时候,不会有相同的中断或者优先级低于该中断的其它中断来打断当前中断的执行;但是高优先级中断可以打断低优先级中断。 
    2、 在X86 CPU层次(当前CPU),从中断执行开始到IRET,IF位都被清零,也就是只有不可屏蔽中断能够打断当前中断的执行。 
    3、 在Generic interrupt layer层次,如果一个中断已经在系统中执行,会阻止该中断在其它CPU上的执行。 
    4、 在外设/驱动中断处理函数层次往往也有中断使能的功能,比如启用了NAPI的网卡,在中断处理函数开始执行的时候,往往会通过硬件功能关闭该中断,要在对应的软中断完成处理后才通过硬件功能使能该中断。 
    注:NMI中断虽然称为不可屏蔽中断,也有一个例外:NMI中断执行过程中,该CPU屏蔽了后来的NMI中断。

    中断的执行CPU

    通过中断初始化过程我们知道:中断在那个CPU上执行,取决于在那个CPU上申请了vector并配置了对应的中断控制器(比如local APIC)。如果想要改变一个中断的执行CPU,必须重新申请vector并配置中断控制器。一般通过echo xxx > /proc/irq/xxx/affinity来完成调整,同时irq_balance一类软件可以用于完成中断的均衡。

    (完)


    "Linux阅码场"是专业的Linux及系统软件技术交流社区,Linux系统人才培养基地,企业和Linux人才的连接枢纽。


    查看我们精华技术文章请移步:

    Linux阅码场精华文章汇总


    求职招聘请移步:

    Linux阅码场: 连接企业和Linux人才的platform总线


    扫描二维码关注我们 

    640?wx_fmt=png

    如果觉得好,请

    转发

    转发

    转发

    展开全文
  • 禁止中断指的是Linux内核停工了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或者屏蔽掉整个机器的一条中断线的能力。通过禁止中断,可以确保某个中断处理程序不会抢占...
  • Linux中断解析

    2007-10-25 18:33:00
    Linux中断解析摘要:本章将向读者依次解释中断概念,解析Linux中的中断实现机理以及Linux中断如何被使用。作为实例我们第一将向《i386体系结构》一章中打造的系统加入一个时钟中断;第二将为大家注解RTC中断,希望...
  • 一、中断的响应和服务在前面一篇博文中,我们分析了i386 CPU的中断机制和内核中有关的初始化,现在我们进一步分析中断的响应过程和服务(和异常的响应机制不同)。我们假设外设驱动都已经完成了初始化,并且已把相应...
  • 1、中断处理程序与其他内核函数真正的区别在于,中断处理程序是被内核调用来相应中断的,而它们运行于中断上下文(原子上下文)中,在该上下文中执行的代码不可阻塞。中断就是由硬件打断操作系统。 2、异常与中断...
  • #include // 标准 GPIO_API  intgpio_request(unsigned gpio, const char *label);  获得并占有 GPIO>。在/proc/mem应该会有地址占用表描述。 这种用法的保护作用前提是大家都遵守先申请再访问,有一个地方没遵守...
  • 硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。现在的服务器上动不动就是多 CPU 多核、多网卡、多硬盘,如果能让...
  • disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回. 那么在中断处理程序中应该使用哪一个函数来关闭中断呢? 在中的按键驱动中, 使用disable_irq来关闭中断, 但是我在测试时进入中断后系统...
  • 一、并发与竞态三个要点 1、只要并发的执行单元同时访问共享内存是就会出现竞态 ...2、解决竞态的唯一途径是保证共享资源的互斥访问,即一个执行单元在访问共享资源时,其他的执行单元被禁止访问。...
  • 本文着重介绍Linux内核中中断处理的始末流程,因此对一些基本的概念应该有所了解。 2.硬件支持 我们知道,CPU有一个INTR引脚,用于接收中断请求信号。 而中断控制器用于提供中断向量,即第几号中断。 3.内核需要...
  • 来自:linux内核修炼之道 中断处理基本过程:首先设备产生中断,并通过中断线将中断信号送往中断控制器。如果该中断没有被屏蔽,则会被送往 CPU 的 INTR 引脚。CPU 立即停止当前的工作,根据从中断控制器获得的中断...
  • Linux内核中断嵌套

    2016-10-31 20:07:14
    为了支持中断的嵌套执行,Linux内核在进入中断服务程序之前会将硬中断开启,运行完中断服务程序之后再将硬中断关闭,在这期间硬件中断时可以被抢占的,而软中断执行过程中硬件中断始终是开启的。如果没
  • linux中断的实现

    2012-02-24 19:09:21
    中断服务程序往往都是在CPU关中断的条件下执行的,以避免中断嵌套而使控制复杂化。但是CPU关中断的时间不能太长,否则容易丢失中断信号。为此, Linux将中断服务程序一分为二,各称作“Top Half”和“Bottom Half”...
  • 关调度与关中断

    2019-08-08 22:18:56
    关调度与关中断 关中断与关调度是两种进入临界区的方式,它们有各自的使用环境。 最近在研究 ucos-III 的源代码时发现 ucos-III 中有对临界区的优化,以关调度来代替关中断的方式工作。系统可以在特定的情况下用关...
  • linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被...
1 2 3 4 5 ... 20
收藏数 18,480
精华内容 7,392
关键字:

linux 如何关中断