本章导读 块设备是与字符设备并列的概念,这两类设备在Linux中驱动的结构有较大差异,总体而言,块设备驱动比字符设备驱动要复杂得多,在I/O操作上表现出极大的不同,缓冲、I/O调度、请求队列等都是与块设备驱动相关的概念。本章将向您展示Linux块设备驱动的编程方法。 13.1节分析块设备I/O操作的特点,对比字符设备与块设备在I/O操作上的差异。 13.2节从整体上描述Linux块设备驱动的结构,分析主要的数据结构、函数及其关系。 13.3~13.5节分别阐述块设备驱动模块加载与卸载、打开与释放和ioctl()函数。 13.6节非常重要,它讲述了块设备I/O操作所依赖的请求队列的概念及用法。 13.2节与13.3~13.6节是整体与部分的关系,13.2~13.6节与13.7节是迭代递进关系。 13.7节在13.1~13.6节讲解内容的基础上,总结Linux下块设备的读写流程。而13.7节则给出了块设备驱动的具体实例,即RAMDISK的驱动。 13.1块设备的I/O操作特点 字符设备与块设备I/O操作的不同在于: ① 块设备只能以块为单位接受输入和返回输出,而字符设备则以字节为单位。大多数设备是字符设备,因为它们不需要缓冲而且不以固定块大小进行操作。 ② 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无需缓冲且被直接读写。对于存储设备而言调整读写的顺序作用巨大,因为在读写连续的扇区比分离的扇区更快。 ③ 字符设备只能被顺序读写,而块设备可以随机访问。虽然块设备可随机访问,但是对于磁盘这类机械设备而言,顺序地组织块设备的访问可以提高性能。如图 13.1,对磁盘1、10、3、2的请求被调整为对1、2、3、10的请求可以提高读写性能。注意,对SD卡、RAMDISK等块设备而言,不存在机械上 的原因,进行这样的调整没有必要。 图13.1 调整块设备I/O操作的顺序 13.2 Linux块设备驱动结构 13.2.1 block_device_operations结构体 在块设备驱动中,有1个类似于字符设备驱动中file_operations结构体的block_device_operations结构体,它是对块设备操作的集合,定义如代码清单13.1。 代码清单13.1 block_device_operations结构体 1 struct block_device_operations 2 { 3 int(*open)(struct inode *, struct file*); //打开 4 int(*release)(struct inode *, struct file*); //释放 5 int(*ioctl)(struct inode *, struct file *, unsigned, unsigned long); //ioctl 6 long(*unlocked_ioctl)(struct file *, unsigned, unsigned long); 7 long(*compat_ioctl)(struct file *, unsigned, unsigned long); 8 int(*direct_access)(struct block_device *, sector_t, unsigned long*); 9 int(*media_changed)(struct gendisk*); //介质被改变? 10 int(*revalidate_disk)(struct gendisk*); //使介质有效 11 int(*getgeo)(struct block_device *, struct hd_geometry*);//填充驱动器信息 12 struct module *owner; //模块拥有者 13 }; 下面对其主要的成员函数进行分析: ? 打开和释放 int (*open)(struct inode *inode, struct file *filp); int (*release)(struct inode *inode, struct file *filp); 与字符设备驱动类似,当设备被打开和关闭时将调用它们。 ? IO控制 int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); 上述函数是ioctl() 系统调用的实现,块设备包含大量的标准请求,这些标准请求由Linux块设备层处理,因此大部分块设备驱动的ioctl()函数相当短。 ? 介质改变 int (*media_changed) (struct gendisk *gd); 被内核调用来检查是否驱动器中的介质已经改变,如果是,则返回一个非零值,否则返回0。这个函数仅适用于支持可移动介质的驱动器(非可移动设备的驱动不需要实现这个方法),通常需要在驱动中增加1个表示介质状态是否改变的标志变量。 ? 使介质有效 int (*revalidate_disk) (struct gendisk *gd); revalidate_disk()函数被调用来响应一个介质改变,它给驱动一个机会来进行必要的工作以使新介质准备好。 ? 获得驱动器信息 int (*getgeo)(struct block_device *, struct hd_geometry *); 该函数根据驱动器的几何信息填充一个hd_geometry结构体,hd_geometry结构体包含磁头、扇区、柱面等信息。 ? 模块指针 struct module *owner; 一个指向拥有这个结构体的模块的指针,它通常被初始化为THIS_MODULE。 13.2.2 gendisk结构体 在Linux内核中,使用gendisk(通用磁盘)结构体来表示1个独立的磁盘设备(或分区),这个结构体的定义如代码清单13.2。 代码清单13.2 gendisk结构体 1 struct gendisk 2 { 3 int major; /* 主设备号 */ 4 int first_minor; /*第1个次设备号*/ 5 int minors; /* 最大的次设备数,如果不能分区,则为1*/ 6 char disk_name[32]; /* 设备名称 */ 7 struct hd_struct **part; /* 磁盘上的分区信息 */ 8 struct block_device_operations *fops; /*块设备操作结构体*/ 9 struct request_queue *queue; /*请求队列*/ 10 void *private_data; /*私有数据*/ 11 sector_t capacity; /*扇区数,512字节为1个扇区*/ 12 13 int flags; 14 char devfs_name[64]; 15 int number; 16 struct device *driverfs_dev; 17 struct kobject kobj; 18 19 struct timer_rand_state *random; 20 int policy; 21 22 atomic_t sync_io; /* RAID */ 23 unsigned long stamp; 24 int in_flight; 25 #ifdef CONFIG_SMP 26 struct disk_stats *dkstats; 27 #else 28 struct disk_stats dkstats; 29 #endif 30 }; major、 first_minor和minors共同表征了磁盘的主、次设备号,同一个磁盘的各个分区共享1个主设备号,而次设备号则不同。fops为 block_device_operations,即上节描述的块设备操作集合。queue是内核用来管理这个设备的 I/O请求队列的指针。capacity表明设备的容量,以512个字节为单位。private_data可用于指向磁盘的任何私有数据,用法与字符设备 驱动file结构体的private_data类似。 Linux内核提供了一组函数来操作gendisk,主要包括: ? 分配gendisk gendisk结构体是一个动态分配的结构体,它需要特别的内核操作来初始化,驱动不能自己分配这个结构体,而应该使用下列函数来分配gendisk: struct gendisk *alloc_disk(int minors); minors 参数是这个磁盘使用的次设备号的数量,一般也就是磁盘分区的数量,此后minors不能被修改。 ? 增加gendisk gendisk结构体被分配之后,系统还不能使用这个磁盘,需要调用如下函数来注册这个磁盘设备: void add_disk(struct gendisk *gd); 特别要注意的是对add_disk()的调用必须发生在驱动程序的初始化工作完成并能响应磁盘的请求之后。 ? 释放gendisk 当不再需要一个磁盘时,应当使用如下函数释放gendisk: void del_gendisk(struct gendisk *gd); ? gendisk引用计数 gendisk 中包含1个kobject成员,因此,它是一个可被引用计数的结构体。通过get_disk()和put_disk()函数可用来操作引用计数,这个工作 一般不需要驱动亲自做。通常对 del_gendisk()的调用会去掉gendisk的最终引用计数,但是这一点并不是一定的。因此,在del_gendisk()被调用后,这个结构 体可能继续存在。 ? 设置gendisk容量 void set_capacity(struct gendisk *disk, sector_t size); 块 设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备 无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇区也很常见, 比如,很多CD-ROM盘的扇区都是2K大小。 不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512字节为单位。因此,set_capacity()函数也以512字节为单位。 13.2.3 request与bio结构体 1、请求 在Linux块设备驱动中,使用request结构体来表征等待进行的I/O请求,这个结构体的定义如代码清单13.3。 代码清单13.3 request结构体 1 struct request 2 { 3 struct list_head queuelist; /*链表结构*/ 4 unsigned long flags; /* REQ_ */ 5 6 sector_t sector; /* 要传送的下1个扇区 */ 7 unsigned long nr_sectors; /*要传送的扇区数目*/ 8 /*当前要传送的扇区数目*/ 9 unsigned int current_nr_sectors; 10 11 sector_t hard_sector; /*要完成的下1个扇区*/ 12 unsigned long hard_nr_sectors; /*要被完成的扇区数目*/ 13 /*当前要被完成的扇区数目*/ 14 unsigned int hard_cur_sectors; 15 16 struct bio *bio; /*请求的 bio 结构体的链表*/ 17 struct bio *biotail; /*请求的 bio 结构体的链表尾*/ 18 19 void *elevator_private; 20 21 unsigned short ioprio; 22 23 int rq_status; 24 struct gendisk *rq_disk; 25 int errors; 26 unsigned long start_time; 27 28 /*请求在物理内存中占据的不连续的段的数目,scatter/gather列表的尺寸*/ 29 unsigned short nr_phys_segments; 30 31 /*与nr_phys_segments相同,但考虑了系统I/O MMU的remap */ 32 unsigned short nr_hw_segments; 33 34 int tag; 35 char *buffer; /*传送的缓冲,内核虚拟地址*/ 36 37 int ref_count; /* 引用计数 */ 38 ... 39 }; request结构体的主要成员包括: sector_t hard_sector; unsigned long hard_nr_sectors; unsigned int hard_cur_sectors; 上述3个成员标识还未完成的扇区,hard_sector是第1个尚未传输的扇区,hard_nr_sectors是尚待完成的扇区数,hard_cur_sectors是并且当前I/O操作中待完成的扇区数。这些成员只用于内核块设备层,驱动不应当使用它们。 sector_t sector; unsigned long nr_sectors; unsigned int current_nr_sectors; 驱动中会经常与这3个成员打交道,这3个成员在内核和驱动交互中发挥着重大作用。它们以512字节大小为1个扇区,如果硬件的扇区大小不是512字节,则需要进行相应的调整。例如,如果硬件的扇区大小是2048字节,则在进行硬件操作之前,需要用4来除起始扇区号。 hard_sector、hard_nr_sectors、hard_cur_sectors与sector、nr_sectors、current_nr_sectors之间可认为是“副本”关系。 struct bio *bio; bio是这个请求中包含的bio结构体的链表,驱动中不宜直接存取这个成员,而应该使用后文将介绍的rq_for_each_bio()。 char *buffer; 指向缓冲区的指针,数据应当被传送到或者来自这个缓冲区,这个指针是一个内核虚拟地址,可被驱动直接引用。 unsigned short nr_phys_segments; 该值表示相邻的页被合并后,这个请求在物理内存中占据的段的数目。如果设备支持分散/聚集(SG,scatter/gather)操作,可依据此字段申请sizeof(scatterlist)* nr_phys_segments的内存,并使用下列函数进行DMA映射: int blk_rq_map_sg(request_queue_t *q, struct request *req, struct scatterlist *sglist); 该函数与dma_map_sg()类似,它返回scatterlist列表入口的数量。 struct list_head queuelist; 用于链接这个请求到请求队列的链表结构,调用blkdev_dequeue_request()可从队列中移除请求。 使用如下宏可以从request获得数据传送的方向: rq_data_dir(struct request *req); 0返回值表示从设备中读,非 0返回值表示向设备写。 2、请求队列 一个块请求队列是一个块 I/O 请求的队列,其定义如代码清单13.4。 代码清单13.4 request队列结构体 1 struct request_queue 2 { 3 ... 4 /* 保护队列结构体的自旋锁 */ 5 spinlock_t __queue_lock; 6 spinlock_t *queue_lock; 7 8 /* 队列kobject */ 9 struct kobject kobj; 10 11 /* 队列设置 */ 12 unsigned long nr_requests; /* 最大请求数量 */ 13 unsigned int nr_congestion_on; 14 unsigned int nr_congestion_off; 15 unsigned int nr_batching; 16 17 unsigned short max_sectors; /* 最大的扇区数 */ 18 unsigned short max_hw_sectors; 19 unsigned short max_phys_segments; /* 最大的段数 */ 20 unsigned short max_hw_segments; 21 unsigned short hardsect_size; /* 硬件扇区尺寸 */ 22 unsigned int max_segment_size; /* 最大的段尺寸 */ 23 24 unsigned long seg_boundary_mask; /* 段边界掩码 */ 25 unsigned int dma_alignment; /* DMA 传送的内存对齐限制 */ 26 27 struct blk_queue_tag *queue_tags; 28 29 atomic_t refcnt; /* 引用计数 */ 30 31 unsigned int in_flight; 32 33 unsigned int sg_timeout; 34 unsigned int sg_reserved_size; 35 int node; 36 37 struct list_head drain_list; 38 39 struct request *flush_rq; 40 unsigned char ordered; 41 }; 请求队列跟踪等候的块I/O请求,它存储用于描述这个设备能够支持的请求的类型信息、它们的最大大小、多少不同的段可进入一个请求、硬件扇区大小、对齐要求等参数,其结果是:如果请求队列被配置正确了,它不会交给该设备一个不能处理的请求。 请 求队列还实现一个插入接口,这个接口允许使用多个I/O调度器,I/O调度器(也称电梯)的工作是以最优性能的方式向驱动提交I/O请求。大部分I/O 调度器累积批量的 I/O 请求,并将它们排列为递增(或递减)的块索引顺序后提交给驱动。进行这些工作的原因在于,对于磁头而言,当给定顺序排列的请求时,可以使得磁盘顺序地从一 头到另一头工作,非常像一个满载的电梯,在一个方向移动直到所有它的“请求”已被满足。 另外,I/O调度器还负责合并邻近的请求,当一个新 I/O 请求被提交给调度器后,它会在队列里搜寻包含邻近扇区的请求;如果找到一个,并且如果结果的请求不是太大,调度器将合并这2个请求。 对磁盘等块设备进行I/O操作顺序的调度类似于电梯的原理,先服务完上楼的乘客,再服务下楼的乘客效率会更高,而“上蹿下跳”,顺序响应用户的请求则会导致电梯无序地忙乱。 Linux 2.6包含4个I/O调度器,它们分别是No-op I/O scheduler、Anticipatory I/O scheduler、Deadline I/O scheduler与CFQ I/O scheduler。 Noop I/O scheduler是一个简化的调度程序,它只作最基本的合并与排序。 Anticipatory I/O scheduler是当前内核中默认的I/O调度器,它拥有非常好的性能,在2.5中它就相当引人注意。在与2.4内核进行的对比测试中,在2.4中多项 以分钟为单位完成的任务,它则是以秒为单位来完成的,正因为如此它成为目前2.6中默认的I/O调度器。Anticipatory I/O scheduler的缺点是比较庞大与复杂,在一些特殊的情况下,特别是在数据吞吐量非常大的数据库系统中它会变得比较缓慢。 Deadline I/O scheduler是针对Anticipatory I/O scheduler的缺点进行改善而来的,表现出的性能几乎与Anticipatory I/O scheduler一样好,但是比Anticipatory小巧。 CFQ I/O scheduler为系统内的所有任务分配相同的带宽,提供一个公平的工作环境,它比较适合桌面环境。事实上在测试中它也有不错的表现,mplayer、xmms等多媒体播放器与它配合的相当好,回放平滑,几乎没有因访问磁盘而出现的跳帧现象。 内核block目录中的noop-iosched.c、as-iosched.c、deadline-iosched.c和cfq-iosched.c文件分别实现了上述调度算法。 可以通过给kernel添加启动参数,选择使用的IO调度算法,如: kernel elevator=deadline ? 初始化请求队列 request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock); 该函数的第1个参数是请求处理函数的指针,第2个参数是控制访问队列权限的自旋锁,这个函数会发生内存分配的行为,故它可能会失败,因此一定要检查它的返回值。这个函数一般在块设备驱动的模块加载函数中调用。 ? 清除请求队列 void blk_cleanup_queue(request_queue_t * q); 这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。 而blk_put_queue()宏则定义为: #define blk_put_queue(q) blk_cleanup_queue((q)) ? 分配“请求队列” request_queue_t *blk_alloc_queue(int gfp_mask); 对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用上述函数分配1个“请求队列”,并使用如下函数来绑定“请求队列”和“制造请求”函数。 void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn); 在13.6.2节我们会看到,这种方式分配的“请求队列”实际上不包含任何请求,所以给其加上引号。 ? 提取请求 struct request *elv_next_request(request_queue_t *queue); 上 述函数用于返回下一个要处理的请求(由 I/O 调度器决定),如果没有请求则返回NULL。elv_next_request()不会清除请求,它仍然将这个请求保留在队列上,但是标识它为活动的,这 个标识将阻止I/O 调度器合并其它的请求到已开始执行的请求。因为elv_next_request()不从队列里清除请求,因此连续调用它2次,2次会返回同一个请求结构 体。 ? 去除请求 void blkdev_dequeue_request(struct request *req); 上述函数从队列中去除1个请求。如果驱动中同时从同一个队列中操作了多个请求,它必须以这样的方式将它们从队列中去除。 如果需要将1个已经出列的请求归还到队列中,可以调用: void elv_requeue_request(request_queue_t *queue, struct request *req); 另外,块设备层还提供了一套函数,这些函数可被驱动用来控制一个请求队列的操作,主要包括: ? 启停请求队列 void blk_stop_queue(request_queue_t *queue); void blk_start_queue(request_queue_t *queue); 如果块设备到达不能处理等候的命令的状态,应调用blk_stop_queue()来告知块设备层。之后,请求函数将不被调用,除非再次调用blk_start_queue()将设备恢复到可处理请求的状态。 ? 参数设置 void blk_queue_max_sectors(request_queue_t *queue, unsigned short max); void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max); void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max); void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max); 这 些函数用于设置描述块设备可处理的请求的参数。blk_queue_max_sectors()描述任一请求可包含的最大扇区数,缺省值为 255;blk_queue_max_phys_segments()和 blk_queue_max_hw_segments()都控制1个请求中可包含的最大物理段(系统内存中不相邻的 区),blk_queue_max_hw_segments()考虑了系统I/O内存管理单元的重映射,这2个参数缺省都是 128。blk_queue_max_segment_size告知内核请求段的最大字节数,缺省值为65,536。 ? 通告内核 void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr); 该函数用于告知内核块设备执行DMA时可使用的最高物理地址dma_addr,如果一个请求包含超出这个限制的内存引用,一个“反弹”缓冲区将被用来给这个操作。这种方式的代价昂贵,因此应尽量避免使用。 可 以给dma_addr参数提供任何可能的值或使用预先定义的宏,如BLK_BOUNCE_HIGH(对高端内存页使用反弹缓冲区)、 BLK_BOUNCE_ISA(驱动只可在16M的ISA区执行DMA)或者BLK_BOUCE_ANY(驱动可在任何地址执行DMA),缺省值是 BLK_BOUNCE_HIGH。 blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask); 如果我们正在驱动编写的设备无法处理跨越一个特殊大小内存边界的请求,应该使用这个函数来告知内核这个边界。例如,如果设备处理跨4MB 边界的请求有困难,应该传递一个0x3fffff 掩码。缺省的掩码是0xffffffff(对应4GB边界)。 void blk_queue_dma_alignment(request_queue_t *queue, int mask); 该函数用于告知内核块设备施加于DMA 传送的内存对齐限制,所有请求都匹配这个对齐,缺省的屏蔽是 0x1ff,它导致所有的请求被对齐到 512字节边界。 void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max); 该函数用于告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对界。但是,内核块设备层和驱动之间的通信还是以512字节扇区为单位进行。 3、块I/O 通常1个bio对应1个I/O请求,代码清单13.5给出了bio结构体的定义。IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个bio。 代码清单13.5 bio结构体 1 struct bio 2 { 3 sector_t bi_sector; /* 要传输的第1个扇区 */ 4 struct bio *bi_next; /* 下一个bio */ 5 struct block_device *bi_bdev; 6 unsigned long bi_flags; /* 状态,命令等 */ 7 unsigned long bi_rw; /* 低位表示READ/WRITE,高位表示优先级*/ 8 9 unsigned short bi_vcnt; /* bio_vec数量 */ 10 unsigned short bi_idx; /* 当前bvl_vec索引 */ 11 12 /*不相邻的物理段的数目*/ 13 unsigned short bi_phys_segments; 14 15 /*物理合并和DMA remap合并后不相邻的物理段的数目*/ 16 unsigned short bi_hw_segments; 17 18 unsigned int bi_size; /* 以字节为单位所需传输的数据大小 */ 19 20 /* 为了明了最大的hw尺寸,我们考虑这个bio中第1个和最后1个 21 虚拟的可合并的段的尺寸 */ 22 unsigned int bi_hw_front_size; 23 unsigned int bi_hw_back_size; 24 25 unsigned int bi_max_vecs; /* 我们能持有的最大bvl_vecs数 */ 26 27 struct bio_vec *bi_io_vec; /* 实际的vec列表 */ 28 29 bio_end_io_t *bi_end_io; 30 atomic_t bi_cnt; 31 32 void *bi_private; 33 34 bio_destructor_t *bi_destructor; /* destructor */ 35 }; 下面我们对其中的核心成员进行分析: sector_t bi_sector; 标示这个 bio 要传送的第一个(512字节)扇区。 unsigned int bi_size; 被传送的数据大小,以字节为单位,驱动中可以使用bio_sectors(bio)宏获得以扇区为单位的大小。 unsigned long bi_flags; 一组描述 bio 的标志,如果这是一个写请求,最低有效位被置位,可以使用bio_data_dir(bio)宏来获得读写方向。 unsigned short bio_phys_segments; unsigned short bio_hw_segments; 分别表示包含在这个 BIO 中要处理的不连续的物理内存段的数目和考虑DMA重映像后的不连续的内存段的数目。 bio的核心是一个称为 bi_io_vec的数组,它由bio_vec结构体组成,bio_vec结构体的定义如代码清单13.6。 代码清单13.6 bio_vec结构体 1 struct bio_vec 2 { 3 struct page *bv_page; /* 页指针 */ 4 unsigned int bv_len; /* 传输的字节数 */ 5 unsigned int bv_offset; /* 偏移位置 */ 6 }; 我们不应该直接访问bio的bio_vec成员,而应该使用bio_for_each_segment()宏来进行这项工作,可以用这个宏循环遍历整个bio中的每个段,这个宏的定义如代码清单13.7。 代码清单13.7 bio_for_each_segment宏 1 #define __bio_for_each_segment(bvl, bio, i, start_idx) \ 2 for (bvl = bio_iovec_idx((bio), (start_idx)), i = (start_idx); \ 3 i < (bio)->bi_vcnt; \ 4 bvl++, i++) 5 6 #define bio_for_each_segment(bvl, bio, i) \ 7 __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx 图 13.2(a)描述了request队列、request与bio数据结构之间的关系,13.2(b)表示了request、bio和bio_vec数据 结构之间的关系,13.2(c)表示了bio与bio_vec数据结构之间的关系,因此整个图13.2递归地呈现了request队列、request、 bio和bio_vec这4个结构体之间的关系。 (a) request与bio (b) request、bio和bio_vec (c)bio与bio_vec 图13.2 request队列、request、bio和bio_vec结构体之间的关系 内核还提供了一组函数(宏)用于操作bio: int bio_data_dir(struct bio *bio); 这个函数可用于获得数据传输的方向是READ还是WRITE。 struct page *bio_page(struct bio *bio) ; 这个函数可用于获得目前的页指针。 int bio_offset(struct bio *bio) ; 这个函数返回操作对应的当前页内的偏移,通常块I/O操作本身就是页对齐的。 int bio_cur_sectors(struct bio *bio) ; 这个函数返回当前bio_vec要传输的扇区数。 char *bio_data(struct bio *bio) ; 这个函数返回数据缓冲区的内核虚拟地址。 char *bvec_kmap_irq(struct bio_vec *bvec, unsigned long *flags) ; 这个函数返回一个内核虚拟地址,这个地址可用于存取被给定的bio_vec入口指向的数据缓冲区。它也会屏蔽中断并返回1个原子kmap,因此,在bvec_kunmap_irq()被调用以前,驱动不应该睡眠。 void bvec_kunmap_irq(char *buffer, unsigned long *flags); 这个函数是bvec_kmap_irq()函数的“反函数”,它撤销bvec_kmap_irq()创建的映射。 char *bio_kmap_irq(struct bio *bio, unsigned long *flags); 这个函数是对bvec_kmap_irq()的包装,它返回给定的bio的当前bio_vec入口的映射。 char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type); 这个函数通过kmap_atomic()获得返回给定bio的第i个缓冲区的虚拟地址。 void __bio_kunmap_atomic(char *addr, enum km_type type); 这个函数返还由__bio_kmap_atomic()获得的内核虚拟地址。 另外,对bio的引用计数通过如下函数完成: void bio_get(struct bio *bio); //引用bio void bio_put(struct bio *bio); //释放对bio的引用 13.2.4块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为: int register_blkdev(unsigned int major, const char *name); major 参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被显示。 如果major为0,内核会自动分配一个新的主设备号,register_blkdev()函数的返回值就是这个主设备号。如果 register_blkdev()返回1个负值,表明发生了一个错误。 与register_blkdev()对应的注销函数是unregister_blkdev(),其原型为: int unregister_blkdev(unsigned int major, const char *name); 这里,传递给register_blkdev()的参数必须与传递给register_blkdev()的参数匹配,否则这个函数返回-EINVAL。 值得一提的是,在2.6内核中,对 register_blkdev()的调用完全是可选的,register_blkdev()的功能已随时间正在减少,这个调用最多只完全2件事: ① 如果需要,分配一个动态主设备号。 ② 在/proc/devices中创建一个入口。 在将来的内核中,register_blkdev()可能会被去掉。但是目前的大部分驱动仍然调用它。代码清单13.8给出了1个块设备驱动注册的模板。 代码清单13.8 块设备驱动注册模板 1 xxx_major = register_blkdev(xxx_major, "xxx"); 2 if (xxx_major <= 0) //注册失败 3 { 4 printk(KERN_WARNING "xxx: unable to get major number\n"); 5 return -EBUSY; 6 } 13.3 Linux块设备驱动模块加载与卸载 在块设备驱动的模块加载函数中通常需要完成如下工作: ① 分配、初始化请求队列,绑定请求队列和请求函数。 ② 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。 ③ 注册块设备驱动。 代 码清单13.9和13.10分别给出了使用blk_alloc_queue()分配请求队列并使用blk_queue_make_request()绑定 “请求队列”和“制造请求”函数,以及使用blk_init_queue()初始化请求队列并绑定请求队列与请求处理函数2种不同情况下的块设备驱动模块 加载函数模板。 代码清单13.9 块设备驱动模块加载函数模板(使用blk_alloc_queue) 1 static int __init xxx_init(void) 2 { 3 //分配gendisk 4 xxx_disks = alloc_disk(1); 5 if (!xxx_disks) 6 { 7 goto out; 8 } 9 10 //块设备驱动注册 11 if (register_blkdev(XXX_MAJOR, "xxx")) 12 { 13 err = - EIO; 14 goto out; 15 } 16 17 //“请求队列”分配 18 xxx_queue = blk_alloc_queue(GFP_KERNEL); 19 if (!xxx_queue) 20 { 21 goto out_queue; 22 } 23 24 blk_queue_make_request(xxx_queue, &xxx_make_request); //绑定“制造请求”函数 25 blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺寸设置 26 27 //gendisk初始化 28 xxx_disks->major = XXX_MAJOR; 29 xxx_disks->first_minor = 0; 30 xxx_disks->fops = &xxx_op; 31 xxx_disks->queue = xxx_queue; 32 sprintf(xxx_disks->disk_name, "xxx%d", i); 33 set_capacity(xxx_disks, xxx_size); //xxx_size以512bytes为单位 34 add_disk(xxx_disks); //添加gendisk 35 36 return 0; 37 out_queue: unregister_blkdev(XXX_MAJOR, "xxx"); 38 out: put_disk(xxx_disks); 39 blk_cleanup_queue(xxx_queue); 40 41 return - ENOMEM; 42 } 代码清单13.10 块设备驱动模块加载函数模板(使用blk_init_queue) 1 static int __init xxx_init(void) 2 { 3 //块设备驱动注册 4 if (register_blkdev(XXX_MAJOR, "xxx")) 5 { 6 err = - EIO; 7 goto out; 8 } 9 10 //请求队列初始化 11 xxx_queue = blk_init_queue(xxx_request, xxx_lock); 12 if (!xxx_queue) 13 { 14 goto out_queue; 15 } 16 17 blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇区尺寸设置 18 19 //gendisk初始化 20 xxx_disks->major = XXX_MAJOR; 21 xxx_disks->first_minor = 0; 22 xxx_disks->fops = &xxx_op; 23 xxx_disks->queue = xxx_queue; 24 sprintf(xxx_disks->disk_name, "xxx%d", i); 25 set_capacity(xxx_disks, xxx_size *2); 26 add_disk(xxx_disks); //添加gendisk 27 28 return 0; 29 out_queue: unregister_blkdev(XXX_MAJOR, "xxx"); 30 out: put_disk(xxx_disks); 31 blk_cleanup_queue(xxx_queue); 32 33 return - ENOMEM; 34 } 在块设备驱动的模块卸载函数中通常需要与模块加载函数相反的工作: ① 清除请求队列。 ② 删除gendisk和对gendisk的引用。 ③ 删除对块设备的引用,注销块设备驱动。 代码清单13.11给出了块设备驱动模块卸载函数的模板。 代码清单13.11 块设备驱动模块卸载函数模板 1 static void __exit xxx_exit(void) 2 { 3 if (bdev) 4 { 5 invalidate_bdev(xxx_bdev, 1); 6 blkdev_put(xxx_bdev); 7 } 8 del_gendisk(xxx_disks); //删除gendisk 9 put_disk(xxx_disks); 10 blk_cleanup_queue(xxx_queue[i]); //清除请求队列 11 unregister_blkdev(XXX_MAJOR, "xxx"); 12 } 13.4块设备的打开与释放 块设备驱动的open()和release()函数并非是必须的,1个简单的块设备驱动可以不提供open()和release()函数。 块 设备驱动的open()函数和其字符设备驱动中的对等体非常类似,都以相关的inode和file结构体指针作为参数。当一个节点引用一个块设备 时,inode->i_bdev->bd_disk 包含一个指向关联 gendisk 结构体的指针。因此,类似于字符设备驱动,我们也可以将gendisk的private_data赋给file的 private_data,private_data同样最好是指向描述该设备的设备结构体xxx_dev的指针,如代码清单13.12。 代码清单13.12 块设备的open()函数中赋值private_data 1 static int xxx_open(struct inode *inode, struct file *filp) 2 { 3 struct xxx_dev *dev = inode->i_bdev->bd_disk->private_data; 4 filp->private_data = dev; //赋值file的private_data 5 ... 6 return 0; 7 } 在一个处理真实的硬件设备的驱动中,open()和release()方法还应当设置驱动和硬件的状态,这些工作可能包括启停磁盘、加锁一个可移出设备和分配DMA缓冲等。 13.5块设备驱动的ioctl函数 与字符设备驱动一样,块设备可以包含一个 ioctl()函数以提供对设备的I/O控制能力。实际上,高层的块设备层代码处理了绝大多数ioctl(),因此,具体的块设备驱动中通常不再需要实现很多ioctl命令。 代码清单13.13给出的ioctl()函数只实现1个命令HDIO_GETGEO,用于获得磁盘的几何信息(geometry,指CHS,即Cylinder、Head、Sector/Track)。 代码清单13.13 块设备驱动的I/O控制函数模板 1 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, 2 unsigned long arg) 3 { 4 long size; 5 struct hd_geometry geo; 6 struct xxx_dev *dev = filp->private_data; //通过file->private获得设备结构体 7 8 switch (cmd) 9 { 10 case HDIO_GETGEO: 11 size = dev->size *(hardsect_size / KERNEL_SECTOR_SIZE); 12 geo.cylinders = (size &~0x3f) >> 6; 13 geo.heads = 4; 14 geo.sectors = 16; 15 geo.start = 4; 16 if (copy_to_user((void __user*)arg, &geo, sizeof(geo))) 17 { 18 return - EFAULT; 19 } 20 return 0; 21 } 22 23 return - ENOTTY; //不知道的命令 24 } 13.6块设备驱动I/O请求处理 13.6.1使用请求队列 块设备驱动请求函数的原型为: void request(request_queue_t *queue); 这个函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才调用这个函数。 请求函数可以在没有完成请求队列中的所有请求的情况下返回,甚至它1个请求不完成都可以返回。但是,对大部分设备而言,在请求函数中处理完所有请求后再返回通常是值得推荐的方法。代码清单13.14给出了1个简单的request()函数的例子。 代码清单13.14 块设备驱动请求函数例程 1 static void xxx_request(request_queue_t *q) 2 { 3 struct request *req; 4 while ((req = elv_next_request(q)) != NULL) 5 { 6 struct xxx_dev *dev = req->rq_disk->private_data; 7 if (!blk_fs_request(req)) //不是文件系统请求 8 { 9 printk(KERN_NOTICE "Skip non-fs request\n"); 10 end_request(req, 0);//通知请求处理失败 11 continue; 12 } 13 xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer, 14 rq_data_dir(req)); //处理这个请求 15 end_request(req, 1); //通知成功完成这个请求 16 } 17 } 18 19 //完成具体的块设备I/O操作 20 static void xxx_transfer(struct xxx_dev *dev, unsigned long sector, unsigned 21 long nsect, char *buffer, int write) 22 { 23 unsigned long offset = sector * KERNEL_SECTOR_SIZE; 24 unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE; 25 if ((offset + nbytes) > dev->size) 26 { 27 printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes); 28 return ; 29 } 30 if (write) 31 { 32 write_dev(offset, buffer, nbytes); //向设备些nbytes个字节的数据 33 } 34 else 35 { 36 read_dev(offset, buffer, nbytes); //从设备读nbytes个字节的数据 37 } 38 } 上 述代码第4行使用elv_next_request()获得队列中第一个未完成的请求,end_request()会将请求从请求队列中剥离。第7行判断 请求是否为文件系统请求,如果不是,则直接清除,调用end_request(),传递给end_request()的第2个参数为0意味着处理该请求失 败。而第15行传递给end_request()的第2个参数为1意味着该请求处理成功。 end_request()函数非常重要,其源代码如代码清单13.15。 代码清单13.15 end_request()函数源代码 1 void end_request(struct request *req, int uptodate) 2 { 3 4 if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) 5 { 6 add_disk_randomness (req->rq_disk); 7 blkdev_dequeue_request (req); 8 end_that_request_last(req); 9 } 10 } 当设备已经完成1个I/O请求的部分或者全部扇区传输后,它必须通告块设备层,上述代码中的第4行完成这个工作。end_that_request_first()函数的原型为: int end_that_request_first(struct request *req, int success, int count); 这 个函数告知块设备层,块设备驱动已经完成count个扇区的传送。end_that_request_first()的返回值是一个标志,指示是否这个请 求中的所有扇区已经被传送。返回值为0表示所有的扇区已经被传送并且这个请求完成,之后,我们必须使用 blkdev_dequeue_request()来从队列中清除这个请求。最后,将这个请求传递给end_that_request_last()函 数: void end_that_request_last(struct request *req); end_that_request_last()通知所有正在等待这个请求完成的对象请求已经完成并回收这个请求结构体。 第6行的add_disk_randomness()函数的作用是使用块 I/O 请求的定时来给系统的随机数池贡献熵,它不影响块设备驱动。但是,仅当磁盘的操作时间是真正随机的时候(大部分机械设备如此),才应该调用它。 代码清单13.16给出了1个更复杂的请求函数,它进行了3层遍历:遍历请求队列中的每个请求;遍历请求中的每个bio;遍历bio中的每个段。 代码清单13.16 请求函数遍历请求、bio和段 1 static void xxx_full_request(request_queue_t *q) 2 { 3 struct request *req; 4 int sectors_xferred; 5 struct xxx_dev *dev = q->queuedata; 6 /* 遍历每个请求 */ 7 while ((req = elv_next_request(q)) != NULL) 8 { 9 if (!blk_fs_request(req)) 10 { 11 printk(KERN_NOTICE "Skip non-fs request\n"); 12 13 end_request(req, 0); 14 continue; 15 } 16 sectors_xferred = xxx_xfer_request(dev, req); 17 if (!end_that_request_first(req, 1, sectors_xferred)) 18 { 19 blkdev_dequeue_request(req); 20 end_that_request_last(req); 21 } 22 } 23 } 24 /* 请求处理 */ 25 static int xxx_xfer_request(struct xxx_dev *dev, struct request *req) 26 { 27 struct bio *bio; 28 int nsect = 0; 29 /* 遍历请求中的每个bio */ 30 rq_for_each_bio(bio, req) 31 { 32 xxx_xfer_bio(dev, bio); 33 nsect += bio->bi_size / KERNEL_SECTOR_SIZE; 34 } 35 return nsect; 36 } 37 /* bio处理 */ 38 static int xxx_xfer_bio(struct xxx_dev *dev, struct bio *bio) 39 { 40 int i; 41 struct bio_vec *bvec; 42 sector_t sector = bio->bi_sector; 43 44 /* 遍历每1段 */ 45 bio_for_each_segment(bvec, bio, i) 46 { 47 char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); 48 xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio) 49 == WRITE); 50 sector += bio_cur_sectors(bio); 51 __bio_kunmap_atomic(bio, KM_USER0); 52 } 53 return 0; 54 } 图13.3呈现了1个请求队列内request、bio以及bio中segment的层层遍历关系。 图13.3 遍历1个请求队列 13.6.2不使用请求队列 使 用请求队列对于一个机械的磁盘设备而言的确有助于提高系统的性能,但是对于许多块设备,如数码相机的存储卡、RAM盘等完全可真正随机访问的设备而言,无 法从高级的请求队列逻辑中获益。对于这些设备,块层支持“无队列”的操作模式,为使用这个模式,驱动必须提供一个“制造请求”函数,而不是一个请求函数, “制造请求”函数的原型为: typedef int (make_request_fn) (request_queue_t *q, struct bio *bio); 上述函数的第1个参数仍然是“请求队列”,但是这个“请求队列”实际不包含任何请求。因此,“制造请求”函数的主要参数是bio结构体,这个bio结构体表示1个或多个要传送的缓冲区。“制造请求”函数或者直接进行传输,或者把请求重定向给其它设备。 在“制造请求”函数中处理bio的方式与13.6.1节中描述的完全一致,但是在处理完成后应该使用bio_endio()函数通知处理结束: void bio_endio(struct bio *bio, unsigned int bytes, int error); 参 数bytes 是已经传送的字节数,它可以比这个bio所代表的字节数少,这意味着“部分完成”,同时bio结构体中的当前缓冲区指针需要更新。当设备进一步处理这个 bio后,驱动应该再次调用 bio_endio(),如果不能完成这个请求,应指出一个错误,错误码赋值给error参数。 不管对应的I/O处理成功与否,“制造请求”函数都应该返回0。如果“制造请求”函数返回一个非零值,bio 将被再次提交。 代码清单13.17给出了1个“制造请求”函数的例子。 代码清单13.17 “制造请求”函数例程 1 static int xxx_make_request(request_queue_t *q, struct bio *bio) 2 { 3 struct xxx_dev *dev = q->queuedata; 4 int status; 5 status = xxx_xfer_bio(dev, bio); //处理bio 6 bio_endio(bio, bio->bi_size, status); //通告结束 7 return 0; 8 } 为了使用无队列的I/O请求处理,驱动模块加载函数应遵循代码清单13.9的模板而非13.10的模板,而使用请求队列时,驱动模块加载函数应遵循代码清单13.10的模板。 13.7实例1:RAMDISK驱动 13.7.1 RAMDISK的硬件原理 RAMDISK(RAM盘)是一种模拟磁盘,其数据实际上存储在RAM中,它使用一部分内存空间来模拟出一个磁盘,以块设备的方式来访问这片内存,RAMDISK对应的设备文件一般为/dev/ram%d。 使用如下一组命令就可以创建并挂载RAMDISK: mkdir /tmp/ramdisk0 创建装载点 mke2fs /dev/ram0 创建一个文件系统 mount /dev/ram0 /tmp/ramdisk0 装载ramdisk 其中,mke2f /dev/ram0命令的执行会回馈类似于如下的信息: mke2fs 1.14, 9-Jan-1999 for EXT2 FS 0.5b, 95/08/09 Linux ext2 filesystem format Filesystem label= 1024 inodes, 4096 blocks 204 blocks (4.98%) reserved for the super user First data block=1 Block size=1024 (log=0) Fragment size=1024 (log=0) 1 block group 8192 blocks per group, 8192 fragments per group 1024 inodes per group 表明创建了1个4MB的块设备,共包含4096块,每块1024字节。 13.7.2 RAMDISK驱动模块加载与卸载 RAMDISK的驱动模块加载函数完成的工作与13.3节给出的模板完全一致,由于RAM盘属于完全的随机设备,宜使用无队列的I/O处理方式,其驱动中实现了如图13.4所示的与块设备驱动模板对应的函数。 图13.4 块设备驱动模板与RAMDISK设备驱动的映射 代码清单13.18给出了RAMDISK设备驱动的模块加载与卸载函数,实现的功能与模块是一致的。 代码清单13.18 RAMDISK设备驱动的模块加载与卸载函数 1 static int __init rd_init(void) 2 { 3 int i; 4 int err = - ENOMEM; 5 //调整块尺寸 6 if (rd_blocksize > PAGE_SIZE || rd_blocksize < 512 || (rd_blocksize & 7 (rd_blocksize - 1))) 8 { 9 printk("RAMDISK: wrong blocksize %d, reverting to defaults\n", rd_blocksize) ; 10 11 rd_blocksize = BLOCK_SIZE; 12 } 13 //分配gendisk 14 for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++) 15 { 16 rd_disks[i] = alloc_disk(1); //分配gendisk 17 if (!rd_disks[i]) 18 goto out; 19 } 20 //块设备注册 21 if (register_blkdev(RAMDISK_MAJOR, "ramdisk")) 22 //注册块设备 23 { 24 err = - EIO; 25 goto out; 26 } 27 28 devfs_mk_dir("rd"); //创建devfs目录 29 30 for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++) 31 { 32 struct gendisk *disk = rd_disks[i]; 33 //分配并绑定请求队列与“制造请求”函数 34 rd_queue[i] = blk_alloc_queue(GFP_KERNEL); 35 if (!rd_queue[i]) 36 goto out_queue; 37 38 blk_queue_make_request(rd_queue[i], &rd_make_request); //绑定“制造请求”函数 39 blk_queue_hardsect_size(rd_queue[i], rd_blocksize); //硬件扇区尺寸设置 40 41 //初始化gendisk 42 disk->major = RAMDISK_MAJOR; 43 disk->first_minor = i; 44 disk->fops = &rd_bd_op; 45 disk->queue = rd_queue[i]; 46 disk->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO; 47 sprintf(disk->disk_name, "ram%d", i); 48 sprintf(disk->devfs_name, "rd/%d", i); 49 set_capacity(disk, rd_size *2); 50 add_disk(rd_disks[i]); //添加gendisk 51 } 52 53 // rd_size以kB为单位 54 printk("RAMDISK driver initialized: " 55 "%d RAMDISKs of %dK size %d blocksize\n", 56 CONFIG_BLK_DEV_RAM_COUNT,rd_size, rd_blocksize); 57 58 return 0; 59 out_queue: unregister_blkdev(RAMDISK_MAJOR, "ramdisk"); 60 out: 61 while (i--) 62 { 63 put_disk(rd_disks[i]); 64 blk_cleanup_queue(rd_queue[i]); 65 } 66 return err; 67 } 68 69 static void __exit rd_cleanup(void) 70 { 71 int i; 72 73 for (i = 0; i < CONFIG_BLK_DEV_RAM_COUNT; i++) 74 { 75 struct block_device *bdev = rd_bdev[i]; 76 rd_bdev[i] = NULL; 77 if (bdev) 78 { 79 invalidate_bdev(bdev, 1); 80 blkdev_put(bdev); 81 } 82 del_gendisk(rd_disks[i]); //删除gendisk 83 put_disk(rd_disks[i]); //释放对gendisk的引用 84 blk_cleanup_queue(rd_queue[i]); //清除请求队列 85 } 86 devfs_remove("rd"); 87 unregister_blkdev(RAMDISK_MAJOR, "ramdisk"); //块设备注销 88 } 13.7.3 RAMDISK设备驱动block_device_operations及成员函数 RAMDISK 提供block_device_operations结构体中2个成员函数open()和ioctl()的实现,代码清单13.19给出了RAMDISK 设备驱动的block_device_operations结构体定义及open()和ioctl()函数。 代码清单13.19 RAMDISK设备驱动block_device_operations结构体及成员函数 1 static struct block_device_operations rd_bd_op = 2 { 3 .owner = THIS_MODULE, 4 .open = rd_open, 5 .ioctl = rd_ioctl, 6 }; 7 8 static int rd_open(struct inode *inode, struct file *filp) 9 { 10 unsigned unit = iminor(inode);//获得次设备号 11 12 if (rd_bdev[unit] == NULL) { 13 struct block_device *bdev = inode->i_bdev;//获得block_device结构体指针 14 struct address_space *mapping; //地址空间 15 unsigned bsize; 16 gfp_t gfp_mask; 17 /* 设置inode成员 */ 18 inode = igrab(bdev->bd_inode); 19 rd_bdev[unit] = bdev; 20 bdev->bd_openers++; 21 bsize = bdev_hardsect_size(bdev); 22 bdev->bd_block_size = bsize; 23 inode->i_blkbits = blksize_bits(bsize); 24 inode->i_size = get_capacity(bdev->bd_disk)<<9; 25 26 mapping = inode->i_mapping; 27 mapping->a_ops = &ramdisk_aops; 28 mapping->backing_dev_info = &rd_backing_dev_info; 29 bdev->bd_inode_backing_dev_info = &rd_file_backing_dev_info; 30 31 gfp_mask = mapping_gfp_mask(mapping); 32 gfp_mask &= ~(__GFP_FS|__GFP_IO); 33 gfp_mask |= __GFP_HIGH; 34 mapping_set_gfp_mask(mapping, gfp_mask); 35 } 36 37 return 0; 38 } 39 40 static int rd_ioctl(struct inode *inode, struct file *file, 41 unsigned int cmd, unsigned long arg) 42 { 43 int error; 44 struct block_device *bdev = inode->i_bdev; 45 46 if (cmd != BLKFLSBUF) /* 不是flush buffer cache 命令 */ 47 return -ENOTTY; 48 /* 刷新buffer cache */ 49 error = -EBUSY; 50 down(&bdev->bd_sem); 51 if (bdev->bd_openers <= 2) { 52 truncate_inode_pages(bdev->bd_inode->i_mapping, 0); 53 error = 0; 54 } 55 up(&bdev->bd_sem); 56 return error; 57 } 13.7.4 RAMDISK I/O请求处理 鉴于RAMDISK是一种完全随机设备,其驱动中宜使用“制造请求”函数而非请求函数,这个函数的实现如代码清单13.20。 代码清单13.20 RAMDISK设备驱动“制造请求”函数 1 static int rd_make_request(request_queue_t *q, struct bio *bio) 2 { 3 struct block_device *bdev = bio->bi_bdev; 4 struct address_space *mapping = bdev->bd_inode->i_mapping; 5 sector_t sector = bio->bi_sector; 6 unsigned long len = bio->bi_size >> 9; 7 int rw = bio_data_dir(bio);//数据传输方向:读/写? 8 struct bio_vec *bvec; 9 int ret = 0, i; 10 11 if (sector + len > get_capacity(bdev->bd_disk)) 12 //超过容量 13 goto fail; 14 15 if (rw == READA) 16 rw = READ; 17 //遍历每个段 18 bio_for_each_segment(bvec, bio, i) 19 { 20 ret |= rd_blkdev_pagecache_IO(rw, bvec, sector, mapping); 21 sector += bvec->bv_len >> 9; 22 } 23 if (ret) 24 goto fail; 25 26 bio_endio(bio, bio->bi_size, 0); //处理结束 27 return 0; 28 fail: bio_io_error(bio, bio->bi_size); 29 return 0; 30 } 13.8实例2:IDE硬盘设备驱动 13.8.1 IDE硬盘设备原理 IDE(Integrated Drive Electronics)接口,也就是集成驱动器电路接口,原名为ATA(AT Attachment,AT嵌入式)接口,其本意为将硬盘控制器与盘体集成在一起的硬盘驱动器,经历了ATA-1到ATA-7以及SATA-1和 SATA-2的发展历史。ATA-1至ATA-4采用40芯排线缆,ATA-5至ATA-7则采用40针80芯线缆,虽然线缆数量增加了,但是逻辑原理没 有变,只是通过物理上的改变来达到改善PCB信号完整性的目的,它提供更多的地线并使信号线临近地线,从而减少电流回流的面积。SATA-1和SATA- 2与ATA-1至ATA-7相比,数据传输方式由并行转变为串行。 IDE接口的硬件原理实际上非常简单,对CPU的外围总线进行简单的扩展后就可外接IDE控制器,表13.2给出了40针IDE接口的引脚定义。 表13.2 IDE接口的引脚定义 引脚 信号 信号描述 信号方向 引脚 信号 信号描述 信号方向 1 RSET 复位 I 2 GND 地 I/O 3 DD7 数据位7 I/O 4 DD8 数据位8 I/O 5 DD6 数据位6 I/O 6 DD9 数据位9 I/O 7 DD5 数据位5 I/O 8 DD10 数据位10 I/O 9 DD4 数据位4 I/O 10 DD11 数据位11 I/O 11 DD3 数据位3 I/O 12 DD12 数据位12 I/O 13 DD2 数据位2 I/O 14 DD13 数据位13 I/O 15 DD1 数据位1 I/O 16 DD14 数据位14 I/O 17 DD0 数据位0 I/O 18 DD15 数据位15 I/O 19 GND 地 20 N.C 未用 21 DMARQ DMA请求 O 22 GND 地 23 DIOW/ 写选通 I 24 GND 地 25 DIOR/ 读选通 I 26 GND 地 27 IORDY 通道就绪 O 28 DPSYNC:CXEL 同步电缆选择 29 DMACK/ DMA应答 O 30 GND 地 31 INTRQ/ 中断请求 O 32 IOCS13/ 16为IO O 33 DA1 地址1 I 34 PDIAG/ 诊断完成 O 35 DA0 地址0 I 36 DA2 地址2 I 37 CS1FX/ 片选0 I 38 CS3FX/ 片选1 I 39 DASP/ 驱动器激活 O 40 GND 地 IDE控制器提供了一组寄存器,通过这些寄存器,主机能控制IDE驱动器的行为和查询其状态,表13.3给出了IDE接口寄存器的定义。 表13.3 IDE接口寄存器定义 片选1 片选0 地址2 地址1 地址0 读 写 位数 1 0 0 0 0 数据寄存器 数据寄存器 16 1 0 0 0 1 错误寄存器 特征寄存器 8 1 0 0 1 0 扇区数寄存器 扇区数寄存器 8 1 0 0 1 1 扇区号寄存器 扇区号寄存器 8 1 0 1 0 0 柱面号寄存器(低8位) 柱面号寄存器(低8位) 8 1 0 1 0 1 柱面号寄存器(高8位) 柱面号寄存器(高8位) 8 1 0 1 1 0 驱动器选择/磁头寄存器 驱动器选择/磁头寄存器 8 1 0 1 1 1 状态寄存器 命令寄存器 8 0 1 1 1 0 状态寄存器 设备控制器寄存器 8 IDE硬盘的传输模式有以下3种: ? PIO(Programmed I/O)模式:PIO模式是一种通过CPU执行I/O端口指令来进行数据读写的数据交换模式,是最早先的硬盘数据传输模式,数据传输速率低下,CPU占有率也很高。 ? DMA(Driect Memory Access)模式:DMA模式是一种不经过CPU而直接从内存了存取数据的数据交换模式。PIO模式下硬盘和内存之间的数据传输是由CPU来控制的;而 在DMA模式下,CPU只须向DMA控制器下达指令,让DMA控制器来处理数据的传送,数据传送完毕再把信息反馈给CPU,这样就很大程度上减轻了CPU 的资源占有率。 ? Ultra DMA(简称UDMA)模式:它在包含了DMA模式的优点的基础上,又增加了CRC校验技术,提高数据传输过程中的准确性,安全性得到保障。另外,在以往 的硬盘数据传输模式下,一个时钟周期只传输一次数据,而在UDMA 模式中逐渐应用了Double Data Rate(双倍数据传输)技术,它在时钟的上升沿和下降沿各自进行一次数据传输,使数据传输速度成倍增长。 除了可以以CHS(Cylinder、 Head 和 Sector)的方式定位硬盘的扇区外,还可以用LBA(逻辑块线性地址)的方式来定位,CHS可以换算为LBA。CHS 设计最多只允许 65536 个柱面、16 个磁头,以及 255 扇区/磁轨。这就将容量限制为 267386880 个扇区,即大约 137 GB。 假设用 c表示当前柱面号,h表示当前磁头号,cs表示起始柱面号,hs表示起始磁头号,ss表示起始扇区号,ps表示每磁道有多少个扇区,ph表示每柱面有多少 个磁道(一般情况下,cs=0、hs=0、ss=1、ps=63、ph=255),LBA与CHS有如下对应关系: lba=(c-cs)*ph*ps+(h-hs)*ps+(s-ss) LBA 使得系统忽略硬盘的几何结构,交由驱动器来完成。系统不需要去查询 CHS 值,而只需要查询逻辑块地址(Logical Block Address,LBA),驱动器电子装置会找出要读或写的实际扇区。而LBA48(48位逻辑块地址)则可以使系统支持超过137GB的硬盘。 Linux 内核中,与IDE驱动相关的文件被放置在/drivers/ide目录下,这个目录包含ide.c、ide-cd.c、ide-cd.h、ide- disk.c、ide-dma.c、ide-floppy.c、ide-generic.c、ide-io.c、ide-iops.c、ide- lib.c、ide-pnp.c、ide-probe.c、ide-proc.c、ide-tape.c、ide-taskfile.c、ide- timing.h文件以及针对ARM、PPC、MIPS等外围IDE设备驱动的目录。整个IDE设备驱动的体系结构及其复杂,但大多数都不需要关心,驱动 工程师要使Linux支持某嵌入式系统中的IDE硬盘,所需编写的代码量是非常少的。13.8.2~13.8.3纯粹出于学习目的,对IDE硬盘驱动的 block_device_operations及IO请求处理过程进行分析以进一步加深读者对Linux块设备I/O操作方法的印象,13.8.4则从 工程角度出发,讲解如何使Linux支持新系统中的IDE硬盘。 13.8.2 IDE硬盘设备驱动block_device_operations及成员函数 IDE硬盘驱动的block_device_operations中包含了打开、释放、IO控制、获得几何信息、媒介改变和使介质有效的成员函数,这些函数的实现较简单,如代码清单13.21。 代码清单13.21 IDE硬盘驱动block_device_operations结构体及其成员函数 1 static struct block_device_operations idedisk_ops = 2 { 3 .owner = THIS_MODULE, 4 .open = idedisk_open, 5 .release = idedisk_release, 6 .ioctl = idedisk_ioctl, 7 .getgeo = idedisk_getgeo, //得到几何信息 8 .media_changed = idedisk_media_changed, //媒介改变 9 .revalidate_disk= idedisk_revalidate_disk //使介质有效 10 }; 11 12 static int idedisk_ioctl(struct inode *inode, struct file *file, 13 unsigned int cmd, unsigned long arg) 14 { 15 struct block_device *bdev = inode->i_bdev; 16 struct ide_disk_obj *idkp = ide_disk_g(bdev->bd_disk); 17 return generic_ide_ioctl(idkp->drive, file, bdev, cmd, arg);//通用IDE的IO控制 18 } 19 20 21 static int idedisk_getgeo(struct block_device *bdev, struct hd_geometry *geo) 22 { 23 struct ide_disk_obj *idkp = ide_disk_g(bdev->bd_disk); 24 ide_drive_t *drive = idkp->drive; 25 /* 得到几何信息,CHS */ 26 geo->heads = drive->bios_head; 27 geo->sectors = drive->bios_sect; 28 geo->cylinders = (u16)drive->bios_cyl; /* truncate */ 29 return 0; 30 } 31 32 static int idedisk_open(struct inode *inode, struct file *filp) 33 { 34 struct gendisk *disk = inode->i_bdev->bd_disk; 35 struct ide_disk_obj *idkp; 36 ide_drive_t *drive; 37 38 if (!(idkp = ide_disk_get(disk))) 39 return -ENXIO; 40 41 drive = idkp->drive; 42 43 drive->usage++; //使用计数加1 44 if (drive->removable && drive->usage == 1) { 45 ide_task_t args; 46 memset(&args, 0, sizeof(ide_task_t)); 47 args.tfRegister[IDE_COMMAND_OFFSET] = WIN_DOORLOCK; 48 args.command_type = IDE_DRIVE_TASK_NO_DATA; 49 args.handler = &task_no_data_intr; 50 check_disk_change(inode->i_bdev); 51 52 if (drive->doorlocking && ide_raw_taskfile(drive, &args, NULL)) 53 drive->doorlocking = 0; 54 } 55 return 0; 56 } 57 58 static int idedisk_release(struct inode *inode, struct file *filp) 59 { 60 struct gendisk *disk = inode->i_bdev->bd_disk; 61 struct ide_disk_obj *idkp = ide_disk_g(disk); 62 ide_drive_t *drive = idkp->drive; 63 64 if (drive->usage == 1) 65 ide_cacheflush_p(drive); 66 if (drive->removable && drive->usage == 1) { 67 ide_task_t args; 68 memset(&args, 0, sizeof(ide_task_t)); 69 args.tfRegister[IDE_COMMAND_OFFSET] = WIN_DOORUNLOCK; 70 args.command_type = IDE_DRIVE_TASK_NO_DATA; 71 args.handler = &task_no_data_intr; 72 if (drive->doorlocking && ide_raw_taskfile(drive, &args, NULL)) 73 drive->doorlocking = 0; 74 } 75 drive->usage--; //使用计数减1 76 77 ide_disk_put(idkp); 78 return 0; 79 } 13.8.3 IDE硬盘设备驱动I/O请求处理 Linux 对IDE驱动进行了再封装,定义了ide_driver_t结构体,这个结构体容纳了IDE硬盘的探测、移除、请求处理和结束请求处理等函数指针。结束请 求处理函数ide_end_request()是对end_request()函数针对IDE的修改。代码清单13.21给出了ide_driver_t 结构体的定义。 代码清单13.22 ide_driver_t结构体 1 static ide_driver_t idedisk_driver = { 2 .gen_driver = { 3 .owner = THIS_MODULE, 4 .name = "ide-disk", 5 .bus = &ide_bus_type, 6 }, 7 .probe = ide_disk_probe, //探测 8 .remove = ide_disk_remove, //移除 9 .shutdown = ide_device_shutdown, //关闭 10 .version = IDEDISK_VERSION, 11 .media = ide_disk, //媒介类型 12 .supports_dsc_overlap = 0, 13 .do_request = ide_do_rw_disk, //请求处理函数 14 .end_request = ide_end_request, //请求处理结束 15 .error = __ide_error, 16 .abort = __ide_abort, 17 .proc = idedisk_proc, 18 }; 代码清单13.22第13行的ide_do_rw_disk()函数完成硬盘I/O操作请求的处理,如代码清单13.23所示。 代码清单13.23 IDE硬盘驱动I/O请求处理 1 static ide_startstop_t ide_do_rw_disk(ide_drive_t *drive, struct request *rq, 2 sector_t block) 3 { 4 ide_hwif_t *hwif = HWIF(drive); 5 6 BUG_ON(drive->blocked); 7 8 if (!blk_fs_request(rq)) //不是文件系统请求 9 { 10 blk_dump_rq_flags(rq, "ide_do_rw_disk - bad command"); 11 ide_end_request(drive, 0, 0); //以失败结束该请求 12 return ide_stopped; 13 } 14 15 pr_debug("%s: %sing: block=%llu, sectors=%lu, buffer=0x%08lx\n", 16 drive->name,rq_data_dir(rq) == READ ? "read" : "writ", (unsigned long 17 long)block, rq->nr_sectors, (unsigned long)rq->buffer); 18 19 if (hwif->rw_disk) 20 hwif->rw_disk(drive, rq); 21 22 return __ide_do_rw_disk(drive, rq, block); //具体的请求处理 23 } 24 25 static ide_startstop_t __ide_do_rw_disk(ide_drive_t *drive, struct request *rq, 26 sector_t block) 27 { 28 ide_hwif_t *hwif = HWIF(drive); 29 unsigned int dma = drive->using_dma; 30 u8 lba48 = (drive->addressing == 1) ? 1 : 0; 31 task_ioreg_t command = WIN_NOP; 32 ata_nsector_t nsectors; 33 34 nsectors.all = (u16)rq->nr_sectors; //要传送的扇区数 35 36 if (hwif->no_lba48_dma && lba48 && dma) 37 { 38 if (block + rq->nr_sectors > 1ULL << 28) 39 dma = 0; 40 else 41 lba48 = 0; 42 } 43 44 if (!dma) 45 { 46 ide_init_sg_cmd(drive, rq); 47 ide_map_sg(drive, rq); 48 } 49 50 if (IDE_CONTROL_REG) 51 hwif->OUTB(drive->ctl, IDE_CONTROL_REG); 52 53 if (drive->select.b.lba) 54 { 55 if (lba48) //48位LBA 56 { 57 ... 58 } 59 else 60 { 61 //LBA方式,写入要读写的位置信息到IDE寄存器 62 hwif->OUTB(0x00, IDE_FEATURE_REG); 63 hwif->OUTB(nsectors.b.low, IDE_NSECTOR_REG); 64 hwif->OUTB(block, IDE_SECTOR_REG); 65 hwif->OUTB(block >>= 8, IDE_LCYL_REG); 66 hwif->OUTB(block >>= 8, IDE_HCYL_REG); 67 hwif->OUTB(((block >> 8) &0x0f) | drive->select.all,IDE_SELECT_REG); 68 } 69 } 70 else 71 { 72 unsigned int sect, head, cyl, track; 73 track = (int)block / drive->sect; 74 sect = (int)block % drive->sect + 1; 75 hwif->OUTB(sect, IDE_SECTOR_REG); 76 head = track % drive->head; 77 cyl = track / drive->head; 78 79 pr_debug("%s: CHS=%u/%u/%u\n", drive->name, cyl, head, sect); 80 //CHS方式,写入要读写的位置信息到IDE寄存器 81 hwif->OUTB(0x00, IDE_FEATURE_REG); 82 hwif->OUTB(nsectors.b.low, IDE_NSECTOR_REG); 83 hwif->OUTB(cyl, IDE_LCYL_REG); 84 hwif->OUTB(cyl >> 8, IDE_HCYL_REG); 85 hwif->OUTB(head | drive->select.all, IDE_SELECT_REG); 86 } 87 88 if (dma) //DMA方式 89 { 90 if (!hwif->dma_setup(drive)) //设置DMA成功 91 { 92 if (rq_data_dir(rq)) 93 { 94 command = lba48 ? WIN_WRITEDMA_EXT : WIN_WRITEDMA; 95 if (drive->vdma) 96 command = lba48 ? WIN_WRITE_EXT : WIN_WRITE; 97 } 98 else 99 { 100 command = lba48 ? WIN_READDMA_EXT : WIN_READDMA; 101 if (drive->vdma) 102 command = lba48 ? WIN_READ_EXT : WIN_READ; 103 } 104 hwif->dma_exec_cmd(drive, command); 105 hwif->dma_start(drive); 106 return ide_started; 107 } 108 /* 回到PIO模式 */ 109 ide_init_sg_cmd(drive, rq); 110 } 111 112 if (rq_data_dir(rq) == READ) //数据传输方向是读 113 { 114 if (drive->mult_count) 115 { 116 hwif->data_phase = TASKFILE_MULTI_IN; 117 command = lba48 ? WIN_MULTREAD_EXT : WIN_MULTREAD; 118 } 119 else 120 { 121 hwif->data_phase = TASKFILE_IN; 122 command = lba48 ? WIN_READ_EXT : WIN_READ; 123 } 124 //执行读命令 125 ide_execute_command(drive, command, &task_in_intr, WAIT_CMD, NULL); 126 return ide_started; 127 } 128 else //数据传输方向是写 129 { 130 if (drive->mult_count) 131 { 132 hwif->data_phase = TASKFILE_MULTI_OUT; 133 command = lba48 ? WIN_MULTWRITE_EXT : WIN_MULTWRITE; 134 } 135 else 136 { 137 hwif->data_phase = TASKFILE_OUT; 138 command = lba48 ? WIN_WRITE_EXT : WIN_WRITE; 139 } 140 141 //写IDE命令寄存器写入写命令 142 hwif->OUTB(command, IDE_COMMAND_REG); 143 144 return pre_task_out_intr(drive, rq); 145 } 146 } 从代码清单13.23可知,真正开始执行I/O操作的是其22行引用的__ide_do_rw_disk()函数。这个函数会根据不同的操作模式,将要读写的LBA或CHS信息写入IDE寄存器内,并给其命令寄存器写入读、写命令。 为了进行硬盘读写操作,第61~67和80~85行将参数写入地址寄存器和特性寄存器,如果是读,第125行调用的ide_execute_command()会将读命令写入命令寄存器;如果是写,第142行将写命令写入IDE命令寄存器IDE_COMMAND_REG。 真 正调用ide_driver_t结构体中do_request()成员函数即ide_do_rw_disk()的是ide-io.c文件中的 start_request()函数,这个函数会过滤掉一些请求,最终将读写I/O操作请求传递给ide_do_rw_disk()函数,如代码清单 13.24。 代码清单13.24 开始执行1个IDE请求的start_request()函数 1 static ide_startstop_t start_request(ide_drive_t *drive, struct request *rq) 2 { 3 ide_startstop_t startstop; 4 sector_t block; 5 6 BUG_ON(!(rq->flags &REQ_STARTED)); 7 8 /* 超过了最大失败次数 */ 9 if (drive->max_failures && (drive->failures > drive->max_failures)) 10 { 11 goto kill_rq; 12 } 13 14 block = rq->sector; //要传输的下1个扇区 15 if (blk_fs_request(rq) && (drive->media == ide_disk || drive->media == 16 ide_floppy)) //是文件系统请求,是IDE盘 17 { 18 block += drive->sect0; 19 } 20 /* 如果将0扇区重映射到1扇区 */ 21 if (block == 0 && drive->remap_0_to_1 == 1) 22 block = 1; 23 24 if (blk_pm_suspend_request(rq) && rq->pm->pm_step == 25 ide_pm_state_start_suspend) 26 drive->blocked = 1; 27 else if (blk_pm_resume_request(rq) && rq->pm->pm_step == 28 ide_pm_state_start_resume) 29 { 30 /* 醒来后的第1件事就是等待BSY位不忙 */ 31 int rc; 32 33 rc = ide_wait_not_busy(HWIF(drive), 35000); 34 if (rc) 35 printk(KERN_WARNING "%s: bus not ready on wakeup\n", drive->name); 36 SELECT_DRIVE(drive); 37 HWIF(drive)->OUTB(8, HWIF(drive)->io_ports[IDE_CONTROL_OFFSET]); 38 rc = ide_wait_not_busy(HWIF(drive), 10000); 39 if (rc) 40 printk(KERN_WARNING "%s: drive not ready on wakeup\n", drive->name); 41 } 42 43 SELECT_DRIVE(drive); 44 if (ide_wait_stat(&startstop, drive, drive->ready_stat, BUSY_STAT | DRQ_STAT, 45 WAIT_READY)) //等待驱动器READY 46 { 47 printk(KERN_ERR "%s: drive not ready for command\n", drive->name); 48 return startstop; 49 } 50 if (!drive->special.all) 51 { 52 ide_driver_t *drv; 53 54 //其它非读写请求 55 if (rq->flags &(REQ_DRIVE_CMD | REQ_DRIVE_TASK)) 56 return execute_drive_cmd(drive, rq); 57 else if (rq->flags &REQ_DRIVE_TASKFILE) 58 return execute_drive_cmd(drive, rq); 59 else if (blk_pm_request(rq)) 60 { 61 startstop = ide_start_power_step(drive, rq); 62 if (startstop == ide_stopped && rq->pm->pm_step == ide_pm_state_completed) 63 ide_complete_pm_request(drive, rq); 64 return startstop; 65 } 66 67 drv = *(ide_driver_t **)rq->rq_disk->private_data; 68 return drv->do_request(drive, rq, block); //处理IO操作请求 69 } 70 return do_special(drive); 71 kill_rq: ide_kill_rq(drive, rq); 72 return ide_stopped; 73 }