一、lfs mode bufferd write
写操作分为bufferd write和direct write,两种write处理流程有一些区别,这里只讨论lfs模式下bufferd write的写流程。
bufferd write:数据在page cache里缓存一段时间后,worker_thread线程唤醒名称为“writeback”的workqueu,将数据写入存储设备。
ps命令看到的内核线程[kworker/u2:1-wr],就是执行回写的线程(u表示unbound,没有绑定cpu;2表示work pool id,1用于区分线程名,见create_worker函数。“wr”是workqueue的名称,由于struct task_struct->comm[TASK_COMM_LEN]长度只有16个字符,被截断了,全字符串是“writeback”,default_bdi_init函数设置了workqueue名称)。
direct write:数据不经过内核page cache。在执行direct write的线程上下文,将数据写入存储设备。
lfs:Log-structured File system,文件的写操作为append特性,即像记录log一样,在尾部追加写。如下图,File A更新block 5、6数据,新数据写入尾部block10、11,原来的block变成invalid。
direct node:f2fs中组织文件数据块的一种结构,数据组织形式比较复杂,另见XXX文章,待整理,在这里只需要知道direct node是4K大小的block(f2fs会将这个block读入page),block中存放的是文件数据块的block地址。对于非压缩文件,每个direct node含有DEF_ADDRS_PER_BLOCK=1018个block地址。
direct node entry:direct node中每一个存储block地址的单元,就是一个direct node entry。
二、bufferd write三个阶段
忽略block layer、device driver、hard irq、soft irq,简化后的写流程如下:
三、bufferd write代码框架
syscall write --> ksys_write --> vfs_write --> new_sync_write --> call_write_iter -- > file->f_op->write_iter即f2fs_file_write_iter。
f2fs_file_write_iter可以分成两部分去理解。
第1部分是f2fs_preallocate_blocks,设置写操作pos+count涉及的direct node entry,填上适当的block address。如果direct node entry本来就有一个有效的block address(场景:修改文件原有数据),entry不需要修改,保持原有的值。如果entry的值是NULL_ADDR(场景:在文件尾部写新内容,且新内容需要占用新的block),则需要将entry值设置成NEW_ADDR,表示这个entry已经被占用了,但是还没有分配实际的block(kworker将脏页写入device时,才会实际分配block)。
第2部分是f2fs_buffered_write_iter。
首先准备写操作pos+count对应的pages。如果page对应的block在device上已经存在(场景:修改文件原有数据)且block数据没有缓存在page cache,则需要从device读取block到page中,然后加入page cache;如果page对应的block在device上不存在,就直接新申请一个page并加入page cache(场景:文件尾部写新内容,且新内容需要占用新block)。
接着通过内存拷贝函数copy_page_from_iter_atomic将待写的数据拷贝至page对应偏移处。最后将page设置成PG_dirty、更新文件大小等元数据信息,整个bufferd write流程结束。
注意,这个时候数据并没有写入device。后期某时间kwroker线程负责将数据写入device,这个时候才会按lfs模式分配新的block,将脏页写入新block,旧block变成invalid状态不用了。
四、代码阅读
第1部分是f2fs_preallocate_blocks。
第2部分是f2fs_buffered_write_iter。在下图中要注意page的lock状态。