目录
0. 引言 1. Linux 中断的概念 2. 中断处理流程 3. Linux 中断相关的源代码分析 4. Linux 硬件中断 5. Linux 软中断 6. 中断优先级 7. CPU在关中断状态下编程要注意的事项
0. 引言
中断是现代计算机体系结构的重要组成部分,我们回顾历史,现代体系结构的基本输入输出方式有三种
1. 程序查询: CPU周期性询问外部设备是否准备就绪。该方式的明显的缺点就是浪费CPU资源,效率低下。但是在特定的场景下这种"程序查询"的方式还有有它的用武之地的 例如,在网络驱动中,通常接口(Interface)每接收一个报文,就发出一个中断。而对于高速网络,每秒就能接收几千个报文,在这样的负载下,系统性能会受到极大的损害。为了提高系统性能,内核开发者已经为网络子系统开发了一种可选的基于查询的接口NAPI(代表new API)。当系统拥有一个高流量的高速接口时,系统通常会收集足够多的报文,而不是马上中断CPU 2. 中断方式 这是现代CPU最常用的与外围设备通信方式。相对于轮询,该方式不用浪费稀缺的CPU资源,所以高效而灵活。中断处理方式的缺点是每传送一个字符都要进行中断,启动中断控制器,还要保留和恢复现场以便能继续原程序的执行,花费的工作量很大,这样如果需要大量数据交换,系统的性能会很低 3. DMA方式 通常用于高速设备,设备请求直接访问内存,不用CPU干涉。但是这种方式需要DMA控制器,增加了硬件成本。在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制 权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU今天我们要重点学习的就是"中断"这种输入输出机制
Relevant Link:
http://blog.sina.com.cn/s/blog_5d0e8d0d01019cds.html
1. Linux 中断的概念
今天我们谈论"中断",会发现这个词在很多场景下都会以不同的身份出现,我们今天对这些概念进行一个梳理
对于这张图,有一点需要说明的是,在linux 2.6内核之后,系统调用已经不使用int 0x80的方式进行了,关于这部分的知识,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/4111692.html0x1: 中断的分类
翻阅不同的书籍和内核相关资料,我们会发现关于中断的分类有很多种标准,事实情况也就是这样的,根据不同的观察角度,可以有如下的分类
1. 异步(外部中断) OR 同步(内部中断):根据发生中断的时机来分类
1. 异步中断(外部中断) 外部中断是由外部设备引发的中断,这类中断的特点是中断的发生完全是"瞬发"的,例如 1) 鼠标 2) 键盘 2. 同步中断(内部中断) 从CPU的角度看,内部中断是一个同步事件,它是执行某条指令时产生的,例如 1) 缺页异常 2) 除零异常 3) 系统调用systemcall trap2. 可屏蔽中断 OR 不可屏蔽中断:根据处于中断状态中是否可以被其他中断递归打断来分类
中断的屏蔽基于CPU中的"中断屏蔽寄存器"进行实现
1. 可被屏蔽中断:允许延迟响应 2. 不可被屏蔽中断:高实时场景需求,必须立即被响应3. 硬件中断 OR 软件中断:根据中断源来分类
1. 软件中断 1) Ring3代码中触发的指令中断 2) 操作系统自身触发的缺页中断,这是操作系统提供的对CPU中断的一个接口,以中断号的形式进行编号 2. 硬件中断 1) 外设处理过程中产生的,通过硬件控制器通知cpu自己的状态变化 2) 外部硬件设备触发的硬件中断(电平信号) 3) 硬中断应该很快完成,才能有快的响应,所以将一部分可以延迟的处理从硬中断里独立出来,当硬中断处理完之后再处理这部分,就是软中断可以看到,中断归根结底是CPU提供的一种"硬件机制",是CPU的引脚配置总线和中断控制器在最底层的硬件实现,我们以Linux为例,来深入学习一下中断的概念
0x2: 中断控制器
需要明白的是,中断最本质的概念是一种硬件机制,从单片机到intel CPU都具备中断机制,CPU(微控制器)提供了专门的引脚用于实现中断功能,我们以51单片机进行概念解释,在其他更高级的CPU上本质道理都是相同的
1. 51单片机中有5个中断源 1) 中断号: 0、中断源: 1(最高)、中断入口地址:外部中断0 0003H 2) 中断号: 1、中断源: 2、中断入口地址: 定时器0 000BH 3) 中断号: 2、中断源: 3、中断入口地址: 外部中断1 0013H 4) 中断号: 3、中断源: 4、中断入口地址: 定时器1 0018H 5) 中断号: 4、中断源: 5、中断入口地址: 串口总段 0023H 2. Linux/Windows在内核中提供的"软中断"是一个封装后的概念,实际上是CPU给内核单独提供了一个中断引脚,用于实现软中断,由程序员控制/或者内核进行中断响应处理 3. 所有的中断,包括软中断和外设的中断响应都最终都需要通过CPU上的中断引脚由硬件方式实现 4. 中断源当发生中断事件时,向对应的中断引脚发送下降沿电平信号,这个动作将将对应的中断寄存器置位 5. 51单片机有6个中断寄存器,分别是 1) 总选通中断寄存器EA,类似于Linux中的"关CPU"中断,当这个寄存器未置位时,所有中断将无法响应,即所有中断引脚将不响应 2) 每个中断源都和一个中断引脚相连,对应一个中断寄存器,只有EA置位的前提下,中断源对应的中断寄存器才有效 6. 中断优先级是在所有中断寄存器"更后面"的一个选通逻辑,通过与非门的bit选通逻辑,来控制优先响应哪些中断 7. 每个中断源都对应一个中断源,这是一个内置的硬件例程(也可以通过软件方式写入),我们说的软中断也有一个硬件例程,而软中断号可以理解为传入的参数,进入软中断例程后,程序会根据中断号进行一次再分发,分发到相应的软中断子程序中 8. CPU在每个时钟周期的stage5 s2下降沿会检查所有的中断寄存器,以此判断当前是否有中断事件发生中断控制器是连接设备和 CPU 的桥梁,一个设备产生中断后,需要经过中断控制器的转发,才能最终到达 CPU。时代发展至今,中断控制器经历了几个阶段
1. PIC(Programmable Interrupt Controller 可编程中断控制器): 在 UP(Uni-processor 单处理器) 上应用较广 2. APIC (Advanced Programmable Interrupt Controller 高级可编程中断控制器): 随着 SMP (Symmetric Multiple Processor 对称多处理器) 的流行,APIC 已广为流行并将最终取代 PIC8259A (PIC) 管脚图
1. IR0~IR7 (Interrupt Request0~7): 用于连接设备 2. INT: 连接CPU,当有中断请求时,拉高该管脚(提高电平)以通知 CPU 中断的到来 3. INTA: 连接CPU,CPU通过该管脚应答中断请求,并通知PIC提交中断的vector(中断向量)到数据线 4. CS: 片选,用于将两个8259A串联成可连接15个设备的PIC8259A中的寄存器
1. ICW(Initialization Command Word): 初始化命令寄存器,用于初始化8259A 2. OCW(Operation Command Word 操作命令字): 用于控制8259A 3. IRR(Interrupt Request Register 中断请求寄存器): 共 8bit,对应 IR0~IR7 八个中断管脚。当某个管脚的中断请求到来后,若该管脚没有被屏蔽,IRR 中对应的 bit 被置1。表示 PIC 已经收到设备的中断请求,但还未提交给 CPU。 4. ISR(In Service Register 服务中寄存器): 共 8bit,每 bit 对应 IR0~IR7 八个中断管脚。当 IRR 中的某个中断请求被发送给 CPU 后,ISR 中对应的 bit 被置1。表示中断已发送给 CPU,但 CPU 还未处理完 5. IMR(Interrupt Mask Register 中断屏蔽寄存器): 共 8bit,每 bit 对应IR0~IR7 八个中断管脚。用于屏蔽中断。当某 bit 置1时,对应的中断管脚被屏蔽(通过电路上的与非门实现)arch/x86/kernel/i8259.c 中通过位运算来开启和关闭中断
void disable_8259A_irq(unsigned int irq) { unsigned int mask = 1 << irq; unsigned long flags; // 用 spinlock 锁住 spin_lock_irqsave(&i8259A_lock, flags); // 将 IRQ 的相应位置1,屏蔽中断 cached_irq_mask |= mask; if (irq & 8) outb(cached_slave_mask, PIC_SLAVE_IMR); else outb(cached_master_mask, PIC_MASTER_IMR); // 解开自旋锁 spin_unlock_irqrestore(&i8259A_lock, flags); } void enable_8259A_irq(unsigned int irq) { unsigned int mask = ~(1 << irq); unsigned long flags; // 用 spinlock 锁住 spin_lock_irqsave(&i8259A_lock, flags); // 将 IRQ 的相应位置0,开启中断 cached_irq_mask &= mask; if (irq & 8) // IR2 管脚负责 8259A 的级联,为0时使用主片,为1时使用从片 outb(cached_slave_mask, PIC_SLAVE_IMR); else outb(cached_master_mask, PIC_MASTER_IMR); // 解开自旋锁 spin_unlock_irqrestore(&i8259A_lock, flags); }PIC 的每个管脚具有优先级,连接号码较小的设备具有较高的中断优先级,在 PIC 默认的 Full Nested 模式下,通过 PIC 发起中断的流程如下
1. 一个或多个 IR 管脚上产生电平信号,若对应的中断没有被屏蔽,IRR 中相应的 bit 被置1,表示已接收中断请求 2. PIC 拉高 INT 管脚通知 CPU 中断发生 3. CPU 通过 INTA 管脚应答 PIC,表示中断请求收到 4. PIC 收到 INTA 应答后,将 IRR 中具有最高优先级的 bit 清零,并设置 ISR 中对应的 bit 5. CPU 通过 INTA 管脚第二次发出脉冲,PIC 收到后计算最高优先级中断的 vector,并将它提交到数据线上,vector用于之外的中断例程的寻址 6. 等待 CPU 写 EOI (End of Interrupt)。收到 EOI 后(表示中断处理结束),ISR 中最高优先级的 bit 被清零(中断例程处理结束)。如果 PIC 处于 AEOI 模式,当第二个 INTA 脉冲收到后,ISR 中最高优先级的 bit 自动清零PIC 还有优先级轮转模式,即 PIC 在服务完一个管脚之后将其优先级临时降低,并升高未服务管脚的优先级,以实现类似轮询的模式,避免一个管脚持续发出中断导致其他设备"饿死"下图是一个典型的 PIC 中断分配,管脚基本上都被古董级设备占据了
arch/x86/kernel/i8259.c 中 8259A 引脚的分配
void init_8259A(int auto_eoi) { unsigned long flags; i8259A_auto_eoi = auto_eoi; spin_lock_irqsave(&i8259A_lock, flags); outb(0xff, PIC_MASTER_IMR); /* mask all of 8259A-1 */ outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */ /* * outb_pic - this has to work on a wide range of PC hardware. */ outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */ /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64, to 0x20-0x27 on i386 */ outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR); /* 8259A-1 (the master) has a slave on IR2 */ outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR); if (auto_eoi) /* master does Auto EOI */ outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR); else /* master expects normal EOI */ outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR); outb_pic(0x11, PIC_SLAVE_CMD); /* ICW1: select 8259A-2 init */ /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */ outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR); /* 8259A-2 is a slave on master's IR2 */ outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR); /* (slave's support for AEOI in flat mode is to be investigated) */ outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR); if (auto_eoi) /* * In AEOI mode we just have to mask the interrupt * when acking. */ i8259A_chip.mask_ack = disable_8259A_irq; else i8259A_chip.mask_ack = mask_and_ack_8259A; udelay(100); /* wait for 8259A to initialize */ outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */ outb(cached_slave_mask, PIC_SLAVE_IMR); /* restore slave IRQ mask */ spin_unlock_irqrestore(&i8259A_lock, flags); }从代码中可以看出,PIC 能接的设备数量实在太少了,而且不支持多处理器,为了使用 8259A 级联连接较多的设备,可以采用两种方式
1. IRQ 共享:中断处理程序执行多个中断服务程序(ISR),每个 ISR 是一个与共享 IRQ 线相关的函数 2. IRQ 共享需要满足两个条件 1) 每个 ISR 都愿意共享 IRQ,即 request_irq() 时指定了 IRQF_SHARED 2) 所有 ISR 具有相同的触发条件(电平触发或边沿触发、高低电平或上下边沿) 3. IRQ 动态分配:在可能的最后时刻,才把 IRQ 线分配给一个设备当然,APIC 是现代的解决方案。即使是 APIC,也需要使用 IRQ 共享
这张图的核心: I/O APIC 的组成为
1. 一组 24 条 IRQ 线 2. 一张 24 项的中断重定向表 3. 可编程寄存器,通过 APIC 总线发送和接收 APIC 信息的一个信息单元与 8259A 不同,中断优先级不与引脚号相关联,中断重定向表中的每一项都可以被单独编程以指明中断向量和优先级、目标处理器和选择处理器的方式来自外部硬件设备的中断以两种方式在可用 CPU 之间分发
1. 静态分发 2. 动态分发0x3: 中断描述符
Intel 提供了三种类型的中断描述符
1. 任务门 2. 中断门 3. 陷阱门描述符 1. 系统将所有的中断信号统一进行了编号(一共256个: 0 ~ 255),这个号称为中断向量,它们统一组织成IDT,具体哪个中断向量表示哪种中断有的是规定好的,也有的是在给定范围内自行设定的 2. 中断向量和中断服务程序的对应关系主要是由IDT(中断向量表)负责。操作系统在IDT中设置好各种中断向量对应的中断描述符,从概念类型上来说,Linux对这些中断号进行了一次分类,Linux 使用与 Intel 稍有不同的分类,把中断描述符分为五类 1) 中断门(interrupt gate) 用户态的进程不能访问Intel中断门(门的DPL字段为0)。所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态 set_intr_gate(n,addr) 上述系统调用在 IDT 的第 n 个表项插入一个中断门。门中的段选择符设置成内核代码的段选择符,偏移量设置为中断处理程序的地址 addr,DPL 字段设置为0 2) 系统门(system gate) 用户态的进程可以访问Intel陷阱门(trap)(门的DPL字段为3)。通过系统门来激活三个Linux异常处理程序,它们的向量分别对应用户态下对应的汇编指令 2.1) 4: into 2.2) 5: bound 2.3) 128: int $0x80(系统调用) set_system_gate(n,addr) 3) 系统中断门(system interrupt gate) 能够被用户态进程访问的Intel中断门(门的DPL字段为3)。与向量3相关的异常处理程序是由系统中断门激活的,因此,在用户态可以使用汇编语言指令int3 set_system_intr_gate(n,addr) 4) 陷阱门(trap gate) 用户态的进程不能访问的一个Intel陷阱门(门的DPL字段为0)。大部分Linux异常处理程序(例如除零、缺页异常)都通过陷阱门来激活 set_trap_gate(n,addr) 5) 任务门(task gate) 不能被用户态进程访问的Intel任务门(门的DPL字段为0)。Linux对"Double fault"异常的处理程序是由任务门激活的 set_task_gate(n,gdt) //IDT本身的位置是由idtr寄存器保存的,当然这个地址也是由OS填充的门中的段选择符中存放一个TSS的全局描述符表的指针,该TSS中包含要被激活的函数在 IDT 中插入门的函数定义在 \linux-2.6.32.63\arch\x86\include\asm\desc.h 中,这些函数以不同的参数调用内部函数 _set_gate()。_set_gate 调用两个内部函数
1. pack_gate: 设置门的数据结构 1) 中断号 2) 门类型 3) 处理函数地址 4) DPL 5) ist 6) 目录段寄存器 2. write_idt_entry: 宏定义为 native_write_idt_entry,用 memcpy 将设置好的门写入 IDT\linux-2.6.32.63\arch\x86\include\asm\desc.h
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); } static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func, unsigned dpl, unsigned ist, unsigned seg) { gate->offset_low = PTR_LOW(func); gate->segment = __KERNEL_CS; gate->ist = ist; gate->p = 1; gate->dpl = dpl; gate->zero0 = 0; gate->zero1 = 0; gate->type = type; gate->offset_middle = PTR_MIDDLE(func); gate->offset_high = PTR_HIGH(func); } #define write_idt_entry(dt, entry, g) \ native_write_idt_entry(dt, entry, g) static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate) { memcpy(&idt[entry], gate, sizeof(*gate)); }0x4: 中断数据结构
在 Linux 中,中断描述符的核心数据结构是 include/linux/irq.h 中的 irq_desc 结构体。每个 irq_desc 实例描述一条中断线
struct irq_desc { //1. interrupt number for this descriptor unsigned int irq; //2. irq stats per cpu unsigned int *kstat_irqs; #ifdef CONFIG_INTR_REMAP //3. iommu with this irq struct irq_2_iommu *irq_2_iommu; #endif /* 4. highlevel irq-events handler [if NULL, __do_IRQ()],中断事件处理函数 handle_irq 是函数指针,指向 kernel/irq/chip.c 中的中断事件处理函数 1) handle_simple_irq 2) handle_level_irq 3) handle_fasteoi_irq 4) handle_edge_irq 5) handle_percpu_irq 这个函数指针是由 kernel/irq/chip.c 中的 __set_irq_handler() 设置 */ irq_flow_handler_t handle_irq; /* 5. low level interrupt hardware access,irq_chip 指针,描述了一些硬件信息 chip 是 irq_chip 结构体指针,include/linux/irq.h 中的 irq_chip 结构体定义了对每根中断线的底层硬件操作 struct irq_chip { /* 1. name for /proc/interrupts 包含一个短的字符串,用于标识硬件控制器 1) IA-32: XTPIC 2) AMD64: IO-APIC */ const char *name; //2. start up the interrupt (defaults to ->enable if NULL) unsigned int (*startup)(unsigned int irq); //3. shut down the interrupt (defaults to ->disable if NULL) void (*shutdown)(unsigned int irq); //4. enable the interrupt (defaults to chip->unmask if NULL) void (*enable)(unsigned int irq); //5. disable the interrupt (defaults to chip->mask if NULL) void (*disable)(unsigned int irq); //6. start of a new interrupt void (*ack)(unsigned int irq); //7. mask an interrupt source void (*mask)(unsigned int irq); //8. ack and mask an interrupt source void (*mask_ack)(unsigned int irq); //9. unmask an interrupt source void (*unmask)(unsigned int irq); //10. end of interrupt - chip level void (*eoi)(unsigned int irq); //11. end of interrupt - flow level void (*end)(unsigned int irq); //12. set the CPU affinity on SMP machines int (*set_affinity)(unsigned int irq, const struct cpumask *dest); //13. resend an IRQ to the CPU int (*retrigger)(unsigned int irq); //14. set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ int (*set_type)(unsigned int irq, unsigned int flow_type); //15. enable/disable power-management wake-on of an IRQ int (*set_wake)(unsigned int irq, unsigned int on); //16. function to lock access to slow bus (i2c) chips void (*bus_lock)(unsigned int irq); //17. function to sync and unlock slow bus (i2c) chips void (*bus_sync_unlock)(unsigned int irq); /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD //18. release function solely used by UML void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ //19. obsoleted by name, kept as migration helper const char *typename; }; */ struct irq_chip *chip; //6. MSI descriptor struct msi_desc *msi_desc; //7. per-IRQ data for the irq_chip methods void *handler_data; //8. platform-specific per-chip private data for the chip methods, to allow shared chip implementations void *chip_data; /* IRQ action list */ /* 9. the irq action chain,irqaction 指针链 action 是 irqaction 结构体指针,指向一个 irqaction 链表。irqaction 在 include/linux/interrupt.h 中定义,每个结构体描述一个中断处理程序 struct irqaction { // 中断处理程序的函数指针 irq_handler_t handler; unsigned long flags; // 中断处理程序名称,显示在 /proc/interrupts 中 const char *name; // 设备 ID void *dev_id; // 指向链表中的下一个 irqaction 结构体 struct irqaction *next; // 中断通道号 int irq; // 在 /proc 文件系统中的目录 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; }; */ struct irqaction *action; /* IRQ status */ /* 10. status information,IRQ 线状态标志 status 是描述 IRQ 线状态的一组标志。在同一文件中宏定义 #define IRQ_TYPE_NONE 0x00000000 /* Default, unspecified type */ #define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */ #define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */ #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) #define IRQ_TYPE_LEVEL_HIGH 0x00000004 /* Level high type */ #define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */ #define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */ #define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */ /* Internal flags */ #define IRQ_INPROGRESS 0x00000100 /* IRQ handler active - do not enter! */ #define IRQ_DISABLED 0x00000200 /* IRQ disabled - do not enter! */ #define IRQ_PENDING 0x00000400 /* IRQ pending - replay on enable */ #define IRQ_REPLAY 0x00000800 /* IRQ has been replayed but not acked yet */ #define IRQ_AUTODETECT 0x00001000 /* IRQ is being autodetected */ #define IRQ_WAITING 0x00002000 /* IRQ not yet seen - for autodetection */ #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 */ #ifdef CONFIG_IRQ_PER_CPU # define CHECK_IRQ_PER_CPU(var) ((var) & IRQ_PER_CPU) # define IRQ_NO_BALANCING_MASK (IRQ_PER_CPU | IRQ_NO_BALANCING) #else # define CHECK_IRQ_PER_CPU(var) 0 # define IRQ_NO_BALANCING_MASK IRQ_NO_BALANCING #endif */ unsigned int status; /* nested irq disables */ //11. disable-depth, for nested irq_disable() calls unsigned int depth; /* nested wake enables */ //12. enable depth, for multiple set_irq_wake() callers unsigned int wake_depth; /* For detecting broken IRQs */ //13. stats field to detect stalled irqs,中断计数 unsigned int irq_count; /* Aging timer for unhandled count */ //14. aging timer for unhandled count,无法处理的中断计数 unsigned long last_unhandled; //15. stats field for spurious unhandled interrupts unsigned int irqs_unhandled; //16. locking for SMP spinlock_t lock; #ifdef CONFIG_SMP //17. IRQ affinity on SMP,多处理器中的处理器亲和性 cpumask_var_t affinity; //18. node index useful for balancing unsigned int node; #ifdef CONFIG_GENERIC_PENDING_IRQ //19. pending rebalanced interrupts cpumask_var_t pending_mask; #endif #endif //20. number of irqaction threads currently running atomic_t threads_active; //21. wait queue for sync_irq to wait for threaded handlers wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS //22. /proc/irq/ procfs entry,在 /proc 文件系统中的目录 struct proc_dir_entry *dir; #endif //23. flow handler name for /proc/interrupts output,中断名称 const char *name; } ____cacheline_internodealigned_in_smp;irq_desc 在 kernel/irq/handle.c 中被使用,此文件是 IRQ 机制的核心入口,描述了各中断线sourcecode\kernel\irq\handle.c
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { /* IRQ的最大数量主要取决于辅助CPU管理IRQ的辅助芯片 1. alpha: 32个中断 2. wildfire: 2048个中断 3. IA-64: 256个中断 4. IA-32、8256A控制器: 16个中断 */ [0 ... NR_IRQS-1] = { .status = IRQ_DISABLED, // 默认屏蔽中断 .chip = &no_irq_chip, // 没有与 chip 相关联 .handle_irq = handle_bad_irq, // 未知(坏的)IRQ 处理程序,输出 IRQ 信息供调试,更新 CPU IRQ 次数计数器,回应 IRQ .depth = 1, // 默认是第一层(没有嵌套中断) .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock), // 还没有自旋锁 } };综上所述,内核中的中断描述符表是一个 irq_desc 数组,数组的每一项描述一根中断线的信息,包括芯片中断处理程序、底层硬件操作函数、注册的中断处理程序链表等中断向量表可以通过 /proc/interrupts 查看
CPU0 CPU1 0: 659558570 0 IO-APIC-edge timer 1: 8 0 IO-APIC-edge i8042 6: 2 0 IO-APIC-edge floppy 8: 0 0 IO-APIC-edge rtc 9: 0 0 IO-APIC-level acpi 12: 63 0 IO-APIC-edge i8042 169: 41216157 0 IO-APIC-level xen-platform-pci 177: 23 0 IO-APIC-level uhci_hcd:usb1 NMI: 0 0 LOC: 659562619 659562537 ERR: 0 MIS: 0在Linux 2.6的内核开发期间,重构出了一个新的通用的IRQ子系统。它能够以统一的方式处理不同的中断控制器和不同类型的中断
1. 高层ISR(high-level interrupt service routines 高层中断服务例程): irq_flow_handler_t handle_irq 针对设备驱动程序端(或其他内核组件)的中断,执行由此引起的所有必要的工作。例如,如果设备使用中断通知一些数据已经到达,那么高层ISR的工作应该是将数据复制到适当的位置 2. 中断电流处理(interrup flow handling): struct irq_chip *chip 处理不同的中断电流类型之间的各种差别,如 1) 边沿触发(edge-triggering): 硬件通过感知线路上的点位差来检测中断 2) 电平触发(level-triggering): 根据特定的电势值检测中断,与电势是否改变无关 http://www.cnblogs.com/LittleHann/p/3865490.html //搜索:struct irq_chip //搜索:struct irqaction 在2.6之后的内核代码中,电流处理的实现已经被极大地简化了,内核代码封装了大量的体系结构相关的代码,并在高层提供了一个几乎可以使用于所有硬件的通用框架 2.1. 设置控制器硬件 内核提供了一系列的标准函数,用于注册irq_chip和设置电流程序 1) extern int set_irq_chip(unsigned int irq, struct irq_chip *chip); 将一个IRQ芯片以irq_chip实例的形式关联到某个特定的中断 2) static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle); 3) static inline void set_irq_chained_handler(unsigned int irq, irq_flow_handler_t handle); 为某个给定的IRQ编号设置电流处理程序 4) extern void set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip, irq_flow_handler_t handle); 5) extern void set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip, irq_flow_handler_t handle, const char *name); 这是一个快捷方式,它相当于连续调用上述的各函数 2.2. 电流处理 1) 边沿触发中断 2) 电平触发中断 3) 其他中断类型 3. 芯片级硬件封装(chip-level hardware encapsultion) 需要与在电子学层次上产生中断的底层硬件直接通信,该抽象层可以视为中断控制器的某种"设备驱动程序"我们继续回到irq_desc[NR_IRQS]这个保存中断处理程序的全局数组上来,用于表示IRQ描述符的结构定义请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html从内核中高层代码的角度来看,每个IRQ都可以由该结构完全描述,事实上,操作系统的每一种机制在背后都一定有一个完善的数据结构提供支持,回到上面的那张IRQ子系统的架构图其中的3个抽象层在该结构(struct irq_desc)中表示如下
1. 电流层ISR: 由handle_irq提供 handler_data可以指向任意数据,该数据可以是特定于IRQ或处理程序的。每当发生中断时,特定于体系结构的代码都会调用handle_irq。该函数负责使用chip中提供的特定于控制器的方法,进行处理中断所必需的一些底层操作 2. action提供了一个操作链: 需要在中断发生时执行 由中断通知的设备驱动程序,可以将与之相关的处理程序函数放置在此处 3. 电流处理和芯片相关操作被封装在chip中 chip_data指向可能与chip相关的任意数据 4. name指定了电流层处理程序的名称,将显示在/proc/interrupts中。对边沿触发中断,通常是"edge",对电平触发中断,通常是"level" 5. delth: 用于确定IRQ电路是启用的还是禁用的 1) 0: 表示启用 2) 正值: 表示禁用 6. irq_count、irq_unhandled字段提供了一些统计量,可用于检测停顿和未处理,但持续发生的中断。即假中断(spurious interrupt) 7. status: 描述了IRQ的当前状态 IRQ不仅可以在处理程序安装期间改变其状态,而且可以在运行时改变,根据status当前的值,内核很容易获知某个IRQ的状态,而无需了解底层实现的硬件相关特性 irq.h中定义了各种表示当前状态的常数,可用于描述IRQ电路当前的状态。每个常数表示位串中的一个置为的标志位(可以同时设置)0x5: 中断的初始化
中断机制的初始化分为三步
1. arch/x86/kernel/head_32.S 中 setup_IDT 2. init/main.c 的 start_kernel() 中的 trap_init() 3. init/main.c 的 start_kernel() 中的 init_IRQ()1. setup_IDT
\linux-2.6.32.63\arch\x86\kernel\head_32.S
setup_idt: lea ignore_int,%edx movl $(__KERNEL_CS << 16),%eax movw %dx,%ax /* selector = 0x0010 = cs */ movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ lea idt_table,%edi mov $256,%ecx rp_sidt: movl