F2FS源码分析系列文章

主目录
一、文件系统布局以及元数据结构
二、文件数据的存储以及读写
  1. F2FS文件数据组织方式
  2. 一般文件写流程
  3. 一般文件读流程
  4. 目录文件读流程(未完成)
  5. 目录文件写流程(未完成)
三、文件与目录的创建以及删除(未完成)
四、垃圾回收机制
五、数据恢复机制
六、重要数据结构或者函数的分析

F2FS的写流程

写流程介绍

F2FS的写流程主要包含了以下几个子流程:

  1. 调用vfs_write函数
  2. 调用f2fs_file_write_iter函数: 初始化f2fs_node的信息
  3. 调用f2fs_write_begin函数: 创建page cache,并填充数据
  4. 写入到page cache: 等待系统触发writeback回写到磁盘
  5. 调用f2fs_write_end函数: 将page设置为最新状态
  6. 调用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_beginwrite_end函数分别是数据写入page cache前以及写入后的处理。写入page cache后,系统会维护一段时间,直到满足一定条件后(如fsync和writeback会写),VFS会调用writepages函数,将这些缓存在内存中的page一次性写入到磁盘中。write_beginwrite_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_beginwrite_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文件系统,它包含如下步骤:

  1. f2fs_write_data_pages&__f2fs_write_data_pages函数: 做一些不那么重要的预处理
  2. f2fs_write_cache_pages函数: 从inode->mapping的radix tree中取出page
  3. __write_data_page函数: 判断文件类型(内联文件,目录文件,普通文件)进行不同的写入
  4. f2fs_do_write_data_page: 根据F2FS的状态选择进行就地回写(在原物理地址更新)还是异地回写(在其他物理地址更新)
  5. f2fs_outplace_write_data: 执行回写,更新f2fs_inode的状态
  6. 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函数

这个函数主要用作异地更新,所谓异地更新即不在原先的物理地址更新数据,因此包含了如下四个步骤:

  1. 分配一个新的物理地址
  2. 将数据写入新的物理地址
  3. 将旧的物理地址无效掉,然后等GC回收
  4. 更新逻辑地址和物理地址的映射关系

本函数即完成以上四个步骤:

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的一般文件写流程分析相关推荐

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

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  2. F2FS源码分析-2.1 [F2FS 读写部分] F2FS文件数据组织方式以及物理地址的映射

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  3. F2FS源码分析-1.4 [F2FS 元数据布局部分] Segment Infomation Table-SIT结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  4. F2FS源码分析-1.6 [F2FS 元数据布局部分] Segment Summary Area-SSA结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  5. F2FS源码分析-1.3 [F2FS 元数据布局部分] Checkpoint结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  6. F2FS源码分析-1.2 [F2FS 元数据布局部分] Superblock结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  7. F2FS源码分析-6.6 [其他重要数据结构以及函数] F2FS的重命名过程-f2fs_rename函数

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 三.文件与目录的创建以及删除(未完成) 四.垃圾回收机制 五.数据恢复机制 六.重要数据结构或者函数的分析 ...

  8. F2FS源码分析-5.2 [数据恢复流程] 后滚恢复和Checkpoint的作用与实现

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 三.文件与目录的创建以及删除(未完成) 四.垃圾回收机制 五.数据恢复机制 数据恢复的原理以及方式 后滚恢 ...

  9. F2FS源码分析系列文章目录

    一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node Address Table区域( ...

最新文章

  1. 如何把文件隐藏在一张图片里面
  2. 几种开源软件名字的读音
  3. Android开发中反编译时出现Unsupported major.minor version 51.0错误的问题
  4. [工具]-脚本自动化工具:按照linux kernel标准格式化输出文件(format_file)
  5. 项目使用encode_Spring Cloud Security:Oauth2使用入门
  6. 手把手教你Chrome浏览器安装Postman(含下载云盘链接)【转载】
  7. 企业级精致 Blazor 套件 BootstrapBlazor 介绍
  8. php session存到redis,php Session存储到Redis的方法
  9. python 使用pexpect实现自动交互示例
  10. 保存查看翻译:Thrift: Scalable Cross-Language Services Implementation中文翻译(Thrift:Œ可扩展的跨语言服务实现)...
  11. EditPlus使用技巧(汇总)
  12. 从苹果店员到机器学习工程师,高中学历澳洲小哥的自学路
  13. python pickle库_Python使用Pickle库实现读写序列操作示例
  14. android meminfo,Android中dumpsys meminfo与/proc/meminfo获取空闲内存不一致的问题
  15. 笔记《鸟哥的Linux私房菜》6 Linux的档案权限与目录配置
  16. SECS I II HSMS 和GEM初步资料总结
  17. 海龟python词树_python海龟画树
  18. m基于matlab的PCM-FM码同步和GMSK的调制和解调方法,包括多符号检测MSD和Turbo编解码
  19. 已知IP地址,如何计算其子网掩码,默认网关地址,网络地址等。
  20. 50 个杀手级人工智能项目

热门文章

  1. 设计模式总结--李建忠
  2. 手把手教你用 Python 下载手机小视频
  3. 京东开源asyncTool之线程编排
  4. 用 Java 实现坦克大战,这个有点强了!
  5. 李刚疯狂JAVA面向对象章节
  6. 联想笔记本电脑没声音,显卡驱动正常,驱动人生教你扬声器安装程序unknown如何解决?
  7. SwitchyOmega使用
  8. SAP中利润中心清单输出请求处理实例
  9. java微秒精度怎么弄,java时间精确到微秒
  10. aliyun mysql 端口_阿里云怎么查看数据库端口怎么设置