btrfs 空闲空间管理(一) free-space-cache

源文件:
      linux/fs/btrfs/free-space-cache.h
      linux/fs/btrfs/free-space-cache.c
      linux/fs/btrfs/ctree.h

btrfs 使用 free-space-cache 提供了对空闲空间管理的支持,另外,ino map 也是基于 free-space-cache
来实现的。free-space-cache 的两个作用:
      * 支持 extent/bitmap 两种方式的空闲空间管理,每一个 block group 有自己的空闲空间
      * 支持 ino map;这个是文件系统范围的,linux/fs/btrfs/inode-map.c

本篇只是针对空闲空间的管理做些分析,对于 ino map,后面会专门写一篇来讲。

I   数据结构

btrfs 中的空闲空间信息,分磁盘和内存两种描述结构。这个跟大多数磁盘文件系统类似,磁盘有磁盘的格式定义,内存
有内存的结构,两者有一定的对应,可以通过一些函数或者数据结构来辅助转换。

先看一下磁盘数据结构

 1     #define BTRFS_FREE_SPACE_EXTENT 1
 2     #define BTRFS_FREE_SPACE_BITMAP 2
 3  
 4     struct btrfs_free_space_entry {
 5            __le64 offset;                  记录空闲空间的起始地址
 6             __le64 bytes;                  空闲空间的大小;0 表示没有空闲空间可用
 7             u8 type;                       BTRFS_FREE_SPACE_EXTENT 没有使用 bitmap
 8                                            BTRFS_FREE_SPACE_BITMAP    使用 bitmap
 9     } __attribute__ ((__packed__));            
10 
11     struct btrfs_free_space_header {
12             struct btrfs_disk_key location;  对应 free space inode 的 disk key 值
13             __le64 generation;               写该记录的 transid
14             __le64 num_entries;              free space inode 中 btrfs_free_space_entry 数量
15             __le64 num_bitmaps;              free space inode 中的 bitmap 页面数量
16                                        (.type 为 BTRFS_FREE_SPACE_BITMAP 的 entry 数量)
17     } __attribute__ ((__packed__));

每个块组有自己的空闲空间信息,这些信息记录到块组的一个文件中,文件的节点称为 free space inode。每个 btrfs_free_space_header 都关联到一个 free space inode,这个 free space inode 的数据文件中会写入 struct btrfs_free_space_entry 表项和 bitmap(如果有的话)。这与 ext4 中每个块组的固定位置会写入位图信息不同。

btrfs_free_space_header、free space inode 和 block group 构成了 1:1:1 的关系。

一个 bitmap 占用一个页面的空间:
       #define BITS_PER_BITMAP        (PAGE_CACHE_SIZE * 8)
在磁盘中,每个 bitmap 也是有自己的页面的。

接下来是内存数据结构

 1     struct btrfs_free_space {        
 2         struct rb_node offset_index;    链接到 struct btrfs_free_space_ctl 的 free_space_offset
 3         u64 offset;                 空闲空间的起始地址
 4         u64 bytes;                  空闲空间的大小
 5         unsigned long *bitmap;        
 6                                 1 bit 对应 1 个 ctl->unit;设置 1 表示对应范围为空闲;
 7                                 位图表示的空间大小为 BITS_PER_BITMAP * ctl->unit;
 8                                 对应的磁盘结构 struct btrfs_free_space_entry 有 type 字段表明是否使用了 bitmap;
10                            表项一开始 bytes 为 0,添加了空闲空间后其值才会变大;初始的 offset 表明起始地址;
11                            对于 bitmap 表项来说,bytes 并不表示 offset -> offset + bytes - 1 这部分连续空间
12                        可用,只是说明从 offset 起
能够找到总共 bytes 大小的空闲空间; 13          对于空闲信息表项,如果范围临接,则是可以合并的,但是,如果临接表项有自己的 bitmap 则不能合并 15 struct list_head list;    函数内部使用,比如在遍历 rbtree 时收集具有 bitmap 的表项 16 }; 17 18 (这里的值是针对 free space 来的,ino map 有自己的初始设置) 19 struct btrfs_free_space_ctl { block_group->free_space_ctl 指向该结构 20 spinlock_t tree_lock; 21 struct rb_root free_space_offset; 22 u64 free_space; struct btrfs_free_space 空闲空间 bytes 的总和,即当前的空闲空间大小 24 int extents_thresh; 初始为 ((1024 * 32) / 2) / sizeof(strurecalc_thresholdsct btrfs_free_space) 26        使用过程中会由 op->recalc_thresholds 调整 27 int free_extents;  struct btrfs_free_space 表项中没有 bitmap 的数量,即 extent 空闲表项的数量 28 int total_bitmaps; struct btrfs_free_space 表项中有 bitmap 的数量,即 bitmap 页面的数量 29 int unit;    初始化为 block_group->sectorsize 30 u64 start;    初始化为 block_group->key.objectid,即 bg 的起始地址 31 struct btrfs_free_space_op *op; 指向 free_space_op 32 void *private; 指向 struct btrfs_block_group_cache,如果是用于 free space 33       而不是 ino map 的话(ino map 是属于文件系统范围的,不是 bg 的) 34 }; 35 36 static struct btrfs_free_space_op free_space_op = { 37 .recalc_thresholds = recalculate_thresholds, 38 .use_bitmap = use_bitmap, 39 };

