第 7 章 块设备驱动程序
7.1 块设备驱动介绍
7.1.1 块设备驱动相关的重要结构
与字符设备中使用的 file_operations 结构类似,块设备使其专门的数据结构,即在
<include/linux/fs.h>文件中声明的 block_device_operations。与块设备相关的还有一个重要的 数据结构 gendisk,它被声明在<include/linux/genhd.h>文件中。此外,块设备驱动中还有一 个非常重要的 request 结构,它被经常使用在 request 函数中,它在<include/linux/blkdev.h>
文件中被声明。接下来分别介绍这三个重要的数据结构。
7.1.1.1block_device_operations(块设备操作)结构
块设备操作 block_device_operations 结构告诉系统它能提供哪些接口,与 file_operations 结构的作用类似,只不过它是专门用于块设备驱动而已。首先让我们看一下内核中是如何声 明 block_device_operations 结构的:
struct block_device_operations {
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
struct module *owner;
};
block_device_operations 结构成员与 file_operations 结构相比,确实少了很多。接下来分 析一下 block_device_operations(块设备操作)结构中各个成员的作用。
n int (*open) (struct inode *, struct file *);
该成员是 block_device_operations 结构的一个方法,当打开设备时调用它,功能与字符设 备中对应的函数相同。
n int (*release) (struct inode *, struct file *);
该成员是 block_device_operations 结构的一个方法,当关闭设备时调用它,功能与字符设 备中对应的函数相同。如果用户将介质放入设备中锁住,那么在调用 release 函数时一定 要进行解锁。
n int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
该成员是 block_device_operations 结构的一个方法,实现 ioctl 系统调用的函数。块设备会 首先截取大量的标准请求,因此大多数块设备的 ioctl 函数都比较短小。
n int (*media_changed) (struct gendisk *);
该成员是 block_device_operations 结构的一个方法,内核调用该函数以检查用户是否更换 了驱动器内的介质,如果更换了,则返回一个非零值。该函数适用那些支持可移动介质的 驱动器,其他情况下,该函数被省略。
n int (*revalidate_disk) (struct gendisk *);
该成员是 block_device_operations 结构的一个方法,当介质被更换时,调用该函数做出响 应,它告诉驱动程序完成必要的工作,以便使用新的介质。
n struct module *owner;
该成员不是 block_device_operations 结构的一个方法,它是一个指向拥有该结构的模块指 针,通常被初始化为 THIS_MODULE,与 file_operations 结构中对应成员的作用相同。
以上是对 block_device_operations 结构成员的一一介绍,细心的读者会发现该结构没有 包含 read/write 方法。其实在块设备的 I/O 子系统中,这些操作是由 request 函数处理的,这 也就是字符设备驱动程序与块设备驱动程序实现的不同之处,本章后面会专门介绍 request 函数。
7.1.1.2 gendisk 结构
内核使用 gendisk 结构来表示一个独立的磁盘设备,此外内核还使用 gendisk 结构表示 分区。gendisk 结构的声明如下:
struct gendisk {
int major; /* major number of driver */
int first_minor;
int minors; /* maximum number of minors, =1 for * disks that can't be partitioned. */
char disk_name[32]; /* name of major driver */
struct hd_struct **part; /* [indexed by minor] */
struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
sector_t capacity;
int flags;
char devfs_name[64]; /* devfs crap */
int number; /* more of the same */
struct device *driverfs_dev;
struct kobject kobj;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
unsigned long stamp, stamp_idle;
int in_flight;
#ifdef CONFIG_SMP struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif };
接下来分析一下 gendisk 结构的各个成员。
n int major;
该成员表示设备驱动的主设备号。
n int first_minor;
该成员表示设备驱动第一个次设备号。
n int minors;
该成员表示设备驱动有多少个次设备号,可以包含 minors-1 个分区。
n char disk_name[32];
该成员表示磁盘设备名字,该名字将显示在/proc/partitions 和 sysfs 中。
n struct hd_struct **part;
该成员表示通过次设备号索引的硬件设备扇区信息。
n struct block_device_operations *fops;
该成员表示块设备的各种操作。
n struct request_queue *queue;
该成员表示设备管理 I/O 请求。
n void *private_data;
该成员用来保存其内部数据的指针。
n sector_t capacity;
该成员表示该驱动器可包含的扇区数,以 512 字节为一个扇区。
n int flags;
该成员表示驱动器状态的标志。
n char devfs_name[64];
该成员表示该驱动器的设备文件系统名字。
n int number;
该成员表示多少个相同的块设备。
n struct device *driverfs_dev;
该成员表示该驱动器的设备文件系统。
n struct timer_rand_state *random;
该成员表示定时器随机状态。
n int policy;
该成员用来表示该设备驱动的可读/可写属性。
n atomic_t sync_io;
该成员用来表示同步 I/O 的原子计数器。
n unsigned long stamp, stamp_idle;
该成员表示磁盘设备的时间戳。
n int in_flight;
该成员表示设备驱动的时间信息包数。
n struct disk_stats *dkstats;
该成员表示磁盘的状态信息。
虽然 gendisk 结构的成员比较繁多,但实际应用中有许多成员是没有用到的。内核为 gendisk 结构提供了一些函数,在这里一同介绍一下。
ü struct gendisk *alloc_disk(int minors);
该函数用来动态分配 gendisk 结构,并进行初始化。参数 minors 是磁盘使用次设备号 的个数。该函数的实现代码在<drivers/block/genhd.c>文件。
ü void del_gendisk(struct gendisk *disk);
该函数用来卸载磁盘,当不再使用一个磁盘时,调用该函数来卸载它。该函数的实现 代码在<fs/partitions/check.c>文件中。
ü void add_disk(struct gendisk *disk);
该函数用来激活磁盘设备,并提供随时调用它提供的方法。由于 alloc_disk 函数只是 分配了 gendisk 结构并不能使磁盘对系统可用,所以需要调用 add_disk 函数。该函数 的实现代码在<drivers/block/genhd.c>文件。
7.1.1.3 request 结构
request 结构用来代表一个块设备的 I/O 执行请求,是块设备驱动中非常重要的一个结 构。request 结构在内核中的声明如下:
struct request {
struct list_head queuelist;
unsigned long flags; /* see REQ_ bits below */
sector_t sector; /* next sector to submit */
unsigned long nr_sectors; /* no. of sectors left to submit */
unsigned int current_nr_sectors;
sector_t hard_sector; /* next sector to complete */
unsigned long hard_nr_sectors; /* no. of sectors left to complete */
unsigned int hard_cur_sectors;
struct bio *bio;
struct bio *biotail;
void *elevator_private;
int rq_status; /* should split this into a few status bits */
struct gendisk *rq_disk;
int errors;
unsigned long start_time;
unsigned short nr_phys_segments;
unsigned short nr_hw_segments;
int tag;
char *buffer;
int ref_count;
request_queue_t *q;
struct request_list *rl;
struct completion *waiting;
void *special;
unsigned int cmd_len;
unsigned char cmd[BLK_MAX_CDB];
unsigned int data_len;
void *data;
unsigned int sense_len;
void *sense;
unsigned int timeout;
struct request_pm_state *pm;
};
由于 request 结构包含许多成员,而在实际应用中只用到很少一部分,所以这里只介绍 一些 request 结构的常用成员:
n struct list_head queuelist;
该 成 员 用 于 把 请 求 链 接 到 请 求 队 列 中 , 一 般 不 能 直 接 访 问 它 , 而 是 通 过 blkdev_dequeue_request 函数来访问。
n sector_t sector;
该成员用来表示下一个要提交的扇区。
n unsigned long nr_sectors;
该成员表示剩下多少个扇区没有被提交。
n sector_t hard_sector;
该成员表示还未传输的第一个扇区。
n unsigned long hard_nr_sectors;
该成员表示等待传输扇区的总数。
n struct bio *bio;
该成员表示该请求的 bio 结构链表,不能直接访问该成员,而要使用 rq_for_each_bio 函数 访问。其中 bio 结构是在底层对部分块设备 I/O 请求的描述。
n unsigned short nr_phys_segments;
该成员表示相邻的页被合并后,在物理内存中被这个请求所占用的段的数目。
n char *buffer;
该成员表示查找需要传输的缓冲区。
n request_queue_t *q;
该成员表示一个请求队列。
每个 request 结构都代表了一个块设备的 I/O 请求,在较高层次,它可能是通过对多个 独立请求合并而来。一个 request 结构是作为一个 bio 结构的链表实现的。当然是通过一些 管理信息来组合的,这样保证在执行请求时驱动程序能知道执行的位置。关于 request 结构 和 bio 结构的结合如图 7.1 所示。从图中可知,cbio 和 buffer 成员指向第一个未被传输的 bio
结构。下一节将介绍请求处理。
图 7.1 正在处理请求的请求队列