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