struct btrfs_free_space 跟 struct btrfs_free_space_entry 有类似字段来表示空闲空间的范围,offset 和 bytes;对于struct btrfs_free_space 的 bitmap,则磁盘上之需要专门的分配一个页面大小的空间来存放即可。这样,内存和磁盘数据结构有了简单的对应关系。

struct btrfs_free_space_ctl 和 struct btrfs_free_space_header 都有记录表项数量的信息字段。

空闲空间信息的管理和组织

空闲信息的建立,经历了 ”在内存中做好镜像-> 写入磁盘-> 从磁盘加载-> 使用/更新-> 再写入磁盘“ 这样一个循环的过程。struct io_ctl 在这里面起了一个非常重要的作用:struct btrfs_free_space_ctl 从逻辑上将 struct btrfs_free_space 组织在一起,而 struct io_ctl 从物理上将对应的 struct btrfs_free_space_entry 表项和 bitmap 组织在一起。

 1     struct io_ctl {        用于管理对 bitmap/entry 的内存访问
 2         void *cur, *orig;         cur 对应 index map 后的页面内的指针,可以指向页面内的不同区域
 4                                orig 对应 page map 后的起始指针
 5         struct page *page;        index/page 为游标;使用 page 的时候会先 map 一下 
7
struct page **pages; num_pages 个 page 指针 8       page 里面存放的是 9       元数据:chunk,crc area,generation (第 1 个页面) 10       struct btrfs_free_space_entry,或者 struct btrfs_free_space->bitmap 12          crc 的空间不应该超过一个页框 13 struct btrfs_root *root; 14 unsigned long size; 当前页大小,固定为 PAGE_CACHE_SIZE 15 int index; 16 int num_pages; 对应 free space inode 的 size 对应的页面数量 17 unsigned check_crcs:1; 如果对应的 ino 不是 BTRFS_FREE_INO_OBJECTID,则设置为 1,在第 1 个 page 会有 crc area 18 };


struct io_ctl 实际上管理了内存中的一组页框,用于存放 struct btrfs_free_space_entry,struct btrfs_free_space->bitmap 等,而 struct btrfs_free_space_ctl 则通过 rbtree 管理组织 struct btrfs_free_space。struct io_ctl 中的内容最终会写入对应 free space inode 的文件中。

每个 free space inode 的开始部分都会有一个 crc area,用于对文件中的 struct btrfs_free_space_entry 和 bitmap 做校验。struct io_ctl 的页面结构如下图所示:

 

II   extent 和 bitmap

extent 的定义为一段连续的空间,这段连续的空间由 offset/bytes 来描述,没有更细分的粒度;bitmap 则通过 bit 来描述一个 ctl->unit 大小的单元,最小的粒度是 unit,对于 bg 来说是 sectorsize 大小。

从一个 extent 中分配空间的时候,entry 的 offset 起始地址后移,bytes 减少,即从 entry 前面分配;extent 表项如果描述的范围临接,则可以合并,比如,对一个大的 extent 经过多次分配、释放后,会出现多个 extent 表项,每个表项只能描述一段连续区域,如果相邻则可以将这些表项合并。

对于 bitmap 的 free space,每次分配的时候,将 offset 开始对应的 bits 清 0(0 表示分配,1 表示空闲);由于 bitmap 有 sectorsize 大小的粒度,所以整个范围内的空闲空间可能是破碎的,即可能有比较大的总空闲空间,但是这些空间并不连续。搜索位图,按照两个 0 bit 之间的空间来看连续空闲空间是否可以满足分配的需求。对于 bitmap 表项来说,bytes 并不表示 offset -> offset + bytes - 1 这部分连续空间空闲,只是说明从 offset 起在位图表示的范围内能够找到总共 bytes 大小的空闲空间。

.bytes 表示当前范围中的空闲空间总大小,为 0 表示已经没有空闲空间了,对应的表项可以释放掉了。

extent/bitmap 空闲空间表项位于同一个 rbtree 中,它们可以有相同的起始地址,解决冲突的方式是将 extent 表项放在 bitmap表项之前:

* bitmap entry and extent entry may share same offset,
* in that case, bitmap entry comes after extent entry.

