LDD3第十章的学习-中断处理

作者:Aningsk ,本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

 

今天端午节来写一下Linux驱动里的中断处理吧,感觉内容蛮多的啊,慢慢写吧;反正闲着也是闲着。

 

什么是中断

书中对“中断”含义的描述挺贴切的,所以在这里提一下:一个“中断”仅仅是一个信号,当硬件需要获得处理器对它的关注时,就可以发送这个信号。也就是硬件设备求关注求互粉啦~我们在驱动中要做的就是描述硬件在它的中断里要做什么事情,并且把这个处理过程告诉内核(因为内核管理着所有的东西);那么当内核察觉到有中断到来时,就会对应地调用我们制定的处理过程。

 

安装中断处理例程

内核维护了一个中断信号线的注册表,我觉得这个表我们不用太关心,我们关心的是如何在这个表中注册(或说申请)我们要用的中断通道——也就是中断号(书中也写为“中断信号线”)。中断号与中断处理函数是一一对应的,内核会根据CPU收到的中断号来调用对应的中断处理函数。那么,问题来了:我怎么知道我的硬件是对应哪个中断号?书中264页讨论了这个问题,一个方法是根据硬件的不同,来判断使用哪个中断号,书里是根据short设备不同的IO内存地址使用不同的中断号。另外的方法就是探测硬件:先不管别的,让你中断一下,看看CPU哪一个中断号有动作,那你就是对应了那个中断号了;然后我就用这个中断号注册处理函数啦。这个方法在265页有描述。不过,对于我目前接触的嵌入式系统里,中断号往往都是写死的;因为嵌入式的硬件在设计完成后,就不会轻易改变了——总不能用户哪天自己玩HIGH了,自己把PCB上的芯片焊下来装上另外不同的芯片吧。CPU、外接的硬件以及它们之间的引脚链接,都是确定不会改变的。那么,我们就可以在软件中把中断号写死。“你怎么知道写死哪一个中断号?”看看CPU的说明书,它会告诉你哪个引脚或外设的中断号是多少的。

那我们回过头来看看怎么注册中断处理函数。在259页有说明:注册使用request_irq(),释放使用free_irq()。在注册时,制定了中断处理函数的指针。中断处理函数不是随便定义的,它有它确定的函数类型(当然函数名字没有硬性的要求,但还是看一眼就能明白的好)。另外,硬件常常有开启/关闭中断的功能,在注册成功后,要记得开启这个硬件的中断。

 

实现中断处理例程

中断处理函数也是普通的函数,但是因为它的上下文的特殊,它的行为要受到一些限制(与内核定时器类似 第七章)。中断处理函数一定是在中断时间内被调用的,它不在任何进程的上下文中执行。1.处理例程不能向用户空间发送或者接收数据。2.处理例程也不能做任何可能发生休眠的操作。3.处理例程不能调用schdule函数。

中断处理例程的功能就是将有关中断的接收的信息反馈给设备,并根据正在服务的中断的不同含义对数据进行相应的读写。(这句是照着书上抄的。)大多数硬件设备在中断挂起位被清除之前不会产生其他的中断,我们常常在中断处理函数的最后清除掉这个标记位,当然有的硬件没有这个标记位。在270页的代码展示了中断处理函数的一个典型任务:如果中断通知进程所等待的事件已经发生,比如新的数据到达,就会唤醒在该设备上休眠的进程。

另外插播一段:在271页的代码,写函数里实现了交替写入0x00与0xFF,只用了两行来实现,好厉害。8位二进制数乘以0xFF,可以发现,其结果的低8位是原二进制数的补码,即取反加1。

中断处理函数有3个参数,其中一个是void *dev_id,这个也是在申请中断的时候传入的。它可以用来指向驱动程序自己的私有数据区(用来识别哪个设备产生了中断),如果这个中断号没有被多个设备驱动共享,这个参数可以为NULL。

至于返回值,如果处理例程发现其设备的确需要处理,则应该返回IRQ_HANDLED;否则,返回值应该是IRQ_NONE。如果设备无法告诉我们是否被真正中断,则应该返回IRQ_HANDLED。

 

顶半部和底半部

中断所要完成的任务常常需要快速完成,因为毕竟中断了CPU本来正在忙活的任务,中断事件过长会影响系统性能;但是,这个任务本身经常有很多事情要做,会消耗很多的时间。解决的方法就是将整个中断的任务分为两部分:一部分用于处理硬件的中断事件,快速处理系统相关的中断任务并返回;另一部分用于实际完成这个中断对于设备而言的任务,这也是耗时很长的那一部分。第一部分叫做顶半部,第二部分叫做底半部。

顶半部是实际响应中断的程序,也就是用request_irq注册的中断例程。底半部是一个会被顶半部调度,在稍后更加安全的时间内执行的程序。当底半部执行的时候,所有的中断都是打开的——这就是在更加安全的时间内运行。典型的情况是顶半部保存设备的数据到一个设备的特定缓冲区并调度它的底半部,然后退出:这个操作是非常快的。然后,底半部执行其他必要的工作,例如唤醒进程、启动另外的IO操作等等。这种方式允许在底半部工作期间,顶半部还可以继续为新的的中断服务。

