[TOC]
fs-writeback
fs-writeback.c 是 Linux 内核中负责处理文件系统数据写回(writeback)机制的核心模块。它的主要功能是管理脏页和脏 inode 的写回操作,以确保数据从内存缓冲区最终写入磁盘。以下是对其原理、使用场景、优缺点以及其他方案的详细阐述:
原理
脏页与脏 inode:
- 当文件系统中的数据被修改时,内核会将这些修改暂时存储在内存中(页缓存或 inode 缓存),并标记为“脏”。
- 脏页指的是页缓存中未写入磁盘的修改数据,脏 inode 则是文件元数据(如时间戳、权限等)的未写入部分。
写回机制:
- fs-writeback.c 通过后台线程(flusher threads)定期扫描脏页和脏 inode,并将它们写入磁盘。
- 写回操作可以是异步的(WB_SYNC_NONE),也可以是同步的(WB_SYNC_ALL),具体取决于调用场景。
核心组件:
bdi_writeback
: 表示一个设备的写回上下文,管理该设备的脏页和脏 inode。
wb_writeback_work
: 描述写回任务的结构体,包括写回页数、同步模式等。
wb_workfn
: 写回线程的主函数,负责执行写回任务。
调度与触发:
- 写回任务可以由以下事件触发:
- 内存压力(如内存不足时触发写回以释放页缓存)。
- 定期写回(如
dirty_writeback_interval
配置的时间间隔)。
- 显式调用(如
sync
系统调用或 fsync
文件操作)。
使用场景
数据完整性:
- 确保文件系统的数据和元数据最终写入磁盘,避免数据丢失。
- 在系统崩溃或断电时,脏页和脏 inode 的及时写回可以减少数据丢失的风险。
性能优化:
- 写回机制通过延迟写入操作,减少频繁的磁盘 I/O,提高系统性能。
- 通过批量写回,优化磁盘写入效率。
文件系统操作:
- 文件系统的
sync
和 fsync
操作依赖写回机制来确保数据一致性。
- 数据库等应用程序通常使用
fsync
来确保事务的持久性。
优缺点
优点
性能提升:
- 延迟写入减少了磁盘 I/O 的频率,提高了系统的整体性能。
- 批量写回优化了磁盘的写入效率。
灵活性:
- 支持多种写回模式(异步、同步),适应不同场景的需求。
- 可配置参数(如
dirty_writeback_interval
和 dirty_ratio
)允许用户根据系统负载调整写回行为。
数据安全性:
- 通过定期写回和显式触发写回,确保数据最终写入磁盘,减少数据丢失的风险。
缺点
延迟风险:
- 延迟写入可能导致数据丢失,尤其是在系统崩溃或断电时。
- 如果写回线程无法及时处理脏页,可能导致内存压力增加。
复杂性:
- 写回机制涉及多个线程和复杂的调度逻辑,可能增加代码维护难度。
- 在高负载场景下,写回线程可能成为性能瓶颈。
I/O 干扰:
- 批量写回可能导致突发的磁盘 I/O 峰值,影响其他 I/O 操作的性能。
其他方案
1. Direct I/O
- 原理: 直接将数据写入磁盘,绕过页缓存。
- 优点:
- 缺点:
- 性能可能较低,尤其是小块数据写入。
- 不支持缓存优化。
2. Journaling 文件系统
- 原理: 使用日志记录文件系统的元数据和数据操作,确保一致性。
- 优点:
- 缺点:
3. 用户空间缓存
- 原理: 应用程序在用户空间实现自己的缓存机制。
- 优点:
- 缺点:
4. 内存映射文件(mmap)
- 原理: 将文件映射到内存,直接操作内存中的数据。
- 优点:
- 缺点:
- 需要显式调用
msync
或 munmap
来确保数据写入磁盘。
总结
fs-writeback.c 是 Linux 文件系统中不可或缺的模块,负责管理脏页和脏 inode 的写回操作。它通过延迟写入和批量处理优化了系统性能,同时提供了数据完整性保障。然而,它也存在延迟写入的风险和复杂性问题。在实际应用中,可以根据场景选择合适的写回机制或替代方案,以平衡性能与数据安全性。
include/linux/fs.h
mark_inode_dirty_sync 将 inode 标记为脏同步
1 2 3 4
| static inline void mark_inode_dirty_sync(struct inode *inode) { __mark_inode_dirty(inode, I_DIRTY_SYNC); }
|
inode_unhashed inode 无hash
1 2 3 4
| static inline int inode_unhashed(struct inode *inode) { return hlist_unhashed(&inode->i_hash); }
|
mapping_tagged 用于检查页面缓存(address_space)中的任何页面是否被标记为指定的标签(tag)
- 页面缓存是文件系统中用于存储文件数据的内存区域,而标签(tag)是用于标识页面状态的标记,例如页面是否脏、是否正在写回等。
1 2 3 4 5 6 7 8
|
static inline bool mapping_tagged(struct address_space *mapping, xa_mark_t tag) { return xa_marked(&mapping->i_pages, tag); }
|
mark_inode_dirty_sync 将 inode 标记为脏同步
1 2 3 4
| static inline void mark_inode_dirty_sync(struct inode *inode) { __mark_inode_dirty(inode, I_DIRTY_SYNC); }
|
fs/fs-writeback.c
locked_inode_to_wb_and_lock_list 从给定的 inode 中提取关联的 bdi_writeback 结构,并在锁的上下文中进行转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static struct bdi_writeback * locked_inode_to_wb_and_lock_list(struct inode *inode) __releases(&inode->i_lock) __acquires(&wb->list_lock) { struct bdi_writeback *wb = inode_to_wb(inode);
spin_unlock(&inode->i_lock); spin_lock(&wb->list_lock); return wb; }
|
wb_io_lists_populated 检查 bdi_writeback 是否已经标记为有脏 IO 数据。如果没有脏 IO 数据,它会设置 WB_has_dirty_io 标志,并更新相关的写带宽统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static bool wb_io_lists_populated(struct bdi_writeback *wb) { if (wb_has_dirty_io(wb)) { return false; } else { set_bit(WB_has_dirty_io, &wb->state); WARN_ON_ONCE(!wb->avg_write_bandwidth); atomic_long_add(wb->avg_write_bandwidth, &wb->bdi->tot_write_bandwidth); return true; } }
|
wb_io_lists_depopulated 清除 WB_has_dirty_io 标志,当 bdi_writeback 的所有 IO 列表都为空时,同时更新写带宽统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static void wb_io_lists_depopulated(struct bdi_writeback *wb) {
if (wb_has_dirty_io(wb) && list_empty(&wb->b_dirty) && list_empty(&wb->b_io) && list_empty(&wb->b_more_io)) { clear_bit(WB_has_dirty_io, &wb->state);
WARN_ON_ONCE(atomic_long_sub_return(wb->avg_write_bandwidth, &wb->bdi->tot_write_bandwidth) < 0); } }
|
inode_io_list_move_locked 将 inode 移动到 bdi_writeback IO 列表中
- 将 inode->i_io_list 移动到目标 bdi_writeback 的指定列表中(如 b_dirty、b_io、b_more_io 或 b_dirty_time)。
- 同时,它会设置 WB_has_dirty_io 标志以表示该写回设备有脏 IO 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
static bool inode_io_list_move_locked(struct inode *inode, struct bdi_writeback *wb, struct list_head *head) { assert_spin_locked(&wb->list_lock); assert_spin_locked(&inode->i_lock); WARN_ON_ONCE(inode->i_state & I_FREEING);
list_move(&inode->i_io_list, head);
if (head != &wb->b_dirty_time) return wb_io_lists_populated(wb);
wb_io_lists_depopulated(wb); return false; }
|
1. wb->b_dirty
- 含义: 存储脏的 inode,这些 inode 的数据需要被写回磁盘。
- 用途: 这是主要的脏 inode 列表,表示需要立即处理的写回任务。
- 场景: 当 inode 的数据被修改后,它会被标记为脏并加入该列表。
2. wb->b_io
- 含义: 存储正在进行写回操作的 inode。
- 用途: 这是一个临时列表,用于存储当前正在被写回线程处理的 inode。
- 场景: 当写回线程开始处理某个 inode 时,它会从
b_dirty
移动到 b_io
。
3. wb->b_more_io
- 含义: 存储需要进一步写回的 inode。
- 用途: 这是一个延迟处理的列表,表示写回操作未完成,需要后续处理。
- 场景: 当写回线程无法一次性完成某个 inode 的写回任务时,该 inode 会被移到
b_more_io
。
4. wb->b_dirty_time
- 含义: 存储时间戳脏的 inode,这些 inode 的元数据(如时间戳)需要被写回磁盘。
- 用途: 专门用于管理时间戳相关的写回任务,与数据写回任务分开。
- 场景: 当 inode 的时间戳被修改但数据未修改时,它会被加入该列表。
wb_wakeup_delayed 唤醒相应的 bdi 线程,然后该线程应该负责定期后台写出脏 inode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
unsigned int dirty_writeback_interval = 5 * 100;
EXPORT_SYMBOL_GPL(dirty_writeback_interval);
static void wb_wakeup_delayed(struct bdi_writeback *wb) { unsigned long timeout; timeout = msecs_to_jiffies(dirty_writeback_interval * 10); spin_lock_irq(&wb->work_lock); if (test_bit(WB_registered, &wb->state)) queue_delayed_work(bdi_wq, &wb->dwork, timeout); spin_unlock_irq(&wb->work_lock); }
|
__mark_inode_dirty 用于将 inode 标记为脏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
void __mark_inode_dirty(struct inode *inode, int flags) { struct super_block *sb = inode->i_sb; int dirtytime = 0; struct bdi_writeback *wb = NULL;
trace_writeback_mark_inode_dirty(inode, flags);
if (flags & I_DIRTY_INODE) {
if (inode->i_state & I_DIRTY_TIME) { spin_lock(&inode->i_lock); if (inode->i_state & I_DIRTY_TIME) { inode->i_state &= ~I_DIRTY_TIME; flags |= I_DIRTY_TIME; } spin_unlock(&inode->i_lock); }
trace_writeback_dirty_inode_start(inode, flags); if (sb->s_op->dirty_inode) sb->s_op->dirty_inode(inode, flags & (I_DIRTY_INODE | I_DIRTY_TIME)); trace_writeback_dirty_inode(inode, flags);
flags &= ~I_DIRTY_TIME; } else {
dirtytime = flags & I_DIRTY_TIME; WARN_ON_ONCE(dirtytime && flags != I_DIRTY_TIME); }
smp_mb();
if ((inode->i_state & flags) == flags) return;
spin_lock(&inode->i_lock); if ((inode->i_state & flags) != flags) { const int was_dirty = inode->i_state & I_DIRTY;
inode_attach_wb(inode, NULL);
inode->i_state |= flags;
if (!was_dirty) { wb = locked_inode_to_wb_and_lock_list(inode); spin_lock(&inode->i_lock); }
if (inode->i_state & I_SYNC_QUEUED) goto out_unlock;
if (!S_ISBLK(inode->i_mode)) { if (inode_unhashed(inode)) goto out_unlock; } if (inode->i_state & I_FREEING) goto out_unlock;
if (!was_dirty) { struct list_head *dirty_list; bool wakeup_bdi = false;
inode->dirtied_when = jiffies; if (dirtytime) inode->dirtied_time_when = jiffies;
if (inode->i_state & I_DIRTY) dirty_list = &wb->b_dirty; else dirty_list = &wb->b_dirty_time;
wakeup_bdi = inode_io_list_move_locked(inode, wb, dirty_list);
spin_unlock(&wb->list_lock); spin_unlock(&inode->i_lock); trace_writeback_dirty_inode_enqueue(inode);
if (wakeup_bdi && (wb->bdi->capabilities & BDI_CAP_WRITEBACK)) wb_wakeup_delayed(wb); return; } } out_unlock: if (wb) spin_unlock(&wb->list_lock); spin_unlock(&inode->i_lock); } EXPORT_SYMBOL(__mark_inode_dirty);
|
write_inode_now 立即将一个脏的 inode 写入磁盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
int write_inode_now(struct inode *inode, int sync) { struct writeback_control wbc = { .nr_to_write = LONG_MAX,
.sync_mode = sync ? WB_SYNC_ALL : WB_SYNC_NONE, .range_start = 0, .range_end = LLONG_MAX, }; if (!mapping_can_writeback(inode->i_mapping)) wbc.nr_to_write = 0; might_sleep(); return writeback_single_inode(inode, &wbc); } EXPORT_SYMBOL(write_inode_now);
|
write_inode 标记 inode(文件系统中的索引节点)为脏状态
1 2 3 4 5 6 7 8 9 10 11 12
| static int write_inode(struct inode *inode, struct writeback_control *wbc) { int ret;
if (inode->i_sb->s_op->write_inode && !is_bad_inode(inode)) { trace_writeback_write_inode_start(inode, wbc); ret = inode->i_sb->s_op->write_inode(inode, wbc); trace_writeback_write_inode(inode, wbc); return ret; } return 0; }
|
inode_wait_for_writeback 等待 inode 的写回操作完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
void inode_wait_for_writeback(struct inode *inode) { struct wait_bit_queue_entry wqe; struct wait_queue_head *wq_head;
assert_spin_locked(&inode->i_lock);
if (!(inode->i_state & I_SYNC)) return; wq_head = inode_bit_waitqueue(&wqe, inode, __I_SYNC); for (;;) { prepare_to_wait_event(wq_head, &wqe.wq_entry, TASK_UNINTERRUPTIBLE); if (!(inode->i_state & I_SYNC)) break; spin_unlock(&inode->i_lock); schedule(); spin_lock(&inode->i_lock); } finish_wait(wq_head, &wqe.wq_entry); }
|
__writeback_single_inode 将 inode 的脏数据(包括页面和元数据)写入磁盘,并清除相关的脏标志(I_DIRTY)以维护文件系统的一致性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
static int __writeback_single_inode(struct inode *inode, struct writeback_control *wbc) { struct address_space *mapping = inode->i_mapping; long nr_to_write = wbc->nr_to_write; unsigned dirty; int ret;
WARN_ON(!(inode->i_state & I_SYNC));
trace_writeback_single_inode_start(inode, wbc, nr_to_write); ret = do_writepages(mapping, wbc);
if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync) { int err = filemap_fdatawait(mapping); if (ret == 0) ret = err; }
if ((inode->i_state & I_DIRTY_TIME) && (wbc->sync_mode == WB_SYNC_ALL || time_after(jiffies, inode->dirtied_time_when + dirtytime_expire_interval * HZ))) { trace_writeback_lazytime(inode); mark_inode_dirty_sync(inode); }
spin_lock(&inode->i_lock); dirty = inode->i_state & I_DIRTY; inode->i_state &= ~dirty;
smp_mb();
if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) inode->i_state |= I_DIRTY_PAGES; else if (unlikely(inode->i_state & I_PINNING_NETFS_WB)) { if (!(inode->i_state & I_DIRTY_PAGES)) { inode->i_state &= ~I_PINNING_NETFS_WB; wbc->unpinned_netfs_wb = true; dirty |= I_PINNING_NETFS_WB; } }
spin_unlock(&inode->i_lock);
if (dirty & ~I_DIRTY_PAGES) { int err = write_inode(inode, wbc); if (ret == 0) ret = err; } wbc->unpinned_netfs_wb = false; trace_writeback_single_inode(inode, wbc, nr_to_write); return ret; }
|
writeback_single_inode 将单个 inode 写回磁盘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
static int writeback_single_inode(struct inode *inode, struct writeback_control *wbc) { struct bdi_writeback *wb; int ret = 0;
spin_lock(&inode->i_lock); if (!atomic_read(&inode->i_count)) WARN_ON(!(inode->i_state & (I_WILL_FREE|I_FREEING))); else WARN_ON(inode->i_state & I_WILL_FREE);
if (inode->i_state & I_SYNC) {
if (wbc->sync_mode != WB_SYNC_ALL) goto out; inode_wait_for_writeback(inode); } WARN_ON(inode->i_state & I_SYNC);
if (!(inode->i_state & I_DIRTY_ALL) && (wbc->sync_mode != WB_SYNC_ALL || !mapping_tagged(inode->i_mapping, PAGECACHE_TAG_WRITEBACK))) goto out; inode->i_state |= I_SYNC; wbc_attach_and_unlock_inode(wbc, inode); ret = __writeback_single_inode(inode, wbc); wbc_detach_inode(wbc);
wb = inode_to_wb_and_lock_list(inode); spin_lock(&inode->i_lock);
if (!(inode->i_state & I_FREEING)) {
if (!(inode->i_state & I_DIRTY_ALL)) inode_cgwb_move_to_attached(inode, wb); else if (!(inode->i_state & I_SYNC_QUEUED)) { if ((inode->i_state & I_DIRTY)) redirty_tail_locked(inode, wb); else if (inode->i_state & I_DIRTY_TIME) { inode->dirtied_when = jiffies; inode_io_list_move_locked(inode, wb, &wb->b_dirty_time); } } }
spin_unlock(&wb->list_lock); inode_sync_complete(inode); out: spin_unlock(&inode->i_lock); return ret; }
|
sync_inodes_sb: 同步并等待单个文件系统的Inode写回
此函数是Linux VFS(虚拟文件系统)层中一个至关重要的I/O同步函数。它的核心作用是为一个指定的文件系统(sb
), 强制启动其所有脏Inode(包括其关联的数据页)的写回(writeback)操作, 并且阻塞等待, 直到这些写回操作全部完成。它提供了从”数据在内存”到”数据已提交到存储设备”的强一致性保证。
该函数的原理是利用内核中复杂且高效的后台写回(background writeback)机制, 但以一种同步的方式来驱动它:
构建”工作订单”: 函数首先会创建一个wb_writeback_work
结构体。这可以被理解为一个详细的”工作订单”, 它精确地描述了需要执行的写回任务:
sb
: 任务目标是哪个文件系统。
sync_mode = WB_SYNC_ALL
: 任务模式是”全部同步”, 意味着不考虑任何延迟策略, 必须写回所有脏数据。
nr_pages = LONG_MAX
: 任务范围是”无限”, 再次强调要写回所有脏页。
done = &done
: 这是关键的同步点。它将这个工作订单与一个wb_completion
完成事件关联起来。
提交工作并等待: 函数并不会自己去一个一个地写数据。它调用bdi_split_work_to_wbs
将这个”工作订单”提交给与该文件系统底层块设备相关联的写回工作队列(writeback workqueues)。内核的I/O工作者线程会接收这个任务并开始在后台执行实际的I/O操作。提交任务后, sync_inodes_sb
函数立即调用wb_wait_for_completion(&done)
, 在此处进入休眠状态, 等待。当后台的工作者线程完成了”工作订单”中指定的所有I/O操作后, 它们会发出done
事件的完成信号, 此时wb_wait_for_completion
才会返回, sync_inodes_sb
函数才能继续执行。
最终清理等待: 在主体的写回完成后, 它还会调用wait_sb_inodes
。这是一个额外的、更强的保证步骤, 用于等待那些可能由其他原因(并非本次sync
调用)发起但尚未完成的Inode I/O。
在STM32H750这样的单核抢占式系统上, 这种机制依然至关重要。虽然不存在多核并行执行, 但并发依然存在于任务与中断之间, 或高优先级任务对低优先级任务的抢占。
bdi_down_write_wb_switch_rwsem
等锁机制可以防止在提交写回任务时, I/O队列的结构被其他任务(例如, 响应系统负载变化的管理任务)修改, 保证了任务提交的原子性。
- 将实际I/O操作推迟到内核工作者线程, 并通过完成事件进行同步, 是一个标准的内核设计模式, 它将I/O发起者(可能是任何任务)与I/O执行者(专职的工作者线程)解耦, 使得代码结构更清晰, 更容易管理复杂的I/O状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
void sync_inodes_sb(struct super_block *sb) {
struct backing_dev_info *bdi = sb->s_bdi;
DEFINE_WB_COMPLETION(done, bdi);
struct wb_writeback_work work = { .sb = sb, .sync_mode = WB_SYNC_ALL, .nr_pages = LONG_MAX, .range_cyclic = 0, .done = &done, .reason = WB_REASON_SYNC, .for_sync = 1, };
if (bdi == &noop_backing_dev_info) return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
bdi_down_write_wb_switch_rwsem(bdi);
bdi_split_work_to_wbs(bdi, &work, false);
wb_wait_for_completion(&done);
bdi_up_write_wb_switch_rwsem(bdi);
wait_sb_inodes(sb); }
EXPORT_SYMBOL(sync_inodes_sb);
|