块设备
系统中能够随机访问固定大小数据片的硬件设备成为块设备。比如硬盘,软盘,光盘,闪存等。
另一种基础设备类型是字符设备。比如键盘,串口等。
块设备最小的寻址单元是扇区。
扇区大小是2的整数倍,一般是512字节。
扇区的大小是设备的物理属性,扇区是所有块设备的基本单元。
内核执行的所有磁盘操作是按照块进行的。
块不能比扇区小,只能是数倍于扇区的大小。
块不能超过一个页的长度。
块的大小通常是512字节,1KB和4KB。
块和扇区的关系:
缓冲区
当一个块调入内存时,它要存储在一个缓冲区中。
缓冲区相当于磁盘块在内存中的表示。
一个页可以容纳一个或多个内存中的块。
每个缓冲区都有一个对应的描述符,用buffer_head结构体表示,称为缓冲区头(include\linux\buffer_head.h):
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; /* pointer to data within the page */
struct block_device *b_bdev;
bh_end_io_t *b_end_io; /* I/O completion */
void *b_private; /* reserved for b_end_io */
struct list_head b_assoc_buffers; /* associated with another mapping */
struct address_space *b_assoc_map; /* mapping this buffer is
associated with */
atomic_t b_count; /* users using this buffer_head */
};
b_state:表示缓冲区的状态,是下面标志中的一种或多种的组合。
还有一个bh_state是BH_PrivateStart,该标志不是可用状态的标志,使用它是为了指明可被其它代码使用的起始位。
b_count:表示缓冲区的使用计数,通过get_bh()和put_bh()来增加或者减少,在操作缓冲区头之前应该使用get_bh()函数增加缓冲区头的引用计数,确保该缓冲区头不会再被分配出去。
b_blocknr:表示与缓冲区对应的磁盘物理块。
b_bdev:指明块设备中的逻辑块号。
b_page:表示与缓冲区对应的内存物理页。
b_data:指向相应的块。
b_size:表示块的大小。
缓冲区头描述了磁盘块和物理内存缓冲区之间的映射关系。
bio
内核中块IO操作的基本容器由bio结构体表示。
该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块IO操作。
一个片段是一小块连续的内存缓冲区,这样就不需要保证单个缓冲区一定要连续。
通过片段来描述缓冲区,即使缓冲区分散在内存的多个位置上,bio结构体也能对内核保证IO操作的执行。
bio结构体如下(include\linux\bio.h):
struct bio {
sector_t bi_sector; /* device address in 512 byte
sectors */
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
*/
unsigned short bi_vcnt; /* how many bio_vec's */
unsigned short bi_idx; /* current index into bvl_vec */
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned int bi_phys_segments;
unsigned int bi_size; /* residual I/O count */
/*
* 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;
unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
unsigned int bi_comp_cpu; /* completion CPU */
atomic_t bi_cnt; /* pin count */
struct bio_vec *bi_io_vec; /* the actual vec list */
bio_end_io_t *bi_end_io;
void *bi_private;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
bio_destructor_t *bi_destructor; /* destructor */
/*
* 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];
};
bi_io_vec指向一个bio_vec结构图链表,它包含了一个特定IO操作锁需要使用到的所有片段(include\linux\bio.h):
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
bv_page是片段所在的物理页,bv_len是块在物理页中的偏移地址,bv_offset是给定偏移量开始的块长度。通过它可以看出bio描述的块不需要连续存储。
bi_vcnt表示的bi_io_vec指向的bio_vec的个数。
bi_idx是指向bi_io_vec的当前索引。
bi_cnt记录bio结构体的引用计数,为0时就应该撤销该bio结构体。
每一个块IO请求都通过一个bio结构体表示。
IO调度
块设备将它们挂起的块IO请求保存在请求队列,它有结构体request_queue表示。
每个请求request可以由多个bio结构体组成。
磁盘寻址是整个计算机中最慢的操作之一。
为了优化寻址操作,内核既不会简单地按请求接收次序,也不会立即将其提交给磁盘。
会先进行合并和排序的预操作。
IO调度程序负责提交IO请求,它的工作是管理块设备的请求队列。
IO调度算法类似与电梯,因此也称电梯调度。
目前有的四种调度:
- Linus电梯IO调度
- 最终期限IO调度
- 完全公正的排队IO调度
- 空操作IO调度