• 沒有找到結果。

第 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 正在处理请求的请求队列