📄 (ldd) ch09-中断处理(上)(转载).htm
字号:
<P> printk(KERN_INFO "short: can't get assigned irq %i\n",<BR>short_irq);<BR> <BR> short_irq=-1;<BR> <BR> }<BR> <BR> else { /*<BR> <BR> outb(0x10, short_base+2);<BR> <BR> }<BR> <BR> }<BR> <BR> <BR> <BR> 这段代码显示安装的处理程序是个快速中断处理程序(SA_INTERRUPT),不支持中<BR>断共享(没有设置SA_SHIRQ),并且对系统熵池无贡献(没有设置SA_SAMPLE_RANDOM)。然<BR>后调用outb打开并口的中断报告。<BR> <BR>/proc接口<BR> 当处理器被硬件中断时,一个内部计数器会被加1,这为检查设备是否正常工作<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 当处理器被硬件中断时,一个内部计数器会被加1,这为检查设备是否正常工作<BR>提供了一个方法。报告的中断显示在文件/proc/interrupts中。下面是我的486启动一个<BR>半小时(uptime)后该文件的一个快照:<BR> <BR> <BR> <BR> 0: 537598 timer<BR> <BR> 1: 23070 keyboard<BR> <BR> 2: 0 cascade<BR> <BR> 3: 7930 + serial<BR> <BR> 5: 4568 NE2000<BR> <BR> 7: 15920 + short<BR> <BR>13: 0 math error<BR> <BR>14: 48163 + ide0<BR> <BR>15: 1278 + ide1<BR></P></FONT><FONT
color=#ffffff size=3>
<P>15: 1278 + ide1<BR> <BR> <BR> <BR> 第一列是IRQ中断号。你可以从显示中缺少一些中断推知该文件只会显示已经安<BR>装了驱动程序的那些中断。例如,第一个串口(使用中断号4)没有显示,这表明我现在没<BR>有使用调制解调器。实际上,即使我在获取这个快照之前使用过调制解调器,它也不会<BR>出现在这个文件中;串口的行为很良好,当设备关闭时会释放它们的中断处理程序。出<BR>现在各记录中的加号标志该行中断采用了快速中断处理程序。<BR> <BR> <BR> <BR> /proc树中还包含了另一个与中断有关的文件,/proc/stat;有时你可能会发现<BR>一个文件更有用,但有时又更愿意使用另一个。/proc/stat文件记录了关于系统活动的<BR>一些底层的统计信息,包括(但不仅限于)只系统启动以来接收到的中断次数。stat文件<BR>的每一行都以一个字符串开始,它是该行的关键字;intr标记正是我们要找的。下面的<BR>快照是在得到前面那个快照后半分钟获得的:<BR> <BR> <BR> <BR>intr 947102 540971 23346 0 8795 4907 4568 0 15920 0 0 0 0 0 0 48317<BR>1278<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR> 第一个数是总的中断次数,而其它每个数都代表一个中断信号,从0号中断开始<BR>。上面的快照显示4号中断被使用了4907次,虽然当前它的处理程序没有安装上。如果你<BR>测试的驱动程序是在每次打开和关闭设备的循环中获取和释放中断的话,那么你会发现/<BR>proc/stat文件要比/proc/interrupts文件更有用。<BR> <BR> <BR> <BR> 两个文件另一处不同是interrupts文件与体系结构无关,而stat文件则与体系结<BR>构有关:其字段的个数取决于内核之下的硬件。可以获取的中断个数在Sparc上只有15个<BR>,而在Atari(M68k处理器)上则多达72个。<BR> <BR> <BR> <BR>下面的快照给出我的Alpha工作站(共有16个中断,和x86机器一样)上的文件内容:<BR> <BR> <BR> <BR>1: 2 keyboard<BR> <BR>5: 4641 NE2000<BR></P></FONT><FONT
color=#ffffff size=3>
<P>5: 4641 NE2000<BR> <BR> 15: 22909 + 53c7,8xx<BR> <BR> <BR> <BR>intr 27555 0 2 0 1 1 4642 0 0 0 0 0 0 0 0 0 22909<BR> <BR> <BR> <BR> 这个输出的最值得注意的地方是不出现时钟中断。在Alpha机器上,时钟中断与<BR>其它中断到达处理器的方式不同,没有分配IRQ中断号。<BR> <BR>自动检测中断号<BR> 驱动程序初始化时最迫切的问题之一就是如何决定设备要使用哪条中断信号线。<BR>驱动程序需要该信息以便安装正确的处理程序。虽然程序员可以要求用户在装载是指定<BR>中断号,但这并不好,因为一般用户并不知道中断号,或者是因为他没有配置跳线或者<BR>因为该设备根本就没有跳线。自动检测中断号是对驱动程序使用的基本要求。<BR> <BR> <BR> <BR> 有时自动检测依赖于一些设备拥有的较少改变的缺省特性。此时,驱动程序可以<BR>就假定设备使用了这些缺省值。short在检测并口时就正是这么作的。正如short的代码<BR></P></FONT><FONT
color=#ffffff size=3>
<P>就假定设备使用了这些缺省值。short在检测并口时就正是这么作的。正如short的代码<BR>中所给出的,实现起来相当简明:<BR> <BR> <BR> <BR> if (short_irq<0) /* 尚未指定:强制为缺省的 */<BR> <BR> switch(short_base){<BR> <BR> case 0x378: short_irq=7; break;<BR> <BR> case 0x278: short_irq=2; break;<BR> <BR> case 0x3bc: short_irq=5; break;<BR> <BR> }<BR> <BR> <BR> <BR> 这段代码根据选定的I/O地址来分配中断号,但也允许用户在装载驱动程序时通<BR>过调用insmod short short_irq=x来覆盖缺省值。short_base缺省为0x378,因此short_<BR>irq缺省为7。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR> 有些设备设计得更为先进,会简单地“声明”它们要使用那个中断。此时,驱动<BR>程序可以通过读设备的某个I/O端口的一个状态字节来获得中断号。当目标设备能告述设<BR>备要使用哪个中断时,那么自动检测中断号就是探测设备,不需要额外工作来探测中断<BR>。<BR> <BR> <BR> <BR> 值得注意的是,现代的设备能提供自己的中断配置信息。PCI标准通过要求外围<BR>设备声明要使用的中断信号线的方法来解决这个问题。关于PCI标准的讨论可参见第15章<BR>“外设总线概貌”。<BR> <BR> <BR> <BR> 遗憾的是,不是所有设备都对程序员友好,自动检测可能还是需要一些探测的。<BR>技术很简单:驱动程序告诉设备产生中断,然后观察会发生些什么。如果一切正常,那<BR>么只有一条中断信号线被激活了。<BR> <BR> <BR> <BR> 尽管探测在理论上很简单,实际的实现则并不那么简明。下面我们看看执行该任<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 尽管探测在理论上很简单,实际的实现则并不那么简明。下面我们看看执行该任<BR>务的两种方法:调用内核定义的帮助函数和实现我们自己的版本。<BR> <BR>核心帮助下的检测<BR> 主流的内核版本都提供探测中断号的底层工具。这种工具包括两个函数,都在头<BR>文件<linux/interrupt.h>中声明(该头文件也描述了探测的机制):<BR> <BR> <BR> <BR>unsigned long probe_irq_on(void);<BR> <BR>这个函数返回尚未分配的中断的位掩码。驱动程序必须保留返回的位掩码以便随后能将<BR>它传递给probe_irq_off函数。调用该函数后,驱动程序要安排相应设备至少产生一次中<BR>断。<BR> <BR> <BR> <BR>int probe_irq_off(unsigned long);<BR> <BR>在设备已经申请了中断之后,驱动程序要调用这个函数,传递给它的参数是先前调用pro<BR>be_irq_on返回的位掩码。probe_irq_off返回“启动探测”后发出的中断次数。如果没<BR>有发生任何中断,就返回0(因此无法探测0号中断,但在能支持的所有体系结构上也没有<BR>什么定制设备能使用它)。如果产生了多次中断(二义性检测),probe_irq_off将返回一<BR></P></FONT><FONT
color=#ffffff size=3>
<P>什么定制设备能使用它)。如果产生了多次中断(二义性检测),probe_irq_off将返回一<BR>个负值。<BR> <BR> <BR> <BR> 程序员要注意在调用probe_irq_on后启动设备,并在调用probe_irq_off后关闭<BR>它。此外,在调用probe_irq_off之后,不要忘了处理你的设备尚未处理的那些中断。<BR> <BR> <BR> <BR> short模块演示了如何进行这样的探测。如果你在装载模块时指定probe=1并且并<BR>口插座的9号和10号引脚相连,就会执行下面的代码进行中断信号线的检测。<BR> <BR> <BR> <BR> int count=0;<BR> <BR> do {<BR> <BR> unsigned long mask;<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> mask=probe_irq_on();<BR> <BR>outb_p(0x10, short_base+2); /* 启动中断报告 */<BR> <BR>outb_p(0x00,short_base); /* 清位 */<BR> <BR>outb_p(0xFF, short_base); /* 置位:中断!*/<BR> <BR>outb_p(0x00, short_base+2); /* 关闭中断报告 */<BR> <BR>short_irq=probe_irq_off(mask);<BR> <BR> <BR> <BR> if (short_irq==0){ /* 没有探测到中断报告?*/<BR> <BR> printk(KERN_INFO "short: no irq reported by probe\n");<BR> <BR> short_irq=-1;<BR> <BR> }<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> /*<BR> <BR>* 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt<BR> <BR>* 端口)并且再次进行循环。最多循环5次,然后放弃<BR> <BR>*/<BR> <BR> } while (short_irq<0 && count++<5);<BR> <BR> if (short_irq<0)<BR> <BR> printk("short: probe failed %i times, giving up\n",count);<BR> <BR> <BR> <BR> 探测很耗时。尽管short的探测很快,但象探测帧捕捉卡,就至少需要延迟20ms(<BR>相对处理器时间就太长了),而探测其它设备可能会更花时间。因此,最好就只在模块初<BR>始化时探测中断信号线一次,不管你是在打开设备时(你应该这样做)或者在init_module<BR>中(你无论如何不应该这样做)安装你的中断处理程序的。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 值得注意的是,在Sparc和M68k上,中断探测全无必要,因此也不必实现。探测<BR>是种“黑客”行为,象PCI这样的成熟的体系结构会提供所有必要的信息。实际上,M68k<BR>和Sparc的内核开放给模块桩(stub)的探测函数总是返回0——每种体系结构都必须定义<BR>这些函数,因为它们是由体系结构无关的源文件来开放的。所有其它的体系结构都允许<BR>使用上面给出的探测技术。<BR> <BR> <BR> <BR> probe_irq_on和probe_irq_off的问题是早期的内核版本并不开放这两个函数。<BR>因此,如果你希望写的模块能移植到1.2版的内核,你必须自己做中断探测。<BR> <BR>DIY(Do It Yourself自己做)检测<BR> 探测也可以有驱动程序自己较容易地实现。如果装载是指定probe=2,short模块<BR>将对中断信号线进行DIY检测。<BR> <BR> <BR> <BR> 实现机制和前面讨论的内核帮助下的检测是一样的:启动所有未被占用的中断,<BR>然后等着看会发生些什么。但我们可以利用拥有的对设备的一些知识。通常一个设备可<BR>以配置成使用3或4个中断号中的一个;只需要探测这些中断号,这使我们不必测试所有<BR>可能的中断号就可以检测到正确的中断号。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>可能的中断号就可以检测到正确的中断号。<BR> <BR> <BR> <BR> 在short的实现中假定可能的中断号只有3,5,7和9。这些数值实际上是一些并<BR>口允许你选取的值的范围。<BR> <BR> <BR> <BR> 下面的代码通过测试所有“可能的”中断和会观察发生什么来进行中断探测。tr<BR>ials数组列出所有要尝试的中断号,0是该列表的结束标志;trials数组用于记录实际上<BR>哪个处理程序被驱动程序注册了。<BR> <BR> <BR> <BR> int trials[]={3,5,7,9,0};<BR> <BR> int tried[]={0,0,0,0,0};<BR> <BR>int i,count=0;<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> /*<BR> <BR>*为所有可能的中断信号线安装探测处理程序。记录下结果(0表示成功,-EBUSY<BR> <BR>*表示失败)以便只释放申请的中断<BR> <BR>*/<BR> <BR>for (i=0; trials[i]; i++)<BR> <BR> tried[i]=request_irq(trials[i], short_probing, SA_INTERRUPT, "short<BR>probe", NULL);<BR> <BR> <BR> <BR>do {<BR> <BR> short_irq=0; /* 尚未取得中断号 */<BR> <BR> outb_p(0x10, short_base+2); /* 启动 */<BR> <BR> outb_p(0x00, short_base);<BR></P></FONT><FONT
color=#ffffff size=3>
<P> outb_p(0x00, short_base);<BR> <BR> outb_p(0xFF, short_base); /* 置位 */<BR> <BR> outb_p(0x10, short_base+2); /* 关闭 */<BR> <BR> <BR> <BR> /* 处理程序已经设置了这个值 */<BR> <BR> if (short_irq==0) { /*<BR> <BR> printk(KERN_INFO "short: no irq reported by probe\n");<BR> <BR> }<BR> <BR>/*<BR> <BR>* 如果激活了一个以上的中断,结果就是负的。我们将为中断提供服务(除非是lpt<BR> <BR>* 端口)并且再次进行循环。最多这样做5次<BR> <BR>*/<BR></P></FONT><FONT
color=#ffffff size=3>
<P>*/<BR> <BR>} while(short_irq<=0 && count++<5);<BR> <BR> <BR> <BR>/* 循环结束,卸载处理程序 */<BR> <BR>for (i=0; trials[i]; i++)<BR> <BR> if (tried[i]==0)<BR> <BR> free_irq(trials[i],NULL);<BR> <BR> <BR> <BR>if (short_irq<0)<BR> <BR> printk("short: probe failed %i times, giving up\n",count);<BR> <BR> <BR> <BR>你可能事先不知道“可能的”中断号。此时,你需要探测所有空闲的中断,而不仅是一<BR></P></FONT><FONT
color=#ffffff size=3>
<P>你可能事先不知道“可能的”中断号。此时,你需要探测所有空闲的中断,而不仅是一<BR>些trials[]。为了探测所有的中断,你不得不从0号中断探测到NR_IRQS-1号中断,NR_IR<BR>QS是在头文件<asm/irq.h>中定义的与平台无关的常数。<BR> <BR> <BR> <BR>现在缺的就是探测处理程序自己了。该处理程序的功能就是根据实际接收到的中断号来<BR>更新short_irq变量。short_irq值为0意味着“什么也没有”,而负值意味着存在“二义<BR>性”。我选取这些值是为了和probe_irq_off保持一致,并可以在short.c中使用同样的<BR>代码来调用任何一种探测方法。<BR> <BR> <BR> <BR>void short_probing(int irq, void *dev_id, struct pt_regs *regs)<BR> <BR>{<BR> <BR> if (short_irq == 0) short_irq = irq; /* 找到 */<BR> <BR> if (short_irq != irq) short_irq = -irq; /* 有二义性 */<BR> <BR>}<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>处理程序的参数稍后会介绍。知道参数irq是要处理的中断号就足以理解上面的函数了。<BR> <BR> <BR>快速和慢速中断处理<BR> 你已经看到,我为short的中断处理程序设置了SA_INTERRUPT标志位,因此是请<BR>求安装一个快速中断处理程序。现在到解释什么是“快速”和“慢速”的时候了。实际<BR>上,不是所有的体系结构都支持快速和慢速中断处理程序两种实现的。例如,Alpha和Sp<BR>arc的移植版本,快速和慢速处理程序是一样处理的。2.1.37版和其后的Intel移植版本<BR>也消除了两者的差别,因为现代处理器的可以获得的处理能力使得我们不必再区分出快<BR>速和慢速两种中断。<BR> <BR> <BR> <BR> 这两种中断处理程序的主要差别就在于,快速中断处理程序保证中断的原子处理<BR>,而慢速中断处理程序则不保证(这种差别在最新的中断处理的实现也保留了)。也就是<BR>说,“开启中断”处理器标志位(IF)在运行快速中断处理程序时是关闭的,因此在服务<BR>该中断时不允许被中断。而调用慢速中断处理时,内核启动微处理器的中断报告,因此<BR>在运行慢速中断处理程序时其它中断仍可以得到服务。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 在调用实际的中断处理程序之前,不管是快速还是慢速中断处理程序,内核都要<BR>执行一项任务,关闭刚才发出报告的那个中断信号线。这对程序员是个好消息-中断服<BR>务例程不必是可重入的。但另一方面,即使是慢速中断处理程序也要实现得运行的尽可<BR>能快,以免丢失后面到达的中断。<BR> <BR> <BR> <BR> 当处理程序还在处理上一个中断时,如果设备又发出新的中断,新的中断会永远<BR>丢失。中断控制器并不缓存被屏蔽的中断,但是处理器会进行缓存-一旦发出sti指令,<BR>待处理的中断就会得到服务。sti函数是“置中断标志位”处理器指令(是在第2章“编写<BR>和运行模块”的“ISA内存”一节引入的)。<BR> <BR> <BR> <BR> 总结快速和慢速两种执行环境如下:<BR> <BR> <BR> <BR>l 快速中断处理程序运行时微处理器关闭了中断报告,中断控制器禁止了被服务这<BR>个中断。但处理程序可以通过调用sti来启动处理器的中断报告。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>l 慢速处理程序运行时启动了处理器的中断报告,但中断控制器也禁止了被服务这<BR>个中断。<BR> <BR> <BR> <BR>但快速和慢速中断处理程序还有另一处不同:内核带来的额外开销。慢速中断处理程序<BR>之所以慢是因为内核带来的一些管理开销造成的。这意味着较频繁的中断最好由快速中<BR>断处理程序为之提供服务。至于short,当把大文件拷贝到/dev/short0时每秒会产生上<BR>千次中断。因此我选择使用了一个快速中断处理程序来控制添加给系统的开销。这种分<BR>别在更新的2.1版的内核中已经得到统一;这个开销现在加到了所有的中断处理程序上。<BR> <BR> <BR> <BR> <BR>帧捕捉卡是使用慢速中断处理程序的一个好的候选者。它每秒只中断处理器50到60次,<BR>选择使用慢速处理程序将帧数据从接口卡拷贝到物理内存就不会阻塞住其它的系统中断<BR>,例如那些由串口或定时器服务产生的中断。<BR> <BR>x86平台上中断处理的内幕<BR> 下面的描述是根据2.0.x版本的内核中的两个文件arch/i386/kernel/irq.c和inc<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 下面的描述是根据2.0.x版本的内核中的两个文件arch/i386/kernel/irq.c和inc<BR>lude/asm-i386/irq.h推断的;虽然基本概念是相同的,但是具体的硬件细节与平台有关<BR>,并且在2.1开发版本中有些修改。<BR> <BR> <BR> <BR> 最底层的中断处理是在头文件irq.h中的声明为宏的一些汇编代码,这些宏在文<BR>件irq.h中被扩展。为每个中断声明了三种处理函数:慢速,快速和伪(bad)处理函数。<BR> <BR> <BR> <BR> “伪”处理程序,它最小,是当没有为中断安装C语言的处理程序时的汇编入口<BR>点。它将中断转交给适当的PIC(Programmable Interrupt Controller,可编程的中断控<BR>制器)设备*的同时禁止它,以避免由于伪中断而进一步浪费处理器时间。在驱动程序处<BR>理完中断信号后调用free_irq时又会重新安装伪处理程序。伪处理程序不会将/proc/sta<BR>t中的计数器加1。<BR> <BR> <BR> <BR> 值得注意的是,在x86和Alpha上的自动探测都是依赖于伪处理程序的这种行为。<BR>probe_irq_on启动所有的伪中断,而不安装处理程序;probe_irq_off只是简单地检查自<BR>调用probe_irq_on以来那些中断被禁止了。如果你想验证这一点,可以在装载short时指<BR>定probe=1(内核帮助下的检测),此时可观察到中断计数器没有加1,而如果装载时指定p<BR></P></FONT><FONT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -