目录
6.5 块设备操作
6.5.5 请求结构
6.5.6 BIO
6.5.7 提交请求
6.5.8 I/O调度
6.5.9 ioctl实现
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
6.5 块设备操作
6.5.5 请求结构
struct request { //放在请求队列上,请求完成放到donelist链表上。
struct request_queue *q;
struct list_head queuelist; //用于挂载到请求队列中。
struct gendisk *rq_disk;
struct hd_struct *part;
sector_t sector; //请求的起始扇区。
unsigned long nr_sectors; //请求包含的的扇区数。
unsigned int current_nr_sectors;
struct bio *bio; //表示底层的I/O 请求。
struct bio *biotail; //一个请求可包含多个bio,指向最后一个bio。
void *elevator_private; //IO调度器设置。
void *elevator_private2;
unsigned short nr_phys_segments; //请求涉及连续物理区域数。
unsigned short nr_hw_segments; //重排序的请求中连续物理区域数。
enum rq_cmd_type_bits cmd_type; //如:REQ_TYPE_FS。
unsigned int cmd_flags; //如:_REQ_RW表示数据传输方向。
unsigned int cmd_len;
}
BIO:用于内核和设备间传输数据。下节讲。
struct request在更高层次表示块设备的整体I/O请求,包含多个struct bio
6.5.6 BIO
bio:描述单个IO读写请求。
struct bio {
struct bio *bi_next;
struct block_device *bi_bdev; // 对应块设备。
sector_t bi_sector; // 请求传输的开始扇区号。
unsigned int bi_size; // 请求数据的长度。
unsigned short bi_vcnt; // bi_io_vec数组元素个数,即数据缓冲区数量。
unsigned short bi_idx; // 作为数组bi_io_vec的索引。
struct bio_vec *bi_io_vec;
}
struct bio_vec {
struct page *bv_page; // 缓冲区所在页。
unsigned int bv_len; // 缓冲区长度。
unsigned int bv_offset; // 缓存区在页内的偏移。
};
6.5.7 提交请求
内核将IO请求提交给设备,步骤:
1. 创建一个bio以描述请求 ,并嵌入到request中,放到request_queue中。
2. 内核处理请求队列,并执行bio中的请求。
bio创建后,调用struct request_queue中make_request_fn函数指针,将新请求加入请求队列。
而request_fn用于提交请求。
1. 创建IO请求
submit_bio:
根据bio创建一个新request。
使用make_request_fn将request加入到request_queue中。
2. 队列插入
为提高IO性能,尽可能重排或合并各个请求。
队列空闲时:处理队列中请求。否则只添加请求到队列,而不处理。
请求插入队列后,后续何时处理请求?
1. 定时器。
2. 请求超过阈值。
3. 执行请求
执行请求:
struct request_queue->request_fn()函数指针。
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
用于设置request_fn()函数指针。
不同设备驱动中调用该函数设为不同函数。
6.5.8 I/O调度
I/O调度器:也叫电梯。
用于调度和重排磁盘IO读写操作请求。以优化磁盘访问的顺序和效率。
一个调度器包含的操作:
struct elevator_ops {
elevator_merge_fn *elevator_merge_fn;
//检查新请求是否可以和现有请求合并。
elevator_merged_fn *elevator_merged_fn;
//合并后调用。
elevator_merge_req_fn *elevator_merge_req_fn;
//执行请求合并。
elevator_allow_merge_fn *elevator_allow_merge_fn;
//是否可以将当前请求与现有请求合并。
elevator_bio_merged_fn *elevator_bio_merged_fn;
//当一个bio被合并到一个请求中时调用。
elevator_dispatch_fn *elevator_dispatch_fn;
//从指定队列中选择下一个调度的请求给设备驱动。
elevator_add_req_fn *elevator_add_req_fn;
//向队列添加请求。
elevator_activate_req_fn *elevator_activate_req_fn;
elevator_deactivate_req_fn *elevator_deactivate_req_fn;
//当请求变为活动/非活动时调用。
elevator_completed_req_fn *elevator_completed_req_fn;
//当一个请求完成时调用。
elevator_init_icq_fn *elevator_init_icq_fn;
elevator_exit_icq_fn *elevator_exit_icq_fn;
//初始化/清理 I/O上下文队列。
elevator_set_req_fn *elevator_set_req_fn;
elevator_put_req_fn *elevator_put_req_fn;
elevator_may_queue_fn *elevator_may_queue_fn;
//是否可将请求添加到请求队列。
elevator_init_fn *elevator_init_fn;
elevator_exit_fn *elevator_exit_fn;
//调度器初始化时、退出时调用。
}
请求上下文(I/O Context Queue, ICQ):
ICQ 存储与请求相关上下文,如文件系统元数据、锁状态等。
表示一个调度器:
struct elevator_type
{
struct kmem_cache *icq_cache;
struct elevator_ops ops;
size_t icq_size;
size_t icq_align;
struct elv_fs_entry *elevator_attrs; //sysfs中的属性。
char elevator_name[ELV_NAME_MAX]; //调度器名称。
struct module *elevator_owner;
struct list_head list; //连接所有IO调度器。
};
内核的IO调度器有:
1. elevator_noop:
简单,先来先服务。可合并请求,但不能重排。
使用场景:
1. 可自行重排的智能硬件。
2. 不用寻道的设备, 如闪存。
2. iosched_deadline:
目的:
最小化寻道次数。
尽可能在一定时间内完成。
3. iosched_cfq:
完全公平队列。默认调度器。
每个进程有一个队列,轮询调度多个队列。
拓展
常见I/O调度算法:
完全公平队列(CFQ):
为每个进程提供公平访问磁盘机会。将磁盘带宽分配给不同进程。
适用于多任务公平共享磁盘资源环境。
Deadline:
适用于低延迟应用,如实时应用,数据库。
Noop:
不重排序不优化。
适用不需要额外调度开销的情况,如SSD设备。
电梯算法:
优化了磁盘访问顺序,减少寻道时间。
模拟电梯上下移动,根据磁头当前位置和移动方向,调度最近请求 。
对于磁盘访问密集型且随机的I/O,显著提高性能。
缺点:
当磁头频繁改变方向时,一些请求可能长时间等待。
因此需要根据实际应用的特点和需求权衡。
6.5.9 ioctl实现
系统调用 - > sys_ioctl -> vfs_ioctl
blkdef_ioctl:实现了某些ioctl,对于所有块设备都可用。
如:读取设备分区信息,设备总长度。