Linux 中断实现

我们知道,在计算机系统中,cpu要与外界设备进行交互,是靠两种基本方式来进行:中断和轮询。中断是指外界设备在有事件发生的情况下,发送硬件信号给CPU,cpu检测到这个硬件信号以后,停止当前手头上的工作(比如正在运行某个进程),转去处理这个硬件信号。有个经典的例子,就是边烧开水,忽然有人敲门,呵呵,我们总是先开门再继续等。其实中断可以理解为一种硬件机制,而linux为了很好的利用这种机制,便设计了一套最优(权衡了很多方面)策略来配合硬件,这就组成了所谓的linux中断系统。
 
下面重点区分几个概念。
1,硬件中断:其实就是我们所认为的中断,从接触微机原理,51时,所认识的中断。
   软件中断和软中断(请允许我这么称呼,因为很多论坛都是这样的),这是两个完全不相干的概念。软件中断是计算机本身的一种异常(在arm中是这样的),在arm中,SWI指令引起swi异常,处理器切换到管理模式。在linux中,当程序执行系统调用时,便是执行了这条指令,然后陷入内核态,这便是所谓的进程上下文。当然在x86中也有相应的机制,我以前看过现在记不清了,好像是什么int 80之类,大家就当没说。
 
   软中断,在ldd中就提的比较多了,它是中断延迟执行的一种方法,即实现中断下半部的一种方法,tasklet是基于软中断实现的,(另一种是工作队列)。具体的到介绍中断系统是详细说。
 
2 快速中断,慢速中断,上下半部一起讲,之所以这样,是因为最初学习时感觉ldd中对它们的介绍似乎有点矛盾,请大家一起分析。
 
首先快速和慢速中断是否对应arm中的fiq和irq呢?这是我看到这个概念是最先想到的问题。答案是否定的。没有任何的联系。linux压根没有处理fiq。
 
ldd中这样写道:当慢速中断正在执行时,慢速中断要求处理器可以再次启用中断。在现在内核中,区别已不大,只剩一个:快速中断执行时,当前处理器其他所有中断都被禁止。先讲到这里。
 
ldd中关于顶半部和底半部是这样描述的:它们最大的不同是:底半部执行时,所有中断时打开的,也就是说顶半部(request_irq注册中断例程)执行时,关闭所有中断,不可打断。
 
以上,还好理解。我还有一个问题,那快速中断和慢速中断的划分依据是顶半部还是底半部,或者是中断处理的整个过程(包括底半部和顶半部)??很多地方在逃避这个问题,可以好好思考下里面的逻辑。首先慢速中断时,中断可以启用,但是上半部是不容打断的,呵呵,有点乱。
 
还是直接说说自己想法(紧供参考):快慢中断的区分是针对整个中断处理过程的(包括上下半部),而且可以保证,不论快速慢速中断,它们的上半部都是不可中断的,都是原子的。在很久很久以前,快速中断的下半部也是不可中断的,这样执行会快点。但随着硬件水平的提高,速度差别以不大,中断处理程序基本都是慢中断了,他们的下半部是可以中断的。
 
还得说一个问题:如果一个设备中断处理正在进行,不管是上半部还是下半部,只要没处理完,这一期间同一设备(很关键)产生的中断都是被忽略的,即中断时不可重入的。(和上面说的并不矛盾,不可重入可理解为同一中断处理程序不能并行)。
 
太乱了,到此为止吧,欢迎讨论。(以上顶半部和上半部是指的同一概念,写的过程中没注意,呵呵)。

与Linux设备驱动中中断处理相关的首先是申请与释放IRQ的API request_irq()和free_irq(),

request_irq()的原型为:

int request_irq(unsigned int irq,

void (*handler)(int irq, void *dev_id, struct pt_regs *regs),

unsigned long irqflags,

const char * devname,

void *dev_id);

.irq是要申请的硬件中断号。在Intel平台,范围0--15。
.handler是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。
.irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。
.
devname 是一个字符串,与该中断相关联的名称,在/proc/interrupt中可看到
.dev_id在中断共享时会用到。一般设置为这个设备的 device结构本身或者NULL,但在注册共享中断时,此参数不能为NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map找到中断对应的设备。 

free_irq()的原型为:

void free_irq(unsigned int irq,void *dev_id);

另外,与Linux中断息息相关的一个重要概念是Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部 执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完 成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的 中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

原理解析:

1、 中断概念

为什么需要中断?

1)外设的处理速度一般慢于CPU

2)CPU不能一直等待外部事件

所以设备必须有一种方法来通知CPU它的工作进度,这种方法就是中断。

2、 中断实现

