您的位置:首页 > 理论基础 > 数据结构算法

BCache源码浅析之二数据结构与操作

2016-01-02 18:01 232 查看
2.主流程与数据结构

2.1 bcache初始化

(1) register_bcache

首先read_super得到超级快的信息,根据该信息能的知道是主设备还是cache设备

(2) register_bdev 主设备注册:

cached_dev_init : 初始化struct cached_dev中的先关结构

disk包含了gendisk和

cache_accounting包含了统计信息

sb_* 用于管理super block io

running表明了主设备的状态

io* 用于记录顺序读写信息

cached_dev_init ==> bcache_device_init 会注册request queue, 并初始化gendisk

cached_dev_init ==>bch_cached_dev_request_init注册request queue的回调

cached_dev_init==>bch_cached_dev_writeback_init 初始化writeback模块

(3) register_cache ==》 register_cache_set

struct cache :

每个cache设备与一个bch_allocator_thread关联,用于bucket的分配

sb* : 用于访问cache设备的超级块

cache_set: 一个cache set中可以有一个或多个cache 设备, bch_cache_set_alloc用于分配一个cache-set; bch_cache_sets是全局cache_set的list.

DECLARE_FIFO(long,free)[RESERVE_NR];用于记录可用的bucket, bcache用bucket来管理磁盘空间。

最后会关联cache与cache_set

ca->set = c; //ca为cache,c为cache_set

ca->set->cache[ca->sb.nr_this_dev]= ca;

c->cache_by_alloc[c->caches_loaded++]= ca;

(4)bch_cache_set_alloc

首先从super block中获取bucket size, block_size等参数, 然后依次调用如下初始化函数:

c->bio_split = bioset_create

c->uuids = alloc_bucket_pages

c-->moving_gc_wq =create_workqueue("bcache_gc")

bch_journal_alloc(c) ;//journal 初始化

bch_btree_cache_alloc(c) ;//建立btree node的分配缓存管理结构

bch_open_buckets_alloc(c) ;//初始化c->data_buckets 一共为6个

bch_bset_sort_state_init;//用于bset中的sort操作

(5) 关联主设备与cache设备bch_cached_dev_attach

a. 主设别状态检察

b. uuid检察,若uuid未分配则写分配uuid并写入bch_uuid_write

c. bcache_device_attach关联cache 与主设备

d. bch_cached_dev_run运行主设备

e. 建立link文件bcache_device_link

bch_cached_dev_run

a. add_disk(d->disk); 这样block层会知道有block设备增加了

b. kobject_uevent_env 发送uevent

c. 建立dev 与bcache链接文件

2.2 btree的基本数据结构

(1) bucket

缓存设备会按照bucket大小划分成很多bucket,bucket的大小最好是设置成与缓存设备ssd的擦除大小一致,一般建议128k~2M+,默认是512k。bucket内空间是追加分配的,只记录当前分配到哪个偏移了,下一次分配的时候从当前记录位置往后分配。另外在选择bucket来缓存数据时有两个优先原则:1)优先考虑io连续性,即使io可能来自于不同的生产者;2)其次考虑相关性,同一个进程产生的数据尽量缓存到相同的bucket里

struct bucket {

atomic_t pin;

uint16_t prio;/*每次hit都会增加,然后所有的bucket的优先级编号都会周期性地减少,不常用的会被回收,这个优先级编号主要是用来实现lru替换的*/

uint8_t gen;//generation,用来invalidate bucket用的。

uint8_t last_gc;/* Most out of date gen in the btree */

uint16_t gc_mark;/* Bitfield used by GC. See below for field */

};

(2) bkey

bcache使用了b+tree, b+tree的node和叶节点指向的数据,最终存到bucket对应的cache disk中的位置。b+树节点中的关键结构就是bkey,bkey就是记录缓存设备缓存数据和后端设备数据的映射关系的,其结构如下。

struct bkey {

__u64 high;

__u64 low;

__u64 ptr[];

};

/*

KEY_INODE:表示一个后端设备的id编号(后端设备在cache set中一般以bdev0,bdev1这种方式出现)

KEY_SIZE:表示该bkey所对应缓存数据的大小

KEY_DIRTY:表示该块缓存数据是否是脏数据

KEY_PTRS:表示cache设备的个数(多个ptr是用来支持多个cache设备的,多个cache设备只对脏数据和元数据做镜像)

KEY_OFFSET:bkey所缓存的hdd上的那段数据区域的结束地址

PTR_DEV:cache设备

PTR_OFFSET:在缓存设备中缓存的数据的起始地址

PTR_GEN:对应cache的bucket的迭代数(版本)

*/

