Btrfs Code documentation

Kernel and Utilities

盡最大的可能在內核和應用程序之間共享代碼。他們代碼主要的不同在於啓動和跟蹤IO的代碼。Btrfs內的任何一個項目都應該試着來保持內核和應用程序的更新,並且保持他們之間的代碼通用。

對於Btrfs理解的重要部分便是理解keys和items是如何交互的,和各個類型的item的數據是如何編排格式的。Btrfs-debug-tree命令可以被用來打印ascii格式的btree結構,如果想要查看數據是如何在磁盤上排列的,這可能會非常有幫助。

Btrfs Terms

  • Btree

所有的元數據都被存放在磁盤上的一個btree中。Btrees存儲key/item對。儘管使用了相同的代碼來實現所有的btrees,但仍有一些不同種類的btree。可以參考 Btrees

  • Key

一個用來爲一棵Btree的item提供標識和分類的固定大小的元組。key被分成3各部分:objectid, type, 和 offsettype字段顯示另外兩個字段應該被使用的方式和在item中找到的信息類型。可以參考Btree Keys

  • Item

存儲在btree葉子中的可變大小的結構。根據key類型的不同,item含有不同類型的數據。可以參考Btree Items

  • Subvolume

一個含有文件和目錄的命名樹。每一個快照是一個subvolume,但並不是每一個subvolume都是一個快照。

  • Snapshot

通過引用另一個subvolume的根來創建一個subvolume。快照是可寫的,但是對於快照的任何修改都不會出現在它的父subvolume中。

  • Extent buffer

一個允許訪問大於一個頁大小的btree塊的抽象。

Main Source Files

  • 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"

Categories: Code | Documentation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章