1、磁盘上的数据时通过其首个扇区号和扇区数目表示的
2、扇区是硬件传送数据的基本单位,块是vfs和文件系统传送数据的基本单位
3、linux中块大小必须是2的幂且不能超过一页大小
4、块设备的块大小不是唯一的创建文件系统时可以指定
5、每个块都需要有块设备缓冲区,缓冲区首部是一个buffer_head结构体
struct buffer_head {
/* First cache line: */
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 */
atomic_t b_count; /* users using this block */
u32 b_size; /* block size */
sector_t b_blocknr; /* block number */
char *b_data; /* pointer to data block */
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 */
};
6、通用块层核心数据结构bio
struct bio {
sector_t bi_sector; //块io操作的第一个磁盘扇区
struct bio *bi_next; //链接到请求队列中的下一个bio
struct block_device *bi_bdev;//指向块设备描述符的指针
unsigned long bi_flags; //bio的状态标志
unsigned long bi_rw; //io操作的标志。或读-0或写-1
unsigned short bi_vcnt; //bi_io_vec数组中段的数目
unsigned short bi_idx; //bi_io_vec数组中当前段的索引
/* Number of segments in this BIO after
* physical address coalescing is performed.
*/
unsigned short bi_phys_segments;//合并之后bio中物理段的数目
unsigned short bi_hw_segments;//合并之后硬件段的数目
unsigned int bi_size; //需要传送的字节数
unsigned int bi_hw_front_size;//硬件段合并算法使用
unsigned int bi_hw_back_size;//硬件段合并算法使用
unsigned int bi_max_vecs; //bi_io_vec数组中允许最大段的数目
struct bio_vec *bi_io_vec; //指向bio数组中段的指针
bio_end_io_t *bi_end_io;//bio中io操作结束时回调的方法
atomic_t bi_cnt; //此bio引用计数器
void *bi_private;//通用块层完成方法使用的指针
bio_destructor_t *bi_destructor; //释放bio回调方法
struct bio_set *bi_set; //内存池相关
};
7、当通用块层启动一个新的io操作时候会调用bio_alloc分配一个新的bio结构且会分配bio_vec数组所使用空间
8、磁盘对象描述符gendisk
struct gendisk {
int major; //主设备号
int first_minor; //与磁盘关联的第一个次设备号
int minors; //次设备数量
char disk_name[32]; //磁盘的标准名称
struct hd_struct **part; //磁盘分区描述符数组
struct block_device_operations *fops;//指向块设备操作表的指针
struct request_queue *queue;//指向磁盘请求队列的指针
void *private_data;//块设备驱动程序的私有数据
sector_t capacity;//磁盘内存区大小(扇区数目)
int flags;//描述磁盘类型标志
char devfs_name[64]; //devfs特殊文件系统中文件名称
int number; //不再使用
struct device *driverfs_dev;//指向磁盘的硬件设备device结构体
struct kobject kobj;//内嵌的kobject结构体
struct timer_rand_state *random;//用于随机数发生器
int policy;//磁盘只读设置为1,否则为0
atomic_t sync_io; //写入磁盘扇区数计数,仅为raid使用
unsigned long stamp, stamp_idle;//时间戳
int in_flight;//正在进行的io操作数
#ifdef CONFIG_SMP
struct disk_stats *dkstats;//统计每个cpu使用的情况的指针用于smp系统
#else
struct disk_stats dkstats;//统计每个cpu使用的情况
#endif
};
struct block_device_operations {
int (*open) (struct inode *, struct file *);//打开块设备文件
int (*release) (struct inode *, struct file *);//关闭对块设备最后一个引用的时候
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);//在块设备上发出ioctl调用(使用大内核锁)
long (*compat_ioctl) (struct file *, unsigned, unsigned long);//在块设备上发出ioctl调用(不使用大内核锁)
int (*media_changed) (struct gendisk *);//检查可移动介质是否变化
int (*revalidate_disk) (struct gendisk *);//检查块设备是否持有有效数据
struct module *owner;//模块所属
};
分区描述符:
struct hd_struct {
sector_t start_sect;//磁盘中分区起始扇区
sector_t nr_sects;//分区的长度
struct kobject kobj;//内嵌的kobject结构
unsigned reads, read_sectors, writes, write_sectors;//读写次数、读写扇区数统计计数
int policy, partno;//policy如果分区只读则设置为1,如果分区可写设置为0;partno表示磁盘分区的相对索引,近似于分区号
};
9、调用alloc_disk 分配新的gendisk描述符,如果调用参数大于1则还会分配分区空间并初始化bi_io_vec
10、通用块层通常使用generic_make_request下发io请求void generic_make_request(struct bio *bio)
a、获取设备的最大扇区号maxsector与此次的请求扇区数比较以及请求的起始扇区号,如果不合理则打印错误消息并且设置BIO_EOF标志,并且调用bio_endio结束此次io请求
b、获取请求队列q,如果请求队列获取为空则打印错误消息并且调用bio_endio结束此次io请求
c、判断请求的扇区数是否大于请求队列的最大物理扇区限制,如果大于则打印错误信息并且调用bio_endio结束此次io请求
d、接着调用block_wait_queue_running函数等待io调度器重新调度
e、调用q->make_request_fn函数下发io请求
11、请求队列描述符request_queue
struct request_queue
{
struct list_head queue_head;//待处理请求链表
struct request *last_merge;//指向队列中第一个可以合并的请求描述符
elevator_t *elevator;//指向elevator对象
struct request_list rq; //为分配请求描述符使用的数据结构
request_fn_proc *request_fn;//实现驱动层序的策略入口函数
merge_request_fn *back_merge_fn;//检查是否可以将bio合并到请求队列最后一个请求中去
merge_request_fn *front_merge_fn;//检查是否可以将bio合并到请求队列第一个请求中去
merge_requests_fn *merge_requests_fn;//试图合并请求中两个相邻请求的方法
make_request_fn *make_request_fn;//将一个新的请求插入到请求队列里的方法
prep_rq_fn *prep_rq_fn;//此函数将处理请求命令发送到硬件
unplug_fn *unplug_fn;//移除块设备的方法
merge_bvec_fn *merge_bvec_fn;//一般不用
activity_fn *activity_fn;//将某个请求加入到请求队列的时候定义的方法
issue_flush_fn *issue_flush_fn;//刷新请求队列调用的方法
prepare_flush_fn *prepare_flush_fn;//刷新请求队列前回调方法
end_flush_fn *end_flush_fn;//刷新请求队列后回调方法
struct timer_list unplug_timer;//插入设备使用的动态定时器
int unplug_thresh; //阈值,如果请求队列中待处理的请求大于该值则失活该请求队列
unsigned long unplug_delay; //失活之后再一次激活的间隔时间
struct work_struct unplug_work; //失活请求队列的工作队列
struct backing_dev_info backing_dev_info; //稍后解释
void *queuedata; //指向块设备驱动程序的私有指针
void *activity_data;//activity_fn函数的私有数据
/*
* queue needs bounce pages for pages above this limit
*/
unsigned long bounce_pfn;//在大于改页框号时必须使用回弹缓冲区
unsigned int bounce_gfp;//回弹缓冲区分配标志
unsigned long queue_flags;//描述请求队列状态的标志
spinlock_t __queue_lock;//队列自旋锁
spinlock_t *queue_lock;//队列自旋锁
struct kobject kobj;//内嵌的kobj结构体
unsigned long nr_requests; //请求队列中允许最大的请求数
unsigned int nr_congestion_on;//阈值,当高于此阈值则认为队列时拥挤的
unsigned int nr_congestion_off;//阈值,当低于此阈值则认为队列时不拥挤的
unsigned int nr_batching;//当满的时候特殊进程还能插入的请求数
unsigned short max_sectors;//单个请求所能处理最大的扇区数
unsigned short max_hw_sectors;//单个请求硬件上所能处理最大的扇区数
unsigned short max_phys_segments;//单个请求所能处理最大的段数
unsigned short max_hw_segments;//单个请求硬件上所能处理最大的段数
unsigned short hardsect_size;//扇区中以字节为单位的大小
unsigned int max_segment_size;//物理段的最大长度
unsigned long seg_boundary_mask;//段合并的内存边界屏蔽字
unsigned int dma_alignment;//dma缓冲区的起始地址和长度对齐字节的掩码
struct blk_queue_tag *queue_tags;//队列空闲忙标记位图
atomic_t refcnt;//队列使用计数
unsigned int in_flight;//请求队列中待处理的请求数
unsigned int sg_timeout; //用户定义命令的超时时间
unsigned int sg_reserved_size;//没有使用
struct list_head drain_list;//临时延时的请求链表首部
/*
* reserved for flush operations
*/
struct request *flush_rq;
unsigned char ordered;
};
struct backing_dev_info {
unsigned long ra_pages; /* max readahead in PAGE_CACHE_SIZE units */
unsigned long state; /* Always use atomic bitops on this */
unsigned int capabilities; /* Device capabilities */
congested_fn *congested_fn; /* Function pointer if device is md/dm */
void *congested_data; /* Pointer to aux data for congested func */
void (*unplug_io_fn)(struct backing_dev_info *, struct page *);
void *unplug_io_data;
};
12、请求描述符
struct request {
struct list_head queuelist;//请求队列链表指针
unsigned long flags; //请求标志
sector_t sector; //下一个要传送的扇区号
unsigned long nr_sectors; //剩余要阐传输的扇区数
/* no. of sectors left to submit in the current segment */
unsigned int current_nr_sectors;//当前段中要传输的剩余扇区数
sector_t hard_sector; //需要完成的下一个扇区号
unsigned long hard_nr_sectors; //需要传输的扇区数,通用块层更新
/* no. of sectors left to complete in the current segment */
unsigned int hard_cur_sectors;当前bio当前段中的要传输的扇区数,通用块层更新
struct bio *bio;//请求中未完成的第一个bio
struct bio *biotail;//请求链表末尾bio
void *elevator_private;//指向io调度程序的私有数据
int rq_status; //请求状态
struct gendisk *rq_disk;//指向此请求关联的磁盘设备
int errors;//用于记录此次传送中的发生的io错误数
unsigned long start_time;//请求的起始时间
unsigned short nr_phys_segments;//请求的物理段数
unsigned short nr_hw_segments;//请求的物理硬件段数
int tag;//与请求相关的标记
char *buffer;//指向当前数据传输缓冲区的指针
int ref_count;//引用计数
request_queue_t *q;//指向包含请求队列描述符的指针
struct request_list *rl;//指向request_list指针
struct completion *waiting;//等待数据传输结束的completion
void *special;//对硬件发出特殊指令所使用的缓冲区指针
unsigned int cmd_len;//命令长度
unsigned char cmd[BLK_MAX_CDB];//命令缓冲区
unsigned int data_len;//数据长度
void *data;//驱动层序跟踪传输数据的指针
unsigned int sense_len;//sense长度
void *sense;//指向sense命令的缓冲区
unsigned int timeout;//请求的超时时间
struct request_pm_state *pm;//电源管理结构
rq_end_io_fn *end_io;//请求接收回调函数
void *end_io_data;//结束回调函数的参数
};
每个请求包含一个或多个bio,最开始通用块层创建包含一个bio的请求,然后io调度程序会添加新的段或者将别的bio链接到此请求中。rq_for_each_bio宏会遍历请求中的所有bio。一旦bio请求总的数据被传输完成则会立即更新rq->bio。
struct request_list {
int count[2];//两个计数器分别用于read、write请求的请求描述符
int starved[2];//两个标志用于读取或者写入传输是否失败
mempool_t *rq_pool;//指向描述分配的内存池
wait_queue_head_t wait[2];//用于读或者写获取请求描述符睡眠的队列
wait_queue_head_t drain;//存放一个等待请求队列被刷新的进程
};
13、blk_get_request函数用于获取请求队列struct request *blk_get_request(request_queue_t *q, int rw, int gfp_mask)
14、请求队列拥塞控制
a、QUEUE_FLAG_READFULL和QUEUE_FLAG_WRITEFULL标志分别表示队列读/写满
15、blk_congestion_wait判断队列如果拥塞则进程睡眠等待,long blk_congestion_wait(int rw, long timeout)此函数也就是睡眠在congestion_wqh专用队列等待唤醒或者超时
16、顺便说下请求队列的plug和unplug
在io处理中当一个请求被加入到请求队列并没有立即下发到底层设备,等待一定时间或者定时器超时后会调用blk_remove_plug将请求队列移除并下发请求。
17、generic_make_request函数最后会调用到__make_request函数static int __make_request(request_queue_t *q, struct bio *bio)
a、如果需要则调用blk_queue_bounce建立回弹缓冲区
b、调用elv_queue_empty 函数判断请求队列是否为空,如果为空则调用blk_plug_device插入请求队列等待新的请求插入到队列,申请一个新的req,并将req插入到请求队列中。
c、调用elv_merge函数去合并请求,此函数会返回三种结果ELEVATOR_BACK_MERGE、ELEVATOR_FRONT_MERGE、ELEVATOR_NO_MERGE,如果类似ELEVATOR_BACK_MERGE、ELEVATOR_FRONT_MERGE可以合并的则将bio挂入返回的req里然后直接返回,如果是ELEVATOR_NO_MERGE则需要进行下一步
d、调用get_request申请一个req,将bio里的信息填充到req里
e、调用add_request函数将请求加入到队列中
add_request函数比较简单,如果存在 q->activity_fn则调用,在调用调度器的顺序插入函数。
f、判断bio是否设置了BIO_RW_SYNC标志,如果设置了则调用__generic_unplug_device拔出设备开始执行q->request_fn下发请求的操作
18、块设备描述符block_device
struct block_device {
dev_t bd_dev; //块设备主次设备号
struct inode * bd_inode; // 指向bdev文件系统中块设备对应的文件索引节点的指针
int bd_openers;//计数器,统计块设备被打开多少次
struct semaphore bd_sem; //保护块设备打开和关闭的信号量
struct semaphore bd_mount_sem; //禁止在块设备进行新安装的信号量
struct list_head bd_inodes;//已经打开的块设备里的文件索引节点的链表
void * bd_holder;//块设备描述符的当前所有者
int bd_holders;//计数器统计对bd_holder设置的次数
struct block_device * bd_contains;//如果设备是个分区则指向整个磁盘的描述符否则指向磁盘描述符
unsigned bd_block_size;//块大小
struct hd_struct * bd_part;//分区描述符指针
unsigned bd_part_count;//计数器,统计在块中的分区设备别的打开多少次
int bd_invalidated;//当需要读取块设备的分区表时设置此值
struct gendisk * bd_disk;//指向块设备对应的磁盘描述符
struct list_head bd_list;//用于块设备描述符链表的指针
struct backing_dev_info *bd_inode_backing_dev_info;//通常为空
unsigned long bd_private;//指向持有块设备的所有者私有的数据指针
};
所有的块设备都被链入全局链表all_bdevs里,bd_claim函数会设置bdev->bd_holder,bd_release函数则是释放。