处理器的速度跟外围硬件设备的速度每每再也不一个数量级上,所以,若是内核采起让处理器向硬件发出一个请求。linux
而后专门等待回应的办法,若是专门等待回应,明显太慢。因此等待期间能够处理其余事务,等待完成了请求操做后,再回来进行处理。编程
因此内核提供了一种机制,让内核在须要的时候再向内核发出信号。这就是中断机制。dom
硬件中断能够随时产生,所以,内核随时可能由于新到来的中断而被打断。async
不一样的设备对应的中断不一样,而每一个中断都经过一个惟一的数字标志。ide
这些中断值一般被称为中断请求(IRQ)线。每一个IRQ线都会被关联一个数值量。函数
异常:它产生时必须考虑处理器时钟同步,经常也被称为同步中断。this
在处理器执行到因为编程失误而致使的错误指令的时候,或者实在执行期间出现特殊状况,必须靠内核来处理的时候,处理器就会产生一个异常。spa
在响应要给特定中断的时候,内核会执行要给一个函数,该函数叫作中断处理程序或中断服务例程(ISR)。线程
linux中断程序是普通的C函数,不过必须按照特定的方式声明,以便内核识别。指针
真正的区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于中断上下文的特殊上下文中。
中断上下文偶尔也称为原子上下文,该上下文中的执行代码不可阻塞。
中断程序最好尽快反应,而且执行时间尽量短。
通常把中断程序分红两个部分,中断处理程序是上半部(接收到一个中断,就当即执行,但只作有限的工做) 。
稍后完成的工做会推迟到下半部去,在适合的时机,下半部会被开中断执行。
驱动程序能够经过request_irq()函数注册终端处理程序,它被声明再文件<linux/interrupt.h>中
/* request_irq:分配一条给定的中断线 */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) /* 第一个参数irq表示要分配的中断号 */ /* 第二个参数handler时一个指针,指向处理这个中断的实际中断处理程序 */ /* handler原型 */ typedef irqreturn_t (*irq_handler_t)(int, void *);
第三个参数flags能够为0,再文件<linux/interrupt.h>中定义了掩码
IRQF_DISABLED:意味着内核在处理中断程序自己期间,要禁止全部的其余中断
IRQF_SAMPLE_RANDOM:此标志代表这个设备产生的中断对内核熵池有贡献
IRQF_TIMER:特别为系统定时器的中断处理而准备的
IRQF_SHARED:能够在多个中断的处理程序之间的贡献中断线
第四个参数name是与中断相关的设备的ASCII文本表示,在/proc/irq和/proc/interrupts
第五个参数dev用于共享中断线,当一个中断处理程序释放时,dev将提供标志信息。若是不须要共享中断线,设空(NULL)就行。
request_irq(); if(request_irq(irqm, my_interrupt, IRQF_SHARED, "my_device", my_dev)) { printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn); return -EIO; }
若是调用返回0,则说明成功。必须先初始化程序,而后再注册中断
调用free_irq注销相应的中断处理程序。
void free_irq(unsigned int irq, void *dev)
若是指定的中断线不是共享的,那么函数删除动做的同时会禁用此中断线
若是设置了共享标志,那么函数删除仅仅对dev对应的处理程序,除非删除了最后一个处理程序后,中断线才会被禁用。
/* irq:参数已经没有太大意义了 */ /* dev:一个通用指针,它与中断处理程序注册时传递的必须一致 */ static irqreturn_t intr_handler(int irq, void *dev)
返回值是一个特殊类型:irqreturn_t,可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED
重入和中断处理程序:中断程序是不须要重入的,程序中断时,其余中断会被屏蔽,并且也不会重复嵌套。
共享和非共享处理程序比较类似,可是差别主要有三处:
任何一个设备没有按规则进行共享,那么中断就没法共享了。
在driver/char/rtc.c中有RTC相关的例子。中断发生时,报警器或定时器就会启动。
当RTC驱动程序装载时,rtc_init()会被调用。
/* 对rtc_irq注册rtc_interrupt */ if(request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) { printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); return -EIO; }
第一个参数说明RTC位于IRQ8,中断程序为rtc_interrupt,而且设置了共享中断线,程序名为"rtc"
static irqreturn_t rtc_interrupt(int irq, void *dev) { /* * 能够是报警中孤单、更新完成的中断或周期性中断 * 咱们把装填保存在rtc_irq_data的低字节中, * 而把从最后一次读取以后所接收的中断号保存在其他字节中 */ spin_lock(&rtc_lick); rtc_irq_data += 0x100; rtc_irq_data &= ~0xff; rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); if(rtc_status & RTC_TIMER_ON) mod_timer(&rtc_irq_timer, jiffies + HZ/rtq_freq + 2*HZ/100); spin_unlock(&rtc_lock); /* * 如今执行其他的操做 */ spin_lock(&rtc_task_lock); if(rtc_callback) rtc_callback->func(rtc_callback->private_data); spin_unlock(&rtc_task_lock); wake_up_interruptible(&rtc_wait); kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); return IRQ_HANDLED; }
使用自旋锁,保证不会被其余处理器同时访问。
rtc_irq_data存放RTC有关的信息。
后面部分,则是可能运行预先设置好的回调函数。每一个RTC驱动程序容许注册要给回调函数,在中断到来时执行。
最后返回IRQ_HANDLED,代表已经正确完成对此设备的操做。
当执行一个中断处理程序时,内核处于中断上下文中。
中断上下问和进程没有什么瓜葛。
中断上下文具备较为严格的时间限制,由于它打断了其余代码。
中断处理程序栈的设置是要给配置选项,32位8KB,64位16KB 。
总而言之,尽可能节约内核栈空间。
中断发生,经过电信号传递给处理器,除非屏蔽了此中断,不然处理器会当即执行。
中断旅程开始于预约义入口,每条中断线,处理器都会跳到对应的一个惟一的位置,这样内核就知道接收的IRQ中断号。
unsigned int do_IRQ(struct pt_regs regs)
若是do_IRQ确保在中断线上有一个有效的处理程序,并且程序已经启动。do_IRQ()就调用handle_IRQ_event()来运行为这条
中断线所安装的中断处理程序。在文件kernel/irq/handler.c中
/** * handle_IRQ_event - irq action chain handler * @irq: the interrupt number * @action: the interrupt action chain for this irq * * Handles the action chain of an irq event */ irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { irqreturn_t ret, retval = IRQ_NONE; unsigned int status = 0; if(!(action->flags & IRQF_DISABLED)) local_irq_enable_in_hardirq(); do { trace_irq_handler_entry(irq, action); ret = action->handler(irq, action->dev_id); trace_irq_handler_exit(irq, action, ret); switch(ret) { case IRQ_WAKE_THREAD: /* * 把返回值设置为已处理,以即可疑的检查再也不触发 */ ret = IRQ_HANDLED; /* * 捕获返回值为WAKE_THREAD的驱动程序,可是并不建立一个线程函数 */ if(unlikely(!action->thread_fn)) { warn_no_thread(irq, action); break; } /* * 为此次中断唤醒处理线程。万一线程崩溃且被杀死,咱们仅仅伪装已经处理了该中断。上述的硬件中断(hardirq)处理程序已经进制设备中断,所以杜绝irq产生 */ if(likely(!test_bit(IRQTF_DIED, &action->thread_flags))) { set_bit(IRQTF_RUNTHREAD, &action->thread_flags); wake_up_process(action->thread); } /* Fall through to add to randomness */ case IRQ_HANDLED: status |= action->flags; break; default: break; } retval |= ret; action = action->next; } while (action); if(status & IRQF_SAMPLE_RANDOM) add_interrupt_randomness(irq); local_irq_disable(); return retval; }
chen@chen-K42F:~/Downloads/4412/linux-4.14.12$ cat /proc/interrupts CPU0 CPU1 0: 18 0 IO-APIC 2-edge timer 1: 25244 0 IO-APIC 1-edge i8042 9: 11 12 IO-APIC 9-fasteoi acpi 12: 207590 21487 IO-APIC 12-edge i8042 16: 197581 103440 IO-APIC 16-fasteoi ehci_hcd:usb1 17: 1087739 322896 IO-APIC 17-fasteoi ath9k 19: 0 0 IO-APIC 19-fasteoi i801_smbus 23: 15 26 IO-APIC 23-fasteoi ehci_hcd:usb2 24: 79350 28510 PCI-MSI 327680-edge xhci_hcd
第一列时中断线,第二列时一个接收中断的计数器,第三列时处理这个中断的中断控制器
相关文件在<asm/system.h> <asm/irq.h>中找到
9.1 禁止和激活中断
用于禁止当前处理器上的本地中断,随后再就激活他们的语句。
local_irq_disable(); /* 禁止中断 */ local_irq_enable();
对中断的状态操做以前禁止设备中断的传递,linux提供了四个接口
void disable_irq(unsigned int irq); void disable_irq_nosync(unsigned int irq); void enable_irq(unsigned int irq); void synchronize_irq(unsigned int irq);
一般有必要了解中断系统的状态,或者你当前是否正处于中断上下文的执行状态中。
宏irqs_disable()定义在<asm/system.h>
若是本地处理器上的中断系统被禁止,则返回非0,不然返回0
在<linux/hardirq.h>
local_irq_disable() 禁止本地中断传递
local_irq_enable() 激活本地中断传递
local_irq_save() 保存本地中断传递的当前状态,而后禁止本地中断传递
local_irq_restore() 恢复本地中断传递到给定的状态
disable_irq() 禁止给定中断线,并确保该函数返回以前在该中断线上没有处理程序再运行
disable_irq_nosync() 禁止给定中断线
enable_irq() 激活给定中断线
irqs_disabled() 若是本地中断传递被禁止,则返回非0,不然返回0
in_interrupt() 若是在中断上下文中,则返回非0,若是在进程上下文中,则返回0
in_irq() 若是当前正在执行中断处理程序,则返回非0,不然返回0