From nobody Fri Sep 25 22:18:48 2026 Received: from va-2-114.ptr.blmpb.com (va-2-114.ptr.blmpb.com [209.127.231.114]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8271C57C9F for ; Tue, 8 Sep 2026 03:24:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.127.231.114 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788837878; cv=none; b=YPF/XyfwZ3gcNsxdNkiHG1tWZOzVpSv6I+wEHA/INToVRRgFxRZjL4gCfhPNeX5ud6GByrmHp4wu01jFfLDKFw3q4Gi400oQCzhMKLUHkgdEvAhaCO2+LmLM+1F+6quooMJDCEdDTGqPfBGtEw7hZPMLmwyL9Mo2FiC1KnQZHp4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788837878; c=relaxed/simple; bh=yT+tSgRjiYfs5SQ3x6TOwrKSbFTMyyzh7O4DrLQxvTA=; h=Cc:From:Date:References:Content-Type:Subject:Message-Id: In-Reply-To:To:Mime-Version; b=n8yk45OiIOzasT3v6PbuCkC2t0TdBXxATlgBxpFxw7iBYDJTNVb08u0NJIweL3Au7cVup7EGmwOAWz1Wuj2Bj59zteqg5isM/7/zGgyAL3FfsQGkSc9442lX1NbApoDcyxvHTd9jqu42FAT6evP93sGJ5MatV83v3OEpwcvrqqw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=bytedance.com; spf=pass smtp.mailfrom=bytedance.com; dkim=pass (2048-bit key) header.d=bytedance.com header.i=@bytedance.com header.b=j/Ah7/gp; arc=none smtp.client-ip=209.127.231.114 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=bytedance.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bytedance.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bytedance.com header.i=@bytedance.com header.b="j/Ah7/gp" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=2212171451; d=bytedance.com; t=1788837872; h=from:subject: mime-version:from:date:message-id:subject:to:cc:reply-to:content-type: mime-version:in-reply-to:message-id; bh=MQ8UTCfaUbpeU5nW9CBGIYMP3Op7scW9OCp3arXnRiA=; b=j/Ah7/gpXkF/LSCivJwhRZlTT062F1Cd2Z0pxP/YKYSFIf1MWgIUyBF4sXSIZBbyVZLZVr cU3Yy8ATZj/fXE2n1InCIaUCK74T4LE6t1kVWTB+E+Ou1jVFizw+eyTcZHTJ71tiUAfzi0 p/lj1dHMfVdfUnKwvi+1wL45VZdlAcVI8HdtYnSWHHi0Z3uO7Iq2BU1fs+zigGeo3ndvES IGZl/o1W0FlcVgUJhdDuC5xApxQP39f19leSJc/ik2JpZuWeHKY/ybsZtnnWNWTniUDNq+ h4t5ki8Ja3ogwDzsZj9xEAfMo4K8NlEoYty4AEcFrs+KE7SaqXwuteQEFANt4A== Cc: "Tejun Heo" , "Jens Axboe" , , , "Xin Yin" From: "Xin Yin" X-Mailer: git-send-email 2.20.1 Date: Tue, 8 Sep 2026 11:23:51 +0800 X-Lms-Return-Path: References: Subject: [PATCH v1 1/2] writeback: size foreign flushes by target wb dirty pages Message-Id: In-Reply-To: X-Original-From: Xin Yin To: "Alexander Viro" , "Christian Brauner" , "Jan Kara" Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Foreign dirty tracking records the bdi and wb memcg IDs of recently dirtied foreign inodes. When the source memcg needs foreign writeback, mem_cgroup_flush_foreign() queues WB_REASON_FOREIGN_FLUSH to the recorded target bdi_writeback. cgroup_writeback_by_id() currently sizes this best-effort work from the target memcg's NR_FILE_DIRTY counter. The work is scoped to one target wb, so a memcg-wide dirty count can over-size a single-bdi flush when the target memcg has dirty pages on other devices. It can also under-size the flush when the target wb has dirty pages charged to other memcgs. Over-sizing keeps the target wb busy longer and can delay later writeback work. Under-sizing can finish before enough pages are written back for the source memcg. Use the target wb's WB_RECLAIMABLE counter and keep the existing 25% headroom. This keeps the budget aligned with the writeback object being queued. Fixes: 97b27821b485 ("writeback, memcg: Implement foreign dirty flushing") Signed-off-by: Xin Yin Reviewed-by: Jan Kara --- fs/fs-writeback.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 7c75ed7e8979..7c2340a5dead 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -1131,7 +1131,7 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id, struct cgroup_subsys_state *memcg_css; struct bdi_writeback *wb; struct wb_writeback_work *work; - unsigned long dirty; + long dirty; int ret; =20 /* lookup bdi and memcg */ @@ -1160,16 +1160,13 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id, } =20 /* - * The caller is attempting to write out most of - * the currently dirty pages. Let's take the current dirty page - * count and inflate it by 25% which should be large enough to - * flush out most dirty pages while avoiding getting livelocked by - * concurrent dirtiers. - * - * BTW the memcg stats are flushed periodically and this is best-effort - * estimation, so some potential error is ok. + * The caller is attempting to write out most of the target wb's + * currently dirty pages. Size the work from the wb's reclaimable pages + * and inflate the count by 25%, which should be large enough to flush + * out most dirty pages while avoiding getting livelocked by concurrent + * dirtiers. */ - dirty =3D memcg_page_state(mem_cgroup_from_css(memcg_css), NR_FILE_DIRTY); + dirty =3D wb_stat_sum(wb, WB_RECLAIMABLE); dirty =3D dirty * 10 / 8; =20 /* issue the writeback work */ --=20 2.20.1 From nobody Fri Sep 25 22:18:48 2026 Received: from va-2-114.ptr.blmpb.com (va-2-114.ptr.blmpb.com [209.127.231.114]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CC39357C9F for ; Tue, 8 Sep 2026 03:25:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.127.231.114 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788837910; cv=none; b=q/N+lo7HB0GJr/9CcJ8+1gOdziJpOWhqrzhJZGjl683OA8DKuynIGfrsFvyTASL53OVXYgYeXrCpH2EW7BaJofn1nS94xuTDinCBRJA1h2Z+uT4MIOYndlL9ri+cFcpeX92L1Usf6VavR2sb0dhNR4rcnUyDW3dpAgsuxSy8T2A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788837910; c=relaxed/simple; bh=EeOHWA/uz2on4QaoEb543EhpLFsgPbddAgUp7QPAoV4=; h=To:Cc:Date:Message-Id:References:From:Subject:In-Reply-To: Content-Type:Mime-Version; b=g9DXxezhBGvOIoJyV86GSffqu9+QavE0MkW1yPGV32Qm8WmrLIQinwi7tLVmTeYIm4Ed2Y9uCGPS69ggr/TitX4V7StxCELIMd3JKHvtYpotc1XBKYYi4lSsETbRn932/pbOuNv7mrY9Xn87blrgiET3E6Hf6EI7vZMa9Q/1xjg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=bytedance.com; spf=pass smtp.mailfrom=bytedance.com; dkim=pass (2048-bit key) header.d=bytedance.com header.i=@bytedance.com header.b=jcPGHiyz; arc=none smtp.client-ip=209.127.231.114 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=bytedance.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bytedance.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bytedance.com header.i=@bytedance.com header.b="jcPGHiyz" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=2212171451; d=bytedance.com; t=1788837905; h=from:subject: mime-version:from:date:message-id:subject:to:cc:reply-to:content-type: mime-version:in-reply-to:message-id; bh=4BEbrlj73U5B9IMw8FD0WKET6Y4SVezy7GRLjv6u1WQ=; b=jcPGHiyz2eV2SqDS+BmH2K+Tm8P5QuKR8vHqmVpMgKH5ldyaHrTediVZDw0YYnC1zrWI+l blVfsZCfy/DnLjJvTmR1GZlc8Kwc3xClWVnvJQG3nQZIYIZ3Y2zmyjFDksMHvIQBO9JWAv aBxWJTHva5YGUr9EAQkPAJwyQ+2QokFctB54lLbG9JFxqRC1PK3hZgTjXQOI99wzMSG9S2 nrruLkouHwmHVb9RjvIA8MhRG0UHGu0yyzldHAAW+S/iRktC2YRi8MUvX6BuwpRlMnGIk7 6rsHj07fr5HPAJH6zG2IiBgEdqZgUz8Vp8Hv9FQTUGUQ7qoq/0aigCUKwX6ayg== X-Mailer: git-send-email 2.20.1 To: "Alexander Viro" , "Christian Brauner" , "Jan Kara" Cc: "Tejun Heo" , "Jens Axboe" , , , "Xin Yin" Date: Tue, 8 Sep 2026 11:23:52 +0800 Message-Id: Content-Transfer-Encoding: quoted-printable References: From: "Xin Yin" Subject: [PATCH v1 2/2] writeback: snapshot foreign flush pages X-Lms-Return-Path: In-Reply-To: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 X-Original-From: Xin Yin Content-Type: text/plain; charset="utf-8" WB_REASON_FOREIGN_FLUSH is best-effort writeback for foreign dirtying. It should write pages that are already visible to the target wb, but should not keep extending the same work with pages dirtied while the work is running. Make foreign flushes use tagged writeback while preserving WB_SYNC_NONE semantics. Use the remaining work budget as the per-inode chunk, so a tagged pass is not truncated to the normal periodic chunk and a large inode still cannot exceed the finite work budget. Queue b_dirty only once for each foreign-flush work. Later passes only retry b_more_io inodes skipped due to I_SYNC, and the work exits once b_io and b_more_io are drained. If the budget is consumed, redirty the inode instead of requeueing it to b_more_io so the same work cannot loop on a continuously dirtied inode. Do not refresh dirtied_when for foreign flushes; later dirtying should be handled by later foreign, kupdate, or background writeback. Fixes: 97b27821b485 ("writeback, memcg: Implement foreign dirty flushing") Signed-off-by: Xin Yin --- fs/fs-writeback.c | 57 ++++++++++++++++++++++++++++++--------- include/linux/writeback.h | 1 + 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 7c2340a5dead..6c5d1c85b6f8 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -40,6 +40,7 @@ struct wb_writeback_work { struct super_block *sb; enum writeback_sync_modes sync_mode; unsigned int tagged_writepages:1; + unsigned int for_foreign_flush:1; unsigned int for_kupdate:1; unsigned int range_cyclic:1; unsigned int for_background:1; @@ -1174,6 +1175,12 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id, if (work) { work->nr_pages =3D dirty; work->sync_mode =3D WB_SYNC_NONE; + /* + * Foreign flushes should write a snapshot of dirty pages without + * chasing concurrent dirtiers, but still honor the finite target + * wb budget calculated above. + */ + work->for_foreign_flush =3D 1; work->range_cyclic =3D 1; work->reason =3D reason; work->done =3D done; @@ -1553,18 +1560,21 @@ static int move_expired_inodes(struct list_head *de= laying_queue, * +--> dequeue for IO */ static void queue_io(struct bdi_writeback *wb, struct wb_writeback_work *w= ork, - unsigned long dirtied_before) + unsigned long dirtied_before, bool queue_dirty) { - int moved; + int moved =3D 0; unsigned long time_expire_jif =3D dirtied_before; =20 assert_spin_locked(&wb->list_lock); list_splice_init(&wb->b_more_io, &wb->b_io); - moved =3D move_expired_inodes(&wb->b_dirty, &wb->b_io, dirtied_before); - if (!work->for_sync) - time_expire_jif =3D jiffies - dirtytime_expire_interval * HZ; - moved +=3D move_expired_inodes(&wb->b_dirty_time, &wb->b_io, - time_expire_jif); + if (queue_dirty) { + moved =3D move_expired_inodes(&wb->b_dirty, &wb->b_io, + dirtied_before); + if (!work->for_sync) + time_expire_jif =3D jiffies - dirtytime_expire_interval * HZ; + moved +=3D move_expired_inodes(&wb->b_dirty_time, &wb->b_io, + time_expire_jif); + } if (moved) wb_io_lists_populated(wb); trace_writeback_queue_io(wb, work, dirtied_before, moved); @@ -1651,11 +1661,13 @@ static void requeue_inode(struct inode *inode, stru= ct bdi_writeback *wb, =20 /* * Sync livelock prevention. Each inode is tagged and synced in one - * shot. If still dirty, it will be redirty_tail()'ed below. Update - * the dirty time to prevent enqueue and sync it again. + * shot for WB_SYNC_ALL or unbudgeted tagged writeback. If still dirty, + * it will be redirty_tail()'ed below. Update the dirty time to prevent + * enqueue and sync it again. */ if ((inode_state_read(inode) & I_DIRTY) && - (wbc->sync_mode =3D=3D WB_SYNC_ALL || wbc->tagged_writepages)) + (wbc->sync_mode =3D=3D WB_SYNC_ALL || + (wbc->tagged_writepages && !wbc->for_foreign_flush))) inode->dirtied_when =3D jiffies; =20 if (wbc->pages_skipped) { @@ -1678,6 +1690,7 @@ static void requeue_inode(struct inode *inode, struct= bdi_writeback *wb, * sometimes bales out without doing anything. */ if (wbc->nr_to_write <=3D 0 && + !wbc->for_foreign_flush && !inode_dirtied_after(inode, dirtied_before)) { /* Slice used up. Queue for next turn. */ requeue_io(inode, wb); @@ -1918,6 +1931,8 @@ static long writeback_chunk_size(struct super_block *= sb, * (quickly) tag currently dirty pages * (maybe slowly) sync all tagged pages */ + if (work->for_foreign_flush) + return work->nr_pages; if (work->sync_mode =3D=3D WB_SYNC_ALL || work->tagged_writepages) return LONG_MAX; =20 @@ -1943,7 +1958,9 @@ static long writeback_sb_inodes(struct super_block *s= b, { struct writeback_control wbc =3D { .sync_mode =3D work->sync_mode, - .tagged_writepages =3D work->tagged_writepages, + .tagged_writepages =3D work->tagged_writepages || + work->for_foreign_flush, + .for_foreign_flush =3D work->for_foreign_flush, .for_kupdate =3D work->for_kupdate, .for_background =3D work->for_background, .for_sync =3D work->for_sync, @@ -2141,7 +2158,7 @@ static long writeback_inodes_wb(struct bdi_writeback = *wb, long nr_pages, blk_start_plug(&plug); spin_lock(&wb->list_lock); if (list_empty(&wb->b_io)) - queue_io(wb, &work, jiffies); + queue_io(wb, &work, jiffies, true); __writeback_inodes_wb(wb, &work); spin_unlock(&wb->list_lock); blk_finish_plug(&plug); @@ -2202,6 +2219,13 @@ static long wb_writeback(struct bdi_writeback *wb, =20 spin_lock(&wb->list_lock); =20 + if (queued && work->for_foreign_flush && + list_empty(&wb->b_io) && + list_empty(&wb->b_more_io)) { + spin_unlock(&wb->list_lock); + break; + } + trace_writeback_start(wb, work); if (list_empty(&wb->b_io)) { /* @@ -2217,7 +2241,14 @@ static long wb_writeback(struct bdi_writeback *wb, } else if (work->for_background) dirtied_before =3D jiffies; =20 - queue_io(wb, work, dirtied_before); + /* + * After the initial queue_io() pass, a foreign flush may + * still have I_SYNC-skipped inodes on b_more_io. Move + * those back to b_io without selecting another batch from + * b_dirty. + */ + queue_io(wb, work, dirtied_before, + !work->for_foreign_flush || !queued); queued =3D true; } if (work->sb) diff --git a/include/linux/writeback.h b/include/linux/writeback.h index e530112c4b3a..096f0963d104 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -59,6 +59,7 @@ struct writeback_control { unsigned for_kupdate:1; /* A kupdate writeback */ unsigned for_background:1; /* A background writeback */ unsigned tagged_writepages:1; /* tag-and-write to avoid livelock */ + unsigned for_foreign_flush:1; /* foreign dirty flushing */ unsigned range_cyclic:1; /* range_start is cyclic */ unsigned for_sync:1; /* sync(2) WB_SYNC_ALL writeback */ unsigned unpinned_netfs_wb:1; /* Cleared I_PINNING_NETFS_WB */ --=20 2.20.1