F2FS源码分析-2.2 [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的一般文件写流程分析相关推荐
- F2FS源码分析-2.3 [F2FS 读写部分] F2FS的一般文件读流程分析
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...
- F2FS源码分析-2.1 [F2FS 读写部分] F2FS文件数据组织方式以及物理地址的映射
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...
- F2FS源码分析-1.4 [F2FS 元数据布局部分] Segment Infomation Table-SIT结构
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...
- F2FS源码分析-1.6 [F2FS 元数据布局部分] Segment Summary Area-SSA结构
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...
- F2FS源码分析-1.3 [F2FS 元数据布局部分] Checkpoint结构
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...
- F2FS源码分析-1.2 [F2FS 元数据布局部分] Superblock结构
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...
- F2FS源码分析-6.6 [其他重要数据结构以及函数] F2FS的重命名过程-f2fs_rename函数
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 三.文件与目录的创建以及删除(未完成) 四.垃圾回收机制 五.数据恢复机制 六.重要数据结构或者函数的分析 ...
- F2FS源码分析-5.2 [数据恢复流程] 后滚恢复和Checkpoint的作用与实现
F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 三.文件与目录的创建以及删除(未完成) 四.垃圾回收机制 五.数据恢复机制 数据恢复的原理以及方式 后滚恢 ...
- F2FS源码分析系列文章目录
一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node Address Table区域( ...
最新文章
- 如何把文件隐藏在一张图片里面
- 几种开源软件名字的读音
- Android开发中反编译时出现Unsupported major.minor version 51.0错误的问题
- [工具]-脚本自动化工具:按照linux kernel标准格式化输出文件(format_file)
- 项目使用encode_Spring Cloud Security:Oauth2使用入门
- 手把手教你Chrome浏览器安装Postman(含下载云盘链接)【转载】
- 企业级精致 Blazor 套件 BootstrapBlazor 介绍
- php session存到redis,php Session存储到Redis的方法
- python 使用pexpect实现自动交互示例
- 保存查看翻译:Thrift: Scalable Cross-Language Services Implementation中文翻译(Thrift:可扩展的跨语言服务实现)...
- EditPlus使用技巧(汇总)
- 从苹果店员到机器学习工程师,高中学历澳洲小哥的自学路
- python pickle库_Python使用Pickle库实现读写序列操作示例
- android meminfo,Android中dumpsys meminfo与/proc/meminfo获取空闲内存不一致的问题
- 笔记《鸟哥的Linux私房菜》6 Linux的档案权限与目录配置
- SECS I II HSMS 和GEM初步资料总结
- 海龟python词树_python海龟画树
- m基于matlab的PCM-FM码同步和GMSK的调制和解调方法,包括多符号检测MSD和Turbo编解码
- 已知IP地址,如何计算其子网掩码,默认网关地址,网络地址等。
- 50 个杀手级人工智能项目