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

📄 ch16s03.html

📁 驱动程序在 Linux 内核里扮演着特殊的角色. 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口. 它们完全隐藏了设备工作的细节. 用户的活动通过一套标准化的调用来进行,
💻 HTML
📖 第 1 页 / 共 4 页
字号:
<dt><span class="term"><span>char *bio_data(struct bio *bio);</span></span></dt>
<dd><p>返回一个内核逻辑地址, 指向被传送的数据. 注意这个地址可用仅当请求的页不在高内存中; 在其他情况下调用它是一个错误. 缺省地, 块子系统不传递高内存缓冲到你的驱动, 但是如果你已使用 blk_queue_bounce_limit 改变设置, 你可能不该使用 bio_data.</p></dd>
<dt><span class="term"><span>char *bio_kmap_irq(struct bio *bio, unsigned long *flags);</span></span></dt>
<dd></dd>
<dt><span class="term"><span>void bio_kunmap_irq(char *buffer, unsigned long *flags);</span></span></dt>
<dd><p>bio_kmap_irq 给任何缓冲返回一个内核虚拟地址, 不管它是否在高或低内存. 一个原子 kmap 被使用, 因此你的驱动在这个映射被激活时不能睡眠. 使用 bio_kunmap_irq 来去映射缓冲. 注意因为使用一个原子 kmap, 你不能一次映射多于一个段.</p></dd>
</dl></div>
<p>刚刚描述的所有函数都存取当前缓冲 -- 还未被传送的第一个缓冲, 只要内核知道. 驱动常常想使用 bio 中的几个缓冲, 在它们任何一个指出完成之前(使用 end_that_request_first, 马上就讲到), 因此这些函数常常没有用. 几个其他的宏存在来使用 bio 结构的内部接口(详情见 &lt;linux/bio.h&gt;).</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Requeststructurefields.sect3"></a>16.3.4.2.&#160;请求结构成员</h4></div></div></div>
<p>现在我们有了 bio 结构如何工作的概念, 我们可以深入 struct request 并且看请求处理如何工作. 这个结构的成员包括:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>sector_t hard_sector;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>unsigned long hard_nr_sectors;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>unsigned int hard_cur_sectors;</span></span></dt>
<dd><p>追踪请求硬件完成的扇区的成员. 第一个尚未被传送的扇区被存储到 hard_sector, 已经传送的扇区总数在 hard_nr_sectors, 并且在当前 bio 中剩余的扇区数是 hard_cur_sectors. 这些成员打算只用在块子系统; 驱动不应当使用它们.</p></dd>
<dt><span class="term"><span>struct bio *bio;</span></span></dt>
<dd><p>bio 是给这个请求的 bio 结构的链表. 你不应当直接存取这个成员; 使用 rq_for_each_bio(后面描述) 代替.</p></dd>
<dt><span class="term"><span>char *buffer;</span></span></dt>
<dd><p>在本章前面的简单驱动例子使用这个成员来找到传送的缓冲. 随着我们的深入理解, 我们现在可见到这个成员仅仅是在当前 bio 上调用 bio_data 的结果.</p></dd>
<dt><span class="term"><span>unsigned short nr_phys_segments;</span></span></dt>
<dd><p>被这个请求在物理内存中占用的独特段的数目, 在邻近页已被合并后.</p></dd>
<dt><span class="term"><span>struct list_head queuelist;</span></span></dt>
<dd><p>链表结构(如同在 11 章中"链表"一节中描述的), 连接这个请求到请求队列. 如果(并且只是)你从队列中去除 blkdev_dequeue_request, 你可能使用这个列表头来跟踪这个请求, 在一个被你的驱动维护的内部列表中.</p></dd>
</dl></div>
<p>图 <a href="ch16s03.html#ldd3-16-2.fig" title="图&#160;16.2.&#160;一个带有一个部分被处理的请求的请求队列">一个带有一个部分被处理的请求的请求队列</a> 展示了请求队列和它的组件 bio 结构如何对应到一起. 在图中, 这个请求已被部分满足. cbio 和 buffer 处于指向尚未传送的第一个 bio.</p>
<div class="figure">
<a name="ldd3-16-2.fig"></a><p class="title"><b>图&#160;16.2.&#160;一个带有一个部分被处理的请求的请求队列</b></p>
<div><img src="images/ldd3-16-2.png" alt="一个带有一个部分被处理的请求的请求队列"></div>
</div>
<p>有许多不同的字段在请求结构中, 但是本节中的列表应当对大部分驱动编写者是足够的.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Barrierrequests.sect3"></a>16.3.4.3.&#160;屏障请求</h4></div></div></div>
<p>块层在你的驱动见到它们之前重新排序来提高 I/O 性能. 你的驱动, 也可以重新排序请求, 如果有理由这样做. 常常地, 这种重新排序通过传递多个请求到驱动并且使硬件考虑优化的顺序来实现. 但是, 对于不严格的请求顺序有一个问题: 有些应用程序要求保证某些操作在其他的启动前完成. 例如, 关系数据库管理者, 必须绝对确保它们的日志信息刷新到驱动器, 在执行在数据库内容上的一次交易之前. 日志式文件系统, 现在在大部分 Linux 系统中使用, 有非常类似的排序限制. 如果错误的操作被重新排序, 结果可能是严重的, 无法探测的数据破坏.</p>
<p>2.6 块层解决这个问题通过一个屏障请求的概念. 如果一个请求被标识为 REQ_HARDBARRER 标志, 它必须被写入驱动器在任何后续的请求被初始化之前. "被写入设备", 我们意思是数据必须实际位于并且是持久的在物理介质中. 许多的驱动器进行写请求的缓存; 这个缓存提高了性能, 但是它可能使屏障请求的目的失败. 如果一个电力失效在关键数据仍然在驱动器的缓存中时发生, 数据仍然被丢失即便驱动器报告完成. 因此一个实现屏障请求的驱动器必须采取步骤来强制驱动器真正写这些数据到介质中.</p>
<p>如果你的驱动器尊敬屏障请求, 第一步是通知块层这个事实. 屏障处理是另一个请求队列; 它被设置为:</p>
<pre class="programlisting">
void blk_queue_ordered(request_queue_t *queue, int flag);
</pre>
<p>为指示你的驱动实现了屏障请求, 设置 flag 参数为一个非零值.</p>
<p>实际的屏障请求实现是简单地测试在请求结构中关联的标志. 已经提供了一个宏来进行这个测试:</p>
<pre class="programlisting">
int blk_barrier_rq(struct request *req); 
</pre>
<p>如果这个宏返回一个非零值, 这个请求是一个屏障请求. 根据你的硬件如何工作, 你可能必须停止从队列中获取请求, 直到屏障请求已经完成. 另外的驱动器能理解屏障请求; 在这个情况中, 你的驱动所有的必须做的是对这些驱动器发出正确的操作.</p>
</div>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Nonretryablerequests.sect3"></a>16.3.4.4.&#160;不可重入请求</h4></div></div></div>
<p>块驱动常常试图重试第一次失败的请求. 这个做法可产生一个更加可靠的系统并且帮助来避免数据丢失. 内核, 但是, 有时标识请求为不可重入的. 这样的请求应当完全尽快失败, 如果它们无法在第一次试的时候执行.</p>
<p>如果你的驱动在考虑重试一个失败的请求, 他应当首先调用:</p>
<pre class="programlisting">
int blk_noretry_request(struct request *req); 
</pre>
<p>如果这个宏返回非零值, 你的驱动应当放弃这个请求, 使用一个错误码来代替重试它.</p>
</div>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="RequestCompletionFunctions.sect2"></a>16.3.5.&#160;请求完成函数</h3></div></div></div>
<p>如同我们将见到的, 有几个不同的方式来使用一个请求结构. 它们所有的都使用几个通用的函数, 但是, 它们处理一个 I/O 请求或者部分请求的完成. 这 2 个函数都是原子的并且可从一个原子上下文被安全地调用.</p>
<p>当你的设备已经完成传送一些或者全部扇区, 在一个 I/O 请求中, 它必须通知块子系统, 使用:</p>
<pre class="programlisting">
int end_that_request_first(struct request *req, int success, int count); 
</pre>
<p>这个函数告知块代码, 你的驱动已经完成 count 个扇区地传送, 从你最后留下的地方开始. 如果 I/O 是成功的, 传递 success 为 1; 否则传递 0. 注意你必须指出完成, 按照从第一个扇区到最后一个的顺序; 如果你的驱动和设备有些共谋来乱序完成请求, 你必须存储这个乱序的完成状态直到介入的扇区已经被传递.</p>
<p>从 end_that_request_first 的返回值是一个指示, 指示是否所有的这个请求中的扇区已经被传送或者没有. 一个 0 返回值表示所有的扇区已经被传送并且这个请求完成. 在这点, 你必须使用 blkdev_dequeue_request 来从队列中解除请求(如果你还没有这样做)并且传递它到:</p>
<pre class="programlisting">
void end_that_request_last(struct request *req); 
</pre>
<p>end_that_request_last 通知任何在等待这个请求的人, 这个请求已经完成并且回收这个请求结构; 它必须在持有队列锁时被调用.</p>
<p>在我们的简单的 sbull 例子里, 我们不使用任何上面的函数. 相反, 那个例子, 被称为 end_request. 为显示这个调用的效果, 这里有整个的 end_request 函数, 如果在 2.6.10 内核中见到的:</p>
<pre class="programlisting">
void end_request(struct request *req, int uptodate)
{

 if (!end_that_request_first(req, uptodate, req-&gt;hard_cur_sectors)) {
 add_disk_randomness(req-&gt;rq_disk);
 blkdev_dequeue_request(req);
 end_that_request_last(req);
 }
}
</pre>
<p>函数 add_disk_randomness 使用块 I/O 请求的定时来贡献熵给系统的随机数池; 它应当被调用仅当磁盘的定时是真正的随机的. 对大部分的机械设备这是真的, 但是对一个基于内存的虚拟设备它不是真的, 例如 sbull. 因此, 下一节中更复杂的 sbull 版本不调用 add_disk_randomness.</p>
<div class="sect3" lang="zh-cn">
<div class="titlepage"><div><div><h4 class="title">
<a name="Workingwithbios.sect3"></a>16.3.5.1.&#160;使用 bio</h4></div></div></div>
<p>现在你了解了足够多的来编写一个块驱动, 可直接使用组成一个请求的 bio 结构. 但是, 一个例子可能会有帮助. 如果这个 sbull 驱动被加载为 request_mode 参数被设为 1, 它注册一个知道 bio 的请求函数来代替我们上面见到的简单函数. 那个函数看来如此:</p>
<pre class="programlisting">
static void sbull_full_request(request_queue_t *q)
{
        struct request *req;
        int sectors_xferred;
        struct sbull_dev *dev = q-&gt;queuedata;
        while ((req = elv_next_request(q)) != NULL) {
                if (! blk_fs_request(req)) {
                        printk (KERN_NOTICE "Skip non-fs request\n");

                        end_request(req, 0);
                        continue;
                }
                sectors_xferred = sbull_xfer_request(dev, req);
                if (! end_that_request_first(req, 1, sectors_xferred)) {
                        blkdev_dequeue_request(req);
                        end_that_request_last(req);
                }
        }
}
</pre>
<p>这个函数简单地获取每个请求, 传递它到 sbull_xfer_request, 接着使用 end_that_request_first 和, 如果需要, end_that_request_last 来完成它. 因此, 这个函数在处理高级队列并且请求管理部分问题. 真正执行一个请求的工作, 但是, 落入 sbull_xfer_request:</p>
<pre class="programlisting">
static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
        struct bio *bio;

⌨️ 快捷键说明

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