块设备的特点是其平均访问时间较长,因此为了提高块设备的访问效率,Linux内核用了很多的笔墨来设计和块设备相关的部分,这样一来,从代码的角度来看,访问一个文件的过程变得尤其的漫长……整个路径包含的过程基本可以概括为虚拟文件系统-->块设备实际文件系统-->通用块层-->I/O scheduler-->块设备驱动程序。为了提高块设备的访问效率,内核主要是在两个方面下功夫:
1.引入缓存,当用户空间要访问文件时,内核不可能每次都去访问块设备,内核会将块设备的内容读取到内存中,以便下次访问时可以直接在内存中找到相应的内容,这其中又涉及到了预读等相关的问题,当然这不是现在关注的重点……
2.对于I/O请求的重排列,I/O请求并不会立即被响应,而是会放在一个队列里进行一段延迟,以期能够和后来的I/O请求进行合并或者进行排序。因为像磁盘这样的块设备,其耗时主要是因为磁头的定位,因此内核会尽量保证磁头只往一个方向移动,而不是来回移动(可以和电梯的运作进行对比),简而言之,就是将存储介质上相邻的数据请求安排在一起,对于I/O请求的处理主要包括合并和排序,具体如何处理,由I/O scheduler决定。
首先,我们先来了解一个块设备是如何表示的。描述块设备的数据结构有两个,一个是struct block_device,用来描述一个块设备或者块设备的一个分区;另一个是struct gendisk,用来描述整个块设备的特性。对于一个包含多个分区的块设备,struct block_device结构有多个,而struct gendisk结构永远只有一个。
struct block_device {
dev_t bd_dev; /* not a kdev_t - it's a search key */
struct inode * bd_inode; /* will die */
struct super_block * bd_super;
int bd_openers;
struct mutex bd_mutex; /* open/close mutex */
struct list_head bd_inodes;
void * bd_holder;
int bd_holders;
#ifdef CONFIG_SYSFS
struct list_head bd_holder_list;
#endif
struct block_device * bd_contains;
unsigned bd_block_size;
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
unsigned bd_part_count;
int bd_invalidated;
struct gendisk * bd_disk;
struct list_head bd_list;
/*
* Private data. You must have bd_claim'ed the block_device
* to use this. NOTE: bd_claim allows an owner to claim
* the same device multiple times, the owner must take special
* care to not mess up bd_private for that case.
*/
unsigned long bd_private;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
};
bd_dev:该设备(分区)的设备号
bd_inode:指向该设备文件的inode
bd_openers:一个引用计数,记录了该块设备打开的次数,或者说有多少个进程打开了该设备
bd_contains:如果该block_device描述的是一个分区,则该变量指向描述主块设备的block_device,反之,其指向本身
bd_part:如果该block_device描述的是一个分区,则该变量指向分区的信息
bd_part_count:如果是分区,该变量记录了分区被打开的次数,在进行分区的重新扫描