[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);
|
脏时间 Inode 周期性回写:start_dirtytime_writeback 与 wakeup_dirtytime_writeback
本代码片段实现了一个内核后台的回写(writeback)安全网机制。其核心功能是周期性地唤醒系统的回写线程,以确保那些仅因时间戳(如访问时间 atime)更新而被标记为“脏”的 inode 能够被最终写入持久存储。这个机制通过一个可配置的、自调度(self-scheduling)的延迟工作队列(delayed work)来实现,弥补了常规回写机制主要由脏数据页驱动的不足,从而保证了文件系统元数据的最终一致性。
实现原理分析
该机制是内核 I/O 子系统中一个精妙的、用于保证数据完整性的设计,它由一个周期性任务和一个用户配置接口组成。
问题的根源: 当一个文件被读取时,其 inode 的访问时间(atime)会被更新。这使得 inode 在内存中变为“脏”状态。然而,因为没有文件数据被修改,所以不会有脏的数据页(dirty pages)产生。内核的回写机制(flusher threads)主要由脏数据页的阈值来驱动。因此,在一个 I/O 不频繁的系统上,一个仅有时间戳更新的 inode 可能会无限期地停留在内存中,如果此时系统崩溃,更新后的时间戳将会丢失。
延迟工作队列 (delayed_work) 作为周期性定时器:
start_dirtytime_writeback 函数在内核启动时被调用,它做的第一件事就是调用 schedule_delayed_work 来安排 dirtytime_work 在 dirtytime_expire_interval 秒之后首次执行。
- 当
dirtytime_work 的处理函数 wakeup_dirtytime_writeback 执行到最后时,它会再次调用 schedule_delayed_work 来重新安排下一次的执行。这种“自我调度”的模式,将一个延迟工作队列变成了一个周期性的定时任务。
全局扫描与唤醒 (wakeup_dirtytime_writeback):
- 这个工作队列的处理函数并不亲自执行 I/O 操作。它的职责是遍历系统中所有后端存储设备(
backing_dev_info)的所有回写队列(bdi_writeback)。
- RCU 安全遍历: 遍历
bdi_list 和 wb_list 这两个可能被并发修改的全局链表时,使用了 rcu_read_lock 进行保护。这允许在不阻塞设备注册/注销的情况下安全地进行只读遍历。
- 检查目标: 对于每一个回写队列
wb,它检查其 b_dirty_time 链表是否为空。这个链表专门用于挂接那些仅因时间戳而变脏的 inode。
- 唤醒而非回写: 如果
b_dirty_time 链表非空,它就调用 wb_wakeup(wb)。这个函数仅仅是唤醒与该队列关联的内核回写线程(flusher thread)。被唤醒的线程接下来会按照标准流程检查其队列,发现 b_dirty_time 链表上有待处理的 inode,然后将它们写回磁盘。这种设计将“发现问题”的策略与“解决问题”的执行完全解耦。
用户空间可配置性 (sysctl):
start_dirtytime_writeback 还通过 register_sysctl_init 注册了一个 sysctl 接口,即 /proc/sys/vm/dirtytime_expire_seconds。这允许系统管理员在运行时动态调整检查周期。
dirtytime_interval_handler 是这个 sysctl 的处理函数。它有一个重要的附加逻辑:当用户写入一个新的时间间隔时,它会调用 mod_delayed_work(..., 0)。这个调用会立即修改定时器(如果正在等待),并以0秒的延迟重新调度它,这意味着工作队列会“几乎立即”执行一次,然后开始按新的时间间隔进行周期调度。这使得配置的更改能够迅速生效。
代码分析
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
|
static unsigned int dirtytime_expire_interval = 12 * 60 * 60;
static void wakeup_dirtytime_writeback(struct work_struct *w);
static DECLARE_DELAYED_WORK(dirtytime_work, wakeup_dirtytime_writeback);
static void wakeup_dirtytime_writeback(struct work_struct *w) { struct backing_dev_info *bdi;
rcu_read_lock(); list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) { struct bdi_writeback *wb;
list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node) if (!list_empty(&wb->b_dirty_time)) wb_wakeup(wb); } rcu_read_unlock(); schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); }
static int dirtytime_interval_handler(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { int ret;
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); if (ret == 0 && write) mod_delayed_work(system_percpu_wq, &dirtytime_work, 0); return ret; }
static const struct ctl_table vm_fs_writeback_table[] = { { .procname = "dirtytime_expire_seconds", .data = &dirtytime_expire_interval, .maxlen = sizeof(dirtytime_expire_interval), .mode = 0644, .proc_handler = dirtytime_interval_handler, .extra1 = SYSCTL_ZERO, }, };
static int __init start_dirtytime_writeback(void) { schedule_delayed_work(&dirtytime_work, dirtytime_expire_interval * HZ); register_sysctl_init("vm", vm_fs_writeback_table); return 0; }
__initcall(start_dirtytime_writeback);
|