Linux 脏页写回流程

Linux 脏页写回流程

在 Linux 系统中,write() 系统调用不会立即将数据写入磁盘,而是利用 Page Cache 进行缓冲,提高写入性能。实际的磁盘写入由内核的后台线程异步完成,从而减少用户进程的阻塞时间。下面详细分析 write() 的执行流程以及数据写回(Writeback)机制。

1. 用户进程调用 write

当用户进程调用 write(fd, buf, len) 进行文件写入时,发生以下操作:

  1. 数据从用户空间复制到内核空间的页缓存(Page Cache)
    • write() 并不会立即将数据写入磁盘,而是将数据从用户进程的缓冲区拷贝到内核页缓存
    • 页缓存是 Linux 内核用于加速文件 I/O 操作的内存区域,减少频繁的磁盘访问。
  2. write() 调用返回,用户进程继续执行
    • 由于数据仅写入页缓存,write() 系统调用很快返回,用户进程无需等待数据真正落盘。
    • 此时,数据仍然只在内存中,并未写入磁盘

此时,用户进程结束了写入操作,它不会直接参与数据的落盘过程。

2. 页缓存中的数据管理

Linux 采用延迟写入(Write-Back Cache)策略,以优化磁盘 I/O:

  • 合并多个写请求:避免小块数据频繁写入磁盘,减少 I/O 负担。
  • 延迟写入,提高吞吐量:数据在合适时机才会写回磁盘,而不是 write() 立即触发写盘。

通常,数据写入页缓存后,只有在以下情况下才会真正写入磁盘:

  1. 系统的页缓存空间不足:
    • 当可用内存减少,内核可能触发脏页回收,将部分页缓存写入磁盘。
  2. 周期性后台写回机制(内核 writeback 进程):
    • Linux 通过 pdflushkworker 内核线程定期扫描页缓存,并将脏页(Dirty Pages)写入磁盘。
  3. 用户调用 fsync()sync() 强制写入磁盘:
    • 如果应用程序需要保证数据持久化,可以调用 fsync(fd)sync(),强制将缓冲区数据同步到磁盘。

3. 内核执行数据落盘

内核有专门的机制负责将页缓存中的数据异步地写入磁盘,这个过程称为写回(writeback)

当数据需要写入磁盘时,内核执行以下步骤:

  1. 触发写回操作(Writeback):
    • kworker 进程定期扫描页缓存,选择需要写回的文件数据。
  2. DMA 方式将数据写入磁盘:
    • DMA 控制器负责将数据从内核页缓存传输到磁盘存储设备,避免 CPU 直接参与数据搬运,提高效率。
  3. 写入完成,释放页缓存:
    • 数据成功写入磁盘后,内核释放相应的页缓存,使其可用于新的写入操作。

4. 缓存和进程之间的交互

  1. Linux 采用异步写入的方式优化性能:
    • 用户进程:执行 write() 仅写入缓存,立即返回,继续运行。
    • 内核进程:负责异步写入磁盘,writebackkworker 线程执行,与用户进程无关。

由于数据写回操作由独立的内核线程执行,而非用户进程本身,因此用户进程的 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;![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/73872c4869f045b792deb08e4a4ac40a.webp#pic_center)

	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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值