由于key中记录了PTR_OFFSET,因此根据该信息能够得到bucket

static inlinesize_t PTR_BUCKET_NR(struct cache_set *c,

const struct bkey *k,

unsigned ptr) {

return sector_to_bucket(c, PTR_OFFSET(k,ptr));

}

BEKY 的比较函数如下:

static__always_inline int64_t bkey_cmp(const struct bkey *l,

const structbkey *r) {

return unlikely(KEY_INODE(l) !=KEY_INODE(r))

? (int64_t) KEY_INODE(l) -(int64_t) KEY_INODE(r)

: (int64_t) KEY_OFFSET(l) -(int64_t) KEY_OFFSET(r);

}

(3) bset

一个bset是一个bkey的数组,在内存中的bset是一段连续的内存,并且以bkey排序的,内存中一个btree node只有4个bset。

struct btree {

。。。。。。

struct btree_keys keys;

。。。。。。。

}

struct btree_keys{

。。。。。。

/*

*Sets of sorted keys - the real btree node - plus a binary search tree

*

*set[0] is special; set[0]->tree, set[0]->prev and set[0]->data point

*to the memory we have allocated for this btree node. Additionally,

*set[0]->data points to the entire btree node as it exists on disk.

*/

struct bset_tree set[MAX_BSETS]; //MAX_BESTS = 4

};

struct bset_tree {

.......

struct bset *data;

}

struct bset {

__u64 csum;

__u64 magic;

__u64 seq;

__u32 version;

__u32 keys;

union {

struct bkey start[0];

__u64 d[0];

};

};

(4) B+ Tree

bcache中以b+tree来维护索引,一个btree node里包含4个bset,每个bset中是排序的bkey。每个btree node以一个bkey来标识,该bkey是其子节点中所有bkey中的最大值,不同于标准的b+树那(父节点存放子节点的地址指针); bcache中的b+树中非叶子节点中存放的bkey用于查找其子节点(并不是存的地址指针),而是根据bkey计算hash,再到hash表中取查找btree node(mca_find函数实现)。叶子节点中的bkey存放的就是实际的映射了(根据这些key可以找到缓存数据以及在hdd上的位置)。

btree 叶节点的遍历: bch_btree_map_keys ==》

intbch_btree_map_keys(struct btree_op *op, struct cache_set *c,

struct bkey *from, btree_map_keys_fn*fn, int flags)

{

return btree_root(map_keys_recurse, c, op, from, fn, flags); //遍历btree+进行查找

}

#definebtree_root(fn, c, op, ...) \

({ \

int _r = -EINTR; \

do { \

struct btree *_b = (c)->root; \

bool _w = insert_lock(op, _b); \

rw_lock(_w, _b, _b->level); \

if (_b == (c)->root && \

_w == insert_lock(op, _b)) { \

_b->parent = NULL; \

_r = bch_btree_ ## fn(_b, op, ##__VA_ARGS__); \

} \

rw_unlock(_w, _b); \

bch_cannibalize_unlock(c); \

if (_r == -EINTR) \

schedule(); \

} while (_r == -EINTR); \

\

finish_wait(&(c)->btree_cache_wait,&(op)->wait); \

_r; \

})

static intbch_btree_map_keys_recurse(struct btree *b, struct btree_op *op,

struct bkey *from, btree_map_keys_fn *fn,

int flags) {

。。。。。

bch_btree_iter_init(&b->keys,&iter, from);

while ((k =bch_btree_iter_next_filter(&iter, &b->keys, bch_ptr_bad))) {

ret = !b->level

? fn(op, b, k) //已经到页节点了

: btree(map_keys_recurse,k, b, op, from, fn, flags); //非页节点

from = NULL;

if (ret != MAP_CONTINUE)

return ret;

}

if (!b->level && (flags &MAP_END_KEY))

ret = fn(op, b,&KEY(KEY_INODE(&b->key),

KEY_OFFSET(&b->key), 0));

return ret;

}

#define btree(fn,key, b, op, ...) \

({ \

int _r, l = (b)->level - 1; \

bool _w = l <= (op)->lock; \

struct btree *_child =bch_btree_node_get((b)->c, op, key, l, _w);\

if (!IS_ERR(_child)) { \

_child->parent = (b); \

_r = bch_btree_ ## fn(_child, op, ##__VA_ARGS__); \

rw_unlock(_w, _child); \

} else \

_r = PTR_ERR(_child); \

_r; \

})

