⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch06s02.html

📁 完整的Linux 设备驱动第3版
💻 HTML
📖 第 1 页 / 共 3 页
字号:
<p>这个代码看来和 read 方法类似, 除了我们已经将睡眠代码放到了一个单独的函数, 称为 scull_getwritespace. 它的工作是确保在缓冲中有空间给新的数据, 睡眠直到有空间可用. 一旦空间在, scull_p_write 可简单地拷贝用户的数据到那里, 调整指针, 并且唤醒可能已经在等待读取数据的进程.</p><p>处理实际的睡眠的代码是:</p><pre class="programlisting">/* Wait for space for writing; caller must hold device semaphore. On * error the semaphore will be released before returning. */static int scull_getwritespace(struct scull_pipe *dev, struct file *filp){        while (spacefree(dev) == 0)        { /* full */                DEFINE_WAIT(wait);                up(&amp;dev-&gt;sem);                if (filp-&gt;f_flags &amp; O_NONBLOCK)                        return -EAGAIN;                PDEBUG("\"%s\" writing: going to sleep\n",current-&gt;comm);                prepare_to_wait(&amp;dev-&gt;outq, &amp;wait, TASK_INTERRUPTIBLE);                if (spacefree(dev) == 0)                        schedule();                finish_wait(&amp;dev-&gt;outq, &amp;wait);                if (signal_pending(current))                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */                if (down_interruptible(&amp;dev-&gt;sem))                        return -ERESTARTSYS;        }        return 0;}</pre><p>再次注意 while 循环. 如果有空间可用而不必睡眠, 这个函数简单地返回. 否则, 它必须丢掉设备旗标并且等待. 这个代码使用 DEFINE_WAIT 来设置一个等待队列入口并且 prepare_to_wait 来准备好实际的睡眠. 接着是对缓冲的必要的检查; 我们必须处理的情况是在我们已经进入 while 循环后以及在我们将自己放入等待队列之前 (并且丢弃了旗标), 缓冲中有空间可用了. 没有这个检查, 如果读进程能够在那时完全清空缓冲, 我们可能错过我们能得到的唯一的唤醒并且永远睡眠. 在说服我们自己必须睡眠之后, 我们调用 schedule. </p><p>值得再看看这个情况: 当睡眠发生在 if 语句测试和调用 schedule 之间, 会发生什么? 在这个情况里, 都好. 这个唤醒重置了进程状态为 TASK_RUNNING 并且 schedule 返回 -- 尽管不必马上. 只要这个测试发生在进程放置自己到等待队列和改变它的状态之后, 事情都会顺利.</p><p>为了结束, 我们调用 finish_wait. 对 signal_pending 的调用告诉我们是否我们被一个信号唤醒; 如果是, 我们需要返回到用户并且使它们稍后再试. 否则, 我们请求旗标, 并且再次照常测试空闲空间.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Exclusivewaits.sect3"></a>6.2.5.3.&#160;互斥等待</h4></div></div></div><p>我们已经见到当一个进程调用 wake_up 在等待队列上, 所有的在这个队列上等待的进程被置为可运行的. 在许多情况下, 这是正确的做法. 但是, 在别的情况下, 可能提前知道只有一个被唤醒的进程将成功获得需要的资源, 并且其余的将简单地再次睡眠. 每个这样的进程, 但是, 必须获得处理器, 竞争资源(和任何的管理用的锁), 并且明确地回到睡眠. 如果在等待队列中的进程数目大, 这个"惊群"行为可能严重降低系统的性能.</p><p>为应对实际世界中的惊群问题, 内核开发者增加了一个"互斥等待"选项到内核中. 一个互斥等待的行为非常象一个正常的睡眠, 有 2 个重要的不同:</p><div class="itemizedlist"><ul type="disc"><li><p>当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始.</p></li><li><p>当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止.</p></li></ul></div><p>最后的结果是进行互斥等待的进程被一次唤醒一个, 以顺序的方式, 并且没有引起惊群问题. 但内核仍然每次唤醒所有的非互斥等待者.</p><p>在驱动中采用互斥等待是要考虑的, 如果满足 2 个条件: 你希望对资源的有效竞争, 并且唤醒一个进程就足够来完全消耗资源当资源可用时. 互斥等待对 Apacheweb 服务器工作地很好, 例如; 当一个新连接进入, 确实地系统中的一个 Apache 进程应当被唤醒来处理它. 我们在 scullpipe 驱动中不使用互斥等待, 但是; 很少见到竞争数据的读者(或者竞争缓冲空间的写者), 并且我们无法知道一个读者, 一旦被唤醒, 将消耗所有的可用数据. </p><p>使一个进程进入可中断的等待, 是调用 prepare_to_wait_exclusive 的简单事情:</p><pre class="programlisting">void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state); </pre><p>这个调用, 当用来代替 prepare_to_wait, 设置"互斥"标志在等待队列入口项并且添加这个进程到等待队列的尾部. 注意没有办法使用 wait_event 和它的变体来进行互斥等待.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Thedetailsofwakingup.sect3"></a>6.2.5.4.&#160;唤醒的细节</h4></div></div></div><p>我们已展现的唤醒进程的样子比内核中真正发生的要简单. 当进程被唤醒时产生的真正动作是被位于等待队列入口项的一个函数控制的. 缺省的唤醒函数<sup>[<a name="id436153" href="#ftn.id436153">22</a>]</sup>设置进程为可运行的状态, 并且可能地进行一个上下文切换到有更高优先级进程. 设备驱动应当从不需要提供一个不同的唤醒函数; 如果你例外, 关于如何做的信息见 &lt;linux/wait.h&gt; </p><p>我们尚未看到所有的 wake_up 变体. 大部分驱动编写者从不需要其他的, 但是, 为完整起见, 这里是整个集合:</p><div class="variablelist"><dl><dt><span class="term"><span>wake_up(wait_queue_head_t *queue);</span></span></dt><dd></dd><dt><span class="term"><span>wake_up_interruptible(wait_queue_head_t *queue);</span></span></dt><dd><p>wake_up 唤醒队列中的每个不是在互斥等待中的进程, 并且就只一个互斥等待者, 如果它存在. wake_up_interruptible 同样, 除了它跳过处于不可中断睡眠的进程. 这些函数, 在返回之前, 使一个或多个进程被唤醒来被调度(尽管如果它们被从一个原子上下文调用, 这就不会发生).</p></dd><dt><span class="term"><span>wake_up_nr(wait_queue_head_t *queue, int nr);</span></span></dt><dd></dd><dt><span class="term"><span>wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);</span></span></dt><dd><p>这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个互斥等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒, 而不是一个没有.</p></dd><dt><span class="term"><span>wake_up_all(wait_queue_head_t *queue);</span></span></dt><dd></dd><dt><span class="term"><span>wake_up_interruptible_all(wait_queue_head_t *queue);</span></span></dt><dd><p>这种 wake_up 唤醒所有的进程, 不管它们是否进行互斥等待(尽管可中断的类型仍然跳过在做不可中断等待的进程)</p></dd><dt><span class="term"><span>wake_up_interruptible_sync(wait_queue_head_t *queue);</span></span></dt><dd><p>正常地, 一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器. 换句话说, 调用 wake_up 可能不是原子的. 如果调用 wake_up 的进程运行在原子上下文(它可能持有一个自旋锁, 例如, 或者是一个中断处理), 这个重调度不会发生. 正常地, 那个保护是足够的. 但是, 如果你需要明确要求不要被调度出处理器在那时, 你可以使用 wake_up_interruptible 的"同步"变体. 这个函数最常用在当调用者要无论如何重新调度, 并且它会更有效的来首先简单地完成剩下的任何小的工作.</p></dd></dl></div><p>如果上面的全部内容在第一次阅读时没有完全清楚, 不必担心. 很少请求会需要调用 wake_up_interruptible 之外的.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Ancienthistorysleepon.sect3"></a>6.2.5.5.&#160;以前的历史: sleep_on</h4></div></div></div><p>如果你花些时间深入内核源码, 你可能遇到我们到目前忽略讨论的 2 个函数:</p><pre class="programlisting">void sleep_on(wait_queue_head_t *queue);void interruptible_sleep_on(wait_queue_head_t *queue);</pre><p>如你可能期望的, 这些函数无条件地使当前进程睡眠在给定队列尚. 这些函数强烈不推荐, 但是, 并且你应当从不使用它们. 如果你想想它则问题是明显的: sleep_on 没提供方法来避免竞争条件. 常常有一个窗口在当你的代码决定它必须睡眠时和当 sleep_on 真正影响到睡眠时. 在那个窗口期间到达的唤醒被错过. 因此, 调用 sleep_on 的代码从不是完全安全的.</p><p>当前计划对 sleep_on 和 它的变体的调用(有多个我们尚未展示的超时的类型)在不太远的将来从内核中去掉.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="TestingtheScullpipeDriver.sect2"></a>6.2.6.&#160;测试 scullpipe 驱动</h3></div></div></div><p>我们已经见到了 scullpipe 驱动如何实现阻塞 I/O. 如果你想试一试, 这个驱动的源码可在剩下的本书例子中找到. 阻塞 I/O 的动作可通过打开 2 个窗口见到. 第一个可运行一个命令诸如 cat /dev/scullpipe. 如果你接着, 在另一个窗口拷贝文件到 /dev/scullpipe, 你可见到文件的内容出现在第一个窗口.</p><p>测试非阻塞的动作是技巧性的, 因为可用于 shell 的传统的程序不做非阻塞操作. misc-progs 源码目录包含下面简单的程序, 称为 nbtest, 来测试非阻塞操作. 所有它做的是拷贝它的输入到它的输出, 使用非阻塞 I/O 和在重试间延时. 延时时间在命令行被传递被缺省是 1 秒.</p><pre class="programlisting">int main(int argc, char **argv){        int delay = 1, n, m = 0;        if (argc &gt; 1)                delay=atoi(argv[1]);        fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */        fcntl(1, F_SETFL, fcntl(1,F_GETFL) | O_NONBLOCK); /* stdout */        while (1) {                n = read(0, buffer, 4096);                if (n &gt;= 0)                        m = write(1, buffer, n);                if ((n &lt; 0 || m &lt; 0) &amp;&amp; (errno != EAGAIN))                        break;                sleep(delay);        }        perror(n &lt; 0 ? "stdin" : "stdout");        exit(1);}</pre><p>如果你在一个进程跟踪工具, 如 strace 下运行这个程序, 你可见到每个操作的成功或者失败, 依赖是否当进行操作时有数据可用.</p></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"><p><sup>[<a name="ftn.id436153" href="#id436153">22</a>] </sup>它有一个想象的名子 default_wake_function.</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch06.html">上一页</a>&#160;</td><td width="20%" align="center"><a accesskey="u" href="ch06.html">上一级</a></td><td width="40%" align="right">&#160;<a accesskey="n" href="ch06s03.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第&#160;6&#160;章&#160;高级字符驱动操作&#160;</td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top">&#160;6.3.&#160;poll 和 select</td></tr></table></div></body></html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -