人生若只如初见,何事秋风悲画扇。
--------纳兰容若 《木兰花令·拟古决绝词》
一、什么是中断:
linux管理所有的硬件设备,要做的第一件事先是通信。然而CPU的处理速度要远快于外围设备,总不能让CPU一直在等待外围设备吧!为了提高效率,
轮询(polling)可能就是一种解决方法,但是缺点是操作系统要做太多的无用功。
所以引入了中断的概念, 中断
本质上是一种特殊的电信号,由硬件设备发向处理器,处理器接收到中断后,会马上向操作系统反应此信号的带来,然后就由OS负责处理这些新到来的数据,中断可以随时发生,才不用操心与处理器的时间同步问题。不同的设备对应的中断不同,他们之间的不同从操作系统级来看,差别就在于一个数字标识-----中断号。专业一点就叫中断请求(IRQ)线,通常IRQ都是一些数值量。有些体系结构上,中断号是固定的,有的是动态分配的,这不是问题所在,问题在于特定的中断总是与特定的设备相关联,并且内核要知道这些信息。
中断从硬件到内核的路由过程:
二、轮询和中断的区别:
轮询和中断是有区别的。轮询需要一直去访问CPU来查看是否有任务需要执行,而中断只需要在有信号传达过来时候才发生响应。轮询就好比你在家里待着看NBA,你需要不断查看门外有没有访客,这要就很蛋疼了,不能让人好好看球赛了。那么中断就不用这么麻烦,你只需要在有人敲门的时候去开门就可以了,是不是方便的多。
三、中断与异常:
异常和中断不一样,它在产生时必须要考虑与处理器的时钟同步,实际上,异常也常常称为同步中断,在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况,必须要靠内核来处理的时候,处理器就会产生一个异常。因为许多处理器体系结构处理异常以及处理中断的方式类似,因此,内核对它们的处理也很类似。这里的讨论,大部分都是适合异常,这时可以看成是处理器本身产生的中断。
四、中断处理程序:
1.
中断处理程序是和特定中断相关联的,而不是和设备相关联,如果一个设备可以产生很多中断,这时该设备的驱动程序也就需要准备多个这样的函数。一个中断处理程序是设备驱动程序的一部分。
中断处理程序是无需重入的,当一个给定的中断处理程序正在执行时,
相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一个中断上接收另外一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断总是被禁止的。由此可见,
同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。
int request_irq (unsigned int irq, //
终端号
irqreturn_t (*handler)(int, void *,struct pt_regs *), //
中断处理函数
unsigned long irqflags, //
中断处理的属性
const char * devname, //
设备名
void *dev_id) //
设备号
irq:是要申请的硬件
中断号。
handler :是向系统注册的
中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,
dev_id参数将被传递给它。
irqflags :是中断处理的属性:
- 若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;
- 若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断。
- 若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可 以通过或的方式同时使用的)
devname 设置中断名称,通常是设备驱动程序的名称 在
cat /proc/interrupts中可以看到此名称。
dev_id 在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
request_irq() 返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回
-EBUSY表示中断已经被占用且不能共享。
2、返回值:
中断处理程序的返回值是一个特殊类型,request_irq,可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED.
- 当中断处理程序检测到一个中断时,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE
- 当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回IRQ_HANDLED
我们也可以使用宏IRQ_RETVAL(x),如果x非0值,那么该宏返回IRQ_HANDLED,否则,返回IRQ_NONE
3、释放中断处理程序:
void free_irq(unsigned int irq, void *dev_id)
四、/proc/interrupts文件:
这个文件存放的是系统中与中断相关的统计信息。
第一列是中断线(中断号),第二列是一个接收中断数目的计数器,第三列是处理这个中断的中断控制器,最后一列是与这个中断有关的设备名字,这个名字是通过参数devname提供给函数request_irq()的。最后,如果中断是共享的,则这条中断线上注册的所有设备都会列出来,如17号中断。
五、禁止中断:
Linux内核给我们提供了一组接口能够让我们控制机器上的中断状态,这些接口可以在<asm/system.h>和<asm/irq.h>中找到。一般来说,控制中断系统的原因在于需要提供同步,通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码。此外,禁止中断还可以禁止内核抢占。然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问。Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自其他处理器对共享数据的并发访问,获取这些锁的同时也伴随着禁止本地中断。锁提供保护机制,防止来自其他处理器的并发访问,而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问。
local_irq_disable(); 禁止本地中断
local_irq_enable(); 使能本地中断
若调用用
local_irq_disable();已经禁止了本地中断,那么可能会存在危险 。同理,若调用 local_irq_enable(); 已经使能了,也会有问题,
因为它将无条件的激活中断。所以,
我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。
unsigned long flags;
local_irq_save(flags);
local_irq_save(flags);
local_irq_restore(flags);
使用如上机制,在禁止中断之前保存中断系统的状态会更加安全一些。相反,在准备激活中断时,只需把中断恢复到它们原来的状态。
对local_irq_save()的调用和local_irq_restore()的调用必须在同一个函数中进行。
===============================================================================================
禁止系统中一条特定的中断(屏蔽掉一条中断线)接口:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronise_irq(unsigned int irq);
另外,我们也可以通过宏定义在<asm/system.h>中的宏
irqs_disable()来获取中断的状态,如果中断系统被禁止,则它返回非0,否则,返回0;用定义在<asm/hardirq.h>中的两个宏in_interrupt()和in_irq()来检查内核的当前上下文的接口。