struct btree*bch_btree_node_get(struct cache_set *c, struct btree_op *op,

struct bkey *k, int level, bool write) {

。。。。。。

retry:

b = mca_find(c, k);

if (!b) { //若key 对应的btree不存在,则先申请分配一个bucket,并

mutex_lock(&c->bucket_lock);

b = mca_alloc(c, op, k, level);

mutex_unlock(&c->bucket_lock);

。。。。。。

bch_btree_node_read(b);

} else {

rw_lock(write, b, level);

if (PTR_HASH(c, &b->key) !=PTR_HASH(c, k)) {

rw_unlock(write, b);

goto retry;

}

}

。。。。。。

return b;

}

static structbtree *mca_find(struct cache_set *c, struct bkey *k)

{

struct btree *b;

rcu_read_lock();

hlist_for_each_entry_rcu(b, mca_hash(c,k), hash) //根据key的hash从hlist中取出下一个btree节点

if (PTR_HASH(c, &b->key) ==PTR_HASH(c, k))

goto out;

b = NULL;

out:

rcu_read_unlock();

return b;

}

为了提高性能, 使用了cache_set中有btree的缓存

struct list_head btree_cache;

struct list_head btree_cache_freeable;

struct list_head btree_cache_freed;

如果btree不存在时,先从cache_set中的btree_cache中分配,失败则最终从kzalloc中分配。(相关机制较简单, 不在详细分析 mca_bucket_alloc, mca_data_alloc, mca_reap)

bch_btree_iter_init和bch_btree_iter_next_filter为btree的访问迭代器,其基本工作原理就是

(1)bch_btree_iter_init 根据要search的bkeys,遍历btree中的bset, bset中的bkey.然后将满足条件的bkey集合以{bkey_start, bkey_end}元素形式添加到heap中。 heap的排序比较函数为:btree_iter_cmp

static inline boolbtree_iter_cmp(struct btree_iter_set l, struct btree_iter_set r) {

return bkey_cmp(l.k, r.k) > 0;

}

bch_btree_iter_init==> __bch_btree_iter_init {

for (; start <= bset_tree_last(b);start++) {

ret = bch_bset_search(b, start,search);

bch_btree_iter_push(iter, ret,bset_bkey_last(start->data));

}

}

(2) bch_btree_iter_next_filter则根据heap中的bkey范围,每次返回一个bkey.

3.底层IO

3.1 Closure

bcache里的closure就是一个引用计数来管理回调或对象析构的机制

structclosure {

union {

struct {

structworkqueue_struct *wq;

struct task_struct *task;

struct llist_node list;

closure_fn *fn;

};

struct work_struct work;

};

struct closure *parent; //父closure对象

atomic_t remaining;

};

当调用closure_get后,需要调用closure_put来减少引用计数。 closure_init后引用计数被设为1; closuer实现较为简单,下面只分析几种常用的使用方法.

(1) Basicusage

foo_endio(struct bio *bio, int error) {

closure_put(cl);

}

closure_init(cl); // ref to 1

do_stuff();

closure_get(cl); //ref to 2

bio1->bi_endio = foo_endio;

do_more_stuff();

closure_get(cl); ref to 3

bio2->bi_endio = foo_endio;

bio_submit(bio1);

bio_submit(bio2); //当bio1 bio2完成后ref set to 1

continue_at(cl, complete_some_read,system_wq); ref--, 如果ref == 0则complete_some_read在system_wq中执行。

continue_at如果wq参数为0,则同步执行。

上面的例子是异步执行方法,它还支持同步应用: A __closure_wait(ref++), B closure_sync会使调用task让出cpu,; C __closure_wake_up(ref--)能唤醒等待的task

最后closure还支持parent机制, closure_init, 如果parent不为NULL, 会closure_get(parent)

closure_put_after_sub时, 如果当前ref==0, 则closure_put(parent)

3.2 基本io.c

bch_bio_max_sectors: 合并bio中的请求,并返回一次最大传输长度

bch_generic_make_request:根据设备能支持一次最大传输长度, 拆分传输。

bch_bbio_alloc: 分配一个对cache设备的bio,

其中bio->bi_bdev = PTR_CACHE(c, &b->key,0)->bdev;

bch_submit_bbio提交对cache设备的io

bch_bbio_endio对cache设备的io的回调

bch_bio_map: 对bio中的bio_vec分配物理地址, 当base参数不为0时执行如下代码:

bv->bv_page =is_vmalloc_addr(base)

? vmalloc_to_page(base)

: virt_to_page(base);

下面时对block层bio操作的小结:

1. bio_alloc_pages:为bio中的每个bio_vec分配物理内存页

2. bio_initbio初始化; bio_reset reinit bio

3.bio_sumit异步提交bio;submit_bio_wait同步提交

4.拆分bio: bio_next_split

5. bioset_create:create bioset, bio_alloc_bioset: allocate a bio for I/O according to bioset
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: