源文件:
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