中断

  中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。这样的事件与CPU芯片内外部硬件电路产生的电信号相对应。

  中断分为同步中断异步中断

    同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为CPU在每条指令执行结束前,都要检查是否有中断信号出现,如果有,那它就要在执行完当前指令之后,转入中断处理,而不继续执行下一条指令。

      指令周期包含如下子周期:

        取址(Fetch):将下一条指令由存储器读入CPU。

        执行(Execute):解释操作码并完成指定的操作。

        中断(interrupt):若中断是允许的并且有中断发生,则保存当前进程的状态并为此中断服务。

      同步中断又分为自陷和异常:

        自陷:表示通过处理器所拥有的软件指令可预期地使处理器正在执行的程序流程发生变化,以执行特定的程序,例如Motorola 68000中的Trap指令

ARM中的SWI指令和Intel 80x86中的INT指令。自陷是显示的事件,需要无条件的执行处理。

        异常:是指处理器自动产生的异常事件。例如被0整除、执行非法指令、内存保护故障等。异常没有对应的处理器指令;当异常发生时,处理器也需要无条件地挂起当前运行的程序,执行特定的处理程序。

    异步中断是由于处理器外部的原因而改变程序执行流程的事件,它属于异步事件,又称为硬件中断或者外部中断。“外部”指的是一些除CPU核之外的控制器所产生的中断,如LCD控制器、I2C控制器等。

  每个能够发出中断请求的硬件设备控制器都有一条名为IRQ(Interrupt ReQuest)的输出线,连接到一个名为中断控制器(INTC)的硬件电路输入引脚上,INTC执行如下工作:

  1.  监视IRQ线,检查产生的信号(raised signal)。如果有一条或者两条以上的IRQ线上产生信号,就选择引脚编号较小的IRQ线。

  2.  如果一个引发信号出现在IRQ线上:

    a. 把接收到的引发信号转换成对应的向量。

    b. 把这个向量存放在中断控制器的一个I/O端口,从而允许CPU通过数据总线读此向量。

    c. 把引发信号发送到处理器的INTR引脚,即产生一个中断。

    d. 等待,直到CPU通过把这个中断信号写进中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。

  3.  返回到第1步。

  可以通过对INTC编程来禁止每条IRQ线,也就是告诉INTC停止对给定的IRQ线向CPU发布中断。虽然禁止了INTC向CPU发布这条IRQ线的中断,但这条IRQ线在INTC上的信号是丢失不了的,IRQ线一旦被激活,INTC就又把这条线上的中断发送到CPU。这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ。

  激活/禁止IRQ线不同于可屏蔽中断的屏蔽/非屏蔽。屏蔽可屏蔽中断时,INTC仍会发布对应IRQ线上的中断,但该中断由CPU暂时忽略。

  中断描述符表

    中断描述符表(Interrupt Descriptor Table, IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必须适当地初始化IDT.

    IDT表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因为最多需要256 x 8 = 2048字节来存放IDT.

    idtr CPU寄存器使IDT可以位于内存的热河地方,它指定IDT的线性基地址及其限制(最大长度)。在允许中断之前,必须用lidt会变指令初始化idtr。

    IDT包含三种类型的描述符,每种描述符有64位,在40~43位的Type字段的值表示描述符的类型,这些描述符是:

    任务门(task gate)

      当中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。

    中断门(interrupt gate)

      包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断。

    陷阱门(Trap gate)

      与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志。

    中断门用于处理中断,陷阱门用于处理异常。

   中断处理程序的灵活性是以两种不同的方式实现的:

    1.IRQ共享

      中断处理程序执行多个中断服务例程(interrupt service routine, ISR)。每个ISR是一个与单独设备(共享IRQ线)相关的函数。因为不可能预先知道哪个特定的设备产生IRQ,因此,每个ISR都要被执行,以验证它的设备是否需要关注;如果是,当设备产生中断时,就执行需要执行的所有操作。

    2.IRQ动态分配

      一条IRQ线在可能的最后时刻才与一个设备驱动程序相关联;例如软盘设备的IRQ线只有在用户访问软盘设备时才被分配。这样,即使几个硬件设备并不共享IRQ线,同一个IRQ向量也可以由这几个设备在不同时刻使用

   

  当一个中断发生时,并不是所有的操作都具有相同的急迫性。事实上,把所有的操作都放进中断处理程序本身并不合适。需要时间长的、非重要的操作应该推后,因为当一个中断处理程序正在运行时,相应的IRQ线上发出的信号就被暂时忽略。更重要的是,中断处理程序是代表进程执行的,它所代表的进程必须总处于TASK_RUNNING状态,否则,即可能出现系统僵死情形。因此中断处理程序不能执行任何阻塞过程,如磁盘I/O操作。因此,Linux把紧随中断要执行的操作分为三类:

    1.紧急的。

    2.非紧急的。

    3.非紧急可延迟的。

中断描述符结构体:irq_desc(kernel/include/linux/irq.h)
/**
 * 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 */          标识当出现IRQ时要调用的中断服务例程。该字段指向IRQ的irqaction描述符                                             链表的第一个元素。
    unsigned int        status;        /* IRQ status */           描述IRQ线状态的一组标志。

    unsigned int        depth;        /* nested irq disables */       如果IRQ线被激活,则显示0;如果IRQ线被禁止了不止一次,则显示一个正数。
    unsigned int        wake_depth;    /* nested wake enables */
    unsigned int        irq_count;    /* For detecting broken IRQs */   中断计数器,统计IRQ线上发生中断的次数(仅在诊断时使用)
    unsigned long        last_unhandled;    /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;                     对在IRQ线上发生的无法处理的中断进行计数(仅在诊断时使用)
    spinlock_t        lock;                            用于串行访问IRQ描述符和INTC的自旋锁
#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;
} ____cacheline_internodealigned_in_smp;

 

  如果一个中断内核没有处理,那么这个中断就是意外中断, 也就是说,与某个IRQ线相关的中断处理例程(ISR, interrupt service routine)不存在,或者与某个中断线相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从IRQ先接收的意外中断的数量,当这条IRQ线连接的有故障设备没完没了的发中断时,就禁用这条IRQ线。由于几个设备可能共享IRQ线,内核不会再每检测到一个意外中断时就立刻禁用IRQ线,更合适的办法是:内核把中断和意外中断的总次数分别存放在irq_desc_t描述符的irq_count和irq_unhandled字段中,当第100000次中断产生时,如果意外中断的次数超过99900,内核才禁用这条IRQ线(即来自共享IRQ线的赢家你设备的意外中断,比最近接收的100000次正常中断少101次)。

  

  1)描述IRQ线状态标志(irq_desc中status)的宏定义如下所示(kernel/include/linux/irq.h):

