Linux 脏页写回流程
在 Linux 系统中,write()
系统调用不会立即将数据写入磁盘,而是利用 Page Cache 进行缓冲,提高写入性能。实际的磁盘写入由内核的后台线程异步完成,从而减少用户进程的阻塞时间。下面详细分析 write()
的执行流程以及数据写回(Writeback)机制。
1. 用户进程调用 write
当用户进程调用 write(fd, buf, len)
进行文件写入时,发生以下操作:
- 数据从用户空间复制到内核空间的页缓存(Page Cache):
write()
并不会立即将数据写入磁盘,而是将数据从用户进程的缓冲区拷贝到内核页缓存。- 页缓存是 Linux 内核用于加速文件 I/O 操作的内存区域,减少频繁的磁盘访问。
write()
调用返回,用户进程继续执行:- 由于数据仅写入页缓存,
write()
系统调用很快返回,用户进程无需等待数据真正落盘。 - 此时,数据仍然只在内存中,并未写入磁盘。
- 由于数据仅写入页缓存,
此时,用户进程结束了写入操作,它不会直接参与数据的落盘过程。
2. 页缓存中的数据管理
Linux 采用延迟写入(Write-Back Cache)策略,以优化磁盘 I/O:
- 合并多个写请求:避免小块数据频繁写入磁盘,减少 I/O 负担。
- 延迟写入,提高吞吐量:数据在合适时机才会写回磁盘,而不是
write()
立即触发写盘。
通常,数据写入页缓存后,只有在以下情况下才会真正写入磁盘:
- 系统的页缓存空间不足:
- 当可用内存减少,内核可能触发脏页回收,将部分页缓存写入磁盘。
- 周期性后台写回机制(内核 writeback 进程):
- Linux 通过
pdflush
或kworker
内核线程定期扫描页缓存,并将脏页(Dirty Pages)写入磁盘。
- Linux 通过
- 用户调用
fsync()
或sync()
强制写入磁盘:- 如果应用程序需要保证数据持久化,可以调用
fsync(fd)
或sync()
,强制将缓冲区数据同步到磁盘。
- 如果应用程序需要保证数据持久化,可以调用
3. 内核执行数据落盘
内核有专门的机制负责将页缓存中的数据异步地写入磁盘,这个过程称为写回(writeback)。
当数据需要写入磁盘时,内核执行以下步骤:
- 触发写回操作(Writeback):
- 由
kworker
进程定期扫描页缓存,选择需要写回的文件数据。
- 由
- DMA 方式将数据写入磁盘:
- DMA 控制器负责将数据从内核页缓存传输到磁盘存储设备,避免 CPU 直接参与数据搬运,提高效率。
- 写入完成,释放页缓存:
- 数据成功写入磁盘后,内核释放相应的页缓存,使其可用于新的写入操作。
4. 缓存和进程之间的交互
- Linux 采用异步写入的方式优化性能:
- 用户进程:执行
write()
仅写入缓存,立即返回,继续运行。 - 内核进程:负责异步写入磁盘,
writeback
由kworker
线程执行,与用户进程无关。
- 用户进程:执行
由于数据写回操作由独立的内核线程执行,而非用户进程本身,因此用户进程的 PID 不会因写盘操作发生变化。
5. 总结
write()
仅将数据写入 页缓存(Page Cache),并不会立即写入磁盘。- 内核负责数据写回,通常是异步进行,以提高系统 I/O 效率。
- 实际落盘的触发条件包括:页缓存不足、内核定期写回、显式
fsync()
调用等。 - 用户进程不会直接参与磁盘写入,落盘由
kworker
内核线程 处理,确保用户进程执行不被磁盘 I/O 阻塞。
通过这种机制,Linux 系统实现了较高的写入效率,避免了频繁的磁盘 I/O,从而提高了文件系统的性能。
脏页写回流程4.19源码
核心数据结构初始化
回写机制借助了Linux中工作队列来完成,在内核启动的时候,系统会使用alloc_workqueue函数申请一个用于回写的工作队列。具体实现在函数default_bdi_init中,初始化系统中的默认 backing_dev_info
结构,负责管理写回设备的数据:
static int __init default_bdi_init(void)
{
int err;
// 分配并初始化一个名为 "writeback" 的工作队列 (workqueue)。
// 这个工作队列具有以下属性:
// - WQ_MEM_RECLAIM: 表示当内存不足时,该工作队列可以参与内存回收。
// - WQ_FREEZABLE: 在系统冻结时,该工作队列可以被冻结(例如在休眠过程中)。
// - WQ_UNBOUND: 工作项可以在任意 CPU 上执行,不限制于某个特定的 CPU。
// - WQ_SYSFS: 允许通过 sysfs 查看和操作该工作队列的属性。
// 如果工作队列分配失败,返回内存不足的错误码 (-ENOMEM)。
bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE |
WQ_UNBOUND | WQ_SYSFS, 0);
if (!bdi_wq)
return -ENOMEM; // 分配失败时返回错误码
// 初始化一个 "noop" 的 backing dev info (bdi),
// 用于表示空操作的设备信息结构。
// 如果初始化失败,返回相应的错误码。
err = bdi_init(&noop_backing_dev_info);
return err; // 成功返回 0,失败返回错误码
}
bdi_init()函数初始化bdi (struct backing_dev_info),该结构体包含了块设备信息,代表一个设备。
- struct backing_dev_info:描述一个块设备。
- struct bdi_writeback:管理一个块设备所有的回写任务。
- struct wb_writeback_work :描述需要回写的任务。
backing_dev_info中维护了wb_list链表,管理bdi_writeback,同时每个bdi_writeback中维护了dwork和work_list,前者代表处理任务的函数,后者则是任务列表,在bdi_init中对bdi进行初始化后,会继续调用wb_init(),该函数对bdi中的wb(struct bdi_writeback)进行初始化。
/fs/fs-writeback.c
struct bdi_writeback {
struct backing_dev_info *bdi; /* our parent bdi */
unsigned long state; /* Always use atomic bitops on this */
unsigned long last_old_flush; /* 上次刷写数据的时间,用于周期性回写数据 */
struct list_head b_dirty; /* 暂存dirty inode,mark_dirty_inode会加入到这个list */
struct list_head b_io; /* 用于暂存即将要被writeback处理的inode */
struct list_head b_more_io; /* parked for more writeback */
struct list_head b_dirty_time; /* 暂存在cache过期的inode */
spinlock_t list_lock; /* protects the b_* lists */
struct percpu_counter stat[NR_WB_STAT_ITEMS];
struct bdi_writeback_congested *congested;
unsigned long bw_time_stamp; /* last time write bw is updated */
unsigned long dirtied_stamp;
unsigned long written_stamp; /* pages written at bw_time_stamp */
unsigned long write_bandwidth; /* 单次wb任务的带宽 */
unsigned long avg_write_bandwidth; /* further smoothed write bw, > 0 */
unsigned long dirty_ratelimit;
unsigned long balanced_dirty_ratelimit;
struct fprop_local_percpu completions;
int dirty_exceeded;
enum wb_reason start_all_reason; /* 回写任务的触发原因 ,常见的原因有周期回写、脏页超出阈值回写,和用户主动回写*/
spinlock_t work_lock; /* protects work_list & dwork scheduling */
struct list_head work_list; /* 保存wb_writeback_work结构的list,用于处理这次回写任务下面所有的任务 */
struct delayed_work dwork; /* work item used for writeback */
unsigned long dirty_sleep; /* last wait */
struct list_head bdi_node; /* anchored at bdi->wb_list */
};
//描述一个回写任务
struct wb_writeback_work {
long nr_pages;//待回写页面数量;
struct super_block *sb;// writeback 任务所属的 super_block;
enum writeback_sync_modes sync_mode;/*指定同步模式,WB_SYNC_ALL 表示当遇到锁住的 inode 时,它必须
等待该 inode 解锁,而不能跳过。WB_SYNC_NONE 表示跳过被锁住的 inode;*/
unsigned int tagged_writepages:1;
unsigned int for_kupdate:1;//若值为 1,则表示回写原因是周期性的回写;否则值为 0;
unsigned int range_cyclic:1;
unsigned int for_background:1;//若值为 1,表示后台回写;否则值为 0;
unsigned int for_sync:1; /* sync(2) WB_SYNC_ALL writeback */
unsigned int auto_free:1; /* free on completion */
enum wb_reason reason; /* why was writeback initiated? */
struct list_head list; /* pending work list */
struct wb_completion *done; /* set if the caller waits */
};
wb_init在初始化过程中,给wb->dwork字段赋值了函数wb_workfn,后面触发回写任务时,就会通过该函数进行执行回写:
static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi,
int blkcg_id, gfp_t gfp)
{
INIT_LIST_HEAD(&wb->b_dirty);
...
wb->bw_time_stamp = jiffies;
...
INIT_DELAYED_WORK(&wb->dwork, wb_workfn);
wb->dirty_sleep = jiffies;
...
return 0;
}
至此bdi_writeback机制初始化完成。
触发回写任务的时机
上述操作都会触发回写任务:
void wakeup_flusher_threads(enum wb_reason reason)
{
struct backing_dev_info *bdi;
/*
* If we are expecting writeback progress we must submit plugged IO.
*/
if (blk_needs_flush_plug(current))
blk_schedule_flush_plug(current);
rcu_read_lock();
list_for_each_entry_rcu(bdi, &bdi_list, bdi_list)
__wakeup_flusher_threads_bdi(bdi, reason);
rcu_read_unlock();
}
找到wb_start_writeback函数:
static void wb_start_writeback(struct bdi_writeback *wb, enum wb_reason reason)
{
if (!wb_has_dirty_io(wb))
return;
/*
* All callers of this function want to start writeback of all
* dirty pages. Places like vmscan can call this at a very
* high frequency, causing pointless allocations of tons of
* work items and keeping the flusher threads busy retrieving
* that work. Ensure that we only allow one of them pending and
* inflight at the time.
*/
if (test_bit(WB_start_all, &wb->state) ||
test_and_set_bit(WB_start_all, &wb->state))
return;
wb->start_all_reason = reason;
wb_wakeup(wb);
}
wb_wakeup,这个函数的作用是,当满足条件时,立即唤醒写回任务,将其加入到工作队列中执行:
static void wb_wakeup(struct bdi_writeback *wb)
{
// 加锁,使用 bottom half 锁,避免软中断的竞态条件
spin_lock_bh(&wb->work_lock);
// 检查 `wb->state` 中是否设置了 `WB_registered` 位,
// 也就是检查该 `writeback` 是否已经注册。
if (test_bit(WB_registered, &wb->state))
// 如果已经注册,将写回任务的延迟时间设为 0,立即调度执行。
mod_delayed_work(bdi_wq, &wb->dwork, 0);
// 解锁
spin_unlock_bh(&wb->work_lock);
}
mod_delayed_work:,将延迟的工作项 dwork
安排到工作队列 wq
中执行,并且设置执行的延迟时间
static inline bool mod_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)
{
// 调用 mod_delayed_work_on 函数,将工作项 dwork 加入到指定工作队列 wq 中,并设置延迟时间。
// WORK_CPU_UNBOUND 表示该工作可以在任意 CPU 上执行,不绑定到特定的 CPU。
return mod_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}
mod_delayed_work_on,将延迟工作项加入到指定的工作队列中,允许指定在哪个 CPU 上执行,并处理相关的状态和中断。这个函数主要用于调度工作项,并确保在操作期间的中断安全。
bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
unsigned long flags;
int ret;
// 尝试获取 dwork 的 pending 状态,如果获取失败则循环重试
do {
ret = try_to_grab_pending(&dwork->work, true, &flags);
} while (unlikely(ret == -EAGAIN));
// 如果成功获取或状态正常
if (likely(ret >= 0)) {
// 将 dwork 加入到指定工作队列 wq 中,设置延迟时间
__queue_delayed_work(cpu, wq, dwork, delay);
// 恢复之前保存的中断标志
local_irq_restore(flags);
}
// 从 try_to_grab_pending() 返回的 -ENOENT 视为成功 (true)
return ret;
}
__queue_delayed_work,将延迟工作项 (delayed_work
) 添加到工作队列,并根据提供的延迟时间决定何时开始执行该工作。
static void __queue_delayed_work(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
// 获取 delayed_work 中的定时器和工作项
struct timer_list *timer = &dwork->timer;
struct work_struct *work = &dwork->work;
// 警告,如果工作队列 wq 为空,提醒这是一个不正常的情况
WARN_ON_ONCE(!wq);
// 确保定时器的回调函数是 delayed_work_timer_fn,确保函数类型正确
WARN_ON_ONCE(timer->function != delayed_work_timer_fn);
// 确保定时器当前没有处于挂起状态,避免重复添加
WARN_ON_ONCE(timer_pending(timer));
// 确保工作项没有在其它队列中,防止工作项重复排队
WARN_ON_ONCE(!list_empty(&work->entry));
/*
* 如果延迟为 0,表示不需要等待,直接将工作项加入工作队列立即执行。
* 这样可以避免延迟到下一个系统 tick,从而优化性能。
*/
if (!delay) {
__queue_work(cpu, wq, &dwork->work); // 立即将工作项放入工作队列
return;
}
// 将工作队列和 CPU 信息存储到 delayed_work 中
dwork->wq = wq;
dwork->cpu = cpu;
// 设置定时器的超时时间为当前时间(jiffies)加上延迟
timer->expires = jiffies + delay;
// 如果指定了特定的 CPU,使用 add_timer_on 在该 CPU 上添加定时器
if (unlikely(cpu != WORK_CPU_UNBOUND))
add_timer_on(timer, cpu);
else
// 如果不绑定 CPU,则使用 add_timer,将定时器添加到默认的定时器列表中
add_timer(timer);
}
__queue_work,将一个工作项插入工作队列,并根据 CPU 绑定和池状态选择合适的 pool_workqueue
static void __queue_work(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
struct pool_workqueue *pwq;
struct worker_pool *last_pool;
struct list_head *worklist;
unsigned int work_flags;
unsigned int req_cpu = cpu;
/*
* 当一个工作项处于 PENDING 状态且不在队列中时,
* 尝试抢占该 PENDING 状态的任务可能会陷入忙循环。
* 抢占 PENDING 状态和入队操作应该在中断关闭的情况下进行。
*/
lockdep_assert_irqs_disabled();
// 调试用,标记工作项为激活状态
debug_work_activate(work);
/* 如果当前队列正在被清空(draining),只允许来自相同工作队列的工作项 */
if (unlikely(wq->flags & __WQ_DRAINING) &&
WARN_ON_ONCE(!is_chained_work(wq)))
return;
retry:
// 如果工作项不绑定 CPU(WORK_CPU_UNBOUND),选择一个合适的 CPU 执行
if (req_cpu == WORK_CPU_UNBOUND)
cpu = wq_select_unbound_cpu(raw_smp_processor_id());
/* 为工作项选择合适的 pool_workqueue(pwq) */
if (!(wq->flags & WQ_UNBOUND))
// 如果队列绑定 CPU,则使用特定 CPU 上的工作队列
pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
else
// 如果队列不绑定 CPU,则根据 NUMA 节点选择工作队列
pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
/*
* 如果该工作项之前在不同的 pool(last_pool)中运行,且可能仍在该 pool 中执行,
* 则需要在该 pool 中重新排队以保证工作项的不可重入性。
*/
last_pool = get_work_pool(work);
if (last_pool && last_pool != pwq->pool) {
struct worker *worker;
// 锁定上一个 pool 以检查工作项是否仍在执行
spin_lock(&last_pool->lock);
// 查找正在执行该工作项的 worker
worker = find_worker_executing_work(last_pool, work);
// 如果工作项确实在这个 pool 中执行,使用当前的 pwq
if (worker && worker->current_pwq->wq == wq) {
pwq = worker->current_pwq;
} else {
// 否则释放上一个 pool 的锁,并锁定新的 pwq
spin_unlock(&last_pool->lock);
spin_lock(&pwq->pool->lock);
}
} else {
// 如果工作项没有在其他 pool 中运行,直接锁定当前选择的 pwq
spin_lock(&pwq->pool->lock);
}
/*
* 如果当前选择的 pwq 已经被释放(refcnt 为 0),
* 并且队列不绑定 CPU,则重新选择 pwq 并重试。
*/
if (unlikely(!pwq->refcnt)) {
if (wq->flags & WQ_UNBOUND) {
spin_unlock(&pwq->pool->lock);
cpu_relax();
goto retry;
}
// 如果绑定的 pwq 竟然 refcnt 为 0,发出警告
WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
wq->name, cpu);
}
/* pwq 已经确定,开始排队 */
trace_workqueue_queue_work(req_cpu, pwq, work);
// 如果工作项已经在队列中,发出警告并退出
if (WARN_ON(!list_empty(&work->entry))) {
spin_unlock(&pwq->pool->lock);
return;
}
// 增加该工作队列中当前工作项的数量
pwq->nr_in_flight[pwq->work_color]++;
work_flags = work_color_to_flags(pwq->work_color);
// 如果当前活跃的工作项数量小于最大允许的工作项数,将其放入正常队列
if (likely(pwq->nr_active < pwq->max_active)) {
trace_workqueue_activate_work(work);
pwq->nr_active++;
worklist = &pwq->pool->worklist;
// 如果工作队列是空的,记录当前的 jiffies(时间戳)
if (list_empty(worklist))
pwq->pool->watchdog_ts = jiffies;
} else {
// 如果活跃工作项数量已达到上限,将工作项放入延迟队列
work_flags |= WORK_STRUCT_DELAYED;
worklist = &pwq->delayed_works;
}
// 将工作项插入合适的队列(工作队列或延迟队列)
insert_work(pwq, work, worklist, work_flags);
// 解锁 pool
spin_unlock(&pwq->pool->lock);
}
insert_work:在工作队列中插入一个新的工作项
static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
struct list_head *head, unsigned int extra_flags)
{
// 获取对应的 worker pool
struct worker_pool *pool = pwq->pool;
/*
* 设置 work 的所属工作队列和附加标志信息。
* 将工作插入到 worklist 中的尾部。
*/
set_work_pwq(work, pwq, extra_flags);
list_add_tail(&work->entry, head);
// 增加工作队列的引用计数,防止其在处理中被释放
get_pwq(pwq);
/*
* 保证写入 worklist 后,后续操作能正确看到这个新添加的工作项,
* 或者确保工人线程在处理完成后能正确读取工作队列状态。
* 防止指令重排序带来的不一致。
*/
smp_mb(); // 内存屏障,确保顺序一致
/*
* 如果当前 worker pool 中的可用 worker 不足,
* 就唤醒更多的 worker 来处理工作队列中的任务。
*/
if (__need_more_worker(pool))
wake_up_worker(pool); // 唤醒工人线程
}
wake_up_worker,唤醒一个空闲的 worker
,以便其可以处理新的工作任务。:
static void wake_up_worker(struct worker_pool *pool)
{
// 获取第一个空闲的 worker
struct worker *worker = first_idle_worker(pool);
// 如果找到空闲 worker,唤醒其对应的进程
if (likely(worker))
wake_up_process(worker->task);
}
wake_up_process,唤醒其对应的进程
int wake_up_process(struct task_struct *p)
{
// 调用 try_to_wake_up 函数,尝试唤醒指定的进程 p
// 第二个参数 TASK_NORMAL 表示唤醒进程时设置的状态
// 第三个参数 0 表示没有额外的标志
return try_to_wake_up(p, TASK_NORMAL, 0);
}
**在这之后调用一系列唤醒线程函数,**图示:
workqueue处理work流程:
Concurrency Managed Workqueue之(四):workqueue如何处理work (wowotech.net)
回写任务的执行
wb_workfn()
void wb_workfn(struct work_struct *work)
{
struct bdi_writeback *wb = container_of(to_delayed_work(work),
struct bdi_writeback, dwork);
long pages_written;
// 设置工作描述
set_worker_desc("flush-%s", bdi_dev_name(wb->bdi));
// 设置当前进程标志,表示该进程正在进行交换写入操作
current->flags |= PF_SWAPWRITE;
if (likely(!current_is_workqueue_rescuer() ||
!test_bit(WB_registered, &wb->state))) {
/*
* 正常路径。持续写回 @wb 直到其 work_list 为空。
* 注意,即使 @wb 正在关闭,即使我们在紧急工人上运行,
* 也需要清空 work_list。
*/
do {
pages_written = wb_do_writeback(wb);
trace_writeback_pages_written(pages_written);
} while (!list_empty(&wb->work_list));
} else {
/*
* bdi_wq 无法获得足够的工作线程,我们在紧急工人上运行。
* 不要占用紧急工人。希望 1024 页足以进行高效的 IO。
*/
pages_written = writeback_inodes_wb(wb, 1024, WB_REASON_FORKER_THREAD);
trace_writeback_pages_written(pages_written);
}
if (!list_empty(&wb->work_list))
// 如果还有未完成的工作,继续唤醒 wb
wb_wakeup(wb);
else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
// 如果还有脏的 IO 操作,并且有设定写回间隔,延迟唤醒 wb
wb_wakeup_delayed(wb);
// 清除当前进程的交换写入标志
current->flags &= ~PF_SWAPWRITE;
}
接下来是wb_do_writeback函数:
wb_do_writeback
static long wb_do_writeback(struct bdi_writeback *wb)
{
struct wb_writeback_work *work;
long wrote = 0;
// 设置标志,表示写回正在运行
set_bit(WB_writeback_running, &wb->state);
// 获取并处理写回工作项
while ((work = get_next_work_item(wb)) != NULL) {
trace_writeback_exec(wb, work); // 记录跟踪信息
wrote += wb_writeback(wb, work); // 执行实际的写回操作
finish_writeback_work(wb, work); // 完成写回工作项
}
/*
* 检查是否有全部刷新请求
*/
wrote += wb_check_start_all(wb);
/*
* 检查周期性写回,类似于 kupdated()
*/
wrote += wb_check_old_data_flush(wb);
wrote += wb_check_background_flush(wb);
// 清除写回运行标志
clear_bit(WB_writeback_running, &wb->state);
return wrote;
}
wb_writeback:
static long wb_writeback(struct bdi_writeback *wb,
struct wb_writeback_work *work)
{
unsigned long wb_start = jiffies; // 记录写回开始时间
long nr_pages = work->nr_pages; // 初始化需要写回的页数
unsigned long dirtied_before = jiffies;
struct inode *inode;
long progress;
struct blk_plug plug;
blk_start_plug(&plug); // 开始一个块插队操作
spin_lock(&wb->list_lock);
for (;;) {
/*
* 当需要写回的页数已经被消耗完毕时,停止写回
*/
if (work->nr_pages <= 0)
break;
/*
* 背景写出和 kupdate 风格的写回可能会一直运行。
* 如果有其他工作需要处理,就停止它们,以便同步可以继续。
* 在所有其他工作完成后,它们将重新启动。
*/
if ((work->for_background || work->for_kupdate) &&
!list_empty(&wb->work_list))
break;
/*
* 对于后台写出操作,当低于背景脏阈值时停止
*/
if (work->for_background && !wb_over_bg_thresh(wb))
break;
/*
* Kupdate 和后台工作是特殊的,我们希望包含所有需要写回的 inode。
* 活锁避免通过这些工作让步于任何其他工作来处理,所以我们是安全的。
*/
if (work->for_kupdate) {
dirtied_before = jiffies -
msecs_to_jiffies(dirty_expire_interval * 10);
} else if (work->for_background) {
dirtied_before = jiffies;
}
trace_writeback_start(wb, work); // 记录写回开始的跟踪信息
if (list_empty(&wb->b_io))
queue_io(wb, work, dirtied_before);
if (work->sb)
progress = writeback_sb_inodes(work->sb, wb, work);
else
progress = __writeback_inodes_wb(wb, work);
trace_writeback_written(wb, work); // 记录写回完成的跟踪信息
wb_update_bandwidth(wb, wb_start); // 更新带宽
/*
* 我们是否写回了一些内容?尝试更多
*
* 脏的 inodes 被移动到 b_io 进行批量写回。
* 完成当前批次并不一定意味着整体工作已经完成。
* 因此,只要在清理页面或 inode 方面取得了一些进展,我们就会继续循环。
*/
if (progress)
continue;
/*
* 没有更多的 inode 进行 IO,退出
*/
if (list_empty(&wb->b_more_io))
break;
/*
* 什么都没写。等待一些 inode 可用于写回。
* 否则,我们只会忙等待。
*/
trace_writeback_wait(wb, work); // 记录等待写回的跟踪信息
inode = wb_inode(wb->b_more_io.prev);
spin_lock(&inode->i_lock);
spin_unlock(&wb->list_lock);
/* 这个函数会释放 i_lock... */
inode_sleep_on_writeback(inode);
spin_lock(&wb->list_lock);
}
spin_unlock(&wb->list_lock);
blk_finish_plug(&plug); // 完成块插队操作
return nr_pages - work->nr_pages; // 返回写回的页数
}
writeback_sb_inodes:
static long writeback_sb_inodes(struct super_block *sb,
struct bdi_writeback *wb,
struct wb_writeback_work *work)
{
// 初始化写回控制结构
struct writeback_control wbc = {
.sync_mode = work->sync_mode,
.tagged_writepages = work->tagged_writepages,
.for_kupdate = work->for_kupdate,
.for_background = work->for_background,
.for_sync = work->for_sync,
.range_cyclic = work->range_cyclic,
.range_start = 0,
.range_end = LLONG_MAX,
};
unsigned long start_time = jiffies; // 记录写回开始时间
long write_chunk;
long wrote = 0; // 统计写回的页面和 inodes 数量
// 遍历 b_io 列表中的 inode
while (!list_empty(&wb->b_io)) {
struct inode *inode = wb_inode(wb->b_io.prev);
struct bdi_writeback *tmp_wb;
// 处理不属于当前 superblock 的 inode
if (inode->i_sb != sb) {
if (work->sb) {
redirty_tail(inode, wb);
continue;
}
break;
}
// 忽略新建或正在释放的 inode
spin_lock(&inode->i_lock);
if (inode->i_state & (I_NEW | I_FREEING | I_WILL_FREE)) {
redirty_tail_locked(inode, wb);
spin_unlock(&inode->i_lock);
continue;
}
if ((inode->i_state & I_SYNC) && wbc.sync_mode != WB_SYNC_ALL) {
spin_unlock(&inode->i_lock);
requeue_io(inode, wb);
trace_writeback_sb_inodes_requeue(inode);
continue;
}
spin_unlock(&wb->list_lock);
// 等待 I_SYNC 标志被清除
if (inode->i_state & I_SYNC) {
inode_sleep_on_writeback(inode);
spin_lock(&wb->list_lock);
continue;
}
inode->i_state |= I_SYNC;
wbc_attach_and_unlock_inode(&wbc, inode);
// 获取写回块大小并设置 wbc 中需要写回的页面数
write_chunk = writeback_chunk_size(wb, work);
wbc.nr_to_write = write_chunk;
wbc.pages_skipped = 0;
// 执行单个 inode 的写回
__writeback_single_inode(inode, &wbc);
wbc_detach_inode(&wbc);
work->nr_pages -= write_chunk - wbc.nr_to_write;
wrote += write_chunk - wbc.nr_to_write;
// 检查是否需要重新调度
if (need_resched()) {
blk_flush_plug(current);
cond_resched();
}
// 如果 inode 仍然是脏的,重新排队
tmp_wb = inode_to_wb_and_lock_list(inode);
spin_lock(&inode->i_lock);
if (!(inode->i_state & I_DIRTY_ALL))
wrote++;
requeue_inode(inode, tmp_wb, &wbc);
inode_sync_complete(inode);
spin_unlock(&inode->i_lock);
// 检查是否需要切换到另一个 bdi_writeback
if (unlikely(tmp_wb != wb)) {
spin_unlock(&tmp_wb->list_lock);
spin_lock(&wb->list_lock);
}
// 检查是否达到写回条件
if (wrote) {
if (time_is_before_jiffies(start_time + HZ / 10UL))
break;
if (work->nr_pages <= 0)
break;
}
}
return wrote; // 返回写回的页面和 inodes 数量
}
__writeback_single_inode:
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));
// 开始写回单个 inode 的跟踪
trace_writeback_single_inode_start(inode, wbc, nr_to_write);
// 调用 do_writepages 进行实际的页面写回
ret = do_writepages(mapping, wbc);
/*
* 确保在写回元数据之前等待数据写回完成。
* 这对于在数据 I/O 完成时修改元数据的文件系统很重要。
* 对于 sync(2) 写回不执行此操作,因为它有一个单独的外部 IO 完成路径和 ->sync_fs 来保证 inode 元数据正确写回。
*/
if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync) {
int err = filemap_fdatawait(mapping);
if (ret == 0)
ret = err;
}
/*
* 如果 inode 有脏时间戳并且需要写回,调用 mark_inode_dirty_sync() 来通知文件系统并将 I_DIRTY_TIME 转换为 I_DIRTY_SYNC。
*/
if ((inode->i_state & I_DIRTY_TIME) &&
(wbc->sync_mode == WB_SYNC_ALL || wbc->for_sync ||
time_after(jiffies, inode->dirtied_time_when +
dirtytime_expire_interval * HZ))) {
trace_writeback_lazytime(inode);
mark_inode_dirty_sync(inode);
}
/*
* 一些文件系统可能在写回过程中由于延迟分配重新将 inode 标记为脏,清除写回 inode 前的脏元数据标志。
*/
spin_lock(&inode->i_lock);
dirty = inode->i_state & I_DIRTY;
inode->i_state &= ~dirty;
/*
* 与 __mark_inode_dirty() 中的 smp_mb() 配对。
* 这允许 __mark_inode_dirty() 在不获取 i_lock 的情况下测试 i_state - 要么它们看到 I_DIRTY 位被清除,要么我们看到脏的 inode。
*
* 即使 @mapping 仍有脏页面,I_DIRTY_PAGES 也总是一起清除。
* 如果需要,在 smp_mb() 后重新设置此标志。这保证了要么 __mark_inode_dirty() 看到清除的 I_DIRTY_PAGES,要么我们看到 PAGECACHE_TAG_DIRTY。
*/
smp_mb();
if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
inode->i_state |= I_DIRTY_PAGES;
spin_unlock(&inode->i_lock);
// 如果只有 I_DIRTY_PAGES 被设置,则不写回 inode
if (dirty & ~I_DIRTY_PAGES) {
int err = write_inode(inode, wbc);
if (ret == 0)
ret = err;
}
trace_writeback_single_inode(inode, wbc, nr_to_write);
return ret;
}
调用 do_writepages 进行实际的页面写回:
int do_writepages(struct address_space *mapping, struct writeback_control *wbc)
{
int ret;
// 如果没有页面需要写回,直接返回 0
if (wbc->nr_to_write <= 0)
return 0;
while (1) {
// 如果文件系统提供了自定义的 writepages 操作,调用它
if (mapping->a_ops->writepages)
ret = mapping->a_ops->writepages(mapping, wbc);
else
// 否则使用通用的 writepages 操作
ret = generic_writepages(mapping, wbc);
// 如果写回成功或者不是同步写回模式,则退出循环
if ((ret != -ENOMEM) || (wbc->sync_mode != WB_SYNC_ALL))
break;
// 如果由于内存不足导致写回失败,并且是同步写回模式,则调度让步并等待
cond_resched();
congestion_wait(BLK_RW_ASYNC, HZ/50);
}
return ret;
}
struct address_space:
struct address_space {
struct inode *host;
struct xarray i_pages;
gfp_t gfp_mask;
atomic_t i_mmap_writable;
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
/* number of thp, only for non-shmem files */
atomic_t nr_thps;
#endif
struct rb_root_cached i_mmap;
struct rw_semaphore i_mmap_rwsem;
unsigned long nrpages;
unsigned long nrexceptional;
pgoff_t writeback_index;
const struct address_space_operations *a_ops; //这项
unsigned long flags;
errseq_t wb_err;
spinlock_t private_lock;
struct list_head private_list;
void *private_data;
} __attribute__((aligned(sizeof(long)))) __randomize_layout;
struct address_space_operations:
struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, struct page *);
/* Write back some dirty pages from this mapping. */
int (*writepages)(struct address_space *, struct writeback_control *);
/* Set a page dirty. Return true if this dirtied it */
int (*set_page_dirty)(struct page *page);
/*
* Reads in the requested pages. Unlike ->readpage(), this is
* PURELY used for read-ahead!.
*/
int (*readpages)(struct file *filp, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages);
void (*readahead)(struct readahead_control *);
int (*write_begin)(struct file *, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata);
int (*write_end)(struct file *, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata);
/* Unfortunately this kludge is needed for FIBMAP. Don't use it */
sector_t (*bmap)(struct address_space *, sector_t);
void (*invalidatepage) (struct page *, unsigned int, unsigned int);
int (*releasepage) (struct page *, gfp_t);
void (*freepage)(struct page *);
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
/*
* migrate the contents of a page to the specified target. If
* migrate_mode is MIGRATE_ASYNC, it must not block.
*/
int (*migratepage) (struct address_space *,
struct page *, struct page *, enum migrate_mode);
bool (*isolate_page)(struct page *, isolate_mode_t);
void (*putback_page)(struct page *);
int (*launder_page) (struct page *);
int (*is_partially_uptodate) (struct page *, unsigned long,
unsigned long);
void (*is_dirty_writeback) (struct page *, bool *, bool *);
int (*error_remove_page)(struct address_space *, struct page *);
/* swapfile support */
int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
sector_t *span);
void (*swap_deactivate)(struct file *file);
};
调用相应的写回writepage或writepages。