Linux提供了两种机制来实现底半部的特性:tasklet和工作队列。在第七章已经说到过他们,虽然我已经忘了我当时写的啥了……tasklet是较常使用的,因为这种机制非常快,但所有的tasklet代码必须是原子的。工作队列具有较高的延迟,但它可以休眠。

对于tasklet要知道就是,对tasklet的调度不会累计,意思是:在这个tasklet被真正调用前,即使存在多次对这个tasklet的调度请求,当这个tasklet真正运行时,它也只会运行一次。在书中276页的例子中可以看到如何处理这种情况:在中断顶半部中将数据依次记录到一个空间中,在底半部,则是使用循环,直到把那个空间里的数据都处理完。

使用工作队列与使用tasklet基本相似。因为工作队列运行在进程上下文中,所以它可以必要的时候休眠。但是我们不能在工作队列里向用户空间复制数据,书里还说“除非使用将在第十五章中描述的高级技术”,我还没看到那里,不知道是什么样的高级技术。

 

中断驱动的IO

书中在这里讲了一些硬件读写需要靠中断实现时,驱动代码的操作技巧,我在这里只是简单写一下我的理解。

如果与驱动程序管理的硬件之间的数据传输,可能会因为某种原因而被延迟,那么我们在驱动中就应该实现缓冲。数据缓冲区,有助于将数据的传送和接收与系统调用write和read分离开,从而提高系统整体性能。也就说,当系统调用write和read调下来时,它们操作的是数据缓冲区里的内容,并没有直接与硬件发生数据读写操作;而真正的硬件层面上的数据读写操作,是在硬件中断处理过程中完成的,中断处理程序将数据读写到数据缓冲区,以备系统调用使用。这样,数据缓冲区隔离了系统调用和真正的硬件读写,提高了系统响应效率:要使用write或read的时候,我们总不能在原地默默地等待硬件中断到来才完成操作——但它依然要等待数据缓冲区里的数据可读,或空间可写——,效率就是体现在这里。

书中给的例子是向并口打印机写入数据,系统调用write调用到驱动,驱动会实现休眠等待数据缓冲区可写的功能;可写时将会把数据放入数据缓冲区里,并拉起一个工作队列;在这个工作队列的工作函数里,会将数据缓冲区里数据实际写入硬件。这个工作函数实现的只是将一个字节写入,可能是打印机的单次接收只能是一个字节吧。工作函数在将一个字节数据发送后,会判断是否唤醒等待写入缓冲区的进程,但不是一定去唤醒,而是等到缓冲区的空闲空间超过某个大小后再去唤醒的。

另外要记录的就是,这个驱动里实现了对可能发生的丢失中断的情况的处理,这里说的“中断”是指并口打印机在可以接收数据时发出的中断,告诉CPU可以发下一个字节了。在拉起那个工作队列前,我们设置了一个定时器;然后在实际向硬件写入数据时,我们重置这个定时器,也就是说:现在正在顺利进行write调用,或正确响应了来自设备的中断,没有出现丢失中断的情况。在定时器的超时处理中,首先判断是否存在向设备输出的动作,没有输出动作就直接返回;然后看看设备是不是在忙,如果是忙的话,那我们也要返回,意思是:打印机在忙于处理,CPU这边的定时器自然可能超时,我们不用管它,让打印机先自己忙着。如果不忙,那么我们必须手动调用运行中断处理函数(函数里拉起了那个写入设备的工作队列),因为运行到这里,意味着:定时器超时、又存在写入动作、打印机又不忙,这说明我们的确丢失了打印机发来的中断,我们必须手工调用中断处理函数,来补上丢失的中断。

好吧,再理一下思路:我们write写入了一堆数据到数据缓冲区,然后拉起了工作队列,工作处理函数将一个字节输出到打印机;打印机忙完后,发一个中断给CPU表示可以再收数据了;我们收到了中断,中断处理函数里再次拉起那个工作队列,工作函数再次向设备写入一个字节。如果我们丢失了中断,数据缓冲区里的内容将不会继续向设备写入了,而此时数据缓冲区并没有足够的空间,所以如果我们再次调用write,就不能向数据缓冲区写入内容了,也就是说系统挂掉咯。所以,我们调用write时,加了一个定时器,在工作处理函数里重置定时器,如果丢失中断就不能重置定时器,但我们加的超时函数能够主动调用中断处理函数,就能够再次拉起工作队列,补上了丢失的中断,一切就又变得如此美好了。当缓冲区的数据都发完了,工作队列函数会删掉那个定时器,我们此次write调用才算最终完成。

 

好啦,就到这里了,写的过程中有中断过,嗯嗯,因为端午节和妹子出去玩呢~嘿嘿。

 

Aningsk

2015-06-21

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值