/* Internal flags */
#define IRQ_INPROGRESS        0x00000100    /* IRQ handler active - do not enter! */  IRQ的一个处理程序正在执行
#define IRQ_DISABLED        0x00000200    /* IRQ disabled - do not enter! */       由一个设备驱动程序故意地禁用IRQ线
#define IRQ_PENDING        0x00000400    /* IRQ pending - replay on enable */      一个IRQ已经出现在线上,它的出现也已对INTC作出应答,但是                                                   内核还没有为它提供服务。
#define IRQ_REPLAY        0x00000800    /* IRQ has been replayed but not acked yet */  IRQ线已被禁用,但是前一个出现的IRQ还没有对INTC做出应答
#define IRQ_AUTODETECT        0x00001000    /* IRQ is being autodetected */        内核在执行硬件设备探测时使用IRQ线。
#define IRQ_WAITING        0x00002000    /* IRQ not yet seen - for autodetection */   内核在执行硬件设备探测时使用IRQ线;此外相应的中断还没                                                    产生
#define IRQ_LEVEL        0x00004000    /* IRQ level triggered */
#define IRQ_MASKED        0x00008000    /* IRQ masked - shouldn't be seen again */
#define IRQ_PER_CPU        0x00010000    /* IRQ is per CPU */
#define IRQ_NOPROBE        0x00020000    /* IRQ is not valid for probing */
#define IRQ_NOREQUEST        0x00040000    /* IRQ cannot be requested */
#define IRQ_NOAUTOEN        0x00080000    /* IRQ will not be enabled on request irq */
#define IRQ_WAKEUP        0x00100000    /* IRQ triggers system wakeup */
#define IRQ_MOVE_PENDING    0x00200000    /* need to re-target IRQ destination */
#define IRQ_NO_BALANCING    0x00400000    /* IRQ is excluded from balancing */
#define IRQ_SPURIOUS_DISABLED    0x00800000    /* IRQ was disabled by the spurious trap */
#define IRQ_MOVE_PCNTXT        0x01000000    /* IRQ migration from process context */
#define IRQ_AFFINITY_SET    0x02000000    /* IRQ affinity was set from userspace*/
#define IRQ_SUSPENDED        0x04000000    /* IRQ has gone through suspend sequence */
#define IRQ_ONESHOT        0x08000000    /* IRQ is not unmasked after hardirq */
#define IRQ_NESTED_THREAD    0x10000000    /* IRQ is nested into another, no own handler thread */

   irq_desc_t描述符的depth字段和IRQ_DISABLED标志标识IRQ线是否被禁用。每次调用disable_irq()或disable_irq_nosync()函数,depth字段的值增加。

