bcache内核模块IO读写、writeback流程梳理

2 篇文章 0 订阅

主要作用

Bcache (block 缓存)是一个Linux内核“块存储层”缓存,类似于flashcache使用ssd作为hdd的缓存方案,允许使用较为高速的固态硬盘作为读写缓存(writeback模式)或者读缓存(writethrough 或者 writearound模式)来为另一个 block 设备(通常是机械硬盘或硬盘阵列)加速。

上述三种模式的原理图如下:

bcache缓存模式

默认情况下,bcache只超速缓存随机读取和写入,这也是 SSD 的强项。尤其在writeback模式下,对写性能的提升会较为明显,本文后续的内核态分析也主要针对该模式的逻辑进行介绍。

使用方法

需要安装bcache-tools用户态工具来配合内核实现bcache功能的部署,具体方法可参见博客:Linux下SSD缓存加速之bcache使用 本文不再赘述。

bcache内核态核心流程梳理(3.10)

IO下发流程

bcache在整个内核IO栈的位置如下,主要是在generic_make_request函数中调用q->make_request_fn的时候调用bcache设备的IO处理方法,进入bcache层。
bcache在IO栈的位置

  • 下发总流程
submit_bio
	-> generic_make_request
		-> q->make_request_fn                              #调用bio对应块设备(bcache)队列队列的make_request_fn方法
			-> cached_dev_make_request                     #bcache模块下发request方法
				-> part_stat_inc                           #更新了读写完成次数(用于计算bcache的iops)
				-> part_stat_add                           #更新了读写完成的扇区数(计算bcache的带宽)
				-> bio->bi_bdev = dc->bdev;                #先设置bio后续下发的设备后端设备
				-> cached_dev_get(dc)                      #判断后端设备是否配置了cache设备
					-> s = search_alloc(bio, d);           #生成一个search
                      -> request_write || request_read       #进入具体读写流程

​ 流程图
在这里插入图片描述

  • 写IO流程
request_write
	-> check_should_skip                                  #检查是否要跳过该IO,如果要跳过,标记s->op.skip为true
	-> if (bio->bi_rw & REQ_DISCARD)                      #判断是否是DISCARD请求或者之前已经被标记skip
		-> goto skip   (见后面的PS)
	-> if (should_writeback(dc, s->orig_bio))             #判断是否要执行writeback模式操作
		-> s->writeback=true
	-> if (!s->writeback)                                 #如果不执行writeback操作
		-> closure_bio_submit                             #直接下发该io到后端设备
	->  else                                              #如果执行writeback操作
		-> bch_writeback_add                              #唤醒回写线程
		-> if (bio->bi_opf & REQ_PREFLUSH) 
            -> closure_bio_submit                          #若IO被为FLUSH,则下刷该到后端设备,使磁盘缓存的数据下刷下去
	以上几个分支走完,都会来到如下流程:
	-> bch_data_insert
		-> bch_data_insert_start
			#判断该请求是否被pass(比方说writearound模式),如果是,直接返回,不下发到cache设备。
			-> if (op->bypass)    
				bch_data_invalidate
			
			#如果请求没有被pass,则继续执行,下发到cache设备                               
			-> bch_submit_bbio
				-> __bch_submit_bbio
					->  bio_set_dev(bio, PTR_CACHE(c, &b->key, 0)->bdev);         #设置bio后续下发的设备为前端cache设备
						-> closure_bio_submit                                     #下发IO到前端cache设备
			-> bch_data_insert_keys                                                #将bio相关信息添加到b+tree中。

​ 写流程图
在这里插入图片描述

  • 读IO流程
request_read
	-> check_should_skip                                      #检查是否要跳过该IO,如果要跳过,标记s->op.skip为true
	-> btree_read_async                                       #在cache中进行搜索
		-> bch_btree_search_recurse
			-> bch_btree_iter_next_filter                     #主要的搜索函数
				#如果cache miss,调用submit_partial_cache_miss,从后端磁盘读数据(包括预读数据)
				-> submit_partial_cache_miss
					-> cached_dev_cache_miss                  #cache_miss相关操作
						-> bio_get(s->op.cache_bio)           #增加s->op.cache_bio计数
						-> closure_bio_submit                 #从后端设备读取数据
				
				#如果是cache命中,走下面流程
				-> submit_partial_cache_hit
					-> __bch_submit_bbio
						->  bio->bi_bdev    = PTR_CACHE(c, &b->key, 0)->bdev;    #设置bio后续下发的设备为前端cache设备
							-> closure_bio_submit                                #下发IO到前端cache设备
	-> request_read_done_bh
		#如果是发生了cache miss 读了后端数据上来,则s->op.cache_bio存在计数,走这个流程
		-> request_read_done      
			-> bio_complete                                     #标记该IO完成
			-> bch_data_insert                                  #将信息插入到cache设备中并根性b+tree
    
		#如果是cache命中,则走这个流程
		-> cached_dev_read_complete    
			-> cached_dev_bio_complete 
				-> search_free
					-> bio_complete                             #标记该IO完成
					-> bio_put; closure_debug_destroy;          #释放各类结构体数据

​ 读流程图
在这里插入图片描述

  • IO下发过程中的其他流程
bio_complete
	-> generic_end_io_acct      #io结束数据统计
	-> bio_endio                #调用块设备层IO结束方法
	
