日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析

發布時間:2023/12/8 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

F2FS源碼分析系列文章

主目錄
一、文件系統布局以及元數據結構
二、文件數據的存儲以及讀寫
  • F2FS文件數據組織方式
  • 一般文件寫流程
  • 一般文件讀流程
  • 目錄文件讀流程(未完成)
  • 目錄文件寫流程(未完成)
  • 三、文件與目錄的創建以及刪除(未完成)
    四、垃圾回收機制
    五、數據恢復機制
    六、重要數據結構或者函數的分析

    F2FS的寫流程

    寫流程介紹

    F2FS的寫流程主要包含了以下幾個子流程:

  • 調用vfs_write函數
  • 調用f2fs_file_write_iter函數: 初始化f2fs_node的信息
  • 調用f2fs_write_begin函數: 創建page cache,并填充數據
  • 寫入到page cache: 等待系統觸發writeback回寫到磁盤
  • 調用f2fs_write_end函數: 將page設置為最新狀態
  • 調用f2fs_write_data_pages函數: 系統writeback或者fsync觸發的時候執行這個函數寫入到磁盤
  • 第一步的vfs_write函數是VFS層面的流程,下面僅針對涉及F2FS的寫流程,且經過簡化的主要流程進行分析。

    f2fs_file_write_iter函數

    這個函數的主要作用是在數據寫入文件之前進行預處理,核心流程就是將該文件對應f2fs_inode或者direct_node對應寫入位置的i_addr或者addr的值進行初始化。例如用戶需要在第4個page的位置寫入數據,那么f2fs_file_write_iter函數會首先找到該文件對應的f2fs_inode,然后找到第4個page對應的數據塊地址記錄,即f2fs_inode->i_addr[3]。如果該位置的值是NULL_ADDR則表示當前是添加寫(Append Write),因此將值初始化為NEW_ADDR;如果是該位置的值是一個具體的block號,那么表示為覆蓋寫(Overwrite),不需要做處理。

    static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) {struct file *file = iocb->ki_filp;struct inode *inode = file_inode(file);ssize_t ret;...err = f2fs_preallocate_blocks(iocb, from); // 進行預處理...ret = __generic_file_write_iter(iocb, from); // 預處理完成后繼續執行下一步寫流程...return ret; }

    下面繼續分析f2fs_preallocate_blocks:

    int f2fs_preallocate_blocks(struct kiocb *iocb, struct iov_iter *from) {struct inode *inode = file_inode(iocb->ki_filp); // 獲取inodestruct f2fs_map_blocks map;map.m_lblk = F2FS_BLK_ALIGN(iocb->ki_pos); // 根據文件指針偏移計算需要從第幾個block開始寫入map.m_len = F2FS_BYTES_TO_BLK(iocb->ki_pos + iov_iter_count(from)); // 計算要寫入block的個數// 初始化一些信息map.m_next_pgofs = NULL;map.m_next_extent = NULL;map.m_seg_type = NO_CHECK_TYPE;flag = F2FS_GET_BLOCK_PRE_AIO;map_blocks:err = f2fs_map_blocks(inode, &map, 1, flag); // 進行初始化return err; }

    f2fs_map_blocks函數的作用非常廣泛,主要作用是通過邏輯地址(文件偏移指針)找到對應的物理地址(block號)。因此在讀寫流程中都有作用。在寫流程中,該函數的主要作用是初始化地址信息:

    int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map,int create, int flag) {unsigned int maxblocks = map->m_len;struct f2fs_sb_info *sbi = F2FS_I_SB(inode);int mode = create ? ALLOC_NODE : LOOKUP_NODE;map->m_len = 0;map->m_flags = 0;pgofs = (pgoff_t)map->m_lblk; // 獲得文件訪問偏移量end = pgofs + maxblocks; // 獲得需要讀取的block的長度next_dnode:set_new_dnode(&dn, inode, NULL, NULL, 0); // 初始化dnode,dnode的作用是根據邏輯地址找到物理地址// 根據inode找到對應的f2fs_inode或者direct_node結構,然后通過pgofs(文件頁偏移)獲得物理地址,記錄在dn中err = f2fs_get_dnode_of_data(&dn, pgofs, mode); start_pgofs = pgofs;prealloc = 0;last_ofs_in_node = ofs_in_node = dn.ofs_in_node;end_offset = ADDRS_PER_PAGE(dn.node_page, inode);next_block:// 根據dn獲得物理地址,ofs_in_node表示這個物理地址位于當前node的第幾個數據塊// 如 f2fs_inode->i_addr[3],那么dn.ofs_in_node=3blkaddr = datablock_addr(dn.inode, dn.node_page, dn.ofs_in_node); ...if (!is_valid_blkaddr(blkaddr)) { // is_valid_blkaddr函數用于判斷是否存在舊數據// 如果不存在舊數據if (create) {if (flag == F2FS_GET_BLOCK_PRE_AIO) {if (blkaddr == NULL_ADDR) {prealloc++; // 記錄有多少個添加寫的blocklast_ofs_in_node = dn.ofs_in_node;}}map->m_flags |= F2FS_MAP_NEW; // F2FS_MAP_NEW表示正在處理一個從未使用的數據blkaddr = dn.data_blkaddr; // 記錄當前的物理地址}}...// 記錄處理了多少個blockdn.ofs_in_node++; pgofs++;...// 這里表示已經處理到最后一個block了if (flag == F2FS_GET_BLOCK_PRE_AIO &&(pgofs == end || dn.ofs_in_node == end_offset)) {dn.ofs_in_node = ofs_in_node; // 回到第一個blockerr = f2fs_reserve_new_blocks(&dn, prealloc); // 通過這個函數將其地址設置為NEW_ADDRmap->m_len += dn.ofs_in_node - ofs_in_node;dn.ofs_in_node = end_offset;}...if (pgofs >= end)goto sync_out; // 表示已經全部處理完,可以退出這個函數了else if (dn.ofs_in_node < end_offset)goto next_block; // 每執行上面的流程就處理一個block,如果沒有處理所有用戶寫入的block,那么回去繼續處理... sync_out:... out:return err; }

    然后分析f2fs_reserve_new_blocks:

    int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count) {struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);int err;...for (; count > 0; dn->ofs_in_node++) {block_t blkaddr = datablock_addr(dn->inode,dn->node_page, dn->ofs_in_node);if (blkaddr == NULL_ADDR) { // 首先判斷是不是NULL_ADDR,如果是則初始化為NEW_ADDRdn->data_blkaddr = NEW_ADDR;__set_data_blkaddr(dn);count--;}}...return 0; }

    f2fs_write_begin和f2fs_write_end函數

    VFS中write_begin和write_end函數分別是數據寫入page cache前以及寫入后的處理。寫入page cache后,系統會維護一段時間,直到滿足一定條件后(如fsync和writeback會寫),VFS會調用writepages函數,將這些緩存在內存中的page一次性寫入到磁盤中。write_begin和write_end函數的調用可以參考VFS的generic_perform_write函數,

    ssize_t generic_perform_write(struct file *file,struct iov_iter *i, loff_t pos) {struct address_space *mapping = file->f_mapping;const struct address_space_operations *a_ops = mapping->a_ops;long status = 0;ssize_t written = 0;unsigned int flags = 0;do {struct page *page;unsigned long offset;unsigned long bytes;size_t copied;void *fsdata;offset = (pos & (PAGE_SIZE - 1)); // 計算文件偏移,按page計算bytes = min_t(unsigned long, PAGE_SIZE - offset, iov_iter_count(i)); // 計算需要寫多少個字節 again:status = a_ops->write_begin(file, mapping, pos, bytes, flags, &page, &fsdata); // 調用write_begin,對page進行初始化copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes); // 將處理后的數據拷貝到page當中flush_dcache_page(page); // 將包含用戶數據的page加入到page cache中,等待系統觸發writeback的時候回寫status = a_ops->write_end(file, mapping, pos, bytes, copied, page, fsdata); // 調用write_end函數進行后續處理copied = status;iov_iter_advance(i, copied);pos += copied;written += copied;balance_dirty_pages_ratelimited(mapping);} while (iov_iter_count(i)); // 直到處理完所有的數據return written ? written : status; }

    然后分析VFS的write_begin和write_end對應的功能,write_begin在F2FS中對應的是f2fs_write_begin,它的作用是將根據用戶需要寫入的數據類型,對page進行初始化,如下所示:

    static int f2fs_write_begin(struct file *file, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata) {struct inode *inode = mapping->host;struct f2fs_sb_info *sbi = F2FS_I_SB(inode);struct page *page = NULL;pgoff_t index = ((unsigned long long) pos) >> PAGE_SHIFT;bool need_balance = false, drop_atomic = false;block_t blkaddr = NULL_ADDR;int err = 0;repeat:page = f2fs_pagecache_get_page(mapping, index,FGP_LOCK | FGP_WRITE | FGP_CREAT, GFP_NOFS); // 第一步創建或者獲取page cache*pagep = page;err = prepare_write_begin(sbi, page, pos, len,&blkaddr, &need_balance); // 第二步根據頁偏移信息獲取到對應的物理地址blkaddr// 第三步,根據寫類型對新創建的page進行初始化處理if (blkaddr == NEW_ADDR) { //如果是添加寫,則將該page直接使用0填充zero_user_segment(page, 0, PAGE_SIZE);SetPageUptodate(page);} else { //如果是覆蓋寫,則將該page直接使用0填充err = f2fs_submit_page_read(inode, page, blkaddr); // 從磁盤中將舊數據讀取出來lock_page(page);if (unlikely(page->mapping != mapping)) {f2fs_put_page(page, 1);goto repeat;}if (unlikely(!PageUptodate(page))) {err = -EIO;goto fail;}}return 0; }

    通過flush_dcache_page函數將用戶數據寫入到page cache之后,進行write_end處理,在F2FS中它對應的是f2fs_write_end函數,它的作用是,如下所述:

    static int f2fs_write_end(struct file *file,struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata) {struct inode *inode = page->mapping->host;if (!PageUptodate(page)) { // 判斷是否已經將page cache在寫入是否到達了最新的狀態if (unlikely(copied != len))copied = 0;elseSetPageUptodate(page); // 如果不是就處理后設置為最新}if (!copied)goto unlock_out;set_page_dirty(page); // 將page設置為dirty,就會加入到inode->mapping的radix tree中,等待系統回寫if (pos + copied > i_size_read(inode))f2fs_i_size_write(inode, pos + copied); // 更新文件尺寸 unlock_out:f2fs_put_page(page, 1);f2fs_update_time(F2FS_I_SB(inode), REQ_TIME); // 更新文件修改日期return copied; }

    f2fs_write_data_pages函數

    如上一節所述,系統會將用戶寫入的數據先寫入到page cache,然后等待時機回寫到磁盤中。page cache的回寫是通過f2fs_write_data_pages函數進行。系統會將page cache中dirty的pages加入到一個list當中,然后傳入到` f2fs_write_data_pages進行處理。針對F2FS文件系統,它包含如下步驟:

  • f2fs_write_data_pages&__f2fs_write_data_pages函數: 做一些不那么重要的預處理
  • f2fs_write_cache_pages函數: 從inode->mapping的radix tree中取出page
  • __write_data_page函數: 判斷文件類型(內聯文件,目錄文件,普通文件)進行不同的寫入
  • f2fs_do_write_data_page: 根據F2FS的狀態選擇進行就地回寫(在原物理地址更新)還是異地回寫(在其他物理地址更新)
  • f2fs_outplace_write_data: 執行回寫,更新f2fs_inode的狀態
  • do_write_page: 從CURSEG分配物理地址,然后寫入到磁盤
    下面各自進行分析。
  • f2fs_write_data_pages&__f2fs_write_data_pages函數

    這兩個函數只是包含了一些不太重要的預處理

    static int f2fs_write_data_pages(struct address_space *mapping,struct writeback_control *wbc) {struct inode *inode = mapping->host;return __f2fs_write_data_pages(mapping, wbc,F2FS_I(inode)->cp_task == current ?FS_CP_DATA_IO : FS_DATA_IO); // 這個函數可以知道當前是普通的寫入,還是Checkpoint數據的寫入 }static int __f2fs_write_data_pages(struct address_space *mapping,struct writeback_control *wbc,enum iostat_type io_type) {struct inode *inode = mapping->host;struct f2fs_sb_info *sbi = F2FS_I_SB(inode);struct blk_plug plug;int ret;blk_start_plug(&plug);ret = f2fs_write_cache_pages(mapping, wbc, io_type); // 取出需要回寫的page,然后寫入blk_finish_plug(&plug);f2fs_remove_dirty_inode(inode); // 寫入后將inode從dirty標志清除,即不需要再回寫return ret; skip_write:wbc->pages_skipped += get_dirty_pages(inode);trace_f2fs_writepages(mapping->host, wbc, DATA);return 0; }

    f2fs_write_cache_pages函數

    這個函數的主要作用是從inode對應的mapping(radix tree的root)中,取出所有需要回寫的page,然后通過一個循環,逐個寫入到磁盤。

    static int f2fs_write_cache_pages(struct address_space *mapping,struct writeback_control *wbc,enum iostat_type io_type) {struct pagevec pvec;pagevec_init(&pvec); // 這是一個用于裝載page的數組,數組大小是15個pageif (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)tag = PAGECACHE_TAG_TOWRITE; // tag是mapping給每一個pae的標志,用于標志這些page的屬性elsetag = PAGECACHE_TAG_DIRTY;retry:if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)tag_pages_for_writeback(mapping, index, end); // SYNC模式下,將所有的tag=PAGECACHE_TAG_DIRTY的page重新標志為PAGECACHE_TAG_TOWRITE,作用是SYNC模式下必須全部回寫到磁盤done_index = index;while (!done && (index <= end)) {int i;// 從mapping中取出tag類型的15個page,裝載到pvec中nr_pages = pagevec_lookup_range_tag(&pvec, mapping, &index, end, tag); // 循環將pvec中的page取出,回寫到磁盤for (i = 0; i < nr_pages; i++) {struct page *page = pvec.pages[i];bool submitted = false;ret = __write_data_page(page, &submitted, wbc, io_type); // 寫入磁盤的核心函數if (--wbc->nr_to_write <= 0 &&wbc->sync_mode == WB_SYNC_NONE) {done = 1; // 如果本次writeback的所有page寫完就退出break;}}pagevec_release(&pvec); // 釋放掉pveccond_resched();}if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))mapping->writeback_index = done_index;if (last_idx != ULONG_MAX)// page通過一些函數后,會放入到bio中,然后提交到磁盤。// f2fs的機制是不會馬上提交bio,需要等到bio包含了一定數目的page之后才會提交// 因此這個函數作用是,即使數目不夠,但是仍要強制提交bio,需要與磁盤同步f2fs_submit_merged_write_cond(F2FS_M_SB(mapping), mapping->host,0, last_idx, DATA);return ret; }

    __write_data_page函數

    這個函數的作用是判斷文件類型(目錄文件,內聯文件,普通文件)進行不同的寫入。F2FS針對普通文件,有兩種保存方式,分別是內聯方式(inline)和普通方式。內聯方式在數據的保存以及邏輯地址和物理地址的映射 這一節已做介紹。這里主要介紹普通文件的寫流程,內聯文件以后再更新。

    static int __write_data_page(struct page *page, bool *submitted,struct writeback_control *wbc,enum iostat_type io_type) {struct inode *inode = page->mapping->host;struct f2fs_sb_info *sbi = F2FS_I_SB(inode);loff_t i_size = i_size_read(inode);const pgoff_t end_index = ((unsigned long long) i_size) >> PAGE_SHIFT;// 這個數據結構在整個寫流程非常重要,記錄了寫入的信息// 關鍵變量是 fio->old_blkaddr 以及 fio->new_blkaddr記錄舊地址和新地址struct f2fs_io_info fio = { .sbi = sbi,.ino = inode->i_ino,.type = DATA,.op = REQ_OP_WRITE,.op_flags = wbc_to_write_flags(wbc),.old_blkaddr = NULL_ADDR,.page = page, // 即將寫入的page.encrypted_page = NULL,.submitted = false,.need_lock = LOCK_RETRY,.io_type = io_type,.io_wbc = wbc,};if (page->index < end_index)goto write;write:if (S_ISDIR(inode->i_mode)) { // 如果是目錄文件,直接寫入不需要修改err = f2fs_do_write_data_page(&fio);goto done;}err = -EAGAIN;if (f2fs_has_inline_data(inode)) { // 內聯文件使用內聯的寫入方式err = f2fs_write_inline_data(inode, page);if (!err)goto out;}if (err == -EAGAIN) { // 普通文件則使用普通的方式err = f2fs_do_write_data_page(&fio);}done:if (err && err != -ENOENT)goto redirty_out;out:inode_dec_dirty_pages(inode); // 每寫入一個page,就清除了inode一個dirty pages,因此數目減去1if (err)ClearPageUptodate(page);unlock_page(page);if (submitted)*submitted = fio.submitted;return 0;redirty_out:redirty_page_for_writepage(wbc, page);if (!err || wbc->for_reclaim)return AOP_WRITEPAGE_ACTIVATE;unlock_page(page);return err; }

    f2fs_do_write_data_page函數

    這個函數的作用是根據系統的狀態選擇就地更新數據(inplace update)還是異地更新數據(outplace update)。一般情況下,系統只會在磁盤空間比較滿的時候選擇就地更新策略,避免觸發過多的gc影響性能。因此,這里主要介紹異地更新的寫流程:

    int f2fs_do_write_data_page(struct f2fs_io_info *fio) // 前面提到fio是寫流程最重要的數據結構 {struct page *page = fio->page;struct inode *inode = page->mapping->host;struct dnode_of_data dn;struct extent_info ei = {0,0,0};bool ipu_force = false;int err = 0;set_new_dnode(&dn, inode, NULL, NULL, 0); // 初始化dnodeerr = f2fs_get_dnode_of_data(&dn, page->index, LOOKUP_NODE); // 根據文件偏移page->index獲取物理地址fio->old_blkaddr = dn.data_blkaddr; // 將舊的物理地址賦值給fio->old_blkaddrif (fio->old_blkaddr == NULL_ADDR) { // 前面提及到f2fs_file_write_iter已經將物理地址設置為NEW_ADDR或者具體的block號,因此這里表示在寫入磁盤之前,用戶又將這部分數據刪除了,所以沒必要寫入了ClearPageUptodate(page);goto out_writepage;} got_it:if (ipu_force || (is_valid_blkaddr(fio->old_blkaddr) &&need_inplace_update(fio))) { // 判斷是否需要就地更新err = encrypt_one_page(fio);if (err)goto out_writepage;set_page_writeback(page);ClearPageError(page);f2fs_put_dnode(&dn);if (fio->need_lock == LOCK_REQ)f2fs_unlock_op(fio->sbi);err = f2fs_inplace_write_data(fio); // 使用就地更新的方式寫入trace_f2fs_do_write_data_page(fio->page, IPU);set_inode_flag(inode, FI_UPDATE_WRITE);return err;}err = encrypt_one_page(fio); // 如果開啟系統加密,會將這個fio->page先加密set_page_writeback(page);ClearPageError(page);f2fs_outplace_write_data(&dn, fio); // 執行異地更新函數set_inode_flag(inode, FI_APPEND_WRITE);if (page->index == 0)set_inode_flag(inode, FI_FIRST_BLOCK_WRITTEN); out_writepage:f2fs_put_dnode(&dn); out:if (fio->need_lock == LOCK_REQ)f2fs_unlock_op(fio->sbi);return err; }

    f2fs_outplace_write_data函數

    這個函數主要用作異地更新,所謂異地更新即不在原先的物理地址更新數據,因此包含了如下四個步驟:

  • 分配一個新的物理地址
  • 將數據寫入新的物理地址
  • 將舊的物理地址無效掉,然后等GC回收
  • 更新邏輯地址和物理地址的映射關系
  • 本函數即完成以上四個步驟:

    void f2fs_outplace_write_data(struct dnode_of_data *dn,struct f2fs_io_info *fio) {struct f2fs_sb_info *sbi = fio->sbi;struct f2fs_summary sum;struct node_info ni;f2fs_get_node_info(sbi, dn->nid, &ni);set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);do_write_page(&sum, fio); // 這里完成第1,2,3步驟f2fs_update_data_blkaddr(dn, fio->new_blkaddr); // 這里完成第四個步驟,重新建立映射 }

    上面多次提及到struct dnode_of_data dn的作用是根據文件inode,找到f2fs_inode或者direct_node,然后再通過文件偏移得到物理地址,因此f2fs_update_data_blkaddr也是通過dnode_of_data將新的物理地址更新到f2fs_inode或者direct_node對應的位置中。

    void f2fs_update_data_blkaddr(struct dnode_of_data *dn, block_t blkaddr) {dn->data_blkaddr = blkaddr; // 獲得新的物理地址f2fs_set_data_blkaddr(dn); // 更新地址到f2fs_inode或者direct_nodef2fs_update_extent_cache(dn); // 更新cache }void f2fs_set_data_blkaddr(struct dnode_of_data *dn) {f2fs_wait_on_page_writeback(dn->node_page, NODE, true); // 因為要更新node,所以要保證當前的node是最新狀態__set_data_blkaddr(dn);if (set_page_dirty(dn->node_page)) // 設置dirty,因為更新后的地址要回寫到磁盤記錄dn->node_changed = true; }static void __set_data_blkaddr(struct dnode_of_data *dn) {struct f2fs_node *rn = F2FS_NODE(dn->node_page); // 根據node page轉換到對應的f2fs_node__le32 *addr_array;int base = 0;addr_array = blkaddr_in_node(rn); // 這個用于獲得f2fs_inode->i_addr地址或者direct_node->addr地址addr_array[base + dn->ofs_in_node] = cpu_to_le32(dn->data_blkaddr); // 根據偏移賦值更新 }static inline __le32 *blkaddr_in_node(struct f2fs_node *node) {// RAW_IS_INODE判斷當前node是屬于f2fs_inode還是f2fs_node,然后返回物理地址數組指針return RAW_IS_INODE(node) ? node->i.i_addr : node->dn.addr; }

    do_write_page函數

    上一節提及到異地更新的1,2,3步驟都是在這里完成,分別是f2fs_allocate_data_block函數完成新物理地址的分配,以及舊物理地址的回收; f2fs_submit_page_write函數完成最后一步,將數據提交到磁盤。下面進行分析:

    static void do_write_page(struct f2fs_summary *sum, struct f2fs_io_info *fio) {int type = __get_segment_type(fio); // 獲取數據類型,這個類型指HOT/WARM/COLD X NODE/DATA的六種類型f2fs_allocate_data_block(fio->sbi, fio->page, fio->old_blkaddr,&fio->new_blkaddr, sum, type, fio, true); // 完成異地更新的1,2步f2fs_submit_page_write(fio); //完成異地更新的第3步}

    f2fs_allocate_data_block函數首先會根據type獲得CURSEG(定義可以參考Active Segment)。然后在CURSEG分配一個新的物理塊,然后將舊的物理塊無效掉。

    void f2fs_allocate_data_block(struct f2fs_sb_info *sbi, struct page *page,block_t old_blkaddr, block_t *new_blkaddr,struct f2fs_summary *sum, int type,struct f2fs_io_info *fio, bool add_list) {struct sit_info *sit_i = SIT_I(sbi);struct curseg_info *curseg = CURSEG_I(sbi, type);*new_blkaddr = NEXT_FREE_BLKADDR(sbi, curseg); // 獲取新的物理地址__add_sum_entry(sbi, type, sum); // 將當前summary更新到CURSEG中__refresh_next_blkoff(sbi, curseg); // 更新下一次可以用的物理地址// 下面更新主要是更新SIT區域的segment信息// 根據new_blkaddr找到對應的sit_entry,然后更新狀態為valid(值為1),表示被用戶使用,不可被其他人所使用update_sit_entry(sbi, *new_blkaddr, 1);// 根據old_blkaddr找到對應的sit_entry,然后更新狀態為invalid(值為-1),表示被覆蓋了,等待GC回收后重新投入使用if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)update_sit_entry(sbi, old_blkaddr, -1);// 如果當前segment沒有空間進行下一次分配了,就分配一個新的segment給CURSEGif (!__has_curseg_space(sbi, type))sit_i->s_ops->allocate_segment(sbi, type, false);// 將segment設置為臟,等CP寫回磁盤locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr));locate_dirty_segment(sbi, GET_SEGNO(sbi, *new_blkaddr));}

    f2fs_submit_page_write完成最后的提交到磁盤的任務,具體步驟是先創建一個bio,然后將page加入到bio中,如果bio滿了就提交到磁盤。

    void f2fs_submit_page_write(struct f2fs_io_info *fio) {struct f2fs_sb_info *sbi = fio->sbi;enum page_type btype = PAGE_TYPE_OF_BIO(fio->type);struct f2fs_bio_info *io = sbi->write_io[btype] + fio->temp; // 這個是F2FS用于臨時存放bio的變量struct page *bio_page;down_write(&io->io_rwsem); next:// 第一步根據是否有加密,將bio_page設置為對應的pageif (fio->encrypted_page)bio_page = fio->encrypted_page;elsebio_page = fio->page;fio->submitted = true;alloc_new:// 如果bio是null,就創建一個新的bioif (io->bio == NULL) {io->bio = __bio_alloc(sbi, fio->new_blkaddr, fio->io_wbc,BIO_MAX_PAGES, false,fio->type, fio->temp); // BIO_MAX_PAGES一般等于256io->fio = *fio;}// 將page加入到bio中,如果 < PAGE_SIZE 表示bio已經滿了,因此就先將這個bio提交,然后重新分配一個新的bioif (bio_add_page(io->bio, bio_page, PAGE_SIZE, 0) < PAGE_SIZE) {__submit_merged_bio(io); // 提交bio,最終會執行submit_bio函數goto alloc_new;} out:up_write(&io->io_rwsem); }

    需要注意的是,在這個函數,當bio還沒有填滿page的時候是不會被提交到磁盤的,這是因為F2FS通過增大bio的size提高了寫性能。因此,在用戶fsync或者系統writeback的時候,為了保證這些page都可以刷寫到磁盤,會如f2fs_write_cache_pages函數所介紹一樣,通過f2fs_submit_merged_write_cond函數或者其他函數強行提交這個page未滿的bio。

    總結

    以上是生活随笔為你收集整理的F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。