中断是指在CPU正常运行期间,因为内外部事件或由程序预先安排的事件引发的CPU暂时中止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中一般分为外部中断(又叫硬件中断)和内部中断(又叫异常)。node
在实地址模式中,CPU把内存中从0开始的1KB空间做为一个中断向量表。表中的每一项占4个字节。可是在保护模式中,有这4个字节的表项构成的中断向量表不知足实际需求,因而根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由8个字节组成,而中断向量表也叫作了中断描述符表(IDT)。在CPU中增长了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。linux
由上述中判定义可知,系统中断向量表中共可保存256个中断向量入口,即IDT中包含的256个中断描述符(对应256个中断向量)。编程
而0-31号中断向量被intel公司保留用来处理异常事件,不能另做它用。对这 0-31号中断向量,操做系统只需提供异常的处理程序,当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序;而事实 上,对于这32个处理异常的中断向量,2.6版本的 Linux只提供了0-17号中断向量的处理程序,其对应处理程序参见下表、中断向量和异常事件对应表;也就是说,17-31号中断向量是空着未用的。网络
中断向量号 | 异常事件 | Linux的处理程序 |
0 | 除法错误 | Divide_error |
1 | 调试异常 | Debug |
2 | NMI中断 | Nmi |
3 | 单字节,int 3 | Int3 |
4 | 溢出 | Overflow |
5 | 边界监测中断 | Bounds |
6 | 无效操做码 | Invalid_op |
7 | 设备不可用 | Device_not_available |
8 | 双重故障 | Double_fault |
9 | 协处理器段溢出 | Coprocessor_segment_overrun |
10 | 无效TSS | Incalid_tss |
11 | 缺段中断 | Segment_not_present |
12 | 堆栈异常 | Stack_segment |
13 | 通常保护异常 | General_protection |
14 | 页异常 | Page_fault |
15 | (intel保留) | Spurious_interrupt_bug |
16 | 协处理器出错 | Coprocessor_error |
17 | 对齐检查中断 | Alignment_check |
0-31号中断向量已被保留,那么剩下32-255共224个中断向量可用。 这224个中断向量又是怎么分配的呢?2.6版本的Linux中,除了0x80 (SYSCALL_VECTOR)用做系统调用总入口以外,其余都用在外部硬件中断源上,其中包括可编程中断控制器8259A的15个irq;事实上,当 没有定义CONFIG_X86_IO_APIC时,其余223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。数据结构
外部设备当须要操做系统作相关的事情的时候,会产生相应的中断。并发
设备经过相应的中断线向中断控制器发送高电平以产生中断信号,而操做系统则会从中断控制器的状态位取得那根中断线上产生的中断。并且只有在设备在对某一条中断线拥有控制权,才能够向这条中断线上发送信号。也因为如今的外设愈来愈多,中断线又是很宝贵的资源不可能被一一对应。所以在使用中断线前,就得对相应的中断线进行申请。不管采用共享中断方式仍是独占一个中断,申请过程都是先讲全部的中断线进行扫描,得出哪些没有别占用,从其中选择一个做为该设备的IRQ。其次,经过中断申请函数申请相应的IRQ。最后,根据申请结果查看中断是否可以被执行。app
中断中核心处理数据结构为irq_desc,它完整的描述了一条中断线,Linux 2.6。22.6中源码以下。
ide
irq_desc定义在include/linux/irq.h中
函数
/** * struct irq_desc - interrupt descriptor * * @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 * @irqs_unhandled: stats field for spurious unhandled interrupts * @lock: locking for SMP * @affinity: IRQ affinity on SMP * @cpu: cpu index useful for balancing * @pending_mask: pending rebalanced interrupts * @dir: /proc/irq/ procfs entry * @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP * @name: flow handler name for /proc/interrupts output */ struct irq_desc { 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 int irqs_unhandled; spinlock_t lock; #ifdef CONFIG_SMP cpumask_t affinity; unsigned int cpu; #endif #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE) cpumask_t pending_mask; #endif #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif const char *name; } ____cacheline_internodealigned_in_smp;
其相关联的几个结构体以下:spa
定义在include/linux/ interrupt.h中的中断行动结构体:struct irqaction
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
定义在include/linux 中的: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 * * @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); void (*set_affinity)(unsigned int irq, cpumask_t 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); /* 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; };
咱们指望让中断处理程序运行得快,并想让它完成的工做量多,这两个目标相互制约,如何解决——上下半部机制。
咱们把中断处理切为两半。中断处理程序是上半部——接受中断,他就当即开始执行,但只有作严格时限的工做。可以被容许稍后完成的工做会推迟到下半部去,此后,在合适的时机,下半部会被开终端执行。上半部简单快速,执行时禁止一些或者所有中断。
下半部稍后执行,并且执行期间能够响应全部的中断。这种设计可使系统处于中断屏蔽状态的时间尽量的短,以此来提升系统的响应能力。上半部只有中断处理程序机制,而下半部的实现有软中断实现,tasklet实现和工做队列实现。
咱们用网卡来解释一下这两半。当网卡接受到数据包时,通知内核,触发中断,所谓的上半部就是,及时读取数据包到内存,防止由于延迟致使丢失,这是很急迫的工做。读到内存后,对这些数据的处理再也不紧迫,此时内核能够去执行中断前运行的程序,而对网络数据包的处理则交给下半部处理。
1) 若是一个任务对时间很是敏感,将其放在中断处理程序中执行;
2) 若是一个任务和硬件有关,将其放在中断处理程序中执行;
3) 若是一个任务要保证不被其余中断打断,将其放在中断处理程序中执行;
4) 其余全部任务,考虑放置在下半部执行。
软中断做为下半部机制的表明,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了必定的机制)。软中断通常是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是由于要知足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,并且能够在多个CPU上并行执行,使得总的系统效率能够更高。它的特性包括:
a)产生后并非立刻能够执行,必需要等待内核的调度才能执行。软中断不能被本身打断,只能被硬件中断打断(上半部)。
b)能够并发运行在多个CPU上(即便同一类型的也能够)。因此软中断必须设计为可重入的函数(容许多个CPU同时操做),所以也须要使用自旋锁来保护其数据结构。
tasklet是经过软中断实现的,因此它自己也是软中断。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完全部的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,因而限制中断个数为32个。
为了提升中断处理数量,顺道改进处理效率,因而产生了tasklet机制。
Tasklet采用无差异的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet做为一种新机制,显然能够承担更多的优势。正好这时候SMP愈来愈火了,所以又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。所以同一种软中断能够在两个cpu上同时执行,极可能形成冲突。
总结下tasklet的优势:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持SMP机制;
它的特性以下:
1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
2)多个不一样类型的tasklet能够并行在多个CPU上。
3)软中断是静态分配的,在内核编译好以后,就不能改变。但tasklet就灵活许多,能够在运行时改变(好比添加模块时)。
上面咱们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),因而致使了一些问题:软中断不能睡眠、不能阻塞。因为中断上下文出于内核态,没有进程切换,因此若是软中断一旦睡眠或者阻塞,将没法退出这种状态,致使内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必需要运行在进程上下文中,例如访问磁盘数据块的函数。所以,可阻塞函数不能用软中断来实现。可是它们每每又具备可延迟的特性。
上面咱们介绍的可延迟函数运行在中断上下文中,因而致使了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,缘由是因为中断上下文出于内核态,没有进程切换,因此若是软中断一旦睡眠或者阻塞,将没法退出这种状态,致使内核会整个僵死。所以,可阻塞函数不能用软中断来实现。可是它们每每又具备可延迟的特性。并且因为是串行执行,所以只要有一个处理时间较长,则会致使其余中断响应的延迟。为了完成这些不可能完成的任务,因而出现了工做队列,它可以在不一样的进程间切换,以完成不一样的工做。
若是推后执行的任务须要睡眠,那么就选择工做队列,若是不须要睡眠,那么就选择软中断或tasklet。工做队列能运行在进程上下文,它将工做托付给一个内核线程。工做队列说白了就是一组内核线程,做为中断守护线程来使用。多个中断能够放在一个线程中,也能够每一个中断分配一个线程。咱们用结构体workqueue_struct表示工做者线程,工做者线程是用内核线程实现的。而工做者线程是如何执行被推后的工做——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工做,一旦这个工做被执行完,相应的work_struct对象就从链表上移去,当链表上再也不有对象时,工做者线程就会继续休眠。由于工做队列是线程,因此咱们可使用全部能够在线程中使用的方法。
Linux中的软中断和工做队列是中断上下部机制中的下半部实现机制。
1.软中断通常是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,软中断不能被本身打断,只能被硬件中断打断(上半部),能够并发的运行在多个CPU上。因此软中断必须设计成可重入的函数,所以也须要自旋锁来保护其数据结构。
2.工做队列中的函数处在进程上下文中,它能够睡眠,也能被阻塞,可以在不一样的进程间切换,以完成不一样的工做。
可延迟函数和工做队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工做队列的函数有内核进程执行,他不能访问用户空间地址。