在Linux驱动程序中,为设备实现一个中断包含两个步骤:

1)向内核注册中断

2)实现中断处理函数

3、 中断注册

request_irq用于实现中断的注册功能:

int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs *), unsigned long flags, const char *devname, void *dev_id)

返回0表示成功,或者返回一个错误码

中断注册(参数)

1) unsigned int irq 中断号。

2)void (*handler)(int,void *,struct pt_regs *) 中断处理函数。

3)unsigned long flags 与中断管理有关的各种选项。

4)const char * devname 设备名

5)void *dev_id 共享中断时使用。

a)中断注册(中断标志)

在flags参数中,可以选择一些与中断管理有关的选项,如:

1)IRQF_DISABLED(SA_INTERRUPT)

如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。

2)IRQF_SHARED(SA_SHIRQ)

该位表明中断可以在设备间共享。

b)快速/慢速中断

这两种类型的中断处理程序的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。

c) 共享中断

共享中断就是将不同的设备挂到同一个中断信号线上。Linux对共享的支持主要是为PCI设备服务。共享中断也是通过request_irq函数来注册的,但有三个特别之处:

1)申请共享中断时,必须在flags参数中指定 IRQF_SHARED位

2)dev_id参数必须是唯一的。

3)共享中断的处理程序中,不能使用disable_irq(unsigned int irq) 为什么? 如果使用了这个函数,共享中断信号线的其它设备将同样无法使用中断,也就无法正常工作了。

4、 中断处理程序

什么是中断处理程序,有何特别之处?中断处理程序就是普通的C代码。特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:

1) 不能向用户空间发送或接受数据

2) 不能使用可能引起阻塞的函数

3) 不能使用可能引起调度的函数

5、 中断处理函数流程

void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{/* 判断是否是本设备产生了中断(为什么要做这样的检测?) */

value = inb(short_base);

if (!(value & 0x80)) return;/* 清除中断位(如果设备支持自动清除,则不需要这步) */

outb(value & 0x7F, short_base);/* 中断处理,通常是数据接收 */

。。。。。。。。。/* 唤醒等待数据的进程 */

ake_up_interruptible(&short_queue);

6、 释放中断

当设备不再需要使用中断时(通常在驱动卸载时), 应当把它们返还给系统,使用:void free_irq(unsigned int irq, void *dev_id)

中断处理(下半部)--推后处理的部分

Linux中断下半部处理有三种方式:软中断、tasklet、工作队列。

一、最简单的中断机制

最简单的中断机制就是像芯片手册上讲的那样,在中断向量表中填入跳转到对应处理函数的指令,然后在处理函数中实现需要的功能。类似下图:

这种方式在原来的单片机课程中常常用到,一些简单的单片机系统也是这样用。
它的好处很明显,简单,直接。

二、下半部

中断处理函数所作的第一件事情是什么?答案是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即,破坏了 interrupt context。

随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。中断接收在屏蔽中断的情况下完成;中断处理在时能中断的情况下完成,这部分被称为中断下半部。

从上图中看,只看int0的处理。Func0为中断接收函数。中断只能简单的触发func0,而func0则能做更多的事情,它与funcA之间可以使用队列等缓存机制。当又有中断发生时,func0被触发,然后发送一个中断请求到缓存队列,然后让funcA去处理。
由于func0做的事情是很简单的,所以不会影响int0的再次接收。而且在func0返回时就会使能int0,因此funcA执行时间再长也不会影响int0的接收。

三、软中断

下面看看linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。

我们不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。

四、tasklet

旧事物跟不上历史的发展时,总会有新事物出现。随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

  • Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。
  • CDMA因为频谱重叠问题,必须降功率。而功率降低后,又发现原来功率低了有助于环保。
  • Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只
  • 在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种中断可以在两个cpu上同时执行,很可能造成冲突。

总结下tasklet的优点:

(1)无类型数量限制;

(2)效率高,无需循环查表;

(3)支持SMP机制;

五、工作队列

前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。什么意思?说明它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。工作队列对线程作了封装,使用起来更方便。
因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。

假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。

为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。

问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。



如下为原创   关于中断是否嵌套(中断被另一个中断打断)?

感觉外设速度比CPU速度慢那么多
其实感觉上半部打断不打断区别不大,(小白,自己的理解)。。
可能嵌套是发生在软中断部分?就是下半部,那个时候如果REQUEST_IRQ没有置SA_INTERUPT的话,下半部是默认打开中断的,就可以抢占了,再恢复回来的话,因为之前上半部登记了之前那个中断的大部分信息,难道是大不了重新再执行一次中断?  个人猜测。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值