1.块设备驱动概念
块设备驱动是针对存储设备,例如SD卡、EMMC、NAND FLASH、NOR FLSASH。
块设备驱动以块为单位进行访问、最小寻址单位是扇区、一个块中包含多个扇区、支持随机访问、带缓冲区,,当发生写入操作时,并不会立马操作硬盘,因为写之前要擦除,擦写频繁影响flash寿命。对于flash设备而言,顺序读写扇区能极大提升效率,所以对于不对存储设备,Linux里有实现不同的I/O调度算法。
而字符设备以字节为单位,不需要缓冲,只能被顺序读写。
借鉴一张在网上找到的图片,框架图当然是学习中必不可备的东西,一眼便能有个清晰的认识
其中块设备驱动请求有两种形式,使用requeset_queue请求队列,以及不使用请求队列的。
2.常用结构体
2.1 block_device结构体
linux内核使用 block_device结构体表示块设备对象, 定义在include/linux/fs.h文件。
struct block_device {
dev_t bd_dev; /* not a kdev_t - it's a search key */
int bd_openers;
struct inode * bd_inode; /* will die */
struct super_block * bd_super;
struct mutex bd_mutex; /* open/close mutex */
struct list_head bd_inodes;
void * bd_claiming;
void * bd_holder;
int bd_holders;
bool bd_write_holder;
#ifdef CONFIG_SYSFS
struct list_head bd_holder_disks;
#endif
struct block_device * bd_contains;
unsigned bd_block_size;
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
unsigned bd_part_count;
int bd_invalidated;
struct gendisk * bd_disk;
struct request_queue * bd_queue;
struct list_head bd_list;
/*
* Private data. You must have bd_claim'ed the block_device
* to use this. NOTE: bd_claim allows an owner to claim
* the same device multiple times, the owner must take special
* care to not mess up bd_private for that case.
*/
unsigned long bd_private;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
};
2.2 gendisk结构体
linux 内核使用 gendisk 来描述一个磁盘设备,定义include/linux/genhd.h。
struct gendisk {
/* major, first_minor and minors are input parameters only,
* don't use directly. Use disk_devt() and disk_max_parts().
*/
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[DISK_NAME_LEN]; /* name of major driver */
char *(*devnode)(struct gendisk *gd, umode_t *mode);
unsigned int events; /* supported events */
unsigned int async_events; /* async events, subset of all */
/* Array of pointers to partitions indexed by partno.
* Protected with matching bdev lock but stat and other
* non-critical accesses use RCU. Always access through
* helpers.
*/
struct disk_part_tbl __rcu *part_tbl;
struct hd_struct part0;
const struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
int flags;
struct device *driverfs_dev; // FIXME: remove
struct kobject *slave_dir;
struct timer_rand_state *random;
atomic_t sync_io; /* RAID */
struct disk_events *ev;
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity *integrity;
#endif
int node_id;
};
major:主设备号
first_minor:第一个次设备号
minors:次设备号数量
part_tbl:磁盘对应的分区表
fops:块设备操作集
queue:磁盘对应的请求队列
2.3 block_device_operations 结构体
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
long (*direct_access)(struct block_device *, sector_t,
void **, unsigned long *pfn, long size);
unsigned int (*check_events) (struct gendisk *disk,
unsigned int clearing);
/* ->media_changed() is DEPRECATED, use ->check_events() instead */
int (*media_changed) (struct gendisk *);
void (*unlock_native_capacity) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
/* this callback is with swap_lock and sometimes page table lock held */
void (*swap_slot_free_notify) (struct block_device *, unsigned long);
struct module *owner;
};
open :打开指定的块设备
release:释放指定块设备
rw_page :数用于读写指定的页
ioctl:块设备IO控制
compat_ioctl:32位设备调用的ioctl函数
getgeo:获取磁盘信息,包括磁头、柱面和扇区
owner:标明属于哪个模块
3.块设备操作常用API
3.1块设备操作
1)注册块设备
int register_blkdev(unsigned int major, const char *name)
2)注销块设备
void unregister_blkdev(unsigned int major, const char *name)
major:主设备号
name:块设备名字
3.2gendisk相关操作
1)申请gendisk
struct gendisk *alloc_disk(int minors)
minors:次设备号数量、即gendisk对应的分区数量
2)删除 gendisk
void del_gendisk(struct gendisk *gp)
3)将 gendisk 添加到内核
void add_disk(struct gendisk *disk)
disk:要添加的gendisk结构体
4)设置 gendisk 容量
void set_capacity(struct gendisk *disk, sector_t size)
size:扇区数量大小,块设备驱动之间的扇区固定512字节,无论物理扇区是多大
5)增加gendisk 引用计数
struct kobject *get_disk(struct gendisk *disk)
5)减小gendisk 引用计数
void put_disk(struct gendisk *disk)
4.块设备IO请求过程
4.1 请求队列 request_queue
内核将块设备的读写,都放入到请求队列request_queue中,通过request请求,其中包含块设备从哪个地址读取,读取的数据长度,读取到哪里等。每个gendisk结构体中都有一个requeset_queue,即每个磁盘gendisk都会分配一个request_queue。
初始化请求队列之后,赋值给gendisk的queue成员变量
1)初始化请求队列(使用默认IO调度器)
当访问外部硬件、光盘、磁盘等,需要控制访问顺序来提升访问效率,则需要使用默认IO调度器,即需要使用这个API。
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
rfn:请求处理函数指针
lcok:自旋锁指针
2)删除请求队列
void blk_cleanup_queue(struct request_queue *q)
q:删除的请求队列
3)分配请求队列(不使用默认IO调度器),并绑定制造请求函数
当访问简单的SD卡等,可以进行随机访问,不需要控制访问顺序来提升访问效率,则不需要设置IO调度器,即可直接使用这个API。
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
gfp_mask : 内存分配的方式。 GFP_KERNEL和GFP_ATOMIC,
GFP_ATOMIC: 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠
GFP_KERNEL: 内核内存的正常分配. 可能睡眠
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
mfs:make_request_fn函数指针
blk_alloc_queue不使用默认IO调度器,需要自己实现mfs函数。上层代码向请求队列发生请求,调用make_request_fn函数。
4.2 请求request
请求队列(request_queue)里面包含的就是一系列的请求(request),其中的bio成员变量保存真正的数据。
1)获取请求
从request_queue中依次获取每个request
request *blk_peek_request(struct request_queue *q)
q:指定request_queue
返回值:request_queue 中下一个要处理的请求(request)
2)开启请求
从request_queue中获取到request后,调用该函数开始处理这个request
void blk_start_request(struct request *req)
**req:**要开始处理的请求
3)获取并处理请求
一次性完成请求的获取和开启
struct request *blk_fetch_request(struct request_queue *q)
4)其他API
blk_end_request() //请求中指定字节数据被处理完成。
blk_end_request_all() //请求中所有数据全部处理完成。
blk_end_request_cur() //当前请求中的 chunk。
blk_end_request_err() //处理完请求,直到下一个错误产生。
__blk_end_request() //和 blk_end_request 函数一样,但是需要持有队列锁。
__blk_end_request_all() //和 blk_end_request_all 函数一样,但是需要持有队列锁。
__blk_end_request_cur() //和 blk_end_request_cur 函数一样,但是需要持有队列锁。
__blk_end_request_err() //和 blk_end_request_err 函数一样,但是需要持有队列锁
4.3 bio结构
每个 request 里面会有多个 bio,bio 保存着最终要读写的数据、地址等信息。
从 request_queue 中取出一个一个的 request,然后再从每个 request 里面取出 bio, 最后根据 bio 的描述讲数据写入到块设备,或者从块设备中读取数据
源码如下:
struct bio {
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev;
unsigned long bi_flags; /* status, command, etc */
unsigned long bi_rw; /* bottom bits READ/WRITE,
* top bits priority
*/
struct bvec_iter bi_iter;
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned int bi_phys_segments;
/*
* To keep track of the max segment size, we account for the
* sizes of the first and last mergeable segments in this bio.
*/
unsigned int bi_seg_front_size;
unsigned int bi_seg_back_size;
atomic_t bi_remaining;
bio_end_io_t *bi_end_io;
void *bi_private;
#ifdef CONFIG_BLK_CGROUP
/*
* Optional ioc and css associated with this bio. Put on bio
* release. Read comment on top of bio_associate_current().
*/
struct io_context *bi_ioc;
struct cgroup_subsys_state *bi_css;
#endif
union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
};
unsigned short bi_vcnt; /* how many bio_vec's */
/*
* Everything starting with bi_max_vecs will be preserved by bio_reset()
*/
unsigned short bi_max_vecs; /* max bvl_vecs we can hold */
atomic_t bi_cnt; /* pin count */
struct bio_vec *bi_io_vec; /* the actual vec list */
struct bio_set *bi_pool;
/*
* We can inline a number of vecs at the end of the bio, to avoid
* double allocations for a small number of bio_vecs. This member
* MUST obviously be kept at the very end of the bio.
*/
struct bio_vec bi_inline_vecs[0];
};
struct bvec_iter {
sector_t bi_sector; /* I/O 请求的设备起始扇区(512 字节) */
unsigned int bi_size; /* 剩余的 I/O 数量 */
unsigned int bi_idx; /* blv_vec 中当前索引 */
unsigned int bi_bvec_done; /* 当前 bvec 中已经处理完成的字节数 */
};
struct bio_vec {
struct page *bv_page; /* 页 */
unsigned int bv_len; /* 长度 */
unsigned int bv_offset; /* 偏移 */
};
其中的bio与bio_iter 与 bio_vec之间的关系如下图所示,他们协同完成描述了数据源、数据长度以及数据目的地。
4.3.1 bio操作API
/// 遍历bio请求
#define __rq_for_each_bio(_bio, rq) \
if ((rq->bio)) \
for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
/// 遍历bio中的段
#define bio_for_each_segment(bvl, bio, iter) \
__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
/// 通知bio处理函数
/// 当不使用默认IO调度器,而使用blk_alloc_queue 自己实现制造请求
/// 则在bio处理完成后要通知内核bio处理完成
bvoid bio_endio(struct bio *bio, int error);