Kernel and Utilities
盡最大的可能在內核和應用程序之間共享代碼。他們代碼主要的不同在於啓動和跟蹤IO的代碼。Btrfs內的任何一個項目都應該試着來保持內核和應用程序的更新,並且保持他們之間的代碼通用。
對於Btrfs理解的重要部分便是理解keys和items是如何交互的,和各個類型的item的數據是如何編排格式的。Btrfs-debug-tree命令可以被用來打印ascii格式的btree結構,如果想要查看數據是如何在磁盤上排列的,這可能會非常有幫助。
- Btree
所有的元數據都被存放在磁盤上的一個btree中。Btrees存儲key/item對。儘管使用了相同的代碼來實現所有的btrees,但仍有一些不同種類的btree。可以參考 Btrees。
- Key
一個用來爲一棵Btree的item提供標識和分類的固定大小的元組。key被分成3各部分:objectid, type, 和 offset。type字段顯示另外兩個字段應該被使用的方式和在item中找到的信息類型。可以參考Btree Keys。
- Item
存儲在btree葉子中的可變大小的結構。根據key類型的不同,item含有不同類型的數據。可以參考Btree Items。
- Subvolume
一個含有文件和目錄的命名樹。每一個快照是一個subvolume,但並不是每一個subvolume都是一個快照。
- Snapshot
通過引用另一個subvolume的根來創建一個subvolume。快照是可寫的,但是對於快照的任何修改都不會出現在它的父subvolume中。
- Extent buffer
一個允許訪問大於一個頁大小的btree塊的抽象。
- ctree.c 核心的btree管理代碼
- ctree.h 定義了keys和大多數元數據使用的數據結構
- dir-item.c 創建和使用目錄items的輔助函數
- disk-io.c 元數據IO操作和FS open/close 例程
- extent-tree.c btree塊和文件數據extents使用的tracks and space
- extent_io.c 跟蹤狀態(locked, writeback 等)和實現extent_buffers
- extent_map.c 映射一個文件的邏輯偏移到磁盤上的邏輯塊地址
- file-item.c 插入和移除文件extents 和數據校驗和的輔助函數
- file.c 文件寫例程(kernel only)
- inode-item.c 在btree中分配inode結構的輔助函數
- inode.c kernel使用的大部分文件和目錄操作
- ordered-data.c 爲data=ordered 維護inodes鏈表
- print-tree.c 查找一棵btree並打印它找到的items
- root-tree.c 管理tree roots的tree中的items的輔助函數
- struct-funcs.c 例示所有的元數據的set/get 函數的宏
- super.c 內核超級塊及相關函數
- transaction.c 處理事件的提交
- volumes.c 所有的多設備有關代碼
- tree-log.c 爲快速fsync 處理樹items的日誌
- compression.c zlib壓縮支持例程
Using Extent Buffers
Extent緩衝是提供了對於跨越多個頁的內存區域的讀/寫方法的容器。他們使btrfs可以具有大於單個頁的樹塊,並且抽象出kmap調用以使元數據可以位於高端內存。
爲了維護代碼中的類型安全,Btrfs提供了一些宏可以將extent buffer內的偏移轉換爲一個特定的類型,還有一些宏來爲數據結構中各個字段執行set/get操作。These macros including caching to lower then number of times kmap must be called while looping.
宏在ctree.h聲明(查找BTRFS_SETGET_FUNCS),但是由於他們相當大,在文件struct-funcs.c有他們的例示。用法的例子可以在下面的算法部分找到。
Tree Searching
我們希望通過對於樹查找過程的研究,來了解B+樹在btrfs中的應用。
int btrfs_search_slot(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_key *key,
struct btrfs_path *p, int ins_len, int cow);
書查找是基於關鍵字的,查找結果保存在一個btrfs_path結構中,路徑給了你從跟向下到頁的所有的樹塊。
---------------------------------------------------------------------
fs/btrfs/ctree.h
430 struct btrfs_path {
431 struct extent_buffer *nodes[BTRFS_MAX_LEVEL];
432 int slots[BTRFS_MAX_LEVEL];
433 /* if there is real range locking, this locks field will change */
434 int locks[BTRFS_MAX_LEVEL];
435 int reada;
436 /* keep some upper locks as we walk down */
437 int lowest_level;
438
439 /*
440 * set by btrfs_split_item, tells search_slot to keep all locks
441 * and to force calls to keep space in the nodes
442 */
443 unsigned int search_for_split:1;
444 unsigned int keep_locks:1;
445 unsigned int skip_locking:1;
446 unsigned int leave_spinning:1;
447 unsigned int search_commit_root:1;
448 };
---------------------------------------------------------------------
上面的BTRFS_MAX_LEVEL定義爲8。
path->nodes[0]總是葉子節點,path->nodes[1]爲指向葉子節點的btree節點。slots數組指示使用的是btree塊中的哪一個key或item。path->slots[1]告訴我們使用path->nodes[1]中的哪一個塊指針來查找葉子節點。path->slots[0]告訴我們找到的是path->nodes[0]中的哪個item。
ins_len參數告訴btrfs_search_slot()來準備樹以在樹中中插入一個item(ins_len > 0)或移除一個item(ins_len < 0)。在插入的情形下,btrfs_search_slot()確保葉子節點有可以插入一個大小爲ins_len的item的空間。對於移除items,btrfs_search_slot()確保高層節點被適當的拆分。
cow參數告訴btrfs_search_slot()你打算改變路徑中的一個或多個buffers。這種情況下,它在從根到頁的所有的buffers上執行適當的cow操作。
btrfs_search_slot()出錯時返回 < 0,如果找到要查找的關鍵字則返回0,如果沒找到,則返回1。如果沒找到關鍵字,則路徑指向關鍵字應該被插入的位置(即使ins_len爲0)。這使你可以容易的查看接近於某個給定關鍵字的items。
查找關鍵字(0, 0, 0)將使路徑指向數的第一個item。查找關鍵字將使返回值爲1,並且路徑指向樹的最後一個item(假設那個關鍵字還沒有出現,它也本不應該出現)。
btrfs_previous_item()可以被用以基於一個路徑的查找給定類型的前一個item。
btrfs_next_leaf()可以被用以基於一個路徑的查找給定類型的下一個item。
btrfs_prev_leaf()可以被用以基於一個路徑的查找給定類型的前一個葉子節點。
Sample Search Code
volumes.c:find_next_chunk()是一個非常簡單的爲一個新的邏輯磁盤chunk查找起始點的例程。它查找chunk btree的最高的關鍵字,並返回一個更大的值。chunk樹中的關鍵字具有如下的形式:
---------------------------------------------------------------------
key.objectid = logical byte offset
key.type = BTRFS_CHUNK_ITEM_KEY
key.offset = number of bytes in this chunk
---------------------------------------------------------------------
---------------------------------------------------------------------
947 static noinline int find_next_chunk(struct btrfs_root *root,
948 u64 objectid, u64 *offset)
949 {
950 struct btrfs_path *path;
951 int ret;
952 struct btrfs_key key;
953 struct btrfs_chunk *chunk;
954 struct btrfs_key found_key;
955
956 path = btrfs_alloc_path();
957 BUG_ON(!path);
958
959 key.objectid = objectid;
960 key.offset = (u64)-1;
961 key.type = BTRFS_CHUNK_ITEM_KEY;
962
963 ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
---------------------------------------------------------------------
樹搜索是一個只讀的操作,所以btrfs_search_slot()不需要一個“事件處理”,並且ins_len和cow參數也爲0。分配了一個path以用於記錄搜索的結構,關鍵字被設置來搜索樹中最高的可能的BTRFS_CHUNK_ITEM_KEY
---------------------------------------------------------------------
964 if (ret < 0)
965 goto error;
966
967 BUG_ON(ret == 0);
968
---------------------------------------------------------------------
我們期望搜索返回1,它不應該可能會有一個chunk以邏輯偏移2^64處爲起始。
---------------------------------------------------------------------
969 ret = btrfs_previous_item(root, path, 0, BTRFS_CHUNK_ITEM_KEY);
---------------------------------------------------------------------
btrfs_previous_item() 被用來在樹中向後查找,直到找到一個具有類型BTRFS_CHUNK_ITEM_KEY的關鍵字。如果他返回非零,那麼它沒有找到任何東西。如果需要,btrfs_previous_item() 可能會執行IO來讀取通過查找父指針而找到的樹中的前一個頁子節點。
---------------------------------------------------------------------
970 if (ret) {
971 *offset = 0;
972 } else {
973 btrfs_item_key_to_cpu(path->nodes[0], &found_key,
974 path->slots[0]);
975 if (found_key.objectid != objectid)
976 *offset = 0;
977 else {
978 chunk = btrfs_item_ptr(path->nodes[0], path->slots[0],
979 struct btrfs_chunk);
980 *offset = found_key.offset +
981 btrfs_chunk_length(path->nodes[0], chunk);
982 }
983 }
---------------------------------------------------------------------
btrfs_previous_item() 執行之後,path指向樹中type == BTRFS_CHUNK_ITEM_KEY的最後一個item。btrfs_item_key_to_cpu() 從extent_buffer中拷貝出關鍵字,並執行尾端轉換來把它變爲CPU字節序,將結果保存在found_key 中。我們用*objectid返回新的chunk的起始字節。
---------------------------------------------------------------------
984 ret = 0;
985 error:
986 btrfs_free_path(path);
987 return ret;
988 }
---------------------------------------------------------------------
最後的步驟是釋放path並返回。
Sample Item Insertion
在內部,item插入僅僅是一次搜索,然後在葉子節點中騰出空間,接着把item複製到適當的地方而已。下面的例子來自volumes.c:btrfs_add_device()。它在chunk樹中添加包括每一個設備的總大小、設備類型、已使用的字節、最優的IO參數的信息。
設備item具有如下形式的關鍵字:
---------------------------------------------------------------------
key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
key.type = BTRFS_DEV_ITEM_KEY;
key.offset = device id
---------------------------------------------------------------------
設備items共享同一課完成邏輯->物理設備映射的樹。他們的關鍵字具有相同的objectid和type字段,只有offset用來區分兩個不同的設備item。傳遞給函數btrfs_add_device()的device 具有所有用來填充磁盤上元數據的數據。
---------------------------------------------------------------------
1032 int btrfs_add_device(struct btrfs_trans_handle *trans,
1033 struct btrfs_root *root,
1034 struct btrfs_device *device)
1035 {
1036 int ret;
1037 struct btrfs_path *path;
1038 struct btrfs_dev_item *dev_item;
1039 struct extent_buffer *leaf;
1040 struct btrfs_key key;
1041 unsigned long ptr;
1042
1043 root = root->fs_info->chunk_root;
1044
1045 path = btrfs_alloc_path();
1046 if (!path)
1047 return -ENOMEM;
1048
1049 key.objectid = BTRFS_DEV_ITEMS_OBJECTID;
1050 key.type = BTRFS_DEV_ITEM_KEY;
1051 key.offset = device->devid;
1052
1053 ret = btrfs_insert_empty_item(trans, root, path, &key,
1054 sizeof(*dev_item));
1055 if (ret)
1056 goto out;
---------------------------------------------------------------------
btrfs_insert_empty_item() 完成所有需要的在樹中爲新的item騰出空間的COW和btree 工作。如果返回0,則path 可以被用來填充數據。
---------------------------------------------------------------------
1058 leaf = path->nodes[0];
1059 dev_item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dev_item);
1060
---------------------------------------------------------------------
btrfs_item_ptr() 把extent buffer leaf 中的一個偏移轉換爲一個struct btrfs_dev_item。樹的葉子節點和slot的信息來自於path。 下面的代碼使用set/get函數來填充元數據。
---------------------------------------------------------------------
1061 btrfs_set_device_id(leaf, dev_item, device->devid);
1062 btrfs_set_device_generation(leaf, dev_item, 0);
1063 btrfs_set_device_type(leaf, dev_item, device->type);
1064 btrfs_set_device_io_align(leaf, dev_item, device->io_align);
1065 btrfs_set_device_io_width(leaf, dev_item, device->io_width);
1066 btrfs_set_device_sector_size(leaf, dev_item, device->sector_size);
1067 btrfs_set_device_total_bytes(leaf, dev_item, device->total_bytes);
1068 btrfs_set_device_bytes_used(leaf, dev_item, device->bytes_used);
1069 btrfs_set_device_group(leaf, dev_item, 0);
1070 btrfs_set_device_seek_speed(leaf, dev_item, 0);
1071 btrfs_set_device_bandwidth(leaf, dev_item, 0);
1072 btrfs_set_device_start_offset(leaf, dev_item, 0);
1073
1074 ptr = (unsigned long)btrfs_device_uuid(dev_item);
1075 write_extent_buffer(leaf, device->uuid, ptr, BTRFS_UUID_SIZE);
---------------------------------------------------------------------
btrfs_device_uuid()用來查找設備item的uuid字段的偏移。write_extent_buffer() 使用這個字段作爲一個到葉子節點頁的memcpy操作的目的地址。
---------------------------------------------------------------------
1076 ptr = (unsigned long)btrfs_device_fsid(dev_item);
1077 write_extent_buffer(leaf, root->fs_info->fsid, ptr, BTRFS_UUID_SIZE);
1078 btrfs_mark_buffer_dirty(leaf);
1079
1080 ret = 0;
1081 out:
1082 btrfs_free_path(path);
1083 return ret;
1084 }
---------------------------------------------------------------------
最後的步驟是標記buffer爲“髒”以使它被pdflush或transaction commit寫回磁盤,然後釋放path。
Sample Item Removal
移除items通過查找item,然後調用btrfs_del_item()來完成。下面的代碼來自xattr.c:btrfs_delete_xattrs(),它刪除一個給定inode的所有的xattrs。在inode.c:btrfs_truncate_inode_items()中可以找到一個這種循環的更高效的版本。基本的結構是循環搜索最後的類型爲BTRFS_XATTR_ITEM_KEY的item並刪除它。循環在你發現一些不是一個BTRFS_XATTR_ITEM_KEY的item的時候或者一些屬於不同的objectid的item的時候終止。
xattr關鍵字具有如下的形式:
---------------------------------------------------------------------
key.objectid = objectid of the inode owning the xattr
key.type = BTRFS_XATTR_ITEM_KEY
key.offset = hash of xattr name
---------------------------------------------------------------------
---------------------------------------------------------------------
int btrfs_delete_xattrs(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct inode *inode)
{
struct btrfs_path *path;
struct btrfs_key key, found_key;
struct extent_buffer *leaf;
int ret;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
path->reada = -1;
---------------------------------------------------------------------
path->reada = -1告訴預讀代碼你正在向後查找樹。
---------------------------------------------------------------------
key.objectid = inode->i_ino;
btrfs_set_key_type(&key, BTRFS_XATTR_ITEM_KEY);
key.offset = (u64)-1;
while(1) {
/* look for our next xattr */
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
---------------------------------------------------------------------
btrfs_search_slot()的ins_len爲-1,由於我們在刪除一個item。COW是1是因爲樹將會被修改。我們希望搜索的返回值爲1,並且在搜索之後,path指向樹中適於出入關鍵字的位置。如果我們從那個位置向前一個slot,我們將會找到這個inode的上一個xattr item或一個具有完全不同的類型或objectid的item。
---------------------------------------------------------------------
if (ret < 0)
goto out;
BUG_ON(ret == 0);
if (path->slots[0] == 0)
break;
path->slots[0]--;
leaf = path->nodes[0];
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
if (found_key.objectid != key.objectid)
break;
if (btrfs_key_type(&found_key) != BTRFS_XATTR_ITEM_KEY)
break;
ret = btrfs_del_item(trans, root, path);
BUG_ON(ret);
btrfs_release_path(root, path);
---------------------------------------------------------------------
在確認了item具有我們查找的類型和objectid後,調用btrfs_del_item()來講那個item從btree中刪除。btrfs_release_path()減少path中所有的extent_buffers的引用計數,以使btrfs_search_slot()可以被再次調用。while循環終止後的最後的步驟便是釋放path和返回了。 }
---------------------------------------------------------------------
ret = 0;
out:
btrfs_free_path(path);
return ret;
}
---------------------------------------------------------------------
Retrieved from "https://btrfs.wiki.kernel.org/index.php/Code_documentation"