📄 ch10s02.html
字号:
<p>这个函数已经仔细编写来回卷指向环形缓存的指针, 没有暴露一个不正确的值. 这里的 barrier 调用来阻止编译器在这个函数的其他 2 行之间优化. 如果没有 barrier, 编译器可能决定优化掉 new 变量并且直接赋值给 *index. 这个优化可能暴露一个 index 的不正确值一段时间, 在它回卷的地方. 通过小心阻止对其他线程可见的不一致的值, 我们能够安全操作环形缓存指针而不用锁.</p><p>用来读取中断时填充的缓存的设备文件是 /dev/shortint. 这个设备特殊文件, 同 /dev/shortprint 一起, 不在第 9 章介绍, 因为它的使用对中断处理是特殊的. /dev/shortint 内部特别地为中断产生和报告剪裁过. 写到设备会每隔一个字节产生一个中断; 读取设备给出了每个中断被报告的时间.</p><p>如果你连接并口连接器的管脚 9 和 10, 你可产生中断通过拉高并口数据字节的高位. 这可通过写二进制数据到 /dev/short0 或者通过写任何东西到 /dev/shortint 来完成.</p><p><sup>[<a name="id462202" href="#ftn.id462202">38</a>]</sup>下列代码为 /dev/shortint 实现读和写:</p><pre class="programlisting">ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos){ int count0; DEFINE_WAIT(wait); while (short_head == short_tail) { prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE); if (short_head == short_tail) schedule(); finish_wait(&short_queue, &wait); if (signal_pending (current)) /* a signal arrived */ return -ERESTARTSYS; /* tell the fs layer to handle it */ } /* count0 is the number of readable data bytes */ count0 = short_head - short_tail; if (count0 < 0) /* wrapped */ count0 = short_buffer + PAGE_SIZE - short_tail; if (count0 < count) count = count0; if (copy_to_user(buf, (char *)short_tail, count)) return -EFAULT; short_incr_bp (&short_tail, count); return count;}ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){ int written = 0, odd = *f_pos & 1; unsigned long port = short_base; /* output to the parallel data latch */ void *address = (void *) short_base; if (use_mem) { while (written < count) iowrite8(0xff * ((++written + odd) & 1), address); } else { while (written < count) outb(0xff * ((++written + odd) & 1), port); } *f_pos += count; return written;}</pre><p>其他设备特殊文件, /dev/shortprint, 使用并口来驱动一个打印机; 你可用使用它, 如果你想避免连接一个 D-25 连接器管脚 9 和 10. shortprint 的写实现使用一个环形缓存来存储要打印的数据, 而写实现是刚刚展示的那个(因此你能够读取你的打印机吃进每个字符用的时间).</p><p>为了支持打印机操作, 中断处理从刚刚展示的那个已经稍微修改, 增加了发送下一个数据字节到打印机的能力, 如果没有更多数据传送.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="HandlerArgumentsandReturnValue.sect"></a>10.2.5. 处理者的参数和返回值</h3></div></div></div><p>尽管 short 忽略了它们, 一个传递给一个中断处理的参数: irq, dev_id, 和 regs. 我们看一下每个的角色.</p><p>中断号( int irq )作为你可能在你的 log 消息中打印的信息是有用的, 如果有. 第二个参数, void *dev_id, 是一类客户数据; 一个 void* 参数传递给 request_irq, 并且同样的指针接着作为一个参数传回给处理者, 当中断发生时. 你常常传递一个指向你的在 dev_id 中的设备数据结构的指针, 因此一个管理相同设备的几个实例的驱动不需要任何额外的代码, 在中断处理中找出哪个设备要负责当前的中断事件.</p><p>这个参数在中断处理中的典型使用如下:</p><pre class="programlisting">static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct sample_dev *dev = dev_id; /* now `dev' points to the right hardware item */ /* .... */}</pre><p>和这个处理者关联的典型的打开代码看来如此:</p><pre class="programlisting">static void sample_open(struct inode *inode, struct file *filp){ struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev); request_irq(dev->irq, sample_interrupt, 0 /* flags */, "sample", dev /* dev_id */); /*....*/ return 0;}</pre><p>最后一个参数, struct pt_regs *regs, 很少用到. 它持有一个处理器的上下文在进入中断状态前的快照. 寄存器可用来监视和调试; 对于常规地设备驱动任务, 正常地不需要它们.</p><p>中断处理应当返回一个值指示是否真正有一个中断要处理. 如果处理者发现它的设备确实需要注意, 它应当返回 IRQ_HANDLED; 否则返回值应当是 IRQ_NONE. 你也可产生返回值, 使用这个宏:</p><pre class="programlisting">IRQ_RETVAL(handled)</pre><p>这里, handled 是非零, 如果你能够处理中断. 内核用返回值来检测和抑制假中断. 如果你的设备没有给你方法来告知是否它确实中断, 你应当返回 IRQ_HANDLED.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="EnablingandDisablingInterrupts.sect"></a>10.2.6. 使能和禁止中断</h3></div></div></div><p>有时设备驱动必须阻塞中断的递交一段时间(希望地短)(我们在第 5 章的 "自旋锁"一节看到过这样的一个情况). 常常, 中断必须被阻塞当持有一个自旋锁来避免死锁系统时. 有几个方法来禁止不涉及自旋锁的中断. 但是在我们讨论它们之前, 注意禁止中断应当是一个相对少见的行为, 即便在设备驱动中, 并且这个技术应当从不在驱动中用做互斥机制.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Disablingasingleinterrupt.sect"></a>10.2.6.1. 禁止单个中断</h4></div></div></div><p>有时(但是很少!)一个驱动需要禁止一个特定中断线的中断递交. 内核提供了 3 个函数为此目的, 所有都声明在 <asm/irq.h>. 这些函数是内核 API 的一部分, 因此我们描述它们, 但是它们的使用在大部分驱动中不鼓励. 在其他的中, 你不能禁止共享的中断线, 并且, 在现代的系统中, 共享的中断是规范. 已说过的, 它们在这里:</p><pre class="programlisting">void disable_irq(int irq);void disable_irq_nosync(int irq);void enable_irq(int irq);</pre><p>调用任一函数可能更新在可编程控制器(PIC)中的特定 irq 的掩码, 因此禁止或使能跨所有处理器的特定 IRQ. 对这些函数的调用能够嵌套 -- 如果 disable_irq 被连续调用 2 次, 需要 2 个 enable_irq 调用在 IRQ 被真正重新使能前. 可能调用这些函数从一个中断处理中, 但是在处理它时使能你自己的 IRQ 常常不是一个好做法.</p><p>disable_irq 不仅禁止给定的中断, 还等待一个当前执行的中断处理结束, 如果有. 要知道如果调用 disable_irq 的线程持有中断处理需要的任何资源(例如自旋锁), 系统可能死锁. disable_irq_nosync 与 disable_irq 不同, 它立刻返回. 因此, 使用disable_irq_nosync 快一点, 但是可能使你的设备有竞争情况.</p><p>但是为什么禁止中断? 坚持说并口, 我们看一下 plip 网络接口. 一个 plip 设备使用裸并口来传送数据. 因为只有 5 位可以从并口连接器读出, 它们被解释为 4 个数据位和一个时钟/握手信号. 当一个报文的第一个 4 位被 initiator (发送报文的接口) 传送, 时钟线被拉高, 使接收接口来中断处理器. plip 处理者接着被调用来处理新到达的数据.</p><p>在设备已经被提醒了后, 数据传送继续, 使用握手线来传送数据到接收接口(这可能不是最好的实现, 但是有必要与使用并口的其他报文驱动兼容). 如果接收接口不得不为每个接收的字节处理 2 次中断, 性能可能不可忍受. 因此, 驱动在接收报文的时候禁止中断; 相反, 一个查询并延时的循环用来引入数据.</p><p>类似地, 因为从接收器到发送器的握手线用来确认数据接收, 发送接口禁止它的 IRQ 线在报文发送时.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Disablingallinterrupts.sect"></a>10.2.6.2. 禁止所有中断</h4></div></div></div><p>如果你需要禁止所有中断如何? 在 2.6 内核, 可能关闭在当前处理器上所有中断处理, 使用任一个下面 2 个函数(定义在 <asm/system.h>):</p><pre class="programlisting">void local_irq_save(unsigned long flags);void local_irq_disable(void);</pre><p>一个对 local_irq_save 的调用在当前处理器上禁止中断递交, 在保存当前中断状态到 flags 之后. 注意, flags 是直接传递, 不是通过指针. local_irq_disable 关闭本地中断递交而不保存状态; 你应当使用这个版本只在你知道中断没有在别处被禁止.</p><p>完成打开中断, 使用:</p><pre class="programlisting">void local_irq_restore(unsigned long flags); void local_irq_enable(void);</pre><p>第一个版本恢复由 local_irq_save 存储于 flags 的状态, 而 local_irq_enable 无条件打开中断. 不象 disable_irq, local_irq_disable 不跟踪多次调用. 如果调用链中有多于一个函数可能需要禁止中断, 应该使用 local_irq_save.</p><p>在 2.6 内核, 没有方法全局性地跨整个系统禁止所有的中断. 内核开发者决定, 关闭所有中断的开销太高, 并且在任何情况下没有必要有这个能力. 如果你在使用一个旧版本驱动, 它调用诸如 cli 和 sti, 你需要在它在 2.6 下工作前更新它为使用正确的加锁</p></div></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"><p><sup>[<a name="ftn.id461370" href="#id461370">37</a>] </sup>尽管, 一些大系统明确使用中断平衡机制来在系统间分散中断负载.</p></div><div class="footnote"><p><sup>[<a name="ftn.id462202" href="#id462202">38</a>] </sup>这个 shortint 设备完成它的任务, 通过交替地写入 0x00 和 0xff 到并口.</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch10.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch10.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch10s03.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 10 章 中断处理 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 10.3. 前和后半部</td></tr></table></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -