现在的位置: 首页 > 综合 > 正文

中断和中断处理程序

2018年01月21日 ⁄ 综合 ⁄ 共 3324字 ⁄ 字号 评论关闭

本文转载:http://blog.chinaunix.net/space.php?uid=20779306&do=blog&id=1845691

在学习网络接口驱动的时候,已经接触过中断和中断处理程序了,这里在具体的说一说有关中断和中断处理程序的相关知识点.
一,中断
中断使得硬件可以与处理器进行通讯.比如说,当敲打键盘时,键盘控制器会发送一个中断,通知os有健按下.
中断本质上来说是一种特殊的电信号,由硬件设备发向处理器.处理器接收到中断后,会马上向os反映此信号的到来,然后就由os负责处理.注意,中断随时都可以产生.也就是说,内核随时都可能因为新到来的中断而被打断.
不同的设备对应不同的中断请求(IRQ).注意,中断号可以动态的进行分配,但是特定的中断总是与特定的设备相关联,并且内核要知道这些信息.比如说,IRQ0是时钟中断,IRQ1是键盘中断.
中断 VS 异常:
异常与中断不同,在产生时必须考虑与处理器始终的同步问题.通常我们也称异常为同步中断.在处理器执行到由于编程失误而导致的错误指令(例如被0除)的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,就会产生一个异常.
两者区别如下:
中断:异常中断,由硬件引起.
异常:同步中断,由软件引起.
注意,我们在上一篇笔记中提到的如何利用软中断来实现系统调用.方法就是先陷入内核,然后引起一种特殊的异常----系统调用处理程序异常,然后调用系统调用处理程序,选择相应的系统调用,执行.

二,中断处理程序
在响应一个特定中断的时候,内核会执行一个函数,该函数就叫做中断处理程序.一个设备的中断处理程序应该是它的设备驱动程序的一部分.
中断处理程序和其他内核函数的真正区别在于:中断处理程序是被内核调用来相应中断的,而它运行在中断上下文中.
因为中断处理程序运行在中断上下文中,所以不允许被抢占,所以执行时间应该越短越好,但有时中断处理程序还需要做很多工作,为了两点都满足,所以一般我们把中断处理切为两个部分:上半部(top half)和下半部(bottom half).
上半部:接收到一个中断,它就立即开始执行,但只做有严格时限的工作.例如对接收的中断进行应答或者复位硬件,这些工作都是在所有中断被禁止的亲光下完成的.
下半部:能够被允许稍后完成的工作会推迟到下半部.这里的"稍后"在时间上强调只要不是现在必须做的就行.
举个top half和bottom half的例子,比如说网卡驱动:
top half:应答硬件,把skb铐到内存,读取网卡更多的数据包.
bottom hafl:处理和操作数据包.

三,注册和注销中断处理程序
注册并激活中断处理程序:
int request_irq( unsigned int irq, irqretrun_t(*handler)(int, void*, struct pt_regs*), unsigned long irqflags,const char* devname, void *dev_id )
第一个参数irq表示要分配的中断号.有些设备的中断号是固定的(时钟或者键盘),而有些设备的中断号可以通过探测获取,或者可以通过变成动态确定.
第二个参数为处理这个中断的实际中断处理程序.注意该函数只接收三个参数,并有一个类型为irqreturn_t的返回值.
第三个参数irqflags为中断号的标识,可以为0,SA_INTERRUPT,SA_SAMPLE_RANDOM,SA_SHIRQ中的一个或者多个组合.各个标志的意义如下:
SA_INTERRUPT:表明该中断处理程序是一个快速中断处理程序.即,在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行.而默认情况下(没有这个标志),除了正在运行的中断处理程序对应的那条中断线被屏蔽外,其他所有中断都是激活的.
SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献.内核熵池负责提供从各种随机事件导出的真正的随机数.
SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线.
第四个参数devname是鱼中断相关的设备ASCII文本表示法.
第五个参数dev_id主要用于共享中断线.因为共享中断线的时候,一个中断线对应多个中断处理程序,所以当其中一个中断处理程序需要注销时,依靠dev_id来表明是哪个设备的中断处理程序.如果无需共享中断线,则此参数置为NULL即可.
注意:request_irq可能会睡眠,所以不能在中断上下文或者其他不允许阻塞的代码中调用该函数.
注销中断处理程序:
当卸载驱动程序的时候,需要注销相应的中断处理程序,并释放中断线.
void free_irq(unsigned int irq,void *dev_id)
如果指定中断线不是共享的,那么该函数删除处理程序的同时将禁用这条中断线.如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用.

四,编写一个中断处理程序
一个典型的中断处理程序声明:
static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)
irq和dev_id和request_irq()中的一致.regs是一个指向结构的指针,该结构包含处中断之前处理器和寄存器的状态.除了调试的时候,很少使用.
该函数的两个返回值:
IRQ_NONE:中断对应的设备与注册时的不符.
IRQ_HANDLED:一切正常.
linux中的中断处理程序时无需重入的.因为默认情况下(没有设置SA_INTERRUPT),当中断处理程序执行时,对应的IRQ被禁止,而其他的中断时打开的.而如果设置了SA_INTERRUPT,则所有中断线都被禁止,更不会重入.
共享的中断处理程序:
共享的中断处理程序和非共享的中断处理程序的差异:
1)request_irq的参数flags必须设置SA_SHIRQ标志.
2)对每个注册的中断处理程序来说,dev_id必须唯一.
3)中断处理程序必须能够区分与它对应的设备是否真的产生了中断.这需要硬件的支持.
内核接收到一个中断后,它将依次调用在该中断线上注册的每一个处理程序.因此,一个处理程序必须知道它是否应该响应这个中断,如果与他相关的设备没有产生中断,则处理程序应该立即退出.

五,中断上下文
在以前的一篇笔记中专门对比了中断上下文和进程上下文的区别,这里再来简单的回忆一下.
进程上下文是一种内核所处的操作模式,此时内核代表进程执行.在此上下文中,可以使用current宏关联当前进程.可以睡眠,也可以调用调度程序.
中断上下文和进程并没有什么关系,所以与current无关,不可以睡眠,不可以调用调度函数.因此,如果一个函数可能睡眠,则就不能在中断处理程序中使用.
注意:中断处理程序打断了其他的代码.正是因为这种异步的特性,所以所有的中断处理程序必须尽可能的迅速,简洁.尽量把工作从中断处理程序中分离出来,放在下半部来执行,因为下半部可以在更合适的时间运行.

六,中断处理机制的实现
在前面的例子中说到键盘产生中断后的简单步骤.这里以键盘为例再来说明一下中断从硬件到内核的路由.
设备产生中断,通过总线把电信号发送给中断控制器.如果中断线是激活的,那么中断控制器就会把中断发往处理器.处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码.这个预定义的位置是由内核设置的,是中断处理程序的入口点.这个过程的图如下:

七,中断控制
linux内核提供了一种组接口用于操作机器上的中断状态.一般来说,控制中断系统的原因归根结底是需要提供同步.通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码.此外,禁止中断还可以禁止内核抢占.
下面是中断控制方法的列表:
注意,禁止和激活中断的时候推荐使用local_irq_save()和local_irq_restore().
而irqs_disabled()很有用:如果内核处于中断上下文中,它返回非0.说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序.
ok,it is over.

抱歉!评论已关闭.