为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

存储技术- 块设备驱动程序

2013-10-14 17页 pdf 438KB 38阅读

用户头像

is_688385

暂无简介

举报
存储技术- 块设备驱动程序 14.1 存储技术 291 13 14 16 18 22 23 20 17 15 21 19 块设备驱动程序 本章内容 ‰ 存储技术 ‰ Linux块I/O层 ‰ I/O调度器 ‰ 块驱动程序数据结构和方法 ‰ 设备实例:简单存储控制器 ‰ 高级主题 ‰ 调试 ‰ 查看源代码 块设备是一种能随机访问的存储介质。与字符设备不同,块设备能保存文件系统数据。本章 介绍Linux是如何支持存储总线和设备的。 14.1 存储技术 首先回顾当今计算机系...
存储技术- 块设备驱动程序
14.1 存储技术 291 13 14 16 18 22 23 20 17 15 21 19 块设备驱动程序 本章内容 ‰ 存储技术 ‰ Linux块I/O层 ‰ I/O调度器 ‰ 块驱动程序数据结构和方法 ‰ 设备实例:简单存储控制器 ‰ 高级主 ‰ 调试 ‰ 查看源代码 块设备是一种能随机访问的存储介质。与字符设备不同,块设备能保存文件系统数据。本章 介绍Linux是如何支持存储总线和设备的。 14.1 存储技术 首先回顾当今计算机系统中流行的存储技术,同时我们会将这些技术与内核源码树中相应设 备驱动程序子系统联系起来。 IDE(Integrated Drive Electronics,集成驱动电子设备)是PC中常见的存储接口技术,ATA (Advanced Technology Attachment,高级技术配件)则是相关规范的官方名称。IDE/ATA的第 一个版本是ATA-1,最新版本是支持最高带宽达133MB/s的ATA-7。其间的规范版本包括:引入了 LBA(Logic Block Addressing,逻辑块设备寻址)的ATA-2,支持SMART功能的ATA-3,支持Ultra DMA的具有33MB/s吞吐量的ATA-4,最大传输速率达66MB/s的ATA-5,以及最大传输速率达 100MB/s的ATA-6。 CD-ROM和磁带等存储设备则使用一种称为ATAPI(ATA Packet Interface,ATA包接口)①的 特殊协议与标准IDE电缆相连接,ATAPI是在ATA-4中引入的。 在PC系统中,软盘控制器传统上一直是超级I/O芯片组的一部分,这个我们已经在第6章学习 ① ATAPI协议更接近于SCSI而不是IDE。 第 14 章 292 第 14 章 块设备驱动程序 过了。然而在当今的PC中,这些内部驱动器已经让位给更快的外部USB软盘启动器了。 图14-1显示了一个ATA-7磁盘驱动器连接到IDE适配器的情形,这个适配器是PC系统上南桥 芯片组的一部分。同时也显示了与一个ATAPI CD-ROM驱动器和一个软盘驱动器的连接情形。 图14-1 PC系统中的存储媒介 第10章介绍PCIe时说过,IDE/ATA是一种并行总线技术(有时称为并行ATA或PATA),它不 能扩展至高速传输。SATA(Serial ATA,串行ATA)是由PATA发展而来的现代串行总线技术,它 支持300MB/s甚至更高的传输速度。除了有比PATA更高的传输速度外,SATA还支持热交换(hot swapping)等功能。SATA技术正在逐步取代PATA。有关内核中的新型ATA子系统——libATA的介 绍见下面的补充内容。libATA能同时支持SATA和PATA。 libATA libATA是Linux内核中的一种新型ATA子系统。它由一套ATA库例程和使用这个库例程的 底层驱动程序组成。 libATA同时支持SATA和PATA。 libATA中的SATA驱动程序之前在 drivers/scsi/目录下,但是从2.6.19内核版本开始, PATA驱动程序和新目录drivers/ata/已经囊括 了所有libATA源代码。 如果你的系统支持SATA存储器,你需要libATA和SCSI子系统的服务。支持PATA的libATA 仍处于试验阶段,默认情况下,PATA驱动程序继续使用原来的IDE驱动程序,它挂在driver/ide/ 处理器 北桥 集线器接口 南桥 IDE主机 适配器 ATA-7硬盘驱动器 ATAPI CD-ROM 超级I/O 软盘 驱动器 LPC PCI 14.1 存储技术 293 13 14 16 18 22 23 20 17 15 21 19 目录下。 假设系统在Intel ICH7南桥芯片组支持下具有SATA功能,你需要使能以下libATA部件来访 问磁盘。 (1) libATA核心。要装载这个,请在内核配置阶段设置CONFIG_ATA。要查明核心提供的库 函数列表,请在drivers/ata/目录下查找EXPORT_SYMBOL_GPL字符串。 (2) AHCI(Advanced Host Controller Interface,高级主机控制器接口)支持。AHCI了 SATA主机适配器所支持的寄存器接口,可以在配置阶段通过选择CONFIG_AHCI来启用AHCI。 (3) 主机控制器适配驱动程序。对于ICH7,请启用CONFIG_ATA_PIIX。 另外,还需要中级和更高一级的SCSI驱动程序(CONFIG_SCSI及友员)。在下载了所有这 些内核部件后,你的SATA磁盘分区在系统中会显示为/dev/sd*,就像SCSI或USB大存储分区一样。 libATA 项目的主页为 http://linux-ata.org/ 。 Doc 文档是内核源码树的一部分,在 Documentation/DocBook/libata.tmp/目录下, libATA开发手册可从www.kernel.org/pub/linux/ kernel/people/jgarzik/libata.pdf下载。 SCSI(Small Computer System Interface,小型计算机系统接口)是服务器和高端工作站选用 的一种存储技术,SCSI比SATA要快一点,支持320MB/s的读写速度。SCSI传统上是一种并行接 口标准,但是像ATA一样,随着SAS(Serial Attached SCSI,串行附加SCSI)总线技术的发展, 它也在转向串行操作。 内核的SCSI子系统在结构上分为3层:介质(如磁盘、CD-ROM和磁带)所对应的顶层驱动 程序;扫描SCSI总线或配置设备的中间层;底层主机适配器驱动程序。我们在11.6.1节中已经学 习过这些层。参考图11-4可以明白SCSI子系统的这些不同部件间是如何进行互操作的①。USB大 容量存储器内部使用闪存,但使用SCSI协议与主机通信。 RAID(Redundant Array of Inexpensive Disk,廉价磁盘冗余阵列)是某些SCSI和SATA控制 器的一种内建技术,有了它就可以实现冗余性和可靠性。人们已经定义了各种RAID级别,如 RAID-1规定了在分散磁盘上备份数据的磁盘镜像。Linux驱动程序能用于许多有RAID功能的磁盘 驱动器。内核还提供了md(multidisk,多磁盘)驱动程序,它用软件方式实现了大部分的RAID 级别。 在嵌入式消费电子领域常使用小型存储器,这些存储器的传输速度比迄今讨论的技术所提供 的要慢得多。SD(Secure Digital,安全数码)卡和其衍生的外形更小的miniSD卡、microSD卡是 相机、手机、播放器等设备广泛采用的存储介质②。遵循SD卡规范1.01版本的卡支持最高10MB/s 的传输速率。SD存储器是从早期较慢的兼容性技术多媒体MMC(MultiMediaCard)卡(支持最 高2.5MB/s的数据率)发展起来的。内核在/drivers/mmc/目录下包含了一个SD/MMC子系统。 ① SCSI支持还将在本书的其他部分讨论。19.4节讨论sg(SCSI Generic,SISI通用)接口,有了它,就可以将命令从 用户空间直接发送到SCSI设备。20.10.14节简要介绍iSCSI协议,它能将SCSI分组通过TCP/IP网络传输给远端块 设备。 ② 要了解基于SD形式的非存储技术,请参见第16章。 294 第 14 章 块设备驱动程序 9.8节介绍了不同PCMCIA/CF存储卡的特点以及它们相应的内核驱动程序。PCMCIA记忆卡 (如微硬盘机等)支持真正的IDE操作,不过这些卡内部用固态记忆体模拟IDE,并向内核呈现 为IDE编程模型(IDE programming model)。这两种情形下内核的IDE子系统都可以用于启用 存储卡。 表14-1了重要的存储技术以及相关设备驱动程序在内核源码树中的位置。 表14-1 存储技术及相关设备驱动程序 存储技术 说 明 源 文 件 IDE/ATA PC的存储接口技术,ATA-7支持133MB/s 的速率 drivers/ide/ide-disk.c 、 drivers/ide/ide-io.c 、 drivers/ide/ide-prob.c或者drivers/ata/(实验性) ATAPI CD-ROM和磁带等存储设备,用ATAPI协 议连接标准IDE电缆 drivers/ide/ide-cd.c或drivers/ata/(实验性) 软盘(内部) 软盘控制器存在于PC兼容系统LPC总线上 的超级I/O芯片中,支持150KB/s的传输速率 drivers/block/floppy.c SATA IDE/ATA的串行演进,支持超过300MB/s 的传输速率 drivers/ata/、drivers/scsi/ SCSI 服务器环境中流行的存储技术,Ultra320 SCSI支持320MB/s drivers/scsi/ USB大容量存储技术 是指USB硬盘、笔驱动器、CD-ROM以及 软盘驱动器。参见11.6.1节,USB 2.0设备能 以最高60MB/s速度通信 drivers/usb/storage/和 drivers/scsi/ RAID 硬件RAID 为达到冗余性和可靠性在高端SCSI/SATA 磁盘控制器中建立的功能 drivers/scsi/和drivers/ata/ 软件RAID 在Linux中,md驱动程序用软件实现了几 级RAID drivers/md/ SD/miniSD/microSD 在相机、手机等消费电子设备中流行使用 的小外形的介质,支持最高10MB/s的传输速率 drivers/mmc/ MMC 早期的可移动存储器标准,与SD卡兼容, 支持2.5MB/s drivers/mmc/ PCMCIA/ CF存储卡 PCMCIA/ CF迷你外形IDE驱动器,或模拟 IDE的固态内存卡。参见第9章 drivers/ide/legacy/ide-cs.c或drivers/ata/pata_ pcmcia.c(实验用的) 闪存上的块设备枚举 在闪存上模拟硬盘。参见17.6.1节 drivers/mtd/mtdblock.c和drivers/mtd/mtd_ blkdevs.c Linux上的虚拟块设备 RAMDISK 实现对将一块RAM区域作为一个块设备 的支持 drivers/block/rd.c loopback设备 实现对将一个规则文件作为一个块设备的 支持 drivers/block/loop.c 14.3 I/O 调度器 295 13 14 16 18 22 23 20 17 15 21 19 14.2 Linux 块 I/O 层 块I/O层在内核的2.4和2.6发布版本间做了大量的修改,重新的目的是因为块层相比于其 他内核子系统更可能影响系统整体性能。 根据图14-2我们可以了解Linux块I/O层的工作。存储介质包含了驻留于文件系统(如EXT3 或Reiserfs)中的文件。用户应用程序唤醒I/O系统调用来访问这些文件,相关文件系统操作在到 达各自文件系统驱动程序前,会先经过通用VFS(Virtual File System,虚拟文件系统)层。高速 缓冲区通过缓冲磁盘块来加速文件系统对块设备的访问。如果能够在高速缓冲区中找到块,就可 以节省通过访问磁盘读取块的时间。每个块设备指定的数据在请求队列中排队。文件系统驱动程 序将请求加入指定块设备的请求队列,同时块驱动程序从相应队列中取出请求。在这期间,I/O 调度器操控请求队列,使磁盘访问延时最小,同时使吞吐量最大。 图14-2 Linux的块I/O 我们接下来介绍Linux系统中的I/O调度器。 14.3 I/O 调度器 块设备会有寻道时间,即磁头从当前位置移动到目标磁盘扇区花费的时间。I/O调度器的主 要目标是通过尽量减少寻道时间来增加系统吞吐量。为此,I/O调度器维持一个排序过的请求队 文件I/O 文件I/O 用户空间 内核空间 VFS层 单个文件系统(EXT3、EXT4、JFFS2、Reiseris、VFAT等) 缓冲区高速缓存(页面缓存) I/O调度 块驱动程序 块驱动程序 磁盘 CD驱动设备 内核空间 存储媒介 请求队列 请求队列 296 第 14 章 块设备驱动程序 列,排序是将请求按相关磁盘扇区连续性进行排列。新的请求依据排序算法被插入到适当位置。 如果队列中已有的某个请求正好要访问一个邻近的磁盘扇区,那么新请求就与它合二为一。因为 这些属性,I/O调度器的运行方式跟电梯类似:电梯只会在一个方向上安排各个请求,直到队列 中的最后一个请求者得到服务。 2.4内核中的I/O调度器简明地实现了这个算法,称为Linus电梯。然而实践证明,仅仅这么做 是不够的,于是在2.6内核中被含4个调度器的一套算法所取代:限时式(deadline)、预测式 (anticipatory)、完全公平排队(CFQ)、空操作(noop)。默认的调度器是预测式,不过这可以在 内核配置时更改,或者通过改动/sys/block/[disk]/queue/scheduler的值来更改(比如,如果使用IDE 磁盘,就将[disk]换成hda)。表14-2简要描述了Linux I/O调度器。 表14-2 Linux I/O调度器 I/O调度器 说 明 源 文 件 Linus电梯 标准的合并与排序I/O调度算法的直接实现 drivers/block/elevator.c ( 在 2.4内核树中) 限时式 除了Linus电梯实现的之外,限时式调度器对每个请求还设置 了溢出时间,以防止大量对同一磁盘区域的请求会“饿死”那些 对远处区域访问的请求。并且,读操作被赋予比写操作更高的优 先级,因为用户进程通常更多地会被阻塞,直到他们的读请求完 成。限时调度器因此确保每个I/O请求能在一定时限内得到服务, 这对一些数据库应用很重要 block/deadline-iosched.c (在 2.6内核树中) 预测式 预测式调度器与限时式调度器类似,不同的是在为读请求服务 后,它会等待一个预定的时间以期待未来的请求,这种调度技术 适合于工作站/交互式的负荷 block/as-iosched.c(在 2.6 内 核树中) 完全公平调度 队列(CFQ) 与Linus电梯类似,不同的是CFQ会为每个操作进程维护一个 请求队列,而不是所有进程使用一个公共队列。这确保了每个进 程(或进程组)得到公平的I/O,防止一个进程“饿死”其他进 程 block/cfq-iosched.c(在 2.6 内 核树中) 空操作 空操作调度器不需要搜寻请求队列来找最优插入点,而只是简 单地将新请求插入请求队列尾部。因此这种调度器对半导体存储 介质是理想的,因为它们没有移动部件,没有寻道延时。例如 DOM内部使用闪存 block/noop-iosched.c(在 2.6 内核树中) 从概念上看,I/O调度类似于进程调度。I/O调度让进程觉得它拥有磁盘,而进程调度则让进 程觉得它拥有CPU。近年来,Linux的I/O和进程调度器都发生了很大变化。进程调度将在第19章 中讨论。 14.4 块驱动程序数据结构和方法 现在我们将注意力集中到本章的主题块驱动程序。本节介绍重要的数据结构和驱动程序函 数,这在你实现块设备驱动程序时可能会遇到。在下一节我们在实现一个虚拟存储控制器的块驱 动程序时会使用这些结构体和方法。 下面是一个块驱动程序的主要数据结构。 14.4 块驱动程序数据结构和方法 297 13 14 16 18 22 23 20 17 15 21 19 (1) 内核用include/linux/genhd.h中定义的gendisk[通用磁盘(generic disk)的缩写]结构体 表示一个磁盘: struct gendisk { int major; /* Device major number */ int first_minor; /* Starting minor number */ int minors; /* Maximum number of minors.You have one minor number per disk partition */ char disk_name[32]; /* Disk name */ /* ... */ struct block_device_operations *fops; /* Block device operations.Described soon. */ struct request_queue *queue; /* The request queue associated with this disk. Discussed next. */ /* ... */ }; (2) 与每个块驱动程序相关的I/O请求队列用request_queue结构体描述,该结构体定义在 include/linux/blkdev.h中。这是一个大的结构,但你可能只会用到域request结构体(稍后介绍)。 (3) 每个在request_queue队列中的请求用request结构体描述,该结构体定义在 include/linux/blkdev.h中。 struct request { /* ... */ struct request_queue *q; /* The container request queue */ /* ... */ sector_t sector; /* Sector from which data access is requested */ /* ... */ unsigned long nr_sectors; /* Number of sectors left to submit */ /* ... */ struct bio *bio; /* The associated bio. Discussed soon. */ /* ... */ char *buffer; /* The buffer for data transfer */ /* ... */ struct request *next_rq; /* Next request in the queue */ }; (4) block_device_operations是与file_operations结构体对应的块驱动程序结构体。后 者用于字符驱动程序,前者包含了下面与块驱动程序相关的入口函数: ‰ 标准的函数,如open()、release()、ioctl(); ‰ 特别的函数,如支持可移动块设备的media_changed()和revalidate_disk()。 block_device_operations在include/linux/fs.h中定义,如下所示: struct block_device_operations { int (*open) (struct inode *, struct file *); /* Open */ int (*release) (struct inode *, struct file *); /* Close */ int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); /* I/O Control */ /* ... */ 298 第 14 章 块设备驱动程序 int (*media_changed) (struct gendisk *); /* Check if media is available or ejected */ int (*revalidate_disk) (struct gendisk *); /* Gear up for newly inserted media */ /* ... */ }; (5) 如果观察request结构体,会发现它关联了bio。bio结构体是块I/O操作在页级粒度的底 层描述,它在include/linux/bio.h中的定义如下: struct bio { sector_t bi_sector; /* Sector from which data access is requested */ struct bio *bi_next; /* List of bio nodes */ /* .. */ unsigned long bi_rw; /* Bottom bits of bi_rw contain the data-transfer direction */ /* ... */ struct bio_vec *bi_io_vec; /* Pointer to an array of bio_vec structures */ unsigned short bi_vcnt; /* Size of the bio_vec array */ unsigned short bi_idx; /* Index of the current bio_vec in the array */ /* ... */ }; 块数据通过一个bio_vec结构体数组在内部被表示成I/O向量。每个bio_vec数组元素由三元 组(页,页偏移,长度)组成,表示该块I/O的一个段。将I/O请求作为一个页向量有诸多优点, 如更简洁的实现和高效的分散/聚集操作。 最后,让我们简单地看一下块驱动程序入口函数。大部分情况下使用3类方法构建块驱动程 序。 ‰ 常见的初始化和退出函数。 ‰ 前述的block_device_operations中的部分函数。 ‰ request函数。块驱动程序不像字符设备,它不支持read()/write()的数据传输方式, 而是用称为“请求方法(request method)”的特殊方式实现磁盘访问。 块核心层提供了一套驱动程序方法可以使用的库例程,下一节的示例驱动程序会调用其中一 些库例程的服务。 14.5 设备实例:简单存储控制器 考虑图14-3中表示的嵌入式设备。SoC包含了一个内建的与块设备通信的存储控制器。其结 构类似于SD/MMC介质,不同的是我们的存储控制器实例用表14-3所列的基本寄存器集描述。 SECTOR_NUMBER_REGISTER寄存器规定了请求所要访问的起始数据扇区①,SECTOR_COUNT_ REGISTER寄存器存放了要传送的扇区数,数据经由DATA_REGISTER寄存器传输。COMMAND_ REGISTER寄存器指定了存储控制器的操作(如从介质读或向介质写)。STATUS_REGISTER寄存器 ① 我们的示例设备的存储介质采用扁平扇区空间布局,而IDE控制器是支持柱面磁头扇区(Cylinder Head Sector, CHS)布局的,它除了扇区号寄存器(sector number register)外,还由磁头寄存器(device head register)、低位 柱面寄存器(low cylinder register)以及高位柱面寄存器(high cylinder register)一起指定。 14.5 设备实例:简单存储控制器 299 13 14 16 18 22 23 20 17 15 21 19 包含了反映控制器是否正忙于进行操作的位。 图14-3 嵌入式设备上的存储器 表14-3 存储控制器寄存器 寄存器名称 内容描述 SECTOR_NUMBER_REGISTER 下一个磁盘操作所在的扇区 SECTOR_COUNT_REGISTER 要读写的扇区数 COMMAND_REGISTER 要执行的操作(如读或写) STATUS_REGISTER 操作结果、中断状态、错误标识 DATA_REGISTER 在读通道上,存储控制器将数据从磁盘读到内部缓冲区。驱动程序 通过该寄存器访问内部缓冲区。在写通道上,由驱动程序写入寄存器 的数据被转到内部缓冲区,控制器再将它从内部缓冲区复制到磁盘 示例存储控制器被命名为myblkdev。这个简单的设备既不是中断驱动的,也不支持DMA。 假设介质是非移动的。我们的任务是为myblkdev编写块驱动程序。该驱动程序虽然简短,但很完 整。它不处理电源管理,也不追求I/O性能。 14.5.1 初始化 代码清单14-1包含了驱动程序初始化方法,即myblkdev-int(),它执行以下步骤。 (1) 用register_blkdev()注册块设备。该块库例程为myblkdev分配一个未使用的主设备号, 并为设备在/proc/devices中增加一个入口。 (2) 将一个请求方法与该设备关联。这是通过向blk_init_queue()提供myblkdev_ request()的地址实现的。调用blk_init_queue()会返回myblkdev的请求队列request_ queue。参考图14-2可以了解队列request_queue是如何与驱动程序相关联上的。blk_init_ queue()的第二个参数(也就是myblkdev_lock)是自旋锁,用于保护request_queue队列不被 同时访问。 (3) 硬件执行磁盘事务是以扇区为单位的,而软件子系统(如文件系统)是以块为单位处理数 据的。通常扇区大小是512B,块大小是4096B。需要将存储硬件所能支持的扇区大小和驱动程序 在一次请求中能接收的最大扇区数通知块层, myblkdev_init()会通过分别调用 blk_queue_hardsect_size()和blk_queue_max_sectors()完成这个通知。 (4) 通过alloc_disk()分配一个与myblkdev对应的gendisk并初始化其成员。一个由 myblkdev_init()提供的gendisk的重要成员是驱动程序的block_device_operations地址。 嵌入式SoC CPU 核心 存储 控制器 介质插槽/ 连接器 块介质 CPU内连总线 300 第 14 章 块设备驱动程序 另一个由myblkdev_init()填写的参数是myblkdev的存储容量,其单位是扇区。这是通过调用 set_capacity()完成的。每个gendisk还包含一个表示下层存储硬件属性的标识。比如说,如 果驱动器是可移动的,那么gendisk的标识域应该被标记为GENHD_FL_REMOVABLE。 (5) 将步骤 (4)中准备好的gendisk和步骤 (2)中得到的request_queue队列关联,并将 gendisk和设备的主/次设备号及名称连接起来。 (6) 调用add_disk()将磁盘添加到块I/O层。当这一步完成后,驱动程序就要准备接收请求 了。因此这通常是初始化化过程的最后步骤。 现在设备/dev/myblkdev对系统来说是可用的了。如果设备支持多个磁盘分区,它们会显示成 /dev/myblkdevX,其中X是分区号。 代码清单14-1 初始化驱动程序 #include #include static struct gendisk *myblkdisk; /* Representation of a disk */ static struct request_queue *myblkdev_queue; /* Associated request queue */ int myblkdev_major = 0; /* Ask the block subsystem to choose a major number */ static DEFINE_SPINLOCK(myblkdev_lock);/* Spinlock that protects myblkdev_queue from concurrent access */ int myblkdisk_size = 256*1024; /* Disk size in kilobytes. For a PC hard disk, one way to glean this is via the BIOS */ int myblkdev_sect_size = 512; /* Hardware sector size */ /* Initialization */ static int __init myblkdev_init(void) { /* Register this block driver with the kernel */ if ((myblkdev_major = register_blkdev(myblkdev_major, "myblkdev")) <= 0) { return -EIO; } /* Allocate a request_queue associated with this device */ myblkdev_queue = blk_init_queue(myblkdev_request, &myblkdev_lock); if (!myblkdev_queue) return -EIO; /* Set the hardware sector size and the max number of sectors */ blk_queue_hardsect_size(myblkdev_queue, myblkdev_sect_size); blk_queue_max_sectors(myblkdev_queue, 512); /* Allocate an associated gendisk */ myblkdisk = alloc_disk(1); if (!myblkdisk) return -EIO; /* Fill in parameters associated with the gendisk */ myblkdisk->fops = &myblkdev_fops; 14.5 设备实例:简单存储控制器 301 13 14 16 18 22 23 20 17 15 21 19 /* Set the capacity of the storage media in terms of number of sectors */ set_capacity(myblkdisk, myblkdisk_size*2); myblkdisk->queue = myblkdev_queue; myblkdisk->major = myblkdev_major; myblkdisk->first_minor = 0; sprintf(myblkdisk->disk_name, "myblkdev"); /* Add the gendisk to the block I/O subsystem */ add_disk(myblkdisk); return 0; } /* Exit */ static void __exit myblkdev_exit(void) { /* Invalidate partitioning information and perform cleanup */ del_gendisk(myblkdisk); /* Drop references to the gendisk so that it can be freed */ put_disk(myblkdisk); /* Dissociate the driver from the request_queue. Internally calls elevator_exit() */ blk_cleanup_queue(myblkdev_queue); /* Unregister the block device */ unregister_blkdev(myblkdev_major, "myblkdev"); } module_init(myblkdev_init); module_exit(myblkdev_exit); MODULE_LICENSE("GPL"); 14.5.2 块设备操作 接下来看一下块驱动程序的block_device_operations操作中的主要方法。 块驱动程序的open()方法是在诸如挂载介质上的文件系统或执行文件系统检查(fsck)等操 作期间调用的。在open()期间完成的许多任务是依赖于硬件的。比如CD-ROM驱动程序锁住驱 动器门,SCSI驱动程序检查设备是否设置了写保护。如果是,开启写允许请求就会不成功。如果 设备是可移除的,open()会调用check_disk_change()服务例程,检查介质是否已经改变。 如果驱动程序需要支持特殊命令,可以用ioctl()方法实现你的支持。比如,软盘驱动程序 支持弹出介质的命令。 media_changed()方法会检查存储介质是否已经改变(因此myblkdev等固定介质与它无 302 第 14 章 块设备驱动程序 关)。比如SCSI磁盘驱动程序的media_changed()方法会检查已经插入的USB笔驱动器是否已经 改变等。 唯一被myblkdev支持的块设备操作是ioctl()方法,即myblkdev_ioctl()。块层处理一般 I/O控制,或调用驱动程序的ioctl()方法处理设备相关的命令。在代码清单14-2中, myblkdev_ioctl()实现了GET_DEVICE_ID命令,该命令探知控制器的设备 ID。该命令经 COMMAND_REGISTER寄存器发出,ID数据从DATA_REGISTER寄存器获得。 代码清单14-2 块设备操作 #define GET_DEVICE_ID 0xAA00 /* Ioctl command definition */ /* The ioctl operation */ static int myblkdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { unsigned char status; switch (cmd) { case GET_DEVICE_ID: outb(GET_IDENTITY_CMD, COMMAND_REGISTER); /* Wait as long as the controller is busy */ while ((status = inb(STATUS_REGISTER)) & BUSY_STATUS); /* Obtain ID and return it to user space */ return put_user(inb(DATA_REGISTER), (long __user *)arg); default: return -EINVAL; } } /* Block device operations */ static struct block_device_operations myblkdev_fops = { .owner = THIS_MODULE, /* Owner of this structure */ .ioctl = myblkdev_ioctl, /* The following operations are not implemented for our example storage controller: open(), release(), unlocked_ioctl(), compat_ioctl(), direct_access(), getgeo(), revalidate_disk(), and media_changed() */ }; 14.5.3 磁盘访问 前面已经提过,块驱动程序使用request()方法进行磁盘访问操作。当块I/O子系统要处理 驱动程序的request_queue队列中的请求时,它会调用驱动程序的request()方法。但request() 函数并不是运行在发出数据传输请求的用户进程的上下文中。相关request_queue队列的地址被 作为参数传递给request()函数。 14.5 设备实例:简单存储控制器 303 13 14 16 18 22 23 20 17 15 21 19 正如前文所述,内核在调用request()前先占有一个请求锁,这是保护相关request_queue 队列不被并发访问。因此,如果你的request()函数要调用一个可能会引起睡眠的函数,就必须 要在调用前先释放锁,并在它返回前重新获得锁。 代码清单14-3包含了该驱动程序的请求方法,即myblkdev_request()。这个函数使用 elv_next_request()服务来从request_queue队列得到下一个请求。如果队列不再包含等待处 理的请求,elv_next_request()返回NULL。已经介绍了,I/O调度算法会随电梯算法采用的基 本方法的变化而变化,所以才有elv_next_request()这个名称。在处理完一个请求后,驱动程 序通过调用end_request()告诉块层结束该请求的I/O。可以使用传给end_request()的第二个 参数设置成功或失败代号。 request_queue队列中的请求包含了请求访问数据的起始扇区(代码清单14-3中的 req->sector、需要执行 I/O的扇区数(req->nr_sectors)、被传输数据所在的缓冲区 (request_buffer)以及数据搬移方向(rq_data_dir(req))。如代码清单 14-3所示, myblkdev_request()在这些参数的帮助下运行了要求的寄存器程序。 代码清单14-3 请求函数 #define READ_SECTOR_CMD 1 #define WRITE_SECTOR_CMD 2 #define GET_IDENTITY_CMD 3 #define BUSY_STATUS 0x10 #define SECTOR_NUMBER_REGISTER 0x20000000 #define SECTOR_COUNT_REGISTER 0x20000001 #define COMMAND_REGISTER 0x20000002 #define STATUS_REGISTER 0x20000003 #define DATA_REGISTER 0x20000004 /* Request method */ static void myblkdev_request(struct request_queue *rq) { struct request *req; unsigned char status; int i, good = 0; /* Loop through the requests waiting in line */ while ((req = elv_next_request(rq)) != NULL) { /* Program the start sector and the number of sectors */ outb(req->sector, SECTOR_NUMBER_REGISTER); outb(req->nr_sectors, SECTOR_COUNT_REGISTER); /* We are interested only in filesystem requests. A SCSI command is another possible type of request. For the full list, look at the enumeration of rq_cmd_type_bits in include/linux/blkdev.h */ if (blk_fs_request(req)) { switch(rq_data_dir(req)) { 304 第 14 章 块设备驱动程序 case READ: /* Issue Read Sector Command */ outb(READ_SECTOR_CMD, COMMAND_REGISTER); /* Traverse all requested sectors, byte by byte */ for (i = 0; i < 512*req->nr_sectors; i++) { /* Wait until the disk is ready. Busy duration should be in the order of microseconds. Sitting in a tight loop for simplicity; more intelligence required in the real world */ while ((status = inb(STATUS_REGISTER)) & BUSY_STATUS); /* Read data from disk to the buffer associated with the request */ req->buffer[i] = inb(DATA_REGISTER); } good = 1; break; case WRITE: /* Issue Write Sector Command */ outb(WRITE_SECTOR_CMD, COMMAND_REGISTER); /* Traverse all requested sectors, byte by byte */ for (i = 0; i < 512*req->nr_sectors; i++) { /* Wait until the disk is ready. Busy duration should be in the order of microseconds. Sitting in a tight loop
/
本文档为【存储技术- 块设备驱动程序】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索