F2FS的写流程主要包含了以下几个子流程:
第一步的vfs_write函数是VFS层面的流程,下面仅针对涉及F2FS的写流程,且经过简化的主要流程进行分析。
这个函数的主要作用是在数据写入文件之前进行预处理,核心流程就是将该文件对应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); // 获取inode
struct 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=3
blkaddr = 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++; // 记录有多少个添加写的block
last_ofs_in_node = dn.ofs_in_node;
}
}
map->m_flags |= F2FS_MAP_NEW; // F2FS_MAP_NEW表示正在处理一个从未使用的数据
blkaddr = dn.data_blkaddr; // 记录当前的物理地址
}
}
...
// 记录处理了多少个block
dn.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; // 回到第一个block
err = f2fs_reserve_new_blocks(&dn, prealloc); // 通过这个函数将其地址设置为NEW_ADDR
map->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_ADDR
dn->data_blkaddr = NEW_ADDR;
__set_data_blkaddr(dn);
count--;
}
}
...
return 0;
}
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;
else
SetPageUptodate(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;
}
如上一节所述,系统会将用户写入的数据先写入到page cache,然后等待时机回写到磁盘中。page cache的回写是通过f2fs_write_data_pages
函数进行。系统会将page cache中dirty的pages加入到一个list当中,然后传入到` f2fs_write_data_pages进行处理。针对F2FS文件系统,它包含如下步骤:
这两个函数只是包含了一些不太重要的预处理
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;
}
这个函数的主要作用是从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个page
if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
tag = PAGECACHE_TAG_TOWRITE; // tag是mapping给每一个pae的标志,用于标志这些page的属性
else
tag = 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); // 释放掉pvec
cond_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;
}
这个函数的作用是判断文件类型(目录文件,内联文件,普通文件)进行不同的写入。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,因此数目减去1
if (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;
}
这个函数的作用是根据系统的状态选择就地更新数据(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); // 初始化dnode
err = f2fs_get_dnode_of_data(&dn, page->index, LOOKUP_NODE); // 根据文件偏移page->index获取物理地址
fio->old_blkaddr = dn.data_blkaddr; // 将旧的物理地址赋值给fio->old_blkaddr
if (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;
}
这个函数主要用作异地更新,所谓异地更新即不在原先的物理地址更新数据,因此包含了如下四个步骤:
本函数即完成以上四个步骤:
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_node
f2fs_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;
}
上一节提及到异地更新的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给CURSEG
if (!__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设置为对应的page
if (fio->encrypted_page)
bio_page = fio->encrypted_page;
else
bio_page = fio->page;
fio->submitted = true;
alloc_new:
// 如果bio是null,就创建一个新的bio
if (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一般等于256
io->fio = *fio;
}
// 将page加入到bio中,如果 < PAGE_SIZE 表示bio已经满了,因此就先将这个bio提交,然后重新分配一个新的bio
if (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。