1,区别块设备和字符设备:块设备是系统中能随机访问固定大小的数据片的硬件。
,扇区是所有块设备物理上的最小可寻址单位,通常大小为512Byte,块是文件系统的最小寻址单位,大小是扇区的整数倍,同时不能超过一个页的大小~
操作块设备的时候需要在内存中有一个对应的缓冲区,用struct buffer_head结构体表示。
- struct buffer_head {
- unsigned long b_state; /* buffer state bitmap (see above) *缓冲区的状态标志/
- struct buffer_head *b_this_page;/* circular list of page's buffers *页面中缓冲区/
- struct page *b_page; /* the page this bh is mapped to *存储缓冲区的页面/
- sector_t b_blocknr; /* start block number *逻辑块号/
- size_t b_size; /* size of mapping *块大小/
- char *b_data; /* 相当于私有数据指针,对于fat32,该结构指向fat_boot_sector结构体/
- struct block_device *b_bdev; //对应的块设备
- bh_end_io_t *b_end_io; /* I/O completion */
- void *b_private; /* reserved for b_end_io *I/O完成的方法/
- struct list_head b_assoc_buffers; /* associated with another mapping */
- struct address_space *b_assoc_map; /* mapping this buffer is
- associated with *缓冲区对应的映射,即address_space/
- atomic_t b_count; /* users using this buffer_head *表示缓冲区的使用计数/
2,bio结构体
目前内核中块IO操作的基本容器由bio结构体表示,通常1个bio对应1个I/O请求,IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个bio。
- truct bio {
- sector_t bi_sector; /* associated sector on disk */
- struct bio *bi_next; /* list of requests */
- struct block_device *bi_bdev; /* associated block device */
- unsigned long bi_flags; /* status and command flags */
- unsigned long bi_rw; /* read or write? */
- unsigned short bi_vcnt; /* number of bio_vecs off */
- unsigned short bi_idx; /* current index in bi_io_vec */
- unsigned short bi_phys_segments; /* number of segments after coalescing */
- unsigned short bi_hw_segments; /* number of segments after remapping */
- unsigned int bi_size; /* I/O count */
- unsigned int bi_hw_front_size; /* size of the first mergeable segment */
- unsigned int bi_hw_back_size; /* size of the last mergeable segment */
- unsigned int bi_max_vecs; /* maximum bio_vecs possible */
- struct bio_vec *bi_io_vec; /* bio_vec list */
- bio_end_io_t *bi_end_io; /* I/O completion method */
- atomic_t bi_cnt; /* usage counter */
- void *bi_private; /* owner-private method */
- bio_destructor_t *bi_destructor; /* destructor method */
- };
- struct bio_vec {
- /* pointer to the physical page on which this buffer resides */
- struct page *bv_page;
- /* the length in bytes of this buffer */
- unsigned int bv_len;
- /* the byte offset within the page where the buffer resides */
- unsigned int bv_offset;
- };
在2.6内核中,也既,将原来由buffer_head一个结构来完成的工作,现在由buffer_head和bio共同来完成。现在,buffer_head只给上层提供有关其所描述的块的当前状态,而bio则负责将尽可能多的块合并起来,传递给下层驱动程序,并最终写入硬盘。也即,buffer_head负责描述磁盘块到物理内存的映射,bio负责所有块I/O操作的容器
2,请求队列
上层文件系统如果要对底层的设备数据读写的时候,它们首先会将它们的请求发送到请求队列中,该队列由reques_queue结构体表示,包含
双向请求链表以及相关控制信息,队列中的具体的请求由struct request表示,每个请求又可以由多个bio结构体组成,
3,IO调度程序
IO调度程序试图通过扇区将请求队列合并排序,尽量使磁头的请求顺序是一个方向,
为了优化块设备寻址操作,IO调度程序会在将请求提交给磁盘时,会先执行合并和排序的预操作,可以提高系统的整体性能。合并的含义是当两次请求对于底层块设备请求的扇区临近,那么IO调度程序会合并请求。排序的含义是使多次请求对底层块设备扇区的排列顺序有序排列
2.4内核中采用的是linus电梯IO调度程序,缺点是当某个磁盘区域上频繁操作会使磁盘其他位置请求得不到运行机会,可能造成读写饥饿的情况发生为了改善linus电梯的缺点,后面有了最终期限IO调度程序,
当一个新请求加入I/O请求队列时,linus电梯调度程序可能会发生以下4种操作:
如果队列中已存在一个对相邻磁盘扇区操作的请求,那么新请求将和这个已存在的请求合并成一个请求
如果队列中存在一个驻留时间过长的请求,那么新请求之间查到队列尾部,防止旧的请求发生饥饿
如果队列中已扇区方向为序存在合适的插入位置,那么新请求将被插入该位置,保证队列中的请求是以被访问磁盘物理位置为序进行排列的
如果队列中不存在合适的请求插入位置,请求将被插入到队列尾部
最终期限IO调度程序中每个请求都有一个超时时间,默认读请求的超时时间是500ms,写请求的超时时间是5s,最终期限IO调度除了维护一个以磁盘扇区寻址的排序队列,还维护一个FIFO队列,该调度程序缺点是降低系统吞吐量(而且读写请求的响应时间要求也是不一样的,一般来说,写请求的响应时间要求不高,写请求可以和提交它的应用程序异步执行,但是读请求一般和提交它的应用程序时同步执行,应用程序等获取到读的数据后才会接着往下执行。)
一个新请求加入到I/O请求队列时,最终期限I/O调度和linus电梯调度相比,多出了以下操作:
1.新请求加入到 排序队列(order-FIFO),加入的方法类似 linus电梯新请求加入的方法
2.根据新请求的类型,将其加入 读队列(read-FIFO) 或者写队列(wirte-FIFO) 的尾部(读写队列是按加入时间排序的,所以新请求都是加到尾部)
3.调度程序首先判断 读,写队列头的请求是否超时,如果超时,从读,写队列头取出请求,加入到派发队列(dispatch-FIFO)
4.如果没有超时请求,从 排序队列(order-FIFO)头取出一个请求加入到 派发队列(dispatch-FIFO)
5.派发队列(dispatch-FIFO)按顺序将请求提交到磁盘驱动,完成I/O操作
预测IO调度程序,在最终期限IO调度程序的基础上,在每次处理读请求的时候都会等待几毫秒,默认为6ms
最终期限I/O调度算法优先考虑读请求的响应时间,但系统处于写操作繁重的状态时,会大大降低系统的吞吐量。 因为读请求的超时时间比较短,所以每次有读请求时,都会打断写请求,让磁盘寻址到读的位置,完成读操作后再回来继续写。 这种做法保证读请求的响应速度,却损害了系统的全局吞吐量(磁头先去读再回来写,发生了2次寻址操作)
但有一个新请求加入到I/O请求队列时,预测I/O调度与最终期限I/O调度相比,多了以下操作:
1.新的读请求提交后,并不立即进行请求处理,而是有意等待片刻(默认是6ms)
2.等待期间如果有其他对磁盘相邻位置进行读操作的读请求加入,会立刻处理这些读请求
3.等待期间如果没有其他读请求加入,那么等待时间相当于浪费掉
4.等待时间结束后,继续执行以前剩下的请求
linux系统缺省的IO调度程序是完全公正排队的IO调度程序,CFQ IO调度程序把进入的IO请求放入特定的队列中,这种队列根据引起IO请求的进程组织,每个队列后续再合并和排序操作,CFQ IO调度程序以时间片轮转调度队列,从每个队列选取请求数,默认值是4
完全公正的排队(Complete Fair Queuing, CFQ)I/O调度 是为专有工作负荷设计的,它和之前提到的I/O调度有根本的不同。
CFQ I/O调度 算法中,每个进程都有自己的I/O队列,
CFQ I/O调度程序以时间片轮转调度队列,从每个队列中选取一定的请求数(默认4个),然后进行下一轮调度。
CFQ I/O调度在进程级提供了公平,它的实现位于: block/cfq-iosched.c