1、块设备驱动往往为磁盘设备的驱动,但是由于磁盘设备的IO性能与CPU相比很差,因此,块设备的数据流往往会引入文件系统的Cache机制。
2、实现角度:Linux为块设备和字符设备提供了两套机制。字符设备的实现:内核例程和用户态API一一对应,用户层的Read函数直接对应内核中的read例程,这种映射关系由file_operation维护。
块设备:read、write API没有直接到块设备层,而是直接到文件系统层,再由文件系统层发起读写请求。
3、blok_device_operations结构体
与字符驱动设备程序一样,块设备驱动程序也包含在<linux/fs.h>中定义的blok_device_operations结构体
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
从该结构体的定义可以看出块设备并不提供read() write()等函数接口。
对块设备的读写请求都是以异步方式发送到设备相关request队列之中
4.1、gendisk的操作函数:
/* 分配gendisk */
struct gendisk *alloc_disk(int minors);
/* 删除gendisk */
void del_gendisk(struct gendisk *gd);
/* 增加gendisk */
void add_disk(struct gendisk *gd);
4.2、gendisk
一个块设备物理实体由一个gendisk结构体来表示(<linux/genhd.h>定义),每个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; /* 磁盘上的分区信息 */
int part_uevent_suppress;
struct block_device_operations *fops;/*块设备操作结构体blok_device_operations*/
struct request_queue *queue;
void *private_data;
sector_t capacity;
int flags;
struct device *driverfs_dev;
struct kobject kobj;
struct kobject *holder_dir;
struct kobject *slave_dir;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
unsigned long stamp;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
struct work_struct async_notify;
};
1、major //主设备号,同一磁盘的各个分区共享一个主设备号。
2、register_blkdev()函数用来注册块设备驱动,并申请主设备号
/* 注册块设备驱动 */
int register_blkdev(unsigned int major, const char *name);
/* 注销块设备驱动 */
int unregister_blkdev(unsigned int major, const char *name);
3、int first_minor; //次设备号
如果有分区,每个分区都需要一个次设备号
4、int minors; //次设备的最大数目,未分区则 = 1
5、char disk_name[32]; //磁盘名字
6、struct block_device_operations *fops; //块设备操作函数集合
7、struct request_queue *queue; //请求队列
用来管理该设备的I/O请求,请求队列的相关函数:
/* 初始化请求队列 */
request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
/* 清除请求队列 */
void blk_cleanup_queue(request_queue_t *);
/* 分配请求队列 */
request_queue_t *blk_alloc_queue(int gfp_mask);
/* 绑定制造请求函数 */
void blk_queue_make_request(request_queue_t *q, make_request_fn *mfn);
/* 提取请求 */
struct request *blk_peek_request(struct request_queue *q);
/* 启动请求 */
void blk_start_request(struct request *req);
/* 报告完成 */
void blk_end_request_all(struct request *rq, int error);
void __blk_end_request_all(struct request *rq, int error); //在持有队列锁的场景下调用
对请求的处理有两种模式,可以使用请求队列,也可以不使用,在使用请求队列时,使用blk_init_queue()函数,不用请求队列时,将blk_alloc_queue()和blk_queue_make_request()结合使用,一般模式为;
xxx_queue = blk_alloc_queue(GFP_KERNEL);
blk_queue_make_request(xxx_queue, xxx_make_request);
8、int flags; //描述驱动器状态的标志
如果为可移动介质:GENHD_FL_REMOVABLE
如果为CD_ROM:GENHD_FL_CD
如果不需要分区信息出现在 /proc/partitions, 设置为
GENHD_FL_SUPPRESS_PARTITIONS_INFO
9、sector_t capacity; //磁盘驱动器容量,以512字节的扇区个数为单位
一般通过set_capacity()函数设置:
void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区大小一般是2 的整数倍,最常见的大小是512 字节。扇区的
大小是设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比它还小的单元进行寻址和操作,
不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512 字节,不过其他大小的
扇区也很常见,比如,很多CD-ROM 盘的扇区都是2KB。
不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512 字节为单位。因此,
set_capacity()函数也以512 字节为单位。
10、void *private_data; //指向私有数据的指针
可用于指向磁盘的任何私有数据。,用法与file结构体的private_data类似