F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析
F2FS源碼分析系列文章
主目錄
一、文件系統(tǒng)布局以及元數(shù)據(jù)結(jié)構(gòu)
二、文件數(shù)據(jù)的存儲(chǔ)以及讀寫(xiě)
三、文件與目錄的創(chuàng)建以及刪除(未完成)
四、垃圾回收機(jī)制
五、數(shù)據(jù)恢復(fù)機(jī)制
六、重要數(shù)據(jù)結(jié)構(gòu)或者函數(shù)的分析
F2FS的寫(xiě)流程
寫(xiě)流程介紹
F2FS的寫(xiě)流程主要包含了以下幾個(gè)子流程:
第一步的vfs_write函數(shù)是VFS層面的流程,下面僅針對(duì)涉及F2FS的寫(xiě)流程,且經(jīng)過(guò)簡(jiǎn)化的主要流程進(jìn)行分析。
f2fs_file_write_iter函數(shù)
這個(gè)函數(shù)的主要作用是在數(shù)據(jù)寫(xiě)入文件之前進(jìn)行預(yù)處理,核心流程就是將該文件對(duì)應(yīng)f2fs_inode或者direct_node對(duì)應(yīng)寫(xiě)入位置的i_addr或者addr的值進(jìn)行初始化。例如用戶(hù)需要在第4個(gè)page的位置寫(xiě)入數(shù)據(jù),那么f2fs_file_write_iter函數(shù)會(huì)首先找到該文件對(duì)應(yīng)的f2fs_inode,然后找到第4個(gè)page對(duì)應(yīng)的數(shù)據(jù)塊地址記錄,即f2fs_inode->i_addr[3]。如果該位置的值是NULL_ADDR則表示當(dāng)前是添加寫(xiě)(Append Write),因此將值初始化為NEW_ADDR;如果是該位置的值是一個(gè)具體的block號(hào),那么表示為覆蓋寫(xiě)(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); // 進(jìn)行預(yù)處理...ret = __generic_file_write_iter(iocb, from); // 預(yù)處理完成后繼續(xù)執(zhí)行下一步寫(xiě)流程...return ret; }下面繼續(xù)分析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); // 根據(jù)文件指針偏移計(jì)算需要從第幾個(gè)block開(kāi)始寫(xiě)入map.m_len = F2FS_BYTES_TO_BLK(iocb->ki_pos + iov_iter_count(from)); // 計(jì)算要寫(xiě)入block的個(gè)數(shù)// 初始化一些信息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); // 進(jìn)行初始化return err; }f2fs_map_blocks函數(shù)的作用非常廣泛,主要作用是通過(guò)邏輯地址(文件偏移指針)找到對(duì)應(yīng)的物理地址(block號(hào))。因此在讀寫(xiě)流程中都有作用。在寫(xiě)流程中,該函數(shù)的主要作用是初始化地址信息:
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; // 獲得文件訪(fǎng)問(wèn)偏移量end = pgofs + maxblocks; // 獲得需要讀取的block的長(zhǎng)度next_dnode:set_new_dnode(&dn, inode, NULL, NULL, 0); // 初始化dnode,dnode的作用是根據(jù)邏輯地址找到物理地址// 根據(jù)inode找到對(duì)應(yīng)的f2fs_inode或者direct_node結(jié)構(gòu),然后通過(guò)pgofs(文件頁(yè)偏移)獲得物理地址,記錄在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:// 根據(jù)dn獲得物理地址,ofs_in_node表示這個(gè)物理地址位于當(dāng)前node的第幾個(gè)數(shù)據(jù)塊// 如 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函數(shù)用于判斷是否存在舊數(shù)據(jù)// 如果不存在舊數(shù)據(jù)if (create) {if (flag == F2FS_GET_BLOCK_PRE_AIO) {if (blkaddr == NULL_ADDR) {prealloc++; // 記錄有多少個(gè)添加寫(xiě)的blocklast_ofs_in_node = dn.ofs_in_node;}}map->m_flags |= F2FS_MAP_NEW; // F2FS_MAP_NEW表示正在處理一個(gè)從未使用的數(shù)據(jù)blkaddr = dn.data_blkaddr; // 記錄當(dāng)前的物理地址}}...// 記錄處理了多少個(gè)blockdn.ofs_in_node++; pgofs++;...// 這里表示已經(jīng)處理到最后一個(gè)block了if (flag == F2FS_GET_BLOCK_PRE_AIO &&(pgofs == end || dn.ofs_in_node == end_offset)) {dn.ofs_in_node = ofs_in_node; // 回到第一個(gè)blockerr = f2fs_reserve_new_blocks(&dn, prealloc); // 通過(guò)這個(gè)函數(shù)將其地址設(shè)置為NEW_ADDRmap->m_len += dn.ofs_in_node - ofs_in_node;dn.ofs_in_node = end_offset;}...if (pgofs >= end)goto sync_out; // 表示已經(jīng)全部處理完,可以退出這個(gè)函數(shù)了else if (dn.ofs_in_node < end_offset)goto next_block; // 每執(zhí)行上面的流程就處理一個(gè)block,如果沒(méi)有處理所有用戶(hù)寫(xiě)入的block,那么回去繼續(xù)處理... 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函數(shù)
VFS中write_begin和write_end函數(shù)分別是數(shù)據(jù)寫(xiě)入page cache前以及寫(xiě)入后的處理。寫(xiě)入page cache后,系統(tǒng)會(huì)維護(hù)一段時(shí)間,直到滿(mǎn)足一定條件后(如fsync和writeback會(huì)寫(xiě)),VFS會(huì)調(diào)用writepages函數(shù),將這些緩存在內(nèi)存中的page一次性寫(xiě)入到磁盤(pán)中。write_begin和write_end函數(shù)的調(diào)用可以參考VFS的generic_perform_write函數(shù),
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)); // 計(jì)算文件偏移,按page計(jì)算bytes = min_t(unsigned long, PAGE_SIZE - offset, iov_iter_count(i)); // 計(jì)算需要寫(xiě)多少個(gè)字節(jié) again:status = a_ops->write_begin(file, mapping, pos, bytes, flags, &page, &fsdata); // 調(diào)用write_begin,對(duì)page進(jìn)行初始化copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes); // 將處理后的數(shù)據(jù)拷貝到page當(dāng)中flush_dcache_page(page); // 將包含用戶(hù)數(shù)據(jù)的page加入到page cache中,等待系統(tǒng)觸發(fā)writeback的時(shí)候回寫(xiě)status = a_ops->write_end(file, mapping, pos, bytes, copied, page, fsdata); // 調(diào)用write_end函數(shù)進(jìn)行后續(xù)處理copied = status;iov_iter_advance(i, copied);pos += copied;written += copied;balance_dirty_pages_ratelimited(mapping);} while (iov_iter_count(i)); // 直到處理完所有的數(shù)據(jù)return written ? written : status; }然后分析VFS的write_begin和write_end對(duì)應(yīng)的功能,write_begin在F2FS中對(duì)應(yīng)的是f2fs_write_begin,它的作用是將根據(jù)用戶(hù)需要寫(xiě)入的數(shù)據(jù)類(lèi)型,對(duì)page進(jìn)行初始化,如下所示:
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); // 第一步創(chuàng)建或者獲取page cache*pagep = page;err = prepare_write_begin(sbi, page, pos, len,&blkaddr, &need_balance); // 第二步根據(jù)頁(yè)偏移信息獲取到對(duì)應(yīng)的物理地址blkaddr// 第三步,根據(jù)寫(xiě)類(lèi)型對(duì)新創(chuàng)建的page進(jìn)行初始化處理if (blkaddr == NEW_ADDR) { //如果是添加寫(xiě),則將該page直接使用0填充zero_user_segment(page, 0, PAGE_SIZE);SetPageUptodate(page);} else { //如果是覆蓋寫(xiě),則將該page直接使用0填充err = f2fs_submit_page_read(inode, page, blkaddr); // 從磁盤(pán)中將舊數(shù)據(jù)讀取出來(lái)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; }通過(guò)flush_dcache_page函數(shù)將用戶(hù)數(shù)據(jù)寫(xiě)入到page cache之后,進(jìn)行write_end處理,在F2FS中它對(duì)應(yīng)的是f2fs_write_end函數(shù),它的作用是,如下所述:
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)) { // 判斷是否已經(jīng)將page cache在寫(xiě)入是否到達(dá)了最新的狀態(tài)if (unlikely(copied != len))copied = 0;elseSetPageUptodate(page); // 如果不是就處理后設(shè)置為最新}if (!copied)goto unlock_out;set_page_dirty(page); // 將page設(shè)置為dirty,就會(huì)加入到inode->mapping的radix tree中,等待系統(tǒng)回寫(xiě)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函數(shù)
如上一節(jié)所述,系統(tǒng)會(huì)將用戶(hù)寫(xiě)入的數(shù)據(jù)先寫(xiě)入到page cache,然后等待時(shí)機(jī)回寫(xiě)到磁盤(pán)中。page cache的回寫(xiě)是通過(guò)f2fs_write_data_pages函數(shù)進(jìn)行。系統(tǒng)會(huì)將page cache中dirty的pages加入到一個(gè)list當(dāng)中,然后傳入到` f2fs_write_data_pages進(jìn)行處理。針對(duì)F2FS文件系統(tǒng),它包含如下步驟:
下面各自進(jìn)行分析。
f2fs_write_data_pages&__f2fs_write_data_pages函數(shù)
這兩個(gè)函數(shù)只是包含了一些不太重要的預(yù)處理
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); // 這個(gè)函數(shù)可以知道當(dāng)前是普通的寫(xiě)入,還是Checkpoint數(shù)據(jù)的寫(xiě)入 }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); // 取出需要回寫(xiě)的page,然后寫(xiě)入blk_finish_plug(&plug);f2fs_remove_dirty_inode(inode); // 寫(xiě)入后將inode從dirty標(biāo)志清除,即不需要再回寫(xiě)return ret; skip_write:wbc->pages_skipped += get_dirty_pages(inode);trace_f2fs_writepages(mapping->host, wbc, DATA);return 0; }f2fs_write_cache_pages函數(shù)
這個(gè)函數(shù)的主要作用是從inode對(duì)應(yīng)的mapping(radix tree的root)中,取出所有需要回寫(xiě)的page,然后通過(guò)一個(gè)循環(huán),逐個(gè)寫(xiě)入到磁盤(pán)。
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); // 這是一個(gè)用于裝載page的數(shù)組,數(shù)組大小是15個(gè)pageif (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)tag = PAGECACHE_TAG_TOWRITE; // tag是mapping給每一個(gè)pae的標(biāo)志,用于標(biāo)志這些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重新標(biāo)志為PAGECACHE_TAG_TOWRITE,作用是SYNC模式下必須全部回寫(xiě)到磁盤(pán)done_index = index;while (!done && (index <= end)) {int i;// 從mapping中取出tag類(lèi)型的15個(gè)page,裝載到pvec中nr_pages = pagevec_lookup_range_tag(&pvec, mapping, &index, end, tag); // 循環(huán)將pvec中的page取出,回寫(xiě)到磁盤(pán)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); // 寫(xiě)入磁盤(pán)的核心函數(shù)if (--wbc->nr_to_write <= 0 &&wbc->sync_mode == WB_SYNC_NONE) {done = 1; // 如果本次writeback的所有page寫(xiě)完就退出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通過(guò)一些函數(shù)后,會(huì)放入到bio中,然后提交到磁盤(pán)。// f2fs的機(jī)制是不會(huì)馬上提交bio,需要等到bio包含了一定數(shù)目的page之后才會(huì)提交// 因此這個(gè)函數(shù)作用是,即使數(shù)目不夠,但是仍要強(qiáng)制提交bio,需要與磁盤(pán)同步f2fs_submit_merged_write_cond(F2FS_M_SB(mapping), mapping->host,0, last_idx, DATA);return ret; }__write_data_page函數(shù)
這個(gè)函數(shù)的作用是判斷文件類(lèi)型(目錄文件,內(nèi)聯(lián)文件,普通文件)進(jìn)行不同的寫(xiě)入。F2FS針對(duì)普通文件,有兩種保存方式,分別是內(nèi)聯(lián)方式(inline)和普通方式。內(nèi)聯(lián)方式在數(shù)據(jù)的保存以及邏輯地址和物理地址的映射 這一節(jié)已做介紹。這里主要介紹普通文件的寫(xiě)流程,內(nèi)聯(lián)文件以后再更新。
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;// 這個(gè)數(shù)據(jù)結(jié)構(gòu)在整個(gè)寫(xiě)流程非常重要,記錄了寫(xiě)入的信息// 關(guān)鍵變量是 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, // 即將寫(xiě)入的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)) { // 如果是目錄文件,直接寫(xiě)入不需要修改err = f2fs_do_write_data_page(&fio);goto done;}err = -EAGAIN;if (f2fs_has_inline_data(inode)) { // 內(nèi)聯(lián)文件使用內(nèi)聯(lián)的寫(xiě)入方式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); // 每寫(xiě)入一個(gè)page,就清除了inode一個(gè)dirty pages,因此數(shù)目減去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函數(shù)
這個(gè)函數(shù)的作用是根據(jù)系統(tǒng)的狀態(tài)選擇就地更新數(shù)據(jù)(inplace update)還是異地更新數(shù)據(jù)(outplace update)。一般情況下,系統(tǒng)只會(huì)在磁盤(pán)空間比較滿(mǎn)的時(shí)候選擇就地更新策略,避免觸發(fā)過(guò)多的gc影響性能。因此,這里主要介紹異地更新的寫(xiě)流程:
int f2fs_do_write_data_page(struct f2fs_io_info *fio) // 前面提到fio是寫(xiě)流程最重要的數(shù)據(jù)結(jié)構(gòu) {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); // 根據(jù)文件偏移page->index獲取物理地址fio->old_blkaddr = dn.data_blkaddr; // 將舊的物理地址賦值給fio->old_blkaddrif (fio->old_blkaddr == NULL_ADDR) { // 前面提及到f2fs_file_write_iter已經(jīng)將物理地址設(shè)置為NEW_ADDR或者具體的block號(hào),因此這里表示在寫(xiě)入磁盤(pán)之前,用戶(hù)又將這部分?jǐn)?shù)據(jù)刪除了,所以沒(méi)必要寫(xiě)入了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); // 使用就地更新的方式寫(xiě)入trace_f2fs_do_write_data_page(fio->page, IPU);set_inode_flag(inode, FI_UPDATE_WRITE);return err;}err = encrypt_one_page(fio); // 如果開(kāi)啟系統(tǒng)加密,會(huì)將這個(gè)fio->page先加密set_page_writeback(page);ClearPageError(page);f2fs_outplace_write_data(&dn, fio); // 執(zhí)行異地更新函數(shù)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函數(shù)
這個(gè)函數(shù)主要用作異地更新,所謂異地更新即不在原先的物理地址更新數(shù)據(jù),因此包含了如下四個(gè)步驟:
本函數(shù)即完成以上四個(gè)步驟:
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); // 這里完成第四個(gè)步驟,重新建立映射 }上面多次提及到struct dnode_of_data dn的作用是根據(jù)文件inode,找到f2fs_inode或者direct_node,然后再通過(guò)文件偏移得到物理地址,因此f2fs_update_data_blkaddr也是通過(guò)dnode_of_data將新的物理地址更新到f2fs_inode或者direct_node對(duì)應(yīng)的位置中。
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); // 因?yàn)橐耼ode,所以要保證當(dāng)前的node是最新?tīng)顟B(tài)__set_data_blkaddr(dn);if (set_page_dirty(dn->node_page)) // 設(shè)置dirty,因?yàn)楦潞蟮牡刂芬貙?xiě)到磁盤(pán)記錄dn->node_changed = true; }static void __set_data_blkaddr(struct dnode_of_data *dn) {struct f2fs_node *rn = F2FS_NODE(dn->node_page); // 根據(jù)node page轉(zhuǎn)換到對(duì)應(yīng)的f2fs_node__le32 *addr_array;int base = 0;addr_array = blkaddr_in_node(rn); // 這個(gè)用于獲得f2fs_inode->i_addr地址或者direct_node->addr地址addr_array[base + dn->ofs_in_node] = cpu_to_le32(dn->data_blkaddr); // 根據(jù)偏移賦值更新 }static inline __le32 *blkaddr_in_node(struct f2fs_node *node) {// RAW_IS_INODE判斷當(dāng)前node是屬于f2fs_inode還是f2fs_node,然后返回物理地址數(shù)組指針return RAW_IS_INODE(node) ? node->i.i_addr : node->dn.addr; }do_write_page函數(shù)
上一節(jié)提及到異地更新的1,2,3步驟都是在這里完成,分別是f2fs_allocate_data_block函數(shù)完成新物理地址的分配,以及舊物理地址的回收; f2fs_submit_page_write函數(shù)完成最后一步,將數(shù)據(jù)提交到磁盤(pán)。下面進(jìn)行分析:
static void do_write_page(struct f2fs_summary *sum, struct f2fs_io_info *fio) {int type = __get_segment_type(fio); // 獲取數(shù)據(jù)類(lèi)型,這個(gè)類(lèi)型指HOT/WARM/COLD X NODE/DATA的六種類(lèi)型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函數(shù)首先會(huì)根據(jù)type獲得CURSEG(定義可以參考Active Segment)。然后在CURSEG分配一個(gè)新的物理塊,然后將舊的物理塊無(wú)效掉。
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); // 將當(dāng)前summary更新到CURSEG中__refresh_next_blkoff(sbi, curseg); // 更新下一次可以用的物理地址// 下面更新主要是更新SIT區(qū)域的segment信息// 根據(jù)new_blkaddr找到對(duì)應(yīng)的sit_entry,然后更新?tīng)顟B(tài)為valid(值為1),表示被用戶(hù)使用,不可被其他人所使用update_sit_entry(sbi, *new_blkaddr, 1);// 根據(jù)old_blkaddr找到對(duì)應(yīng)的sit_entry,然后更新?tīng)顟B(tài)為invalid(值為-1),表示被覆蓋了,等待GC回收后重新投入使用if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)update_sit_entry(sbi, old_blkaddr, -1);// 如果當(dāng)前segment沒(méi)有空間進(jìn)行下一次分配了,就分配一個(gè)新的segment給CURSEGif (!__has_curseg_space(sbi, type))sit_i->s_ops->allocate_segment(sbi, type, false);// 將segment設(shè)置為臟,等CP寫(xiě)回磁盤(pán)locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr));locate_dirty_segment(sbi, GET_SEGNO(sbi, *new_blkaddr));}f2fs_submit_page_write完成最后的提交到磁盤(pán)的任務(wù),具體步驟是先創(chuàng)建一個(gè)bio,然后將page加入到bio中,如果bio滿(mǎn)了就提交到磁盤(pán)。
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; // 這個(gè)是F2FS用于臨時(shí)存放bio的變量struct page *bio_page;down_write(&io->io_rwsem); next:// 第一步根據(jù)是否有加密,將bio_page設(shè)置為對(duì)應(yīng)的pageif (fio->encrypted_page)bio_page = fio->encrypted_page;elsebio_page = fio->page;fio->submitted = true;alloc_new:// 如果bio是null,就創(chuàng)建一個(gè)新的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已經(jīng)滿(mǎn)了,因此就先將這個(gè)bio提交,然后重新分配一個(gè)新的bioif (bio_add_page(io->bio, bio_page, PAGE_SIZE, 0) < PAGE_SIZE) {__submit_merged_bio(io); // 提交bio,最終會(huì)執(zhí)行submit_bio函數(shù)goto alloc_new;} out:up_write(&io->io_rwsem); }需要注意的是,在這個(gè)函數(shù),當(dāng)bio還沒(méi)有填滿(mǎn)page的時(shí)候是不會(huì)被提交到磁盤(pán)的,這是因?yàn)镕2FS通過(guò)增大bio的size提高了寫(xiě)性能。因此,在用戶(hù)fsync或者系統(tǒng)writeback的時(shí)候,為了保證這些page都可以刷寫(xiě)到磁盤(pán),會(huì)如f2fs_write_cache_pages函數(shù)所介紹一樣,通過(guò)f2fs_submit_merged_write_cond函數(shù)或者其他函數(shù)強(qiáng)行提交這個(gè)page未滿(mǎn)的bio。
總結(jié)
以上是生活随笔為你收集整理的F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: WIN10 自带的无线投屏功能
- 下一篇: k3595参数_常用三极管参数指标