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

📄 (ldd) ch12-加载块设备驱动程序(转载).htm

📁 LINUX驱动编程
💻 HTM
📖 第 1 页 / 共 4 页
字号:
            color=#ffffff>联系</FONT></A></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE borderColor=#666666 cellPadding=2 width="90%" align=center border=2>
  <TBODY>
  <TR>
    <TD bgColor=#000000>
      <P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT 
      color=#ffffff size=2>目录页</FONT></A> | <A 
      href="http://joyfire.net/lsdp/14.htm"><FONT color=#ffffff 
      size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/16.htm"><FONT 
      color=#ffffff size=2>下一页</FONT></A></P>
      <P align=center><FONT face=黑体 color=#ffffff size=6>(LDD) 
      Ch12-加载块设备驱动程序(转载) </FONT></P><SPAN 
      style="LINE-HEIGHT: 1; LETTER-SPACING: 0pt"><FONT color=#ffffff size=3>
      <P>发信人:&nbsp;Altmayer&nbsp;(alt),&nbsp;信区:&nbsp;GNULinux<BR>标&nbsp;&nbsp;题:&nbsp;(LDD)&nbsp;Ch12-加载块设备驱动程序(转载)<BR>发信站:&nbsp;饮水思源&nbsp;(2001年12月13日08:57:39&nbsp;星期四),&nbsp;站内信件<BR>&nbsp;<BR>【&nbsp;以下文字转载自&nbsp;<FONT 
      color=#00ff00>UNIXpost&nbsp;</FONT>讨论区&nbsp;】<BR>【&nbsp;原文由<FONT 
      color=#00ff00>&nbsp;altmayer.bbs@bbs.nju.edu.cn,</FONT>&nbsp;所发表&nbsp;】<BR>&nbsp;<BR>【&nbsp;以下文字转载自&nbsp;<FONT 
      color=#00ff00>altmayer&nbsp;</FONT>的信箱&nbsp;】<BR>&nbsp;<BR>第十二章&nbsp;加载块设备驱动程序<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>正如在第一章“Linux核心简介”中“设备与模块的分类”中所概述的一样,Unix的设备<BR>驱动程序并不仅限于字符设备。本章就来介绍一下第二大类的设备驱动程序——块设备<BR>驱动程序。所谓面向块的设备是指数据传输是以块为单位的(例如软盘和硬盘),这里<BR>硬件的块一般被称作“扇区(Sector)”。而名词“块”常用来指软件上的概念:驱动<BR>程序常常使用1KB大小的块,即使扇区大小为512字节。<BR>&nbsp;<BR>在这一章,我们将来构造一个全特征的块设备驱动程序sbull(Simple&nbsp;Block&nbsp;Utility<BR>for&nbsp;Loading&nbsp;Localities)。这个驱动程序与scull类似,也是使用计算机的内存作为硬<BR>件设备。换句话说,它是一个RAM-disk的驱动程序。sbull可以在任何Linux计算机上执<BR>行(不过我只在有限的几个平台上作过测试)。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>行(不过我只在有限的几个平台上作过测试)。<BR>&nbsp;<BR>注册驱动程序<BR>&nbsp;<BR>和字符设备驱动程序类似,核心里的块设备驱动程序也是由一个主设备号来标识。用来<BR>对其进行注册和取消注册的函数是:<BR>&nbsp;<BR>int&nbsp;register_blkdev(unsigned&nbsp;int&nbsp;major,&nbsp;const&nbsp;char*name,&nbsp;struct<BR>file_operations&nbsp;*fops)<BR>&nbsp;<BR>int&nbsp;unregister_blkdev(unsigned&nbsp;int&nbsp;major,&nbsp;const&nbsp;char*name);<BR>&nbsp;<BR>参数的含义与字符设备驱动程序一样,对主设备号的动态赋值也类似。因此,一个sbull<BR>设备与scull一样将自己注册:<BR>&nbsp;<BR>result=register_blkdev(sbull_major,“sbull”,$sbull_fops);<BR>&nbsp;<BR>if(result&lt;0){<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printk(KERN_WARNING“sbull:can’t&nbsp;get&nbsp;major&nbsp;%d\n”,sbull_major);<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;result;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;<BR>if&nbsp;(sbull_major==0)&nbsp;&nbsp;sbull_major=result;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*dynamic*/<BR>&nbsp;<BR>major=sbull_major;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*Use&nbsp;“major”later&nbsp;on&nbsp;to&nbsp;save<BR>typing*/<BR>&nbsp;<BR>register_blkdev&nbsp;的fops参数与我们在字符设备驱动程序中使用的类似,为read,write<BR>以及fsync的操作并不要求针对某个驱动程序。通用函数block_read,&nbsp;block_write及blo<BR>ck_fsync被用来代替任何针对某个驱动程序的函数。另外,check_media_change和reval<BR>idate对块设备驱动程序也有意义,二者都在sbull_fops中定义。<BR>&nbsp;<BR>在sbull中使用的fops结构如下:<BR>&nbsp;<BR>(代码236)<BR>&nbsp;<BR>通用的读写操作被用来获得较高的性能。通过数据缓冲获得加速,这在字符设备驱动程<BR>序重中是没有的。块设备驱动程序可以被缓冲是因为它们的数据服从于计算机的文件层<BR>次结构,任何应用程序都无法直接访问,而字符设备驱动程序则不是这样。<BR>&nbsp;<BR>不过,当缓冲的高速缓存不能满足一个读请求或当一个待处理的写操作要刷新到物理磁<BR>盘上时,驱动程序必须被调用来进行真正的数据传送。fops结构除了read和write外,并<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>盘上时,驱动程序必须被调用来进行真正的数据传送。fops结构除了read和write外,并<BR>不带有入口点,因此,必须要一个额外的结构blk_dev_struct来发出对实际数据传送的<BR>请求。<BR>&nbsp;<BR>这个结构在&lt;linux/blkdev.h&gt;定义,它有几个域,但只有第一个域需被驱动程序设置。<BR>下面是这个结构在核心2.0中的定义。<BR>&nbsp;<BR>(代码237)<BR>&nbsp;<BR>当核心需要为sbull设备产生一个I/O操作时,它便调用函数blk_dev[sbull_major].requ<BR>est_fn。因此这个模块的初始化函数须设置这个域使其指向它自己的请求函数。这个结<BR>构中的其它域只供核心函数或宏进行内部使用;你不必在你的代码段中显式地使用它们<BR>。<BR>&nbsp;<BR>一个块设备驱动程序模块与核心的关系见图12-1。<BR>&nbsp;<BR>除了blk_dev还有几个数组带有块设备驱动程序的信息。这些数组一般由主设备号(有时<BR>也用次设备号)进行索引。它们在drivers/block/ll_rw_block.c中被声明和描述。<BR>&nbsp;<BR>int&nbsp;blk_size[][];<BR>&nbsp;<BR>这个数组由主设备号和次设备号索引。它以KB为单位描述了每个设备的大小。如果blk_s<BR>ize[major]是NULL,则不对这个设备的大小进行检查(也就是说,核心可能要求数据传<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>ize[major]是NULL,则不对这个设备的大小进行检查(也就是说,核心可能要求数据传<BR>送通过end_of_device)。<BR>&nbsp;<BR>int&nbsp;blksize_size[][];<BR>&nbsp;<BR>被每个设备所使用的块的大小,以字节为单位。与上一个数组类似,这个二维数组也是<BR>由主设备号和次设备号索引。如果blksize_size[major]是一个空指针,那么便假设其块<BR>大小为BLOCK_SIZE(目前是1KB)。块大小必须是2的幂,因为核心使用移位操作将偏移<BR>量转换为块号。<BR>&nbsp;<BR>int&nbsp;hardsect_size[][];<BR>&nbsp;<BR>与其它的一样,这个数据结构也是由主设备号和次设备号索引。硬件扇区的缺省大小为5<BR>12字节。直到包括2.0.X版本为止,可变扇区大小仍未真正支持,因为一些核心代码仍旧<BR>假设扇区大小为半KB。不过很可能在2.2版本中会真正实现可变扇区大小。<BR>&nbsp;<BR>int&nbsp;read_ahead[];<BR>&nbsp;<BR>这个数组由主设备号索引,它定义了一个文件被顺序读取时,核心可以提前读取多少扇<BR>区。在进程请求数据之前将其读出可以改善系统的性能及总的吞吐率。慢速设备最好指<BR>定一个较大的提前读的值,而一个快速设备则可以在较小的提前读的值下工作的很好。<BR>这个提前读的值越大缓冲高速缓存则需要越多的内存。每个主设备号有一个提前读的值<BR>,它对所有次设备号有效。这个值可以通过驱动程序的ioctl方法来改变;硬盘驱动程序<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>,它对所有次设备号有效。这个值可以通过驱动程序的ioctl方法来改变;硬盘驱动程序<BR>一般设为8个扇区,对应着4KB。<BR>&nbsp;<BR>sbull设备允许在加载时设置这些值,它们作用于示例驱动程序的所有次设备号。在sbul<BR>l中变量名和它们的缺省值为:<BR>&nbsp;<BR>size=2048(KB)<BR>&nbsp;<BR>由sbull生成的每个ramdisk占两兆字节,正如系统的缺省值。<BR>&nbsp;<BR>hardsect=512(B)<BR>&nbsp;<BR>sbull扇区大小是常用的半KB值。改变hardsect的值是不允许的。<BR>&nbsp;<BR>如前所述,其它的扇区大小并不被支持。如果你一定要改它,可以将sbull/sbull.c中的<BR>安全检查去掉。不过请做好发生严重的内存崩溃的危险的准备。除非在你尝试时,已经<BR>加上了对可变扇区大小的支持。<BR>&nbsp;<BR>rahead=2(扇区)<BR>&nbsp;<BR>因为ramdisk是一个快速设备,所以这个缺省提前读的值比较小。<BR>&nbsp;<BR>sbull设备也允许你选择一个设备个数进行安装。devs是设备个数,缺省设为2,表明缺<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>sbull设备也允许你选择一个设备个数进行安装。devs是设备个数,缺省设为2,表明缺<BR>省内存使用量为4兆——2个大小为2MB的盘。<BR>&nbsp;<BR>sbull设备的init_module的实现如下(不含主设备号的注册和错误恢复):<BR>&nbsp;<BR>(代码239)<BR>&nbsp;<BR>相应的清除函数如下所示:<BR>&nbsp;<BR>(代码240)<BR>&nbsp;<BR>这里,调用fsync_dev是必须的,用以清除核心保存在不同高速缓存中的对设备的所有引<BR>用。事实上,fsync_dev是运行在block--_fsync之后的引擎,它是块设备的fsync“方法<BR>”。<BR>&nbsp;<BR>头文件&nbsp;blk.h<BR>&nbsp;<BR>由于块设备驱动程序的绝大部分是设备无关的,核心的开发者通过把大部分相同的代码放<BR>在一个头文件&lt;linux/blk.h&gt;中,来试图简化驱动程序的代码。因此,每个块设备驱动程<BR>序都必须包含这个头文件,在&lt;linux/blk.h&gt;中定义的最重要的函数是end_request,它<BR>被声明为static(静态)的。让它成为静态的,使得不同驱动程序可有一个正确定义的e<BR>nd_request,而不需要每个都写自己的实现。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>在Linux1.2中,这个头文件应该用&lt;linux/../../drivers/block/blk.h&gt;来包含。原因在<BR>于当时还不支持自定义的块设备驱动程序,而这个头文件最初位于drivers/block源码目<BR>录下。<BR>&nbsp;<BR>实际上,blk.h相当不寻常,比如它定义了几个基于符号MAJOR_NR的符号,而MAJOR_NR必<BR>须由驱动程序在它包含这个头文件之前声明。这里,我们再次看到blk.h在设计时并没有<BR>真正考虑自定义驱动程序。<BR>&nbsp;<BR>看看blk.h,你会发现几个设备相关的符号是按照MAJOR_NR的值声明的,也就是说MAJOR_<BR>NR应该提前知道。然而,如果主设备号是动态赋值的,驱动程序无法预知其值,因此也<BR>就不能正确定义MAJOR_NR。如果MAJOR_NR未定义,blk.h就不能设定一些在end_request<BR>中使用的宏。因此,为了让自定义驱动程序从通用的end_request函数受益,从而避免重<BR>新实现它,驱动程序必须在包含blk.h之前定义MAJOR_NR和其它几个符号。<BR>&nbsp;<BR>下面的列表描述了一些必须提前定义的&lt;linux/blk.h&gt;中的符号。列表结尾给出了sbull<BR>中使用的代码。<BR>&nbsp;<BR>MAJOR_NR<BR>&nbsp;<BR>这个符号用来访问一些数组,特别是blk-_dev和blksize-_size。自定义驱动程序(如sb<BR>ull)不能给这个符号赋一个常量值,可以将其定义(#define)为一个存有主设备号的<BR>变量。对sbull而言,它是sbull-_major。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>变量。对sbull而言,它是sbull-_major。<BR>&nbsp;<BR>DEVICE_NAME<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;被生成的设备名。这个字符串用来从end_request中打印错误信息。<BR>&nbsp;<BR>DEVICE_NR(kdev_t&nbsp;device)<BR>&nbsp;<BR>这个符号用来从kdev_t设备号中抽取物理设备的序号。这个宏的值可以是MINOR(device)<BR>或别的表达式。这要依据给设备或分区分配次设备号的常规方式而定。对同一个物理设<BR>备上的所有分区,这个宏应返回同一个设备号——也就是说,DEVICE_NR表达的是磁盘号<BR>,而不是分区号。这个符号被用来声明CURRENT_DEV,它在request_fn中用来确定被一个<BR>传送请求访问的硬件设备的次设备号,可分区设备将在后面“可分区设备”一节中介绍<BR>。<BR>&nbsp;<BR>DEVICE_INTR<BR>&nbsp;<BR>这个符号用来声明一个指向当前下半部处理程序的指针变量。宏SET_INTR(intr)和CLEAR<BR>_INTR用来给这个变量赋值。当设备可以发出具有不同含义的中断时,使用多个处理程序<BR>是很方便的。这个主题将在后面“中断驱动的块设备驱动程序”一节中讨论。<BR>&nbsp;<BR>TIMEOUT_VALUE<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>DEBICE_TIMEOUT<BR>&nbsp;<BR>TIMEOUT_VALUE以记数的方式表达超时,这个超时的值与老计时器之一(特别地指计时器<BR>号DEVICE_TIMEOUT)相关联。一个驱动程序可以在数据传送时间太长时,通过调用一个<BR>回调函数来检测错误条件。不过,由于老计时器由一个预赋值的计时器静态数组组成(<BR>见第六章“时间流”中“核心计时器”一节),一个自定义的驱动程序不能使用它们。<BR>我在sbull中对这两个符号都未定义,而是用一个新的计时器实现超时。<BR>&nbsp;<BR>DEBICE_NO_RANDOM<BR>&nbsp;<BR>在缺省情况下,函数end_request对系统熵值(即所有随机性的总量)有所贡献,这被/d<BR>ev/random所使用。如果一个设备不能对随机设备贡献显著的熵值,DEVICE_NO_RANDOM应<BR>被定义。/dev/random在第九章的“安装中断处理程序”中进行了介绍,SA_SAMPLE_RAND<BR>OM也在那儿做了解释。<BR>&nbsp;<BR>DEVICE_OFF(kdev_t&nbsp;device)<BR>&nbsp;<BR>end_request函数在结束时调用这个宏。例如在软盘驱动程序中,它调用一个函数,这个<BR>函数负责更新用来控制马达停转的一个计时器。如果设备没有被关掉,那么串DEVICE_OF<BR>F可以被定义为空。sbull不使用DEVICE_OFF。<BR>&nbsp;<BR>DEVICE_ON(kdev_t&nbsp;device)<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>DEVICE_ON(kdev_t&nbsp;device)<BR>&nbsp;<BR>DEVICE_REQUEST<BR>&nbsp;<BR>这些函数实际上并未在Linux的头文件中使用,所以驱动程序并不需要定义它们。大多数<BR>官方的Linux设备驱动程序声明这些符号并在内部使用它们,但我在sbull里并没有使用<BR>它。<BR>&nbsp;<BR>sbull驱动程序以如下的方式声明这些符号:<BR>&nbsp;<BR>(代码242)<BR>&nbsp;<BR>头文件blk.h用上面列出的这些宏定义了一些可以由驱动程序使用的额外的宏,我将在后<BR>续章节里对之进行介绍。<BR>&nbsp;<BR>处理请求<BR>&nbsp;<BR>系统性能的方式排序。这些联结表中的请求被传递个驱动程序的请求函数,由它对链接<BR>表中的每个请求执行如下的任务:<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;检查当前请求的有效性。这个工作由在blk.h中定义的宏INIT_REQUEST完成。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;进行实际的数据传送。用变量CURRENT(实际上是个宏)可以获得发出请求的一<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;进行实际的数据传送。用变量CURRENT(实际上是个宏)可以获得发出请求的一<BR>些细节。CURRENT是一个指向结构request的指针,我将在下节介绍这个结构的域。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;清除当前的请求。这个操作由静态函数end_request完成,函数的代码在blk.h<BR>中。驱动程序向这个函数传递一个参数,即成功时为1,失败时为0。当end_request以参<BR>数0调用时,一个“I/O&nbsp;error”消息会被发给系统日志(通过printk)。<BR>&nbsp;<BR>l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;循环回至开始,消化下一个请求。可以按照程序员的喜好使用一个goto或是一<BR>个for(;;),或者while(1)。<BR>&nbsp;<BR>实践中,请求函数的代码如下构造:<BR>&nbsp;<BR>(代码243)<BR>&nbsp;<BR>尽管这段代码除了打印消息外什么都没有做,运行这个函数可以对数据传送的基本设计<BR>有一个很好的了解。到此为止,代码中唯一不清楚的地方是CURRENT的确切含义及它的域<BR>,这个我将在下一节介绍。<BR>&nbsp;<BR>我的第一个sbull实现只包含了所示的空代码。我意在一个“不存在”的设备上构造一个<BR>文件系统,并使用它一会儿,只要数据仍在缓冲高速缓存中。在运行一个象这样罗嗦的<BR>请求函数时,看看系统日志能帮助你理解缓冲高速缓存是如何工作的。<BR>&nbsp;<BR>在编译时,定义符号SBULL_EMPTY_REQUEST,那么这个空且罗嗦的函数可以在sbull&nbsp;中运<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>在编译时,定义符号SBULL_EMPTY_REQUEST,那么这个空且罗嗦的函数可以在sbull&nbsp;中运<BR>行。如果你想理解核心是如何处理不同块大小的,你可以在insmod命令行上实验blksize<BR>=。这个空的请求函数通过打印每个请求的细节揭示了内部核心的工作情况。你或许也可<BR>以试试hardsect=,但目前它被关闭了,因为比较危险。(见本章开始时的“注册驱动程<BR>序”)。<BR>&nbsp;<BR>请求函数的代码并不显式地调用return(),因为当列表中的待处理请求耗尽时,INIT-_RE<BR>QUEST会替你完成这个工作。<BR>&nbsp;<BR>执行实际的数据传送<BR>&nbsp;<BR>为了给sbull构造一个可以工作的数据传送,让我们先来看看核心是如何在结构request<BR>中描述一个请求的。这个结构在&lt;linux/blkdev.h&gt;中定义。通过访问CURRENT的域,驱动<BR>程序可以得到所有为在缓冲高速缓存的物理块设备之间传送数据所需要的信息。<BR>&nbsp;<BR>CURRENT是用来访问当前请求(即被首先服务的那个请求)。正如你可能猜到的,CURREN<BR>T是blk_dev[MAJOR_NR].current_request的缩短形式。<BR>&nbsp;<BR>下面这些当前请求的域包含了请求函数的有用信息:<BR>&nbsp;<BR>kdev_t&nbsp;rq_dev;<BR>&nbsp;<BR>请求所访问的设备。有本驱动程序所管理的所以设备均被使用同一个请求函数。一个请<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>请求所访问的设备。有本驱动程序所管理的所以设备均被使用同一个请求函数。一个请<BR>求函数处理所有的次设备号;rq_dev可以被用来取得被操作的次设备。尽管Linux1.2称<BR>这个域为dev,你仍然可以通过宏CURRENT_DEV来访问这个域。CURRENT_DEV在我们所讨论<BR>的所有版本的核心中是可移植的。<BR>&nbsp;<BR>int&nbsp;cmd;<BR>&nbsp;<BR>这个域是READ或WRITE。<BR>&nbsp;<BR>unsigned&nbsp;long&nbsp;sector;<BR>&nbsp;<BR>请求指向的第一个扇区。<BR>&nbsp;<BR>unsigned&nbsp;long&nbsp;current_nr_sectors;<BR>&nbsp;<BR>unsigned&nbsp;long&nbsp;nr_sectors;<BR>&nbsp;<BR>当前请求的扇区数(大小)。驱动程序应该引用current_nr_sectors,而应该忽略nr_sec<BR>tors(列在这里只是为了完整)。请看下一节“集簇请求”以获得更多的细节。<BR>&nbsp;<BR>char&nbsp;*buffer<BR>&nbsp;<BR>缓冲高速缓存中的域。如果cmd==READ,就是写数据的位置;如果cmd==WRITE,就是读数<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>缓冲高速缓存中的域。如果cmd==READ,就是写数据的位置;如果cmd==WRITE,就是读数<BR>据的位置。<BR>&nbsp;<BR>struct&nbsp;buffer_head&nbsp;*bh<BR>&nbsp;<BR>这个结构描述了这个请求列表中的第一个缓冲区。我们将在“集簇请求”中用到这个域<BR>。<BR>&nbsp;<BR>在这个结构中还有其它的一些域,但它们基本上是核心内部使用的,驱动程序并不期望<BR>使用它们。<BR>&nbsp;<BR>sbull中可工作的请求函数的实现如下所示。在下面的代码中sbull-_devices与scull_de<BR>vice类似。我们在第三章字符设备驱动程序的“打开方法”中介绍过scull_devices。<BR>&nbsp;<BR>(代码245)<BR>&nbsp;<BR>由于sbull只是个RAM盘,所以它的“数据传送”简化为一个memcpy调用。这个函数唯一<BR>“奇怪”的特征是条件语句中限制只能报告最多5个错误。这样做的目的是为了防止系统<BR>日志被太多的信息搞乱,因为end-_request(0)在请求失败时已打印了“I/O&nbsp;error”的<BR>消息。静态计数器是限制消息报告的标准做法,在核心中被多次用到。<BR>&nbsp;<BR>集簇请求<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>上面请求函数中每次循环迭代都传送几个扇区——按照数据的使用,一般情况下,相当<BR>于一个块的“数据”量。例如,交换一次执行PAGE_SIZE大小的数据,而在ext2文件系统<BR>中就是传送1KB的块。<BR>&nbsp;<BR>尽管在I/O中最方便的数据大小是一个块,但如果把相邻块的读或写集簇起来,你会获得<BR>很高的性能改善。在这个意义上,“相邻”指的是在硬盘上块的位置,而“连续”则指<BR>连续的内存区域。<BR>&nbsp;<BR>将相邻块集簇有两个好处。首先,集簇加速了传送(例如,软盘驱动程序将相邻的块组<BR>合在一起,一次传送一个磁道的数据)。另外,它还能通过避免分配冗余的request结构<BR>来节省核心中的内存。<BR>&nbsp;<BR>如果你愿意,也可以完全忽略集簇。上面给出的框架请求函数在没有集簇的情况下可以<BR>完全正确地工作。不过,如果你想利用集簇,你需要更加仔细地研究struct_request的<BR>内部。<BR>&nbsp;<BR>不幸的是,我所知道的所有的核心(至少到2.1.51)都不能为自定义驱动程序进行集簇<BR>,而只对象SCSI和IDE这类内部驱动程序使用。如果你对核心的内部不感兴趣,你可以跳<BR>过本节的其余部分。不过,集簇将来还可能在模块中实现,它是通过减少相邻扇区的请<BR>求延迟来提高数据传送性能的一个有趣的途径。<BR>&nbsp;<BR>在我描述驱动程序如何利用集簇请求之前,让我们先来看看当一个请求被排队时发生了<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>在我描述驱动程序如何利用集簇请求之前,让我们先来看看当一个请求被排队时发生了<BR>什么。<BR>&nbsp;<BR>当核心请求数据块传送时,它扫描目标设备的活动请求链表。当一个新块在盘上与一个<BR>已经被请求的块相邻时,它就被集簇到第一个块上。当前已存在的请求便被扩大了而不<BR>是增加一个新请求。<BR>&nbsp;<BR>不幸的是,磁盘上相邻的两个数据缓冲区在内存中并不一定相邻。这个发现,外加上需<BR>要有效地管理缓冲高速缓存,导致创建一个buffer_head结构。一个buffer_head和一个<BR>数据缓冲相关联。<BR>&nbsp;<BR>因此,一个“集簇”的请求,就是一个指向buffer_head的结构链表的request_struct结<BR>构。end_request函数负责这个问题,这就是为什么前面给出的请求函数可以独立于集簇<BR>而工作。换句话说,end_request要么清除当前请求并准备为下一个服务,要么准备处理<BR>同一个请求中的下一个缓冲区。因此,集簇对不关心它的设备驱动程序是透明的,上面<BR>的sbull函数就是一个例子。<BR>&nbsp;<BR>一个驱动程序可能希望通过在它的request_fn函数中每次循环时处理整个缓冲区头链表<BR>的办法来从集簇中获益。为了做到这一点,驱动程序应该指向CURRENT-&gt;current_nr_sec<BR>tors(这个域我在上面的sbull_request中已经用过)和CURRENT-&gt;nr_sectors,它包含<BR>了集簇在“当前”buffer_heads列表中的相邻扇区的数目。<BR>&nbsp;<BR>当前缓冲区头是CURRENT-&gt;bh,而数据块是CURRENT-&gt;bh-&gt;b_data。后一个指针为了象sbu<BR></P></FONT><FONT 

⌨️ 快捷键说明

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