# 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, &sector); //* 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, &sector); 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>