首先清楚一些概念在磁盘上,最小可寻址单元是扇区,文件系统的最小寻址单元是 块,且块不能比扇区小,得是它的2的n次方倍
而且访问device的块开销相当大
总而言之
操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
块IO的基本组织 struct bio (块I/O的基本容器)bio 结构体包含了大量的基础信息,这些都是一个基本单元的属性,他们代表着当前这个 bio 的状态,比如是读还是写或者是一些特殊的操作命令等。以链表形式组织,因为其分散不连续性,又叫向量I/O
**是块I/O的基本容器,**这个结构体描述的都只是元数据部分,实际数据都包含在紧跟其后的 bio_vec
struct bio {
struct bio *bi_next;
struct block_device *bi_bdev;
unsigned int bi_flags;
int bi_error;
unsigned long bi_rw;
struct bvec_iter bi_iter;
unsigned int bi_phys_segments;
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;
unsigned short bi_vcnt;
unsigned short bi_max_vecs;
atomic_t __bi_cnt;
struct bio_vec *bi_io_vec;
struct bio_set *bi_pool;
struct bio_vec bi_inline_vecs[0];
};
struct bio_vec (bio 数据的最小单位)
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
bio_vec 则是组成 bio 数据的最小单位,他包含了一块数据所在的页,这块数据所在的页内偏移以及长度,通过这些信息就可以很清晰的描述数据具体位于什么位置
通过对这些数据的整合,可以将他们添加到 SGL(散列表) 中直接发送给后端硬件设备
struct bvec_iter (bio的file定位)struct bvec_iter {
sector_t bi_sector;
unsigned int bi_size;
unsigned int bi_idx;
unsigned int bi_bvec_done;
};
除了存储数据,作为一个I/O抽象,不光得定位数据在文件的哪页,还得定位文件在哪吧,把bio比做货车,bvec_iter就是货车上的分拣员,负责定位文件的位置
bio bio_vec page关系图个人总结:
1.bio自身是链表组织的块I/O操作这个抽象
2.每一个bio维护了一个bio_vec结构的数组,保存bio_vec数据,bio.bi_vcnt是这个数组的内容个数,bio.bi_idx指向当前bio_vec数组的索引
3.每一个bio_vec相当于一个唯一的三元组确定数据具体位置
块设备挂起的IO请求保存在请求队列中,由reques_queue表示
struct reques_queue (双向链表请求队列)struct request_queue {
struct list_head queue_head; //待处理请求的链表,请求队列中的请求用链表组织在一起
struct request *last_merge; //指向队列中首先可能合并的请求描述符
struct elevator_queue *elevator;//指向elevator对象的指针(电梯算法)
struct request_list root_rl;///为分配请求描述符所使用的数据结构
request_fn_proc *request_fn;//实现驱动程序的策略例程入口点的方法,由他处理队列中请求
make_request_fn *make_request_fn;//将一个新请求插入请求队列时调用的方法
prep_rq_fn *prep_rq_fn; //该方法把这个处理请求的命令发送给硬件设备
softirq_done_fn *softirq_done_fn;
rq_timed_out_fn *rq_timed_out_fn;
sector_t end_sector;
struct request *boundary_rq;
struct delayed_work delay_work;
struct backing_dev_info backing_dev_info;
void *queuedata;
spinlock_t __queue_lock; //请求队列锁
spinlock_t *queue_lock; //指向请求队列锁的指针
unsigned long nr_requests;
struct queue_limits limits;//队列的其他限制
};
struct request (请求队列中的具体请求)
struct request{
struct list_head queuelist;
unsigned long flags;
sector_t sector;
unsigned long nr_sectors;
unsigned int current_nr_sector;
sector_t hard_sector;
unsigned long hard_nr_sectors;
unsigned int hard_cur_sectors;
struct bio* bio;
struct bio* biotail;
unsigned short nr_phys_segments;
unsigned short nr_hw_segments;
int tag;
char* buffer;
int ref_count;
...
};
主要成员:
sector_t hard_sector;/要完成的下一个扇区/
unsigned long hard_nr_sectors;/要被完成的扇区数目/
unsigned int hard_cur_sectors;/当前要被完成的扇区数目/
分别对应的第一个尚未传输的扇区 尚待完成的扇区数 当前IO待完成的扇区数
在驱动中经常用的成员是:
sector_t sector;/要传输的下一个扇区/
unsigned long nr_sectors;/要传送的扇区数目/
unsigned int current_nr_sector;/当前要传送的扇区/
关于块设备IO更多内容诸如初始化 ddl调度 预测io调度 完全公正调度 空操作调度等 我没有了解需求,暂时打住。
有需要请回来看这个
Linux内核IO调度-http://www.ilinuxkernel.com/



