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

📄 ch05s07.html

📁 完整的Linux 设备驱动第3版
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<dt><span class="term"><span>int test_and_change_bit(nr, void *addr);</span></span></dt><dd><p>原子地动作如同前面列出的, 除了它们还返回这个位以前的值.</p></dd></dl></div><p>当这些函数用来存取和修改一个共享的标志, 除了调用它们不用做任何事; 它们以原子发生进行它们的操作. 使用位操作来管理一个控制存取一个共享变量的锁变量, 另一方面, 是有点复杂并且应该有个例子. 大部分现代的代码不以这种方法来使用位操作, 但是象下面的代码仍然在内核中存在.</p><p>一段需要存取一个共享数据项的代码试图原子地请求一个锁, 使用 test_and_set_bit 或者 test_and_clear_bit. 通常的实现展示在这里; 它假定锁是在地址 addr 的 nr 位. 它还假定当锁空闲是这个位是 0, 忙为 非零.</p><pre class="programlisting">/* try to set lock */while (test_and_set_bit(nr, addr) != 0)    wait_for_a_while(); /* do your work */ /* release lock, and check... */if (test_and_clear_bit(nr, addr) == 0)    something_went_wrong(); /* already released: error */ </pre><p>如果你通读内核源码, 你会发现象这个例子的代码. 但是, 最好在新代码中使用自旋锁; 自旋锁很好地调试过, 它们处理问题如同中断和内核抢占, 并且别人读你代码时不必努力理解你在做什么.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="seqlocks.sect"></a>5.7.4.&#160;seqlock 锁</h3></div></div></div><p>2.6内核包含了一对新机制打算来提供快速地, 无锁地存取一个共享资源. seqlock 在这种情况下工作, 要保护的资源小, 简单, 并且常常被存取, 并且很少写存取但是必须要快. 基本上, 它们通过允许读者释放对资源的存取, 但是要求这些读者来检查与写者的冲突而工作, 并且当发生这样的冲突时, 重试它们的存取. seqlock 通常不能用在保护包含指针的数据结构, 因为读者可能跟随着一个无效指针而写者在改变数据结构.</p><p>seqlock 定义在 &lt;linux/seqlock.h&gt;. 有 2 个通常的方法来初始化一个 seqlock( 有 seqlock_t 类型 ):</p><pre class="programlisting">seqlock_t lock1 = SEQLOCK_UNLOCKED;seqlock_t lock2;seqlock_init(&amp;lock2);</pre><p>读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作. 在退出时, 那个序列值与当前值比较; 如果不匹配, 读存取必须重试. 结果是, 读者代码象下面的形式:</p><pre class="programlisting">unsigned int seq;do {    seq = read_seqbegin(&amp;the_lock);    /* Do what you need to do */} while read_seqretry(&amp;the_lock, seq);</pre><p>这个类型的锁常常用在保护某种简单计算, 需要多个一致的值. 如果这个计算最后的测试表明发生了一个并发的写, 结果被简单地丢弃并且重新计算.</p><p>如果你的 seqlock 可能从一个中断处理里存取, 你应当使用 IRQ 安全的版本来代替:</p><pre class="programlisting">unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);</pre><p>写者必须获取一个排他锁来进入由一个 seqlock 保护的临界区. 为此, 调用:</p><pre class="programlisting">void write_seqlock(seqlock_t *lock); </pre><p>写锁由一个自旋锁实现, 因此所有的通常的限制都适用. 调用:</p><pre class="programlisting">void write_sequnlock(seqlock_t *lock); </pre><p>来释放锁. 因为自旋锁用来控制写存取, 所有通常的变体都可用:</p><pre class="programlisting">void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);void write_seqlock_irq(seqlock_t *lock);void write_seqlock_bh(seqlock_t *lock);void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);void write_sequnlock_irq(seqlock_t *lock);void write_sequnlock_bh(seqlock_t *lock);</pre><p>还有一个 write_tryseqlock 在它能够获得锁时返回非零.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ReadCopyUpdate.sect"></a>5.7.5.&#160;读取-拷贝-更新</h3></div></div></div><p>读取-拷贝-更新(RCU) 是一个高级的互斥方法, 能够有高效率在合适的情况下. 它在驱动中的使用很少但是不是没人知道, 因此这里值得快速浏览下. 那些感兴趣 RCU 算法的完整细节的人可以在由它的创建者出版的白皮书中找到( http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html).</p><p>RCU 对它所保护的数据结构设置了不少限制. 它对经常读而极少写的情况做了优化. 被保护的资源应当通过指针来存取, 并且所有对这些资源的引用必须由原子代码持有. 当数据结构需要改变, 写线程做一个拷贝, 改变这个拷贝, 接着使相关的指针对准新的版本 -- 因此, 有了算法的名子. 当内核确认没有留下对旧版本的引用, 它可以被释放.</p><p>作为在真实世界中使用 RCU 的例子, 考虑一下网络路由表. 每个外出的报文需要请求检查路由表来决定应当使用哪个接口. 这个检查是快速的, 并且, 一旦内核发现了目标接口, 它不再需要路由表入口项. RCU 允许路由查找在没有锁的情况下进行, 具有相当多的性能好处. 内核中的 Startmode 无线 IP 驱动也使用 RCU 来跟踪它的设备列表.</p><p>使用 RCU 的代码应当包含 &lt;linux/rcupdate.h&gt;.</p><p>在读这一边, 使用一个 RCU-保护的数据结构的代码应当用 rcu_read_lock 和 rcu_read_unlock 调用将它的引用包含起来. 结果就是, RCU 代码往往是象这样:</p><pre class="programlisting">struct my_stuff *stuff; rcu_read_lock();stuff = find_the_stuff(args...);do_something_with(stuff);rcu_read_unlock();</pre><p>rcu_read_lock 调用是快的; 它禁止内核抢占但是没有等待任何东西. 在读"锁"被持有时执行的代码必须是原子的. 在对 rcu_read_unlock 调用后, 没有使用对被保护的资源的引用.</p><p>需要改变被保护的结构的代码必须进行几个步骤. 第一步是容易的; 它分配一个新结构, 如果需要就从旧的拷贝数据, 接着替换读代码所看到的指针. 在此, 对于读一边的目的, 改变结束了. 任何进入临界区的代码看到数据的新版本.</p><p>剩下的是释放旧版本. 当然, 问题是在其他处理器上运行的代码可能仍然有对旧数据的一个引用, 因此它不能立刻释放. 相反, 写代码必须等待直到它知道没有这样的引用存在了. 因为所有持有对这个数据结构引用的代码必须(规则规定)是原子的, 我们知道一旦系统中的每个处理器已经被调度了至少一次, 所有的引用必须消失. 这就是 RCU 所做的; 它留下了一个等待直到所有处理器已经调度的回调; 那个回调接下来被运行来进行清理工作.</p><p>改变一个 RCU-保护的数据结构的代码必须通过分配一个 struct rcu_head 来获得它的清理回调, 尽管不需要以任何方式初始化这个结构. 常常, 那个结构被简单地嵌入在 RCU 所保护的大的资源里面. 在改变资源完成后, 应当调用:</p><pre class="programlisting">void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);</pre><p>给定的 func 在释放资源是安全的时候调用; 传递给 call_rcu的是给同一个 arg. 常常, func 需要的唯一的东西是调用 kfree.</p><p>全部 RCU 接口比我们已见的要更加复杂; 它包括, 例如, 辅助函数来使用被保护的链表. 全部内容见相关的头文件.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch05s06.html">上一页</a>&#160;</td><td width="20%" align="center"><a accesskey="u" href="ch05.html">上一级</a></td><td width="40%" align="right">&#160;<a accesskey="n" href="ch05s08.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">5.6.&#160;锁陷阱&#160;</td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top">&#160;5.8.&#160;快速参考</td></tr></table></div></body></html>

⌨️ 快捷键说明

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