# Flash-friendly file system (F2Fs) - (1)
Flash-friendly file system 是專門為 NAND flash 設計的檔案系統 ,F2FS 並不直接面向裸 NAND 閃存設計,而是和其他通用 file system 一樣基於 block device layer interface 實作。
## bio
bio 是在整個 block layer 的最小單位,不可分割。
bio 的組成它代表來自 block device 的讀取和寫入請求, 以及其他一些控制請求。這些請求從 block device發出, 經過 gendisk 再到裝置驅動。
<img src =https://i.imgur.com/WyrQaWD.png width = 400 align = "center">
---
### **bio 的组成** :
```
struct bio {
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev;
unsigned int bi_flags; /* status, command, etc */
struct bvec_iter bi_iter;
...
atomic_t __bi_cnt; /* pin count */
struct bio_vec *bi_io_vec; /* the actual vec list */
struct bio_set *bi_pool;
/*
* We can inline a number of vecs at the end of the bio, to avoid
* double allocations for a small number of bio_vecs. This member
* MUST obviously be kept at the very end of the bio.
*/
...
struct bio_vec bi_inline_vecs[0];
};
```
### 最小資料單位 **bio_vec**
bio_vec 就是一個 bio 的資料容器,專門用來儲存 bio 的資料,當然他是這個 bio 大集體的一個最小項。
bio_vec 則是組成 bio 數據的最小單位,他包含了資料所在的 page ,這塊資料所在的頁內偏移以及長度,通過這些資訊就可以很清晰的描述資料具體位於什麼位置,通過對這些資料的整合,可以將他們新增到 SGL(雜湊表) 中直接發送給後端硬體裝置。
```
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
```
---
### request & bio 的關係
request 可以由一個或多個 bio 組成。
```
struct request {
struct request_queue *q;
struct blk_mq_ctx *mq_ctx;
struct blk_mq_hw_ctx *mq_hctx;
...
struct bio *bio;
struct bio *biotail ; //* tail of bio list
union {
struct list_head queuelist;
struct request *rq_next;
};
...
};
```
<img src =https://i.imgur.com/ElWCbML.png width = 500 align = "center">
## f2fs/data.c source trace
### static inline void __submit_bio(struct f2fs_sb_info *sbi,...)
#### 提交 request 到 block device :
* 將提交的 bio 提交至 block device ,它會檢查輸入的 bio 是讀操作還是寫操作,然後適當地操作它。如果輸入的 bio 是寫操作,它還會檢查是否需要插入虛擬頁到 bio,以確保 bio 的大小滿足硬體要求。
```
static inline void __submit_bio(struct f2fs_sb_info *sbi,
struct bio *bio, enum page_type type)
{
//* @param sbi : 用來保存 F2FS 檔案系統特定的資訊。它包含了 F2FS 檔案系統的 metadata,比如 block size、super block size 的位置、file sysyem 的版本、check point的位置等。這個結構體的資料會被用來操作 F2FS 檔案系統。
//* @param bio : 表示 block I/O request.
//* @param type :表示該頁是屬於哪種類型的 page.
if (!is_read_io(bio_op(bio))) { // * 判斷指定的 bio 是否是讀取
unsigned int start;
if (type != DATA && type != NODE)
goto submit_io;
if (f2fs_lfs_mode(sbi) && current->plug) // *檢查 F2FS 是否在進行 Large File System 模式
blk_finish_plug(current->plug);
if (!F2FS_IO_ALIGNED(sbi)) // *檢查檔案系統的 I/O 操作是否已對齊。
goto submit_io;
start = bio->bi_iter.bi_size >> F2FS_BLKSIZE_BITS; // * start 是把 bio 中的 bi_size 是 F2FS_BLKSIZE_BITS 運算(除)得到的。
// * 經過這一步,start 的值就是 bio 的大小除以 F2FS 的 block 大小。
start %= F2FS_IO_SIZE(sbi);
if (start == 0)
goto submit_io;
// *檢查 bio 的長度是否滿足 F2FS 的 IO 寬度,如果不滿足則在 bio 的末尾填充 dummy page 以增加 bio 的長度,使其滿足 F2FS 的 IO 寬度。
// ! 這樣做的原因是 F2FS 是以一定的 IO 寬度進行讀寫的,因此若 bio 的長度不足 F2FS 的 IO 寬度,就需要在末尾填充 dummy page 來補足。
/* fill dummy pages */
for (; start < F2FS_IO_SIZE(sbi); start++) {
struct page *page =
mempool_alloc(sbi->write_io_dummy, GFP_NOIO | __GFP_NOFAIL);
f2fs_bug_on(sbi, !page);
lock_page(page);
zero_user_segment(page, 0, PAGE_SIZE);
set_page_private_dummy(page);
if (bio_add_page(bio, page, PAGE_SIZE, 0) < PAGE_SIZE)
f2fs_bug_on(sbi, 1);
}
//*--------------------------------
/*
* In the NODE case, we lose next block address chain. So, we
* need to do checkpoint in f2fs_sync_file.
*/
if (type == NODE)
set_sbi_flag(sbi, SBI_NEED_CP);
}
submit_io:
// *將 bio 送出。它會檢查 bio 的讀寫狀態,設定相應的 trace,並呼叫 submit_bio() 將 bio 送出。
//* 如果 bio 是寫入操作,還會檢查 bio 是否需要填滿,並處理操作。
if (is_read_io(bio_op(bio)))
trace_f2fs_submit_read_bio(sbi->sb, type, bio);
else
trace_f2fs_submit_write_bio(sbi->sb, type, bio);
iostat_update_submit_ctx(bio, type);
/**
* @submit_bio:負責將指定的 bio 物件提交到硬體,
* -> 以便進行 I/O 操作。這些 I/O 操作可以是讀寫設備上的資料,或是查詢設備狀態。
* 該函式會根據提交的 bio 物件中指定的操作類型(如讀或寫),將它們分配到適當的 request queue 中。->
* -> 這些 request queue 會通過 driver 來實現,並在硬體完成指定的 I/O 操作後,將結果傳回給提交 request 的應用程式。
*/
submit_bio(bio);
}
```
---
### static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages)
#### 建立並初始化一個 bio 物件,並為它分配 page 。多半用在發送讀/寫指令的時候
```
static struct bio *__bio_alloc(struct f2fs_io_info *fio, int npages) // TODO:
{
//* @param fio :包含有關這個 I/O request 的資訊
//* @param npages :bio :所需的分配 page 數量
struct f2fs_sb_info *sbi = fio->sbi;
struct block_device *bdev; //* 指向要寫入資料的 device
sector_t sector; //* 是要寫入資料的起始 sector
struct bio *bio;
//* 尋找一個能夠將資料寫入的 device,並且回傳該 device 與該寫入的位置。
bdev = f2fs_target_device(sbi, fio->new_blkaddr, §or);
//* f2fs_io_flags(fio) :獲取 fio 的 io flags。
//*io flags 是在 file system 操作中用來標示資料處理方式的標記。
//* GFP_NOIO : 指定在申請 page 時不能掛載
bio = bio_alloc_bioset(bdev, npages,fio->op | fio->op_flags | f2fs_io_flags(fio),
GFP_NOIO, &f2fs_bioset); // * 為 request device 初始化與配置 bio 的空間
bio->bi_iter.bi_sector = sector;
// *來設置 bio 的讀寫回調 function、回調 function 的參數
if (is_read_io(fio->op)) { // * 讀取設定
bio->bi_end_io = f2fs_read_end_io;
bio->bi_private = NULL;
} else { // * 寫入設定
bio->bi_end_io = f2fs_write_end_io;
bio->bi_private = sbi;
}
iostat_alloc_and_bind_ctx(sbi, bio, NULL);
if (fio->io_wbc) //* 判斷是否將資料寫回 block device .
wbc_init_bio(fio->io_wbc, bio); //* 將給定的 Bio bind 到給定的 writeback_control
//* struct writeback_control 包含了與虛擬記憶體寫回操作相關的資料和控制資訊,
//*它用於控制將頁面寫回磁碟的過程。
return bio;
}
```
---
### int f2fs_merge_page_bio(struct f2fs_io_info *fio)
#### 將一個 page 加入(合併)至 bio
```
int f2fs_merge_page_bio(struct f2fs_io_info *fio)
{
//* @param fio : 是 F2FS 中用來描述寫入/讀取資料的 struct。
struct bio *bio = *fio->bio;
struct page *page = fio->encrypted_page ?
fio->encrypted_page : fio->page;
//*檢查新的 block addr. 是否有效
if (!f2fs_is_valid_blkaddr(fio->sbi, fio->new_blkaddr,
__is_meta_io(fio) ? META_GENERIC : DATA_GENERIC)) {
//* 無效
f2fs_handle_error(fio->sbi, ERROR_INVALID_BLKADDR);
return -EFSCORRUPTED;
}
trace_f2fs_submit_page_bio(page, fio); //* 記錄訊息,可以用來追蹤某些事件的發生,例如某個程序的運作狀態
// *檢查是否能將頁面合併到目前的 bio 中,如果不能,則會提交合併後的 bio
if (bio && !page_is_mergeable(fio->sbi, bio, *fio->last_block,
fio->new_blkaddr))
f2fs_submit_merged_ipu_write(fio->sbi, &bio, NULL); //*提交合併後的 bio
alloc_new: //* 檢查是否需要申請新的 bio
if (!bio) { // 判斷是否需要創建新的 bio ,否則將該 page 合併到現有的 bio 中
bio = __bio_alloc(fio, BIO_MAX_VECS);
f2fs_set_bio_crypt_ctx(bio, fio->page->mapping->host,
fio->page->index, fio, GFP_NOIO);
add_bio_entry(fio->sbi, bio, page, fio->temp);
} else {
if (add_ipu_page(fio, &bio, page))//* 嘗試著把 page 加入已有的 bio 中。
goto alloc_new; // 如果加入失敗,則會重新分配一個 bio
}
if (fio->io_wbc) //* 判斷是否寫回
wbc_account_cgroup_owner(fio->io_wbc, page, PAGE_SIZE); //* 計算寫回 page 的資料大小
inc_page_count(fio->sbi, WB_DATA_TYPE(page));
*fio->last_block = fio->new_blkaddr;
*fio->bio = bio;
return 0;
}
```
### static bool page_is_mergeable(struct f2fs_sb_info *sbi, struct bio *bio,...)
#### 判斷指定的 bio 是否能夠與最後一個已經加入到 bio 中的 block 進行合併
```
static bool page_is_mergeable(struct f2fs_sb_info *sbi, struct bio *bio,
block_t last_blkaddr, block_t cur_blkaddr)
{
/*
* 如果滿足以下條件,則會回傳 true:
*
* bio 的大小沒有超過 sbi->max_io_bytes
* 最後一個已經加入到 bio 中的塊的 blkaddr 加 1 等於當前的 blkaddr
* (blkaddr. 的區間必為連續的)
* 最後一個已經加入到 bio 中的塊和當前塊在同一個硬碟中。
* 如果上述條件有任何一項不滿足,則函數會回傳 false。
*/
if (unlikely(sbi->max_io_bytes &&
bio->bi_iter.bi_size >= sbi->max_io_bytes))
return false;
if (last_blkaddr + 1 != cur_blkaddr)
return false;
return bio->bi_bdev == f2fs_target_device(sbi, cur_blkaddr, NULL);
}
```
### static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,...)
#### 為讀取檔案提供一個可用的 Bio。該函數會初始化一個 Bio,並對指定的 inode 進行一些必要的設定
```
static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
unsigned nr_pages, blk_opf_t op_flag,
pgoff_t first_idx, bool for_write)
{
//*@param struct inode *inode: 指向 inode 的指標,用於指定要進行讀取操作的檔案
//*@param block_t blkaddr: 檔案在檔案系統中所對應的 block addr. (即指定檔案在硬碟上的起始位置)
//*@param unsigned nr_pages: 指定要讀取的 page 數量
//*@param blk_opf_t op_flag: 用於指定讀取操作所應使用的選項,例如是否使用 O_SYNC、O_DIRECT 等選項
//* mode option of Linux I/O :
/*
O_SYNC : 表示打開的文件的所有寫操作都是同步的,這代表著每次寫操作都會等待資料寫入硬碟,直到該操作完成才回傳
O_DIRECT :表示該文件的所有寫操作都不會將資料寫入文件內存頁,這代表著資料必須直接從用戶空間寫入硬碟。
*/
//*@param pgoff_t first_idx: 指定要讀取的 page 在 inode 內的第一個索引
//*@param bool for_write: 是否為寫入操作而產生的讀取操作
//*->(若是,則表示操作系統正在將資料寫入檔案,而需要讀取設備上的資料來進行更新)
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct bio *bio;
struct bio_post_read_ctx *ctx = NULL;
unsigned int post_read_steps = 0;
sector_t sector;
struct block_device *bdev = f2fs_target_device(sbi, blkaddr, §or);
if (!bio)
// * 初始化 bio (配置空間、建立與 device 的關係)
bio = bio_alloc_bioset(bdev, bio_max_segs(nr_pages),
REQ_OP_READ | op_flag,
for_write ? GFP_NOIO : GFP_KERNEL, &f2fs_bioset);
return ERR_PTR(-ENOMEM);
bio->bi_iter.bi_sector = sector;
/*
*將指定的 inode 和首個索引設定為指定 bio 的加密 context
*(可在訪問加密檔案時使用,以便在讀取資料時能夠正確解密
*/
f2fs_set_bio_crypt_ctx(bio, inode, first_idx, NULL, GFP_NOFS);
//* 設定 i/o 結束調用時,會調用 f2fs_read_end_io ,進行讀取操作的錯誤檢查
bio->bi_end_io = f2fs_read_end_io;
//*如果指定的 inode 是一個使用檔案系統層加密的加密檔案,
//*STEP_DECRYPT 新增到 post_read_steps 中。
if (fscrypt_inode_uses_fs_layer_crypto(inode))
post_read_steps |= STEP_DECRYPT;
//* 檢查指定的 inode 是否使用了 fs-verity 功能 (檢查檔案完整性),
//* ->表示在讀取操作完成後,需要在 f2fs_read_end_io 中執行 fs-verity 驗證操作。
if (f2fs_need_verity(inode, first_idx)) /
post_read_steps |= STEP_VERITY;
/*
* STEP_DECOMPRESS is handled specially, since a compressed file might
* contain both compressed and uncompressed clusters. We'll allocate a
* bio_post_read_ctx if the file is compressed, but the caller is
* responsible for enabling STEP_DECOMPRESS if it's actually needed.
*/
/*
* 如果 post_read_steps 或者指定的 inode 對應的檔案是壓縮檔案,
* -> 則為 Bio 分配一個 bio_post_read_ctx
*/
if (post_read_steps || f2fs_compressed_file(inode)) { //
/* Due to the mempool, this never fails. */
ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
ctx->bio = bio;
ctx->sbi = sbi;
ctx->enabled_steps = post_read_steps;
ctx->fs_blkaddr = blkaddr;
bio->bi_private = ctx;
}
iostat_alloc_and_bind_ctx(sbi, bio, ctx);
}
```
### static int f2fs_submit_page_read(struct inode *inode, struct page *page,...)
#### 為讀取檔案提供一個可用的 Bio。該函數會初始化一個 Bio,並對指定的 inode 進行一些必要的設定
```
static int f2fs_submit_page_read(struct inode *inode, struct page *page,
block_t blkaddr, blk_opf_t op_flags,
bool for_write)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct bio *bio;
//* 初始化一個被用來讀取資料的 bio
bio = f2fs_grab_read_bio(inode, blkaddr, 1, op_flags,
page->index, for_write);
if (IS_ERR(bio)) //* 檢查 bio 是否正常
return PTR_ERR(bio);
/* wait for GCed page writeback via META_MAPPING */
/*
等待寫回操作完成,會暫停當前執行緒,
-> 直到指定 inode 和指定的 blkaddr 所對應的資料區塊完成寫回操作,
-> 或者操作超時為止。這通常是在讀取資料前,為了確保讀取的資料是最新的
*/
f2fs_wait_on_block_writeback(inode, blkaddr);
//* 新增頁面到 bio 中,如果新增失敗,即新增的 page size 小於一個page,
//*-> 則釋放 bio 並回傳錯誤。
if (bio_add_page(bio, page, PAGE_SIZE, 0) < PAGE_SIZE) {
bio_put(bio);
return -EFAULT;
}
ClearPageError(page); //*清除指定的 page 上的錯誤標記。
inc_page_count(sbi, F2FS_RD_DATA);
f2fs_update_iostat(sbi, NULL, FS_DATA_READ_IO, F2FS_BLKSIZE); //*更新 I/O 統計資料
__submit_bio(sbi, bio, DATA); //* 發起讀取操作
return 0;
}
```
###### tags: `SSD` `File System` `f2fs`
<style>
html, body, .ui-content {
background-color: #333;
color: #ddd;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: #ddd;
}
.markdown-body h1,
.markdown-body h2 {
border-bottom-color: #ffffff69;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #fff;
}
.markdown-body img {
background-color: transparent;
}
.ui-toc-dropdown .nav>.active:focus>a, .ui-toc-dropdown .nav>.active:hover>a, .ui-toc-dropdown .nav>.active>a {
color: white;
border-left: 2px solid white;
}
.expand-toggle:hover,
.expand-toggle:focus,
.back-to-top:hover,
.back-to-top:focus,
.go-to-bottom:hover,
.go-to-bottom:focus {
color: white;
}
.ui-toc-dropdown {
background-color: #333;
}
.ui-toc-label.btn {
background-color: #191919;
color: white;
}
.ui-toc-dropdown .nav>li>a:focus,
.ui-toc-dropdown .nav>li>a:hover {
color: white;
border-left: 1px solid white;
}
.markdown-body blockquote {
color: #bcbcbc;
}
.markdown-body table tr {
background-color: #5f5f5f;
}
.markdown-body table tr:nth-child(2n) {
background-color: #4f4f4f;
}
.markdown-body code,
.markdown-body tt {
color: #eee;
background-color: rgba(230, 230, 230, 0.36);
}
a,
.open-files-container li.selected a {
color: #5EB7E0;
}
</style>