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

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

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 4 页
字号:
      TIMEOUT_VALUE以记数的方式表达超时,这个超时的值与老计时器之一(特别地指计时器
      号DEVICE_TIMEOUT)相关联。一个驱动程序可以在数据传送时间太长时,通过调用一个
      回调函数来检测错误条件。不过,由于老计时器由一个预赋值的计时器静态数组组成(
      见第六章“时间流”中“核心计时器”一节),一个自定义的驱动程序不能使用它们。
      我在sbull中对这两个符号都未定义,而是用一个新的计时器实现超时。
       
      DEBICE_NO_RANDOM
       
      在缺省情况下,函数end_request对系统熵值(即所有随机性的总量)有所贡献,这被/d
      ev/random所使用。如果一个设备不能对随机设备贡献显著的熵值,DEVICE_NO_RANDOM应
      被定义。/dev/random在第九章的“安装中断处理程序”中进行了介绍,SA_SAMPLE_RAND
      OM也在那儿做了解释。
       
      DEVICE_OFF(kdev_t device)
       
      end_request函数在结束时调用这个宏。例如在软盘驱动程序中,它调用一个函数,这个
      函数负责更新用来控制马达停转的一个计时器。如果设备没有被关掉,那么串DEVICE_OF
      F可以被定义为空。sbull不使用DEVICE_OFF。
       
      DEVICE_ON(kdev_t device)

      DEVICE_ON(kdev_t device)
       
      DEVICE_REQUEST
       
      这些函数实际上并未在Linux的头文件中使用,所以驱动程序并不需要定义它们。大多数
      官方的Linux设备驱动程序声明这些符号并在内部使用它们,但我在sbull里并没有使用
      它。
       
      sbull驱动程序以如下的方式声明这些符号:
       
      (代码242)
       
      头文件blk.h用上面列出的这些宏定义了一些可以由驱动程序使用的额外的宏,我将在后
      续章节里对之进行介绍。
       
      处理请求
       
      系统性能的方式排序。这些联结表中的请求被传递个驱动程序的请求函数,由它对链接
      表中的每个请求执行如下的任务:
       
      l       检查当前请求的有效性。这个工作由在blk.h中定义的宏INIT_REQUEST完成。
       
      l       进行实际的数据传送。用变量CURRENT(实际上是个宏)可以获得发出请求的一

      l       进行实际的数据传送。用变量CURRENT(实际上是个宏)可以获得发出请求的一
      些细节。CURRENT是一个指向结构request的指针,我将在下节介绍这个结构的域。
       
      l       清除当前的请求。这个操作由静态函数end_request完成,函数的代码在blk.h
      中。驱动程序向这个函数传递一个参数,即成功时为1,失败时为0。当end_request以参
      数0调用时,一个“I/O error”消息会被发给系统日志(通过printk)。
       
      l       循环回至开始,消化下一个请求。可以按照程序员的喜好使用一个goto或是一
      个for(;;),或者while(1)。
       
      实践中,请求函数的代码如下构造:
       
      (代码243)
       
      尽管这段代码除了打印消息外什么都没有做,运行这个函数可以对数据传送的基本设计
      有一个很好的了解。到此为止,代码中唯一不清楚的地方是CURRENT的确切含义及它的域
      ,这个我将在下一节介绍。
       
      我的第一个sbull实现只包含了所示的空代码。我意在一个“不存在”的设备上构造一个
      文件系统,并使用它一会儿,只要数据仍在缓冲高速缓存中。在运行一个象这样罗嗦的
      请求函数时,看看系统日志能帮助你理解缓冲高速缓存是如何工作的。
       
      在编译时,定义符号SBULL_EMPTY_REQUEST,那么这个空且罗嗦的函数可以在sbull 中运

      在编译时,定义符号SBULL_EMPTY_REQUEST,那么这个空且罗嗦的函数可以在sbull 中运
      行。如果你想理解核心是如何处理不同块大小的,你可以在insmod命令行上实验blksize
      =。这个空的请求函数通过打印每个请求的细节揭示了内部核心的工作情况。你或许也可
      以试试hardsect=,但目前它被关闭了,因为比较危险。(见本章开始时的“注册驱动程
      序”)。
       
      请求函数的代码并不显式地调用return(),因为当列表中的待处理请求耗尽时,INIT-_RE
      QUEST会替你完成这个工作。
       
      执行实际的数据传送
       
      为了给sbull构造一个可以工作的数据传送,让我们先来看看核心是如何在结构request
      中描述一个请求的。这个结构在<linux/blkdev.h>中定义。通过访问CURRENT的域,驱动
      程序可以得到所有为在缓冲高速缓存的物理块设备之间传送数据所需要的信息。
       
      CURRENT是用来访问当前请求(即被首先服务的那个请求)。正如你可能猜到的,CURREN
      T是blk_dev[MAJOR_NR].current_request的缩短形式。
       
      下面这些当前请求的域包含了请求函数的有用信息:
       
      kdev_t rq_dev;
       
      请求所访问的设备。有本驱动程序所管理的所以设备均被使用同一个请求函数。一个请

      请求所访问的设备。有本驱动程序所管理的所以设备均被使用同一个请求函数。一个请
      求函数处理所有的次设备号;rq_dev可以被用来取得被操作的次设备。尽管Linux1.2称
      这个域为dev,你仍然可以通过宏CURRENT_DEV来访问这个域。CURRENT_DEV在我们所讨论
      的所有版本的核心中是可移植的。
       
      int cmd;
       
      这个域是READ或WRITE。
       
      unsigned long sector;
       
      请求指向的第一个扇区。
       
      unsigned long current_nr_sectors;
       
      unsigned long nr_sectors;
       
      当前请求的扇区数(大小)。驱动程序应该引用current_nr_sectors,而应该忽略nr_sec
      tors(列在这里只是为了完整)。请看下一节“集簇请求”以获得更多的细节。
       
      char *buffer
       
      缓冲高速缓存中的域。如果cmd==READ,就是写数据的位置;如果cmd==WRITE,就是读数

      缓冲高速缓存中的域。如果cmd==READ,就是写数据的位置;如果cmd==WRITE,就是读数
      据的位置。
       
      struct buffer_head *bh
       
      这个结构描述了这个请求列表中的第一个缓冲区。我们将在“集簇请求”中用到这个域
       
      在这个结构中还有其它的一些域,但它们基本上是核心内部使用的,驱动程序并不期望
      使用它们。
       
      sbull中可工作的请求函数的实现如下所示。在下面的代码中sbull-_devices与scull_de
      vice类似。我们在第三章字符设备驱动程序的“打开方法”中介绍过scull_devices。
       
      (代码245)
       
      由于sbull只是个RAM盘,所以它的“数据传送”简化为一个memcpy调用。这个函数唯一
      “奇怪”的特征是条件语句中限制只能报告最多5个错误。这样做的目的是为了防止系统
      日志被太多的信息搞乱,因为end-_request(0)在请求失败时已打印了“I/O error”的
      消息。静态计数器是限制消息报告的标准做法,在核心中被多次用到。
       
      集簇请求
       

       
      上面请求函数中每次循环迭代都传送几个扇区——按照数据的使用,一般情况下,相当
      于一个块的“数据”量。例如,交换一次执行PAGE_SIZE大小的数据,而在ext2文件系统
      中就是传送1KB的块。
       
      尽管在I/O中最方便的数据大小是一个块,但如果把相邻块的读或写集簇起来,你会获得
      很高的性能改善。在这个意义上,“相邻”指的是在硬盘上块的位置,而“连续”则指
      连续的内存区域。
       
      将相邻块集簇有两个好处。首先,集簇加速了传送(例如,软盘驱动程序将相邻的块组
      合在一起,一次传送一个磁道的数据)。另外,它还能通过避免分配冗余的request结构
      来节省核心中的内存。
       
      如果你愿意,也可以完全忽略集簇。上面给出的框架请求函数在没有集簇的情况下可以
      完全正确地工作。不过,如果你想利用集簇,你需要更加仔细地研究struct_request的
      内部。
       
      不幸的是,我所知道的所有的核心(至少到2.1.51)都不能为自定义驱动程序进行集簇
      ,而只对象SCSI和IDE这类内部驱动程序使用。如果你对核心的内部不感兴趣,你可以跳
      过本节的其余部分。不过,集簇将来还可能在模块中实现,它是通过减少相邻扇区的请
      求延迟来提高数据传送性能的一个有趣的途径。
       
      在我描述驱动程序如何利用集簇请求之前,让我们先来看看当一个请求被排队时发生了

      在我描述驱动程序如何利用集簇请求之前,让我们先来看看当一个请求被排队时发生了
      什么。
       
      当核心请求数据块传送时,它扫描目标设备的活动请求链表。当一个新块在盘上与一个
      已经被请求的块相邻时,它就被集簇到第一个块上。当前已存在的请求便被扩大了而不
      是增加一个新请求。
       
      不幸的是,磁盘上相邻的两个数据缓冲区在内存中并不一定相邻。这个发现,外加上需
      要有效地管理缓冲高速缓存,导致创建一个buffer_head结构。一个buffer_head和一个
      数据缓冲相关联。
       
      因此,一个“集簇”的请求,就是一个指向buffer_head的结构链表的request_struct结
      构。end_request函数负责这个问题,这就是为什么前面给出的请求函数可以独立于集簇
      而工作。换句话说,end_request要么清除当前请求并准备为下一个服务,要么准备处理
      同一个请求中的下一个缓冲区。因此,集簇对不关心它的设备驱动程序是透明的,上面
      的sbull函数就是一个例子。
       
      一个驱动程序可能希望通过在它的request_fn函数中每次循环时处理整个缓冲区头链表
      的办法来从集簇中获益。为了做到这一点,驱动程序应该指向CURRENT->current_nr_sec
      tors(这个域我在上面的sbull_request中已经用过)和CURRENT->nr_sectors,它包含
      了集簇在“当前”buffer_heads列表中的相邻扇区的数目。
       
      当前缓冲区头是CURRENT->bh,而数据块是CURRENT->bh->b_data。后一个指针为了象sbu

      当前缓冲区头是CURRENT->bh,而数据块是CURRENT->bh->b_data。后一个指针为了象sbu
      ll一类忽略集簇的驱动程序缓冲在CURRENT->buffer中。
       
      请求集簇在drivers/block/ll_rw_block.c的函数make_request中实现。不过,如上所说
      ,集簇只对几个驱动程序有效(软驱,IDE,和SCSI),以其主设备号为准。我曾通过以
      major=34装载sbull看到过集簇是如何工作的,因为34是IDE3_MAJOR,而我的系统中没有
      第三个IDE控制器。
       
      下面列表总结了当扫描一个集簇请求时应做的事项。bh是被处理的缓冲区头——列表的
      第一项。对列表中的每个缓冲区头,驱动程序要完成下面一系列操作:
       
      l        传送位于地址bh->b_data,大小为bh->b_size字节数据块。数据传送的方向通
      常由CURRENT->cmd指出。
       
      l        从列表中找出下一个缓冲区头:bh->b_request。接着通过将b_request置为0
      ,把刚传送过的缓冲区从列表中摘下。b_reqnext指向你刚找出的新缓冲区。
       
      l        通过调用mark_buffer_uptodate(bh,1),unlock_buffer(bh),告诉核心你已
      完成对上个缓冲区的操作。这些调用保证缓冲高速缓存保持正确,不致有错误指向的指
      针。mark_buffer_uptodate中参数“1”表示传送成功,若传送失败,则换为0。
       
      l        循环回到开始,传送下一个相邻块。
       

⌨️ 快捷键说明

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