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

📄 ch16s03.html

📁 真正LDD3中文
💻 HTML
📖 第 1 页 / 共 3 页
字号:
<dd><p>如果你的设备已到到达一个状态, 它不能处理等候的命令, 你可调用 blk_stop_queue 来告知块层. 在这个调用之后, 你的请求函数将不被调用直到你调用 blk_start_queue. 不用说, 你不应当忘记重启队列, 当你的设备可处理更多请求时. 队列锁必须被持有当调用任何一个这些函数时.</p></dd><dt><span class="term"><span>void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);</span></span></dt><dd><p>告知内核你的设备可进行 DMA 的最高物理地址的函数. 如果一个请求包含一个超出这个限制的内存引用, 一个反弹缓冲将被用来给这个操作; 当然, 这是一个进行块 I/O 的昂贵方式, 并且应当尽量避免. 你可在这个参数中提供任何可能的值, 或者使用预先定义的符号 BLK_BOUNCE_HIGH(使用反弹缓冲给高内存页), BLK_BOUNCE_ISA (驱动只可 DMA 到 16MB 的 ISA 区), 或者BLK_BOUCE_ANY(驱动可进行 DMA 到任何地址). 缺省值是 BLK_BOUNCE_HIGH.</p></dd><dt><span class="term"><span>void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);</span></span></dt><dd></dd><dt><span class="term"><span>void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);</span></span></dt><dd></dd><dt><span class="term"><span>void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);</span></span></dt><dd></dd><dt><span class="term"><span>void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max);</span></span></dt><dd><p>设置参数的函数, 这些参数描述可被设备满足的请求. blk_queue_max 可用来以扇区方式设置任一请求的最大的大小; 缺省是 255. blk_queue_max_phys_segments 和 blk_queue_max_hw_segments 都控制多少物理段(系统内存中不相邻的区)可包含在一个请求中. 使用 blk_queue_max_phys_segments 来说你的驱动准备处理多少段; 例如, 这可能是一个静态分配的散布表的大小. blk_queue_max_hw_segments, 相反, 是设备可处理的最多的段数. 这 2 个参数缺省都是 128. 最后, blk_queue_max_segment_size 告知内核任一个请求的段可能是多大字节; 缺省是 65,536 字节.</p></dd><dt><span class="term"><span>blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);</span></span></dt><dd><p>一些设备无法处理跨越一个特殊大小内存边界的请求; 如果你的设备是其中之一, 使用这个函数来告知内核这个边界. 例如, 如果你的设备处理跨 4-MB 边界的请求有困难, 传递一个 0x3fffff 掩码. 缺省的掩码是 0xffffffff.</p></dd><dt><span class="term"><span>void blk_queue_dma_alignment(request_queue_t *queue, int mask);</span></span></dt><dd><p>告知内核关于你的设备施加于 DMA 传送的内存对齐限制的函数. 所有的请求被创建有给定的对齐, 并且请求的长度也匹配这个对齐. 缺省的掩码是 0x1ff, 它导致所有的请求被对齐到 512-字节边界. </p></dd><dt><span class="term"><span>void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);</span></span></dt><dd><p>告知内核你的设备的硬件扇区大小. 所有由内核产生的请求是这个大小的倍数并且被正确对齐. 所有的在块层和驱动之间的通讯继续以 512-字节扇区来表达, 但是.</p></dd></dl></div></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="TheAnatomyofaRequest.sect2"></a>16.3.4.&#160;请求的分析</h3></div></div></div><p>在我们的简单例子里, 我们遇到了这个请求结构. 但是, 我们未曾接触这个复杂的数据结构. 在本节, 我们看, 详细地, 块 I/O 请求在 Linux 内核中如何被表示.</p><p>每个请求结构代表一个块 I/O 请求, 尽管它可能是由几个独立的请求在更高层次合并而成. 对任何特殊的请求而传送的扇区可能分布在整个主内存, 尽管它们常常对应块设备中的多个连续的扇区. 这个请求被表示为多个段, 每个对应一个内存中的缓冲. 内核可能合并多个涉及磁盘上邻近扇区的请求, 但是它从不合并在单个请求结构中的读和写操作. 内核还确保不合并请求, 如果结果会破坏任何的在前面章节中描述的请求队列限制.</p><p>基本上, 一个请求结构被实现为一个 bio 结构的链表, 结合一些维护信息来使驱动可以跟踪它的位置, 当它在完成这个请求中. 这个 bio 结构是一个块 I/O 请求移植的低级描述; 我们现在看看它.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Thebiostructure.sect3"></a>16.3.4.1.&#160;bio 结构</h4></div></div></div><p>当内核, 以一个文件系统的形式, 虚拟文件子系统, 或者一个系统调用, 决定一组块必须传送到或从一个块 I/O 设备; 它装配一个 bio 结构来描述那个操作. 那个结构接着被递给这个块 I/O 代码, 这个代码合并它到一个存在的请求结构, 或者, 如果需要, 创建一个新的. 这个 bio 结构包含一个块驱动需要来进行请求的任何东西, 而不必涉及使这个请求启动的用户空间进程.</p><p>bio 结构, 在 &lt;linux/bio.h&gt; 中定义, 包含许多成员对驱动作者是有用的:</p><div class="variablelist"><dl><dt><span class="term"><span>sector_t bi_sector;</span></span></dt><dd><p>这个 bio 要被传送的第一个(512字节)扇区.</p></dd><dt><span class="term"><span>unsigned int bi_size;</span></span></dt><dd><p>被传送的数据大小, 以字节计. 相反, 常常更易使用 bio_sectors(bio), 一个给定以扇区计的大小的宏.</p></dd><dt><span class="term"><span>unsigned long bi_flags;</span></span></dt><dd><p>一组描述 bio 的标志; 最低有效位被置位如果这是一个写请求(尽管宏 bio_data_dir(bio)应当用来代替直接加锁这个标志).</p></dd><dt><span class="term"><span>unsigned short bio_phys_segments;</span></span></dt><dd></dd><dt><span class="term"><span>unsigned short bio_hw_segments;</span></span></dt><dd><p>包含在这个 BIO 中的物理段的数目, 和在 DMA 映射完成后被硬件看到的段数目, 分别地.</p></dd></dl></div><p>一个 bio 的核心, 但是, 是一个称为 bi_io_vec 的数组, 它由下列结构组成:</p><pre class="programlisting">struct bio_vec { struct page  *bv_page;  unsigned int  bv_len;  unsigned int  bv_offset;  };  </pre><p>图 <a href="ch16s03.html#ldd3-16-1.fig" title="图&#160;16.1.&#160;bio 结构">bio 结构</a>显示了这些结构如何结合在一起. 如同你所见到的, 在一个块 I/O 请求被转换为一个 bio 结构后, 它已被分为单独的物理内存页. 所有的一个驱动需要做的事情是步进全部这个结构数组(它们有 bi_vcnt 个), 和在每个页内传递数据(但是只 len 字节, 从 offset 开始).</p><div class="figure"><a name="ldd3-16-1.fig"></a><p class="title"><b>图&#160;16.1.&#160;bio 结构</b></p><div><img src="images/snagitldd3/ldd3-16-1.png" alt="bio 结构"></div></div><p>直接使用 bi_io_vec 数组不被推荐, 为了内核开发者可以在以后改变 bio 结构而不会引起破坏. 为此, 一组宏被提供来简化使用 bio 结构. 开始的地方是 bio_for_each_segment, 它简单地循环 bi_io_vec 数组中每个未被处理的项. 这个宏应当如下用: </p><pre class="programlisting">int segno;struct bio_vec *bvec;bio_for_each_segment(bvec, bio, segno) { /* Do something with this segment}</pre><p>在这个循环中, bvec 指向当前的 bio_vec 项, 并且 segno 是当前的段号. 这些值可被用来设置 DMA 发送器(一个使用 blk_rq_map_sg 的替代方法在"块请求和 DMA"一节中描述). 如果你需要直接存取页, 你应当首先确保一个正确的内核虚拟地址存在; 为此, 你可使用:</p><pre class="programlisting">char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type); void __bio_kunmap_atomic(char *buffer, enum km_type type);</pre><p>这个底层的函数允许你直接映射在一个给定的 bio_vec 中找到的缓冲, 由索引 i 所指定的. 一个原子的 kmap 被创建; 调用者必须提供合适的来使用的槽位(如同在 15 章的"内存映射和 struct page"一节中描述的).</p><p>块层还维护一组位于 bio 结构的指针来跟踪请求处理的当前状态. 几个宏来提供对这个状态的存取:</p><div class="variablelist"><dl><dt><span class="term"><span>struct page *bio_page(struct bio *bio);</span></span></dt><dd><p>返回一个指向页结构的指针, 表示下一个被传送的页.</p></dd><dt><span class="term"><span>int bio_offset(struct bio *bio);</span></span></dt><dd><p>返回页内的被传送的数据的偏移.</p></dd><dt><span class="term"><span>int bio_cur_sectors(struct bio *bio);</span></span></dt><dd><p>返回要被传送出当前页的扇区数.</p></dd><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/snagitldd3/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">

⌨️ 快捷键说明

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