如果depth等于0,函数禁用IRQ线并设置它的IRQ_DISABLED标志。相反,每当调用enable_irq()函数,depth字段的值减少,如果depth变为0,函数激活IRQ线并清除IRQ_DISABLED标志。

  disable_irq_nosync()直接禁用IRQ线,而disable_irq(n)一直等待,直到在其他CPU上为IRQn运行的所有中断处理程序都完成才返回。

   

  2)如前所述,多个设备能共享一个单独的IRQ.因此,内核要维护多个irqaction描述符,其中的每个描述符涉及一个特定的硬件设备和一个特定的中断。包含在这个描述符中的字段如下所示(kernel/include/linux/interrupt.h):

/**
 * 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;    指向一个I/O设备的中断服务例程。这是允许多个设备共享同一IRQ的关键字段。
    unsigned long flags;     描述IRQ与I/O设备之间的关系,如3)段代码所示。
    const char *name;       I/O设备名(通过读/proc/interrupts文件,在列出所服务的IRQ时也显示设备名,显示的是struct irq_chip结构体中的*name)
    void *dev_id;         I/O设备的私有字段。典型情况下,它标识I/O设备本身(例如,它可能等于其主设备号和此设备号)
    struct irqaction *next;   指向irqaction描述符链表的下一个元素。链表中的元素指向共享同一IRQ的硬件设备。 
    int irq;            IRQ线。
    struct proc_dir_entry *dir; 指向与IRQn相关的/proc/irq/n目录的描述符
    irq_handler_t thread_fn;
    struct task_struct *thread;
    unsigned long thread_flags;
};

 

  3)irqaction中flag成员标志名:

/*
 * These flags used only by the kernel as part of the
 * irq handling routines.
 *
 * IRQF_DISABLED - keep irqs disabled when calling the action handler
 * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
 * IRQF_SHARED - allow sharing the irq among several devices
 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt
 * IRQF_PERCPU - Interrupt is per cpu
 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
 *                registered first in an shared interrupt is considered for
 *                performance reasons)
 * IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
 *                Used by threaded interrupts which need to keep the
 *                irq line disabled until the threaded handler has been run.
 */
#define IRQF_DISABLED        0x00000020      处理程序必须以禁止中断执行
#define IRQF_SAMPLE_RANDOM    0x00000040      设备可以被看作是时间随即的发生源,因此,内核可以用它做随机数产生器(用户可以从/dev/random和/dev/ur                             andom设备文件中取得随机数而访问这种特征)
#define IRQF_SHARED        0x00000080       设备允许它的IRQ线与其他设备共享。
#define IRQF_PROBE_SHARED    0x00000100
#define IRQF_TIMER        0x00000200
#define IRQF_PERCPU        0x00000400
#define IRQF_NOBALANCING    0x00000800
#define IRQF_IRQPOLL        0x00001000
#define IRQF_ONESHOT        0x00002000

 

 

posted @ 2014-02-14 10:20  Watson  阅读(936)  评论(0编辑  收藏  举报