一切皆文件之块设备驱动(一)
- 文件系统
- 2023-06-03
- 170热度
- 0评论
块设备驱动简介
在linux系统中,有3大驱动类型,分别是:字符设备驱动、块设备驱动、网络设备驱动。块设备驱动与文件系统有着密不可分的关系,块设备是文件系统实际的数据传输单位,通常存储设备有eMMC,Nand/Nor flash,机械硬盘,固态硬盘等,这里所说的块设备驱动,实际就是这些存储设备驱动。块设备驱动与字符设备驱动有较大差异,块设备驱动是以块位单位进行读写,而字符设备驱动以字节为单位进行传输。块设备驱动可以进行随机访问,块设备驱动一般都有缓冲区来暂存数据,当对块设备进行写入时会先将数据写到缓冲区中,累计到一定数据量后才一次性刷到设备中,这样既可以提高读写的速度也可以提高快设备驱动的寿命;相对字符设备数据的操作都是字节流的方式,字符设备没有缓冲区。
关键数据结构
block_device
linux系统中使用strcut block_device来表示块设备,可以表示磁盘或一个特定的分区。下面我们看一下strcut block_device数据结构,该数据结构表示块设备。
struct block_device {
sector_t bd_start_sect;
struct disk_stats __percpu *bd_stats;
unsigned long bd_stamp;
bool bd_read_only; /* read-only policy */
dev_t bd_dev;
int bd_openers;
struct inode * bd_inode; /* will die */
struct super_block * bd_super;
void * bd_claiming;
struct device bd_device;
void * bd_holder;
int bd_holders;
bool bd_write_holder;
struct kobject *bd_holder_dir;
u8 bd_partno;
spinlock_t bd_size_lock; /* for bd_inode->i_size updates */
struct gendisk * bd_disk;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
struct super_block *bd_fsfreeze_sb;
struct partition_meta_info *bd_meta_info;
#ifdef CONFIG_FAIL_MAKE_REQUEST
bool bd_make_it_fail;
#endif
} __randomize_layout;
- bd_dev: 该块设备(分区)的设备号
- bd_inode: 设备的文件inode,bdev文件系统将通过该inode来标识块设备。
- bd_disk: 指向描述整个设备的gendisk。
block_device是bdevfs伪文件系统对块设备或设备分区的抽象,它与设备号唯一对应。
gendisk
linux系统中,struct gendisk是通用存储设备描述,跟具体的硬件设备关联,一个具体的硬件存储设备可以分为多个分区,而每个分区的对应用block_device描述。
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 */
unsigned short events; /* supported events */
unsigned short event_flags; /* flags related to event processing */
struct xarray part_tbl; 对应分区表,每一项对应要给分区信息
struct block_device *part0;
const struct block_device_operations *fops;磁盘操作函数集
struct request_queue *queue; 磁盘对应的请求队列,对磁盘操作的请求都放在这个队列中
void *private_data;
int flags;
unsigned long state;
#define GD_NEED_PART_SCAN 0
#define GD_READ_ONLY 1
#define GD_DEAD 2
struct mutex open_mutex; /* open/close mutex */
unsigned open_partitions; /* number of open partitions */
struct backing_dev_info *bdi;
struct kobject *slave_dir;
#ifdef CONFIG_BLOCK_HOLDER_DEPRECATED
struct list_head slave_bdevs;
#endif
struct timer_rand_state *random;
atomic_t sync_io; /* RAID */
struct disk_events *ev;
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct kobject integrity_kobj;
#endif /* CONFIG_BLK_DEV_INTEGRITY */
#if IS_ENABLED(CONFIG_CDROM)
struct cdrom_device_info *cdi;
#endif
int node_id;
struct badblocks *bb;
struct lockdep_map lockdep_map;
u64 diskseq;
}
- major: 存储设备的主设备号
- first_minor: 存储设备的第一个次设备号
- minors:存储设备的次设备号数量,也对应存储设备的分区数量。
- part_tbl:存储设备对应的分区表
- fops:存储设备块操作集合,与字符设备的file_operations一样。
- queue:存储设备对应的请求队列,对磁盘的请求都会放到该队列中。
下面是磁盘的操作函数集合block_device_operations,与字符设备驱动file_operations类似。
struct block_device_operations {
blk_qc_t (*submit_bio) (struct bio *bio);
int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, unsigned int);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
unsigned int (*check_events) (struct gendisk *disk,
unsigned int clearing);
void (*unlock_native_capacity) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
int (*set_read_only)(struct block_device *bdev, bool ro);
/* this callback is with swap_lock and sometimes page table lock held */
void (*swap_slot_free_notify) (struct block_device *, unsigned long);
int (*report_zones)(struct gendisk *, sector_t sector,
unsigned int nr_zones, report_zones_cb cb, void *data);
char *(*devnode)(struct gendisk *disk, umode_t *mode);
struct module *owner;
const struct pr_ops *pr_ops;
/*
* Special callback for probing GPT entry at a given sector.
* Needed by Android devices, used by GPT scanner and MMC blk
* driver.
*/
int (*alternative_gpt_sector)(struct gendisk *disk, sector_t *sector);
};
- submit_bio: 对存储设备读写操作,但不一定会使用submit_bio来传输,而是通过请求队列来完成。
- open: 打开指定的块设备
- rw_page:读写指定的页
- ioctl:块设备的I/O控制
在block_device_operations结构中不像字符设备驱动有对应的read和write函数,有些内核版本甚至没有submit_bio,对于块设备的读写操作,主要是通过request_queue,request和bio来实现的。
- request_queue:内核对存储设备的读写操作会先发送到request_queue中,该队列中包含有一系列的request,在request中具体的基本单元是bio,bio保存了对存储设备读写的实际数据,包括从存储设备的那个地址读写,具体的长度等信息。
- request: 存储设备的请求队列中,包含多个内核对存储设备的多个request,每个请求包含多个bio。
- bio:内核对存储设备读写会构造一个或者多个的bio,bio数据结构包含了对存储设备具体的读写位置和长度等信息。
接下来将单独对三者关系进行深入描述。
bio,request,request_queue
Generic Block layer
文件系统向下就是Generic Block Layer,文件系统请求读写的文件位置需要被转换到对应的存储介质的位置(如磁盘的扇区号)。如下是文件系统写过程,将写请求根据文件的pos位转化为bio。
int __block_write_begin(struct page *page, loff_t pos, unsigned len,
get_block_t *get_block)
-->return __block_write_begin_int(page, pos, len, get_block, NULL);
-->ll_rw_block(int op, int op_flags, int nr, struct buffer_head *bhs[])
-->int submit_bh(int op, int op_flags, struct buffer_head *bh)
-->submit_bio(bio)
bio是块设备数据传输最小单元,下面是struct bio的数据结构。
- bio_vec:标识存储设备中数据缓存在page的位置,描述IO请求在内存段的数据位置属性(page地址,页内偏移,长度)。
- bvec_iter:标识操作数据在存储设备的位置,描述IO请求在存储器段的数据位置属性(起始sector,长度)。
I/O Scheduler Layer
从submit_bio调用开始,bio被block层抽象为request进行管理,request会被组织到request queue中。IO调度器的目的是在现有请求下,让尽可能少操作存储设备,提高存储设备的读写效率。在IO调度器中,从文件系统提交下来的bio被构造成request结构,一个request结构包含了多个bio,而物理存储设备都会有对应的request queue,里面存放着相关的request,新的bio可能被合并到request queue现有的request结构中,也有可能生成新的request,如何合并、插入等取决于设备驱动选择的IO调度算法。
I/O请求
早期阶段,只有一个单队列single-queue,随着多核体系结构的发展,单队列的暴露了较多劣势,如多核请求队列时,需要使用spinlock来做同步,锁的竞争带来比较高的额外开销,因此后来引入了multi-queue,将单个队列请求锁的竞争分散到多个队列中,这样就极大提高了并发IO的处理能力。
multi-queue结构分为两层队列设计,分别是Per-CPU级别的软件暂存队列(Software staging queue,Per CPU)和存储设备硬件派发队列(Hardware Dispatch queue,Per Disk Per channel);
- 软件暂存队列(ctx):对应的数据结构blk_mq_ctx,为每个cpu分配一个软件队列,将bio生成一个新的request或合并到已有的request中。bio的提交/完成处理、IO请求合并/排序/标记、调度记账等block layer操作都在该队列中进行。队列是Per CPU,所以每个CPU io请求并发的时候不存在锁竞争问题。
-
硬件派发队列(hctx):对应的数据结构blk_mq_hw_ctx,每个存储设备的每个硬件队列(通常为一个)分配一个硬件派发队列,用于处理上层软件暂存队列下发的io请求。在存储设备驱动初始化时,blk-mq会将一个或多个软件暂存队列固定映射到一个硬件派发队列,后续软件队列上的io请求就会直接往映射的硬件派发队列下发,最终下发到硬件存储设备。
https://blog.csdn.net/morecrazylove/article/details/128712522