ldd3学习之七:中断处理
一个“中断”仅是一个信号,当硬件需要获得处理器对它的关注时,就可以发送这个信号。 Linux 处理中断的方式非常类似在用户空间处理信号的方式。大多数情况下,一个驱动只需要为它的设备的中断注册一个处理例程,并当中断到来时进行正确的处理。本质上来讲,中断处理例程和其他的代码并行运行。因此,它们不可避免地引起并发问题,并竞争数据结构和硬件。 透彻地理解并发控制技术对中断来讲非常重要。
1.安装中断处理例程
内核维护了一个中断信号线的注册表,类似于 I/O 端口的注册表。模块在使用中断前要先请求一个中断通道(或者 IRQ中断请求),并在使用后释放它。所用的函数声明在 (在此文件中并未真正包含,是通过它include的文件间接包含的,函数在/kernel/irq/Manage.h中),中断注册和释放的函数接口如下:
int request_irq(unsigned int irq, const char *dev_name, void free_irq(unsigned int irq, void *dev_id); |
request_irq 的返回值: 0 指示成功,或返回一个负的错误码,如 -EBUSY 表示另一个驱动已经占用了你所请求的中断线。
函数的参数如下:
中断处理例程可在驱动初始化时或在设备第一次打开时安装。推荐在设备第一次打开、硬件被告知产生中断前时申请中断,因为可以共享有限的中断资源。这样调用 free_irq 的位置是设备最后一次被关闭、硬件被告知不用再中断处理器之后。但这种方式的缺点是必须为每个设备维护一个打开计数。
以下是中断申请的示例(并口):
if (short_irq >= 0) short_irq = -1; |
i386 和 x86_64 体系定义了一个函数来查询一个中断线是否可用:
int can_request_irq(unsigned int irq, unsigned long flags); /*当能够成功分配给定中断,则返回非零值。但注意,在 can_request_irq 和 request_irq 的调用之间给定中断可能被占用*/ |
快速和慢速处理例程
快速中断是那些能够很快处理的中断,而处理慢速中断会花费更长的时间。在处理慢速中断时处理器重新使能中断,避免快速中断被延时过长。在现代内核中,快速和慢速中断的区别已经消失,剩下的只有一个:快速中断(使用 SA_INTERRUPT )执行时禁止所有在当前处理器上的其他中断。注意:其他的处理器仍然能够处理中断。
除非你充足的理由在禁止其他中断情况下来运行中断处理例程,否则不应当使用SA_INTERRUPT.
x86中断处理内幕
这个描述是从 2.6 内核 arch/i386/kernel/irq.c, arch/i386/kernel/ apic.c, arch/i386/kernel/entry.S, arch/i386/kernel/i8259.c, 和 include/asm-i386/hw_irq.h 中得出,尽管基本概念相同,硬件细节与其他平台上不同。
底层中断处理代码在汇编语言文件 entry.S。在所有情况下,这个代码将中断号压栈并且跳转到一个公共段,公共段会调用 do_IRQ(在 irq.c 中定义)。do_IRQ 做的第一件事是应答中断以便中断控制器能够继续其他事情。它接着获取给定 IRQ 号的一个自旋锁,阻止其他 CPU 处理这个 IRQ,然后清除几个状态位(包括IRQ_WAITING )然后查找这个 IRQ 的处理例程。若没有找到,什么也不做;释放自旋锁,处理任何待处理的软件中断,最后 do_IRQ 返回。从中断中返回的最后一件事可能是一次处理器的重新调度。
IRQ的探测是通过为每个缺乏处理例程的IRQ设置 IRQ_WAITING 状态位来完成。当中断发生, 因为没有注册处理例程,do_IRQ 清除这个位并且接着返回。 当probe_irq_off被一个函数调用,只需搜索没有设置 IRQ_WAITING 的 IRQ。
/proc 接口
当硬件中断到达处理器时, 内核提供的一个内部计数器会递增,产生的中断报告显示在文件 /proc/interrupts中。这一方法可以用来检查设备是否按预期地工作。此文件只显示当前已安装处理例程的中断的计数。若以前request_irq的一个中断,现在已经free_irq了,那么就不会显示在这个文件中,但是它可以显示终端共享的情况。
/proc/stat记录了几个关于系统活动的底层统计信息, 包括(但不仅限于)自系统启动以来收到的中断数。 stat 的每一行以一个字符串开始, 是该行的关键词:intr 标志是中断计数。第一个数是所有中断的总数, 而其他每一个代表一个单独的中断线的计数, 从中断 0 开始(包括当前没有安装处理例程的中断),无法显示终端共享的情况。
以上两个文件的一个不同是:/proc/interrupts几乎不依赖体系,而/proc/stat的字段数依赖内核下的硬件中断,其定义在中。ARM的定义为:
#define NR_IRQS 128 |
自动检测 IRQ 号
驱动初始化时最迫切的问题之一是决定设备要使用的IRQ 线,驱动需要信息来正确安装处理例程。自动检测中断号对驱动的可用性来说是一个基本需求。有时自动探测依赖一些设备具有的默认特性,以下是典型的并口中断探测程序:
if (short_irq < 0) /* 没有定义端口号,用默认方法,自动检测,确定中断*/ |
有的驱动允许用户在加载时覆盖默认值:
insmod xxxxx.ko irq=x |
当目标设备有能力告知驱动它要使用的中断号时,自动探测中断号只是意味着探测设备,无需做额外的工作探测中断。
但不是每个设备都对程序员友好,对于他们还是需要一些探测工作。这个工作技术上非常简单: 驱动告知设备产生中断并且观察发生了什么。如果一切顺利,则只有一个中断信号线被激活。尽管探测在理论上简单,但实现可能不简单。有 2 种方法来进行探测中断: 调用内核定义的辅助函数和DIY探测。
(1)调用内核定义的辅助函数
Linux 内核提供了一个底层设施来探测中断号,且只能在非共享中断模式下工作,它包括 2 个函数, 在中声明( 也描述了探测机制 ):
unsigned long probe_irq_on(void); int probe_irq_off(unsigned long); |
程序员应当注意在调用 probe_irq_on 之后启用设备上的中断, 并在调用 probe_irq_off 前禁用。此外还必须记住在 probe_irq_off 之后服务设备中待处理的中断。
以下是LDD3中的并口示例代码,(并口的管脚 9 和 10 连接在一起,探测五次失败后放弃):
int count = 0; if (short_irq == 0) { /* none of them? */ |
最好只在模块初始化时探测中断线一次。
大部分体系定义了这两个函数( 即便是空的 )来简化设备驱动的移植。
(2)DIY探测
DIY探测与前面原理相同: 使能所有未使用的中断, 接着等待并观察发生什么。我们对设备的了解:通常一个设备能够使用3或4个IRQ 号中的一个来进行配置,只探测这些 IRQ 号使我们能不必测试所有可能的中断就探测到正确的IRQ 号。
下面的LDD3中的代码通过测试所有"可能的"中断并且察看发生的事情来探测中断。 trials 数组列出要尝试的中断, 以 0 作为结尾标志; tried 数组用来跟踪哪个中断号已经被这个驱动注册。
int trials[] = {3, 5, 7, 9, 0}; for (i = 0; trials[i]; i++) /* end of loop, uninstall the handler */ if (short_irq < 0) |
以下是handler的源码:
irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs) |
若事先不知道"可能的" IRQ ,就需要探测所有空闲的中断,所以不得不从 IRQ 0 探测到 IRQ NR_IRQS-1 。
处理例程的参数及返回值
传递给一个中断处理例程的参数有: int irq、void *dev_id和 struct pt_regs *regs。
int irq (中断号):若要打印 log 消息时,是很有用。
void *dev_id:一种用户数据类型(驱动程序可用的私有数据),传递给 request_irq的 void* 参数,会在中断发生时作为参数传给处理例程。我们通常传递一个指向设备数据结构的指针到 dev_id 中,这样一个管理若干相同设备的驱动在中断处理例程中不需要任何额外的代码,就可以找出哪个设备产生了当前的中断事件。
struct pt_regs *regs很少用到。
中断处理例程的典型使用如下:
static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
和这个处理例程关联的打开代码如下:
static void sample_open(struct inode *inode, struct file *filp) |
中断处理例程应当返回一个值指示是否真正处理了一个中断。如果处理例程发现设备确实需要处理, 应当返回 IRQ_HANDLED; 否则返回值 IRQ_NONE。以下宏可产生返回值:
IRQ_RETVAL(handled) /*若要处理中断,handled应是非零*/ |
有位网友在处理返回值是按惯例return 0;,导致了oops。吸取经验教训,我们应特别注意这种返回值,以下是有关中断处理例程的返回值的内核定义(#include ),看了就知道导致oops的原因了,以后应多多注意:
typedef int irqreturn_t; |
实现中断处理例程
中断处理例程唯一的特别之处在中断时运行,它能做的事情受到了一些限制. 这些限制与我们在内核定时器上看到的相同:
(1)中断处理例程不能与用户空间传递数据, 因为它不在进程上下文执行;
(2)中断处理例程也不能做任何可能休眠的事情, 例如调用 wait_event, 使用除 GFP_ATOMIC 之外任何东西来分配内存, 或者锁住一个信号量;
(3)处理者不能调用schedule()。
中断处理例程的作用是:将关于中断接收的信息反馈给设备并根据被服务的中断的含义读、写数据。中断处理例程第一步常常包括清除设备的一个中断标志位,大部分硬件设备在清除"中断挂起"位前不会再产生中断。这也要根据硬件的工作原理决定, 这一步也可能需要在最后做而不是开始; 这里没有通用的规则。一些设备不需要这步, 因为它们没有一个"中断挂起"位; 这样的设备是少数。
一个中断处理的典型任务是:如果中断通知它所等待的事件已经发生(例如新数据到达),就会唤醒休眠在设备上的进程。
不管是快速或慢速处理例程,程序员应编写执行时间尽可能短的处理例程。 如果需要进行长时间计算, 最好的方法是使用 tasklet 或者 workqueue 在一个更安全的时间来调度计算任务。
启用和禁止中断
有时设备驱动必须在一段时间(希望较短)内阻塞中断发生。并必须在持有一个自旋锁时阻塞中断,以避免死锁系统。注意:应尽量少禁止中断,即使是在设备驱动中,且这个技术不应当用于驱动中的互斥机制。
有时(但是很少!)一个驱动需要禁止一个特定中断。但不推荐这样做,特别是不能禁止共享中断(在现代系统中, 共享的中断是很常见的)。内核提供了 3 个函数,是内核 API 的一部分,声明在 :
void disable_irq(int irq);/*禁止给定的中断, 并等待当前的中断处理例程结束。如果调用 disable_irq 的线程持有任何中断处理例程需要的资源(例如自旋锁), 系统可能死锁*/ |
在 2.6 内核, 可使用下面 2 个函数中的任一个(定义在 )关闭当前处理器上所有中断:
void local_irq_save(unsigned long flags);/*在保存当前中断状态到 flags 之后禁止中断*/ void local_irq_enable(void); |
顶半部和底半部
中断处理需要很快完成并且不使中断阻塞太长,所以中断处理的一个主要问题是如何在处理例程中完成耗时的任务。
Linux (连同许多其他系统)通过将中断处理分为两部分来解决这个问题:
“顶半部”:是实际响应中断的例程(request_irq 注册的那个例程)。
“底半部”:是被顶半部调度,并在稍后更安全的时间内执行的函数。
他们最大的不同在底半部处理例程执行时,所有中断都是打开的(这就是所谓的在更安全的时间内运行)。典型的情况是:顶半部保存设备数据到一个设备特定的缓存并调度它的底半部,最后退出: 这个操作非常快。底半部接着进行任何其他需要的工作。这种方式的好处是在底半部工作期间,顶半部仍然可以继续为新中断服务。
Linux 内核有 2 个不同的机制可用来实现底半部处理:
(1) tasklet (首选机制),它非常快, 但是所有的 tasklet 代码必须是原子的;
(2)工作队列, 它可能有更高的延时,但允许休眠。
中断共享
Linux 内核支持在所有总线上中断共享。
安装共享的处理例程
通过 request_irq 来安装共享中断与非共享中断有 2 点不同:
(1)当request_irq 时,flags 中必须指定SA_SHIRQ 位; (2)dev_id 必须唯一。任何指向模块地址空间的指针都行,但 dev_id 绝不能设置为 NULL。 |
内核为每个中断维护一个中断共享处理例程列表,dev_id 就是区别不同处理例程的签名。释放处理例程通过执行free_irq实现。 dev_id 用来从这个中断的共享处理例程列表中选择正确的处理例程来释放,这就是为什么 dev_id 必须是唯一的.
请求一个共享的中断时,如果满足下列条件之一,则request_irq 成功:
(1)中断线空闲; (2)所有已经注册该中断信号线的处理例程也标识了IRQ是共享。 |
一个共享的处理例程必须能够识别自己的中断,并且在自己的设备没有被中断时快速退出(返回 IRQ_NONE)。
共享处理例程没有探测函数可用,但使用的中断信号线是空闲时标准的探测机制才有效。
一个使用共享处理例程的驱动需要小心:不能使用 enable_irq 或 disable_irq,否则,对其他共享这条线的设备就无法正常工作了。即便短时间禁止中断,另一设备也可能产生延时而为设备和其用户带来问题。所以程序员必须记住:他的驱动并不是独占这个IRQ,它的行为应当比独占这个中断线更加"社会化"。
中断驱动的 I/O
当与驱动程序管理的硬件间的数据传送可能因为某种原因而延迟,驱动编写者应当实现缓存。一个好的缓存机制需采用中断驱动的 I/O,一个输入缓存在中断时被填充,并由读取设备的进程取走缓冲区的数据,一个输出缓存由写设备的进程填充,并在中断时送出数据。
为正确进行中断驱动的数据传送,硬件应能够按照下列语义产生中断:
输入:当新数据到达时并处理器准备好接受时,设备中断处理器。
输出:当设备准备好接受新数据或确认一个成功的数据传送时,设备产生中断。
<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
阅读(2267) | 评论(1) | 转发(6) |
上一篇:ldd3学习之六:时间、延迟及延缓操作
下一篇:bootloader的概念
- linux 常见服务端口
- xmanager 2.0 for linux配置
- 【ROOTFS搭建】busybox的httpd...
- openwrt中luci学习笔记
- 什么是shell
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
小尾巴鱼1211212011-12-07 23:45:26
DIY探测挺好玩儿的\(^o^)/~
ldd3学习之七:中断处理相关推荐
- FFmpeg学习之七(视音频流缓存)
FFmpeg学习之七(视音频流缓存) 缓存队列实现 源码下载 1.原理 2.实现细节 2.1 结构体定义 2.2 类定义 2.3 初始化队列 2.4 入队 2.5 出队 2.6 重置空闲队列数据 3. ...
- Hive学习之七《 Sqoop import 从关系数据库抽取到HDFS》
一.什么是sqoop Sqoop是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql.postgresql...)间进行数据的传递,可以将一个关系型数据库(例如 :MySQL ...
- LDD3学习之short
http://qchfu2006.blog.163.com/blog/static/14153441020121120395443 编译好short之后,运行short_load会出现以下问题: in ...
- 深度学习之七:卷积神经网络
9.5.Convolutional Neural Networks卷积神经网络 卷积神经网络是人工神经网络的一种,已成为当前语音分析和图像识别领域的研究热点.它的权值共享网络结构使之更类似于生物 ...
- jsp循环输出表格_「翻译」JS可视化学习之七:Promise、事件循环和异步2
喜欢排队吧,它能保护你的时间和精力 - 排队纪律维护员Event Loop Promise和事件循环概览图 请注意上面这张图,Promise和事件循环的那些事,将在这个图上缓缓展开. 微任务和(宏)任 ...
- 优达学城深度学习之七——TensorFlow卷积神经网络
一.胶囊网络 池化运算会丢失一些图像信息.这是因为为了获得更小的特征级图像表示,池化会丢弃像素信息.与池化层相比,有一些分类方法不会丢弃空间信息,而是学习各个部分之间的关系(例如眼睛.鼻子和嘴之间的空 ...
- jQuery学习之七---CSS
这一篇,话不多说,直接来学习吧,我觉得很重要~ CSS 1.css(name|pro|[,val|fn])访问匹配元素的样式属性. 参数解析: name:要访问的属性名称 properties:要设置 ...
- Jquery重新学习之七[Ajax运用总结A]
Jquery中Ajax的运用是另外一个重点,平时项目经常会用它进行一些异步操作:其核心是通过XMLHttpRequest对象以一种异步的方式,向服务器发送数据请求,并通过该对象接收请求返回的数据,从而 ...
- PGM学习之七 MRF,马尔科夫随机场
之前自己做实验也用过MRF(Markov Random Filed,马尔科夫随机场),基本原理理解,但是很多细节的地方都不求甚解.恰好趁学习PGM的时间,整理一下在机器视觉与图像分析领域的MRF的相关 ...
最新文章
- linux下history(历史)命令用法详解
- java中文getbytes为3,java 中文乱码问题
- Matlab中自定义函数(一)
- vjue 点击发送邮件如何处理
- Yii::$app的作用
- tf.nn 和tf.layers以及tf.contrib.layers的简单区别(转)
- 在sql server 发生未指定的错误_一条sql查询是怎么执行的?
- JMP数据清洗之“拆分” — 快速实现一列拆分为多列
- excle表格导出到本地
- excel表格如何转换成word表格_Word表格如何转为Excel表格?这2个小技巧轻松搞定!
- slam十四讲,ch5joinmap报错,已解决
- ue4导入倾斜摄影_一种高精度倾斜摄影建模方法与流程
- lumen报错Class redis does not exist
- 哪有那么多BAT的逆袭?
- 基于[Python]的ATM取款机模拟实战
- 2天线8状态空时网格码 c语言实现
- HC32L072 OPA 性能测试
- Bootstrap学习笔记——菜单、按钮及导航
- Python--回头看--数据类型
- 织梦dedecms手机上,自动跳转到/m目录mobile手机版h5页面
热门文章
- 这18款作品斩获2020 Qualcomm XR 创新应用挑战赛奖项
- 【经验】谷歌翻译不能使用的解决方法
- Laravel后端接口使用mews/captcha验证码注册+登录流程讲解
- 基于Revit模型的现实增强(AR)技术探索(附视频教程和素材)
- c语言程序设计培训班讲课课件,《C语言程序设计》PPT课件43223讲课讲稿.ppt
- OEP30W D类音频功率放大器简单测试
- 十二届蓝桥杯省赛c++(下)
- 微软SDL流程终极整理总结
- 【JAVA】【刷题子】1037. 有效的回旋镖
- Leetcode——回旋镖