closure_bio_submit      #bcache层提交IO方法,实际还是调用generic_make_request调具体的设备函数(所以generic_make_request是可以递归调用)
	-> generic_make_request          
		-> struct request_queue *q = bio->bi_disk->queue;     #找到该bio对应的bi_disk(上面bio_set_dev中指定)->queue
		-> ret = q->make_request_fn(q, bio);                  #调用queue对应的方法,这里也就是直接调用块设备自己的处理方法了
		
check_should_skip
	跳过io的情况:
	1、没有cache设备attach
	2、cache使用量是否达到了95%(CUTOFF_CACHE_ADD)
	3、io是discard请求
	4、cache_mode为NONE或者为writeround
	5、扇区没对齐
	6、历史顺序IO
	7、拥塞控制(读写共用,即如果由于读IO超时造成了cache拥塞,则写IO也会受到影响。目前测试拥塞与cache盘自身性能、下发IO线程数量有关)

should_writeback:
	该函数主要判断两个内容
	1、cache_mode是否是writeback,如果不是则返回false
	2、cache的使用量是否达到了75%(CUTOFF_WRITEBACK_SYNC),如果超过则返回false

IO回写(writeback)流程

**bcache的writeback原理:扫描btree中的脏数据并将其添加到固定的内核缓冲区中,然后在通过遍历该缓冲区的数据,将其对应的数据从cache设备中读出,再写入到后端主设备中。**下面是内核3.10的流程,**通过refill_dirty和read_dirty两个函数协同工作实现。**3.12内核后启用了bcache_writeback线程来进行,流程不同,但原理一致。

  • 触发扫描btree脏数据的时机
1、cache设备attach的时候
2、cache设备detach的时候
3、新的写请求出现,并走到writeback处理流程时,通过bch_writeback_add -> bch_writeback_queue
		
bch_writeback_add
	-> bch_writeback_queue
		-> refill_dirty                                 #触发脏数据填充操作
	-> schedule_delayed_work(writeback_rate_update)     #若配置writeback_percent,开始定时更新writeback_rate
  • 脏数据填充buf流程:refill_dirty
refill_dirty
	-> !dc->writeback_running                            #若writeback_running为0
		-> closure_return(cl);                           #直接返回
	-> !atomic_read(&dc->has_dirty)                      #若没有脏数据
		-> SET_BDEV_STATE; closure_return(cl);           #设置CLEAN标记后返回
	-> bkey_cmp(&buf->last_scanned, &end) >= 0           #如果上次搜索已经到了btree末尾
		-> searched_from_start = true;                   #设置从头开始搜索整个磁盘
	-> bch_refill_keybuf                                 #遍历btree来进行填充dc->writeback_keys->keys
		-> btree_root
			-> bch_btree_refill_keybuf
				-> RB_INSERT                             #将key插入到dc->writeback_keys->keys buffer中
	-> if (searched_from_start)                          #如果是搜索了整个磁盘
		-> if (RB_EMPTY_ROOT(&buf->keys))                #如果buf为空
			-> atomic_set(&dc->has_dirty, 0);            #清除设备的dirty标记
		-> closure_delay( dc->writeback_delay * HZ)      #因为遍历了整个磁盘,睡眠writeback_delay秒(默认30-> read_dirty                                        #触发脏数据回写函数
  • buf数据回写后端盘流程:read_dirty
read_dirty
	/***************************此部分是个循环:START*******************************/
	-> bch_keybuf_next                               #从dc->writeback_keys->keys获取一个key
		-> if (!w);break                             #如果没有数据,代表buf为空,跳出循环
	-> schedule_timeout_uninterruptible (delay)      #根据delay值进行睡眠,这里就是在实际调整下发速率
	-> dirty_init                                    #初始化这个key所代表的io
	-> read_dirty_submit                             #数据回写核心流程
		-> closure_bio_submit                        #先cache设备读取这个数据
		-> write_dirty
			-> closure_bio_submit                    #再将读到的数据下发到后端主设备
	->writeback_delay                                #根据dc->writeback_rate计算delay的值
		-> bch_next_delay         
	/***************************此部分是个循环:END*******************************/
	-> refill_dirty                                   #触发脏数据填充buf函数

​ refill_dirty和read_dirty的联动流程图:
在这里插入图片描述

  • writeback_rate更新相关

从上述writeback流程可以发现,回写的速率实际是靠writeback_rate来控制的。

writeback_rate默认在cache盘有脏数据的情况下以一定的周期(writeback_rate_update_seconds,默认30,高版本为5,可手动调节)实时更新。计算过程会参考writeback_percent值(默认10,最大40)以及当前的dirty数据量,通过PD(比例微分)控制器进行平滑调节。

writeback_rate更新任务是通过一个循环的延迟任务实现的:

writeback_rate更新任务初始化:注册后端主设备的时候,就会初始化更新writeback_rate的延时任务
register_bcache-> register_bdev-> cached_dev_init
	-> bch_cached_dev_writeback_init                                        
		-> INIT_DELAYED_WORK                              #初始化delayed work,work函数为update_writeback_rate
			(&dc->writeback_rate_update, 
			update_writeback_rate);
		-> schedule_delayed_work                          #延时writeback_rate_update_seconds后执行writeback_rate_update
				(&dc->writeback_rate_update, 
				dc->writeback_rate_update_seconds * HZ)
	Delayed work函数:
	update_writeback_rate
		-> if (&dc->has_dirty) && writeback_percent       #如果设备标记为dirty 且 配置了writeback_percent
			-> __update_writeback_rate                    #更新writeback_rate核心函数
											              #其中一方面通过一个PD控制器来实现rate的平滑调整(具体实现待研究)
											              #另一方面调用schedule_delayed_work启动下一个update_writeback_rate延时任务

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值