这样,如果没有特别的要求,会优先从 extent 表项分配空间:

* allocating from an extent is faster than allocating from a bitmap

什么时候使用 bitmap,什么时候使用 extent?

添加空闲空间记录的时候,对于小的空间,倾向于使用 extent;相对较大的空间 >= sectorsize * BITS_PER_BITMAP,则使用 bitmap 来记录。具体可以参考 use_bitmap 和 __btrfs_add_free_space。至于分配,则是两者都会尝试,以找到满足分配要求的空间,优先 extent 表示的空闲空间。至于 block group 中是先创建的 bitmap,还是 extent 表项,这要看 block group 的大小了;有了大小值,可以从 use_bitmap 推断出来。

    sectorsize * BITS_PER_BITMAP >= 512 * 8 * 4096 = 2^24 = 16M

对于 btrfs 来说,当前 sectorsize 为 4096,那么一个 bitmap 可以描述的范围是 2^3 * 2^12 * 2^12 = 2^27 = 128M。

多个 extent/bitmap 表项可以描述更多的空间。

对于 free space inode 来说,按照 crc area 的大小,以及每个页面对应一个 crc 表项来看,最大页面数量为:

    (4096 - sizeof(u64)*2) / sizeof(u32) = (4096 - 16) / 4 = 1024 - 4 = 1020

这样计算,一个 free space 文件的大小限制为 4K * 1020;因为每个表项可以描述不同大小范围的空间,所以 bg 的空间大小取值会有比较大的范围。

它们表示的空间会不会有重叠?

bitmap 和 extent 表项表示的空间应该没有重合:小的空间,直接由 extent 表项来表示;从 bitmap 大空间中分配出来的小空间,后面会添加到 extent 中去,而此时 bitmap 对应的位为 0 表示已分配。另外,如果有重叠区域,则 struct btrfs_free_space_ctl->free_space 这个值就不准确了

另外,从关于 free space cluster 的 commit 中可以看到

    Currently we either want to refill a cluster with
          1) All normal extent entries (those without bitmaps)
          2) A bitmap entry with enough space   

当前,对于一个 cluster 来说,要么有 extent,要么有 bitmap,不可两者共有

 

III   基本操作

定位 free space inode:lookup_free_space_inode

有个 block group,如何定位其下的额 free space inode 呢?这个工作由 lookup_free_space_inode 来完成。
首先,block_group->inode 会缓存这个 inode,但是在此之前,需要有一个从磁盘 search 加载的过程:

1     key.objectid = BTRFS_FREE_SPACE_OBJECTID;
2     key.offset = block_group.key.objectid
3     key.type = 0;

构造如上 key,然后搜索到对应的 struct btrfs_free_space_header,然后获取 header.location,即 free
space inode 对应的 key,再由这个 key 获取对应 inode,并缓存到 block_group->inode 字段:

从磁盘加载 free space 信息:load_free_space_cache
将内存中的 free space 信息写入磁盘:btrfs_write_out_cache

加载和写磁盘涉及 struct btrfs_free_space_header 以及 struct io_ctl 所管理的页面。struct io_ctl 管理的信息会写入到 free space inode 的文件中。

写磁盘的时候,先写 struct io_ctl 第 1 个页面,有 struct btrfs_free_space_entry 表项,crc area 等,之后写入其他表项,最后写 bitmap,这些 bitmap 在磁盘上是连续存放的;写完这些之后,再写入 struct btrfs_free_space_header。

加载的时候先读入 struct btrfs_free_space_header,之后加载所有的 entry 表项,然后再加载 bitmap;在 struct io_ctl 的页面中,bitmap 页面跟在 entry 页面后面。

分配空间:btrfs_find_space_for_alloc

可能用 extent 表项,也可能使用 bitmap 表项

添加/删除空闲空间:btrfs_add_free_space/btrfs_remove_free_space

初始化 bg 的空间信息结构:btrfs_init_free_space_ctl

 

小结

本文简单总结了一下 free-space-cache 中的核心数据结构,以及数据的组织方式。后面会通过介绍 cluster 分配、inode map 等来看 free-space-cache 的应用。


* 尚未弄明白的问题
    * free space 数据结构是在什么时候创建的?
          对于 ino map,理论上可以在 3 个地方初始化:创建文件系统、文件系统首次 mount、首次创建新的 inode
          对于 bg free space,应该可以在 2 个地方初始化:创建新的 bg、在 bg 内部首次分配空间
            ### 需要将来在读代码的过程中验证 ###

    * free space inodes 在哪棵 tree 中进行管理?inodes 的数据,即空闲空间信息呢?

    * struct btrfs_free_space_header 在哪棵 tree 中进行管理? fs_info->tree_root

posted on 2012-11-26 20:19  refrag  阅读(4014)  评论(1编辑  收藏  举报

导航