From nobody Thu Sep 24 12:53:02 2026 Received: from canpmsgout05.his.huawei.com (canpmsgout05.his.huawei.com [113.46.200.220]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5AB69425CF5; Thu, 24 Sep 2026 07:36:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.220 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235371; cv=none; b=C0e7g/wGP4WGWloylKJcFmXwjUMXcJwNTXh/5cx6Fb/Rgg+SVnO/tN0S3OI1dCwpkbsVruT2ryuOEeg3BB7bjMG4gftPMHiJE4BeuPl2MrNRq15zUtGjgamxa/yXTGQLgsDfbufcOlgBwIN3yujxYKsS06oJpx+MNkL0N3d3NGw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235371; c=relaxed/simple; bh=qLMuBNDv/svf+MiW5d7OvRC3JAJu/KsobwYEPuU9GKw=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=EMIudCESPAAhJvI0Ll7aXIEpuoEKMK4/JCQ31tcoFBs7/J8+3uOTNUW65WAmrI/bDOu2S9AjkH80Ek+wGI5WYKIvqzW7uXUDfRVoojKR3s+9yu+DxacU+BZwiWVST2tXfGIZBUNgxQAu8ZdygH9m8joV33BxS7OnGU7WCpvlumc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=3MHOaEjX; arc=none smtp.client-ip=113.46.200.220 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="3MHOaEjX" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=u7DT9d0o2PJbDVblTMIhdGCmIjcogXOH+bce+7uHYoc=; b=3MHOaEjXwApnCXjTQr9sd8oV7q4dPnaLTKAG1dKNGeDzIrP7QU8pLU8EqMrs+h1YjDn1SpsFA RDi+6jdV0+D/zGnTOnRZzqyHEA7PlvzBzbH1/9ja/bIVpF+budAQZVoJlq7YqTecWc83PMEuUf9 hmBZhFs60IVxgSBJI/iy+Zg= Received: from mail.maildlp.com (unknown [172.19.162.197]) by canpmsgout05.his.huawei.com (SkyGuard) with ESMTPS id 4hr5193ZJ3z12LHt; Thu, 24 Sep 2026 15:23:57 +0800 (CST) Received: from whupemk100010.china.huawei.com (unknown [7.152.184.41]) by mail.maildlp.com (Postfix) with ESMTPS id 1C6204057D; Thu, 24 Sep 2026 15:36:03 +0800 (CST) Received: from octopus.huawei.com (10.67.174.191) by whupemk100010.china.huawei.com (7.152.184.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Thu, 24 Sep 2026 15:36:00 +0800 From: Cai Xinchen To: , , , , , , , , , , , , , , , , , , , , CC: , , , , , , , , Subject: [PATCH RFC -next 1/5] net: charge socket memory budget to memcg upfront Date: Thu, 24 Sep 2026 16:02:15 +0800 Message-ID: <20260924080219.1036588-2-caixinchen1@huawei.com> X-Mailer: git-send-email 2.18.0.huawei.25 In-Reply-To: <20260924080219.1036588-1-caixinchen1@huawei.com> References: <20260924080219.1036588-1-caixinchen1@huawei.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To whupemk100010.china.huawei.com (7.152.184.41) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Socket memory is currently charged to the memory cgroup per grant: __sk_mem_schedule() charges pages when it publishes forward allocation and the matching refunds are issued later from skb destructors via sk_mem_reclaim()/udp_rmem_release(). The refund side has to fold per-skb "was this charged to the memcg" snapshots back into the socket balance under concurrent lockless RMW, and races there can drive the memcg socket balance negative, ending with: page_counter underflow WARNING: ... mm/page_counter.c ... page_counter_cancel() Rebalance the model: charge the socket's whole memory budget (sk_sndbuf + sk_rcvbuf + sk_reserved_mem, i.e. everything sk_forward_alloc can possibly hand out) to the socket's memcg when the budget is established or grows, and refund it when the budget shrinks or the socket is destroyed. Grants and per-skb charge/uncharge no longer touch the memcg at all, so the racy refund pairing simply has no code left to go wrong. Global protocol accounting (memory_allocated, tcp_mem pressure) is untouched. A new sock field, sk_memcg_budget, tracks the amount currently charged and upholds the invariant "every charged page is accounted in the tracker exactly once, and every tracker page is refundable exactly once": - growth charges first and only publishes after the charge succeeded (a failed charge merely under-accounts; the socket operation still succeeds and a later budget event retries the full delta), - shrink claims the excess under sk_memcg_budget_lock and refunds only the confirmed excess, - sk_memcg_budget_release() zeroes the tracker under the lock for the final refund in __sk_destruct(), and - sk_mem_reclaim() runs a lazy shrink-only sync (via sk_memcg_budget_shrink(), which claims the excess under the lock) so budget shrinks performed by writers without an explicit hook are still refunded; unhooked growth merely under-accounts. sk_memcg_budget_lock is a leaf lock taken with _bh (the release runs from RCU callbacks); the memcg charge and uncharge run outside of it because they may sleep (memcg reclaim). Concurrent growths can transiently over-count (the next shrink refunds the excess) but refunds can never exceed charges, so the memcg balance cannot underflow. Budget charge points hooked here: sock_init_data(), SO_SNDBUF/SO_SNDBUFFORCE and SO_RCVBUF/SO_RCVBUFFORCE, SO_RESERVE_MEM (the reservation now folds into the budget tracker), sk_clone() (children start with a zero tracker), __sk_charge() (accepted sockets charge their full budget instead of the pre-accept queue pages) and __sk_destruct(). The memcg charge in sk_forced_mem_schedule() is dropped accordingly, and sk_stream_moderate_sndbuf() propagates its pressure shrink to the tracker. Charging the budget instead of in-flight pages is also the more accurate accounting: once sk_sndbuf/sk_rcvbuf/sk_reserved_mem are established, that memory is effectively allocated to the socket. The protocol fills the buffers on demand without asking again, and no other socket in the cgroup can spend that capacity, so the memcg "sock" counter now reports memory the sockets already own rather than memory that happens to be in flight at the sampling instant. This is the same model vm_committed_as uses for the address space a process claims at mmap() time and hugetlb uses for reservations, which are charged at creation rather than at first touch; the visible consequence is that an idle socket holds its full budget charged for its lifetime. Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen --- include/net/sock.h | 21 ++++ net/core/sock.c | 237 ++++++++++++++++++++++++++++++++++++--------- net/ipv4/tcp.c | 6 +- 3 files changed, 215 insertions(+), 49 deletions(-) diff --git a/include/net/sock.h b/include/net/sock.h index 51185222aac2..c6c6294817d1 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -458,6 +458,15 @@ struct sock { __cacheline_group_begin(sock_read_rxtx); int sk_err; struct socket *sk_socket; + /* Pages of the socket's memory budget (sndbuf + rcvbuf + + * SO_RESERVE_MEM) currently charged to the memcg. Paired with + * every mem_cgroup_sk_charge()/mem_cgroup_sk_uncharge() done on + * behalf of this socket, so it can be refunded exactly once. + * Guarded by sk_memcg_budget_lock; the charge/uncharge run + * outside the lock (they may sleep/reclaim). + */ + int sk_memcg_budget; + spinlock_t sk_memcg_budget_lock; #ifdef CONFIG_MEMCG struct mem_cgroup *sk_memcg; #endif @@ -1553,6 +1562,9 @@ int __sk_mem_raise_allocated(struct sock *sk, int siz= e, int amt, int kind); int __sk_mem_schedule(struct sock *sk, int size, int kind); void __sk_mem_reduce_allocated(struct sock *sk, int amount); void __sk_mem_reclaim(struct sock *sk, int amount); +bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp); +void sk_memcg_budget_shrink(struct sock *sk); +void sk_memcg_budget_release(struct sock *sk); =20 #define SK_MEM_SEND 0 #define SK_MEM_RECV 1 @@ -1622,6 +1634,12 @@ static inline void sk_mem_reclaim(struct sock *sk) if (!sk_has_account(sk)) return; =20 + /* Return the part of the memcg budget charge that a budget + * shrink (by any writer) no longer backs. Never charges, so + * it is safe to call locklessly from skb destructors. + */ + sk_memcg_budget_shrink(sk); + reclaimable =3D sk->sk_forward_alloc - sk_unused_reserved_mem(sk); =20 if (reclaimable >=3D (int)PAGE_SIZE) @@ -2638,6 +2656,9 @@ static inline void sk_stream_moderate_sndbuf(struct s= ock *sk) val =3D max_t(u32, val, sk_unused_reserved_mem(sk)); =20 WRITE_ONCE(sk->sk_sndbuf, max_t(u32, val, SOCK_MIN_SNDBUF)); + + /* The sndbuf shrink released part of the memcg budget charge. */ + sk_memcg_budget_shrink(sk); } =20 /** diff --git a/net/core/sock.c b/net/core/sock.c index 1d5927cd49a1..d7bdb8bae3ad 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -985,6 +985,9 @@ static void __sock_set_rcvbuf(struct sock *sk, int val) */ WRITE_ONCE(sk->sk_rcvbuf, max_t(int, val * 2, SOCK_MIN_RCVBUF)); =20 + /* Charge the new budget to the memcg (or refund the released one). */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); + if (sock) { const struct proto_ops *ops =3D READ_ONCE(sock->ops); =20 @@ -1025,12 +1028,13 @@ static void sock_release_reserved_memory(struct soc= k *sk, int bytes) WARN_ON(bytes > sk->sk_reserved_mem); WRITE_ONCE(sk->sk_reserved_mem, sk->sk_reserved_mem - bytes); sk_mem_reclaim(sk); + /* The released reservation shrank the memcg budget charge. */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); } =20 static int sock_reserve_memory(struct sock *sk, int bytes) { long allocated; - bool charged; int pages; =20 if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk)) @@ -1041,12 +1045,6 @@ static int sock_reserve_memory(struct sock *sk, int = bytes) =20 pages =3D sk_mem_pages(bytes); =20 - /* pre-charge to memcg */ - charged =3D mem_cgroup_sk_charge(sk, pages, - GFP_KERNEL | __GFP_RETRY_MAYFAIL); - if (!charged) - return -ENOMEM; - if (sk->sk_bypass_prot_mem) goto success; =20 @@ -1059,16 +1057,29 @@ static int sock_reserve_memory(struct sock *sk, int= bytes) */ if (allocated > sk_prot_mem_limits(sk, 1)) { sk_memory_allocated_sub(sk, pages); - mem_cgroup_sk_uncharge(sk, pages); return -ENOMEM; } =20 success: - sk_forward_alloc_add(sk, pages << PAGE_SHIFT); - + /* The reservation joins the socket budget, so publish it before + * syncing the memcg charge and roll it back if the sync fails. + * The forward_alloc credit is committed last, after all fallible + * steps: softirq reclaim may fold it back into the protocol + * counter, and that cannot be rolled back. + */ WRITE_ONCE(sk->sk_reserved_mem, sk->sk_reserved_mem + (pages << PAGE_SHIFT)); =20 + if (!sk_memcg_budget_sync(sk, GFP_KERNEL | __GFP_RETRY_MAYFAIL)) { + WRITE_ONCE(sk->sk_reserved_mem, + sk->sk_reserved_mem - (pages << PAGE_SHIFT)); + if (!sk->sk_bypass_prot_mem) + sk_memory_allocated_sub(sk, pages); + return -ENOMEM; + } + + sk_forward_alloc_add(sk, pages << PAGE_SHIFT); + return 0; } =20 @@ -1348,6 +1359,10 @@ int sk_setsockopt(struct sock *sk, int level, int op= tname, sk->sk_userlocks |=3D SOCK_SNDBUF_LOCK; WRITE_ONCE(sk->sk_sndbuf, max_t(int, val * 2, SOCK_MIN_SNDBUF)); + /* Charge the new budget to the memcg (or refund the + * released one). + */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); /* Wake up sending tasks if we upped the value. */ sk->sk_write_space(sk); break; @@ -2318,6 +2333,7 @@ struct sock *sk_alloc(struct net *net, int family, gf= p_t priority, =20 sk->sk_kern_sock =3D kern; sock_lock_init(sk); + spin_lock_init(&sk->sk_memcg_budget_lock); =20 sk->sk_net_refcnt =3D kern ? 0 : 1; if (likely(sk->sk_net_refcnt)) { @@ -2355,6 +2371,12 @@ static void __sk_destruct(struct rcu_head *head) if (sk->sk_destruct) sk->sk_destruct(sk); =20 + /* All queues are purged and no reference is left: return the + * remaining memcg budget charge exactly once, before the memcg + * association is dropped by sk_prot_free() below. + */ + sk_memcg_budget_release(sk); + filter =3D rcu_dereference_check(sk->sk_filter, refcount_read(&sk->sk_wmem_alloc) =3D=3D 0); if (filter) { @@ -2534,6 +2556,12 @@ struct sock *sk_clone(const struct sock *sk, const g= fp_t priority, newsk->sk_wmem_queued =3D 0; newsk->sk_forward_alloc =3D 0; newsk->sk_reserved_mem =3D 0; + /* The child has not charged anything to the memcg yet: it will be + * charged for its budget at accept() time (__sk_charge()). + * sock_copy() copied the parent's lock bytes: re-init the lock. + */ + newsk->sk_memcg_budget =3D 0; + spin_lock_init(&newsk->sk_memcg_budget_lock); DEBUG_NET_WARN_ON_ONCE(newsk->sk_drop_counters); sk_drops_reset(newsk); newsk->sk_send_head =3D NULL; @@ -3327,15 +3355,12 @@ EXPORT_SYMBOL(sk_wait_data); * * Similar to __sk_mem_schedule(), but does not update sk_forward_alloc. * - * Unlike the globally shared limits among the sockets under same protocol, - * consuming the budget of a memcg won't have direct effect on other ones. - * So be optimistic about memcg's tolerance, and leave the callers to deci= de - * whether or not to raise allocated through sk_under_memory_pressure() or - * its variants. + * Charges the global protocol counter only. The memcg is charged for + * the socket's full memory budget when that budget is established or + * resized (see sk_memcg_budget_sync()), not per allocation. */ int __sk_mem_raise_allocated(struct sock *sk, int size, int amt, int kind) { - bool memcg_enabled =3D false, charged =3D false; struct proto *prot =3D sk->sk_prot; long allocated =3D 0; =20 @@ -3344,13 +3369,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int si= ze, int amt, int kind) allocated =3D sk_memory_allocated(sk); } =20 - if (mem_cgroup_sk_enabled(sk)) { - memcg_enabled =3D true; - charged =3D mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge()); - if (!charged) - goto suppress_allocation; - } - if (!allocated) return 1; =20 @@ -3421,13 +3439,8 @@ int __sk_mem_raise_allocated(struct sock *sk, int si= ze, int amt, int kind) /* Fail only if socket is _under_ its sndbuf. * In this case we cannot block, so that we have to fail. */ - if (sk->sk_wmem_queued + size >=3D sk->sk_sndbuf) { - /* Force charge with __GFP_NOFAIL */ - if (memcg_enabled && !charged) - mem_cgroup_sk_charge(sk, amt, - gfp_memcg_charge() | __GFP_NOFAIL); + if (sk->sk_wmem_queued + size >=3D sk->sk_sndbuf) return 1; - } } =20 trace_sock_exceed_buf_limit(sk, prot, allocated, kind); @@ -3435,9 +3448,6 @@ int __sk_mem_raise_allocated(struct sock *sk, int siz= e, int amt, int kind) if (allocated) sk_memory_allocated_sub(sk, amt); =20 - if (charged) - mem_cgroup_sk_uncharge(sk, amt); - return 0; } =20 @@ -3468,13 +3478,12 @@ EXPORT_SYMBOL(__sk_mem_schedule); * @sk: socket * @amount: number of quanta * - * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc + * Similar to __sk_mem_reclaim(), but does not update sk_forward_alloc. + * Only returns pages to the global protocol counter; the memcg charge + * follows the socket budget, see sk_memcg_budget_sync(). */ void __sk_mem_reduce_allocated(struct sock *sk, int amount) { - if (mem_cgroup_sk_enabled(sk)) - mem_cgroup_sk_uncharge(sk, amount); - if (sk->sk_bypass_prot_mem) return; =20 @@ -3498,20 +3507,150 @@ void __sk_mem_reclaim(struct sock *sk, int amount) } EXPORT_SYMBOL(__sk_mem_reclaim); =20 -void __sk_charge(struct sock *sk, gfp_t gfp) +/* Pages of the socket's currently established memory budget: the send + * and receive buffers plus the SO_RESERVE_MEM reservation. + */ +static int sk_memcg_budget_pages(const struct sock *sk) { - int amt; + long budget =3D READ_ONCE(sk->sk_sndbuf) + READ_ONCE(sk->sk_rcvbuf) + + READ_ONCE(sk->sk_reserved_mem); =20 - gfp |=3D __GFP_NOFAIL; - if (mem_cgroup_from_sk(sk)) { - /* The socket has not been accepted yet, no need - * to look at newsk->sk_wmem_queued. + return (int)DIV_ROUND_UP(budget, PAGE_SIZE); +} + +/** + * sk_memcg_budget_sync - charge the socket budget to the memcg + * @sk: socket + * @gfp: reclaim mode for the memcg charge + * + * Make the memcg charge match the socket's current budget: charge the + * difference when the budget grew, refund it when the budget shrank. + * + * Growth: the memcg is charged first and only a successful charge is + * accounted in sk->sk_memcg_budget. A failed charge leaves the tracker + * unchanged: the new budget is used uncharged (the safe direction) and + * a later call retries the whole difference. + * + * Concurrency: budget growth usually runs under the socket lock, but + * some setsockopt(2) paths run without it, and shrinks may run + * locklessly (sk_mem_reclaim() from skb destructors), so the tracker + * is a plain int guarded by sk_memcg_budget_lock. The lock is a leaf + * lock taken with _bh (sk_memcg_budget_release() runs from RCU + * callbacks); the memcg charge and uncharge run outside of it because + * they may sleep (memcg reclaim). Charging before publishing the + * tracker, and refunding only the excess claimed under the lock, keep + * every charged page accounted in the tracker exactly once and every + * tracker page refundable exactly once: concurrent growths can + * transiently over-count (the next shrink refunds the excess), while + * cumulative refunds can never exceed cumulative charges, so the + * memcg balance can not underflow. + * + * Returns false when a growth charge failed. + */ +bool sk_memcg_budget_sync(struct sock *sk, gfp_t gfp) +{ + int want, have, delta; + + if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk)) + return true; + + /* The snapshot is unlocked on purpose: the memcg charge below + * may sleep, so the lock cannot be held across it, and a + * lock-coherent snapshot would go stale the same way. + */ + want =3D sk_memcg_budget_pages(sk); + have =3D READ_ONCE(sk->sk_memcg_budget); + + if (want > have) { + delta =3D want - have; + if (!mem_cgroup_sk_charge(sk, delta, gfp)) + return false; + + /* Add the full @delta unconditionally: every charged page + * must land in the tracker exactly once, so the release + * can refund it exactly once. @have may be stale, the + * resulting over-count is refunded by the next shrink. */ - amt =3D sk_mem_pages(sk->sk_forward_alloc + - atomic_read(&sk->sk_rmem_alloc)); - if (amt) - mem_cgroup_sk_charge(sk, amt, gfp); + spin_lock_bh(&sk->sk_memcg_budget_lock); + WRITE_ONCE(sk->sk_memcg_budget, sk->sk_memcg_budget + delta); + spin_unlock_bh(&sk->sk_memcg_budget_lock); + } else if (want < have) { + sk_memcg_budget_shrink(sk); + } + + return true; +} +EXPORT_SYMBOL(sk_memcg_budget_sync); + +/** + * sk_memcg_budget_shrink - refund budget shrinks to the memcg + * @sk: socket + * + * Return the part of the memcg charge that is no longer backed by the + * socket's budget. Never charges and never fails, so it is safe to + * call from arbitrary (including lockless softirq) contexts, e.g. + * from sk_mem_reclaim() to catch budget shrinks done by writers the + * budget sync is not hooked into. + */ +void sk_memcg_budget_shrink(struct sock *sk) +{ + int want, refund =3D 0; + + if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk)) + return; + + /* Claim the excess under the lock, then refund it outside: + * the memcg uncharge may sleep, and the claim guarantees each + * tracker page is refunded exactly once. @want follows the + * socket buffers, which are protected by the socket lock, not + * the budget lock; a claim made stale by a concurrent budget + * update is corrected by the next sync. + */ + spin_lock_bh(&sk->sk_memcg_budget_lock); + want =3D sk_memcg_budget_pages(sk); + if (want < sk->sk_memcg_budget) { + refund =3D sk->sk_memcg_budget - want; + WRITE_ONCE(sk->sk_memcg_budget, want); } + spin_unlock_bh(&sk->sk_memcg_budget_lock); + + if (refund) + mem_cgroup_sk_uncharge(sk, refund); +} +EXPORT_SYMBOL(sk_memcg_budget_shrink); + +/** + * sk_memcg_budget_release - return the socket's memcg budget charge + * @sk: socket + * + * Return the whole remaining budget charge of the socket to its memcg + * and reset the tracker. Used when the socket dies (__sk_destruct(), + * no budget change can happen anymore, the refund is exactly the + * amount that was charged) and when the memcg association of the + * socket moves (the charge is re-established against the new memcg). + */ +void sk_memcg_budget_release(struct sock *sk) +{ + int refund; + + spin_lock_bh(&sk->sk_memcg_budget_lock); + refund =3D sk->sk_memcg_budget; + WRITE_ONCE(sk->sk_memcg_budget, 0); + spin_unlock_bh(&sk->sk_memcg_budget_lock); + + if (refund) + mem_cgroup_sk_uncharge(sk, refund); +} +EXPORT_SYMBOL(sk_memcg_budget_release); + +void __sk_charge(struct sock *sk, gfp_t gfp) +{ + gfp |=3D __GFP_NOFAIL; + + /* Charge the child's full memory budget to its memcg: the budget + * covers whatever the child queued before being accepted. + */ + sk_memcg_budget_sync(sk, gfp); =20 kmem_cache_charge(sk, gfp); } @@ -3753,6 +3892,10 @@ void sock_init_data_uid(struct socket *sock, struct = sock *sk, kuid_t uid) sk->sk_allocation =3D GFP_KERNEL; sk->sk_rcvbuf =3D READ_ONCE(sysctl_rmem_default); sk->sk_sndbuf =3D READ_ONCE(sysctl_wmem_default); + /* The socket's memory budget starts here: charge it to the memcg + * upfront. Protocols raising the default buffers later re-sync. + */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); sk->sk_state =3D TCP_CLOSE; sk->sk_use_task_frag =3D true; sk_set_socket(sk, sock); @@ -4567,6 +4710,8 @@ static int __init sock_struct_check(void) =20 CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_err); CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_socket); + CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budge= t); + CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg_budge= t_lock); #ifdef CONFIG_MEMCG CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_rxtx, sk_memcg); #endif diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 5588310bc648..3c9e1a88a6bd 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -914,9 +914,9 @@ void sk_forced_mem_schedule(struct sock *sk, int size) amt =3D sk_mem_pages(delta); sk_forward_alloc_add(sk, amt << PAGE_SHIFT); =20 - if (mem_cgroup_sk_enabled(sk)) - mem_cgroup_sk_charge(sk, amt, gfp_memcg_charge() | __GFP_NOFAIL); - + /* Only the global protocol counter: the memcg side follows the + * socket budget (sk_memcg_budget_sync()). + */ if (sk->sk_bypass_prot_mem) return; =20 --=20 2.18.0.huawei.25 From nobody Thu Sep 24 12:53:02 2026 Received: from canpmsgout01.his.huawei.com (canpmsgout01.his.huawei.com [113.46.200.216]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A761C30C606; Thu, 24 Sep 2026 07:36:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.216 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235373; cv=none; b=CtWX7GN4MTfzXA6KZdI9CWaiH9HStcqt65dA4jPAwPOfagtb1jeIXFnxfZYMaTIG8CKTPizvukoaGGFhy0cuU/Cw2kfx3JfrX1ZNdZVjCzkM/D4FaLWzswQBPyPqJmiA4dGh4a27L6RqJZcUlYzart+ppYd8nJageXBnwnB22pY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235373; c=relaxed/simple; bh=QvBZ+KNbf1Sv2iCZKL5yKE7kDkDxi2J2crQOn0o0ii0=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=nl1UT6O7upSKXcdXn7xJRE/1bis/xrN9JvWrpKGBA83LTusdDG9iHgEcriV4C7hJ9Khpoy1tqdVNtD7DV0Vt4GCf7oIuH7MXIzGCN7wjvc3vkWfEubZIXArfCqzoiLM8Oxmvn/WQ3xXCjq1rJoI8EpgIRpi2epZhWKxffBPHYxM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=ONhULbsY; arc=none smtp.client-ip=113.46.200.216 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="ONhULbsY" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=Eix0EmnHlcKPpRL88blWx7dbbcEZ9zSeXbIyhIkBRCg=; b=ONhULbsYqqPaOUYOa3JDqZ/SY1mBLmqXQhAASrJE77l3Rsek/yi+TtxaDEQcSnH8v8qzMV3RR ot0cD71MFbm5YZfOYMq7Hm3xIOlH1n4V9bMqj5y7GskAeWnQjBrUKzqiAW2EYw+HcTkR06rPwku Fdz82nUPyla8BslzzZM10rQ= Received: from mail.maildlp.com (unknown [172.19.163.104]) by canpmsgout01.his.huawei.com (SkyGuard) with ESMTPS id 4hr51V4K6kz1T4Km; Thu, 24 Sep 2026 15:24:14 +0800 (CST) Received: from whupemk100010.china.huawei.com (unknown [7.152.184.41]) by mail.maildlp.com (Postfix) with ESMTPS id 60CFB4058C; Thu, 24 Sep 2026 15:36:07 +0800 (CST) Received: from octopus.huawei.com (10.67.174.191) by whupemk100010.china.huawei.com (7.152.184.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Thu, 24 Sep 2026 15:36:05 +0800 From: Cai Xinchen To: , , , , , , , , , , , , , , , , , , , , CC: , , , , , , , , Subject: [PATCH RFC -next 2/5] tcp: sync memcg budget on protocol buffer updates Date: Thu, 24 Sep 2026 16:02:16 +0800 Message-ID: <20260924080219.1036588-3-caixinchen1@huawei.com> X-Mailer: git-send-email 2.18.0.huawei.25 In-Reply-To: <20260924080219.1036588-1-caixinchen1@huawei.com> References: <20260924080219.1036588-1-caixinchen1@huawei.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To whupemk100010.china.huawei.com (7.152.184.41) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" TCP rewrites the socket buffers after sock_init_data() already charged the generic defaults, and keeps rewriting them during autotuning: - tcp_init_sock() overrides sk_sndbuf/sk_rcvbuf with the per-net tcp_wmem/tcp_rmem sysctl defaults, - tcp_set_rcvlowat() raises sk_rcvbuf from the requested rcv window, - tcp_sndbuf_expand(), tcp_clamp_window() and tcp_rcvbuf_grow() grow the buffers during autotuning, and - tcp_should_expand_sndbuf() caps sk_sndbuf under memory pressure. Sync the memcg budget tracker with sk_memcg_budget_sync() at each of these writes so the charged amount follows the actual budget: growth is charged to the memcg, shrink is refunded. Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen --- net/ipv4/tcp.c | 5 +++++ net/ipv4/tcp_input.c | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 3c9e1a88a6bd..0d2a39672aa0 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -471,6 +471,10 @@ void tcp_init_sock(struct sock *sk) =20 WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[1]= )); WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_rmem[1]= )); + /* The default buffers grew from the generic sock_init_data() + * values: charge the difference to the memcg. + */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); tcp_scaling_ratio_init(sk); =20 set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags); @@ -1847,6 +1851,7 @@ int tcp_set_rcvlowat(struct sock *sk, int val) space =3D tcp_space_from_win(sk, val); if (space > sk->sk_rcvbuf) { WRITE_ONCE(sk->sk_rcvbuf, space); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); =20 if (tp->window_clamp && tp->window_clamp < val) WRITE_ONCE(tp->window_clamp, val); diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 0f60a1dbf927..8abec8ebf916 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -629,9 +629,11 @@ static void tcp_sndbuf_expand(struct sock *sk) sndmem =3D ca_ops->sndbuf_expand ? ca_ops->sndbuf_expand(sk) : 2; sndmem *=3D nr_segs * per_mss; =20 - if (sk->sk_sndbuf < sndmem) + if (sk->sk_sndbuf < sndmem) { WRITE_ONCE(sk->sk_sndbuf, min(sndmem, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[2]))); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); + } } =20 /* 2. Tuning advertised window (window_clamp, rcv_ssthresh) @@ -791,6 +793,7 @@ static void tcp_clamp_window(struct sock *sk) sk_memory_allocated(sk) < sk_prot_mem_limits(sk, 0)) { WRITE_ONCE(sk->sk_rcvbuf, min(atomic_read(&sk->sk_rmem_alloc), rmem2)); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); } if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf) tp->rcv_ssthresh =3D min(tp->window_clamp, 2U * tp->advmss); @@ -948,6 +951,7 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval) rcvbuf =3D min_t(u32, tcp_space_from_win(sk, rcvwin), cap); if (rcvbuf > sk->sk_rcvbuf) { WRITE_ONCE(sk->sk_rcvbuf, rcvbuf); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); /* Make the window clamp follow along. */ WRITE_ONCE(tp->window_clamp, tcp_win_from_space(sk, rcvbuf)); @@ -6055,8 +6059,10 @@ static bool tcp_should_expand_sndbuf(struct sock *sk) * it never goes below SOCK_MIN_SNDBUF. * See sk_stream_moderate_sndbuf() for more details. */ - if (unused_mem > SOCK_MIN_SNDBUF) + if (unused_mem > SOCK_MIN_SNDBUF) { WRITE_ONCE(sk->sk_sndbuf, unused_mem); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); + } =20 return false; } --=20 2.18.0.huawei.25 From nobody Thu Sep 24 12:53:03 2026 Received: from canpmsgout07.his.huawei.com (canpmsgout07.his.huawei.com [113.46.200.222]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AC4044334B9 for ; Thu, 24 Sep 2026 07:36:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.222 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235376; cv=none; b=UEG5i+ebLAhn+lztNGkmjpOiCROAfhNtfN/jjISh4qEQh/160waiRs5vfJ46O8C7Tm3Uawby4n7xDxUxjbKW5TQqMlZ7FM8dEcg05n3A+msi27GDNNcdii3Y/wqUV6OJ/vGKPApH4t/pQukASZqGtbdx67P07Sl7h9sm5qYDyc0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235376; c=relaxed/simple; bh=WcUZT6MJnXXh5Y8059u0tZ20ti7YIPoWbnJdF3kDNpA=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ARZAtfjU0Fx0XVRhMT3PdU9h8HQMM2tV/uOSx6PBi8GRMwpB9kSzMamJ3pPBZaOcu6/EC16kU+XlJFWqFHf3DvcYB2o5Zh8aGDoPZqCJUxgQ21jcOpZuL7cEujg1KpndK6tLpxQGJRvmuylHMOIQLWhlNZX5bB3AdsrixCVh+3Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=LY0tlaSG; arc=none smtp.client-ip=113.46.200.222 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="LY0tlaSG" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=Qi10NZuM2ME+TiMv17luLNhhaTKggBnB0E+h6d1MjY0=; b=LY0tlaSGYXvws1q0otD8BVANdrb8tYDMX5CLx6AqJzXm845gKIzsFfieImadi4CdDjFO18Go7 DCieD1D2RCvirKiWZnjxc/3oLLajb5rQ8SgoVhxn7JBdI1kR+pbNEJHsnopJCvqlyzuDvDQi49d s8fZ/UlG0tfCUIN3hsVZVfw= Received: from mail.maildlp.com (unknown [172.19.162.92]) by canpmsgout07.his.huawei.com (SkyGuard) with ESMTPS id 4hr51J460DzLlX8; Thu, 24 Sep 2026 15:24:04 +0800 (CST) Received: from whupemk100010.china.huawei.com (unknown [7.152.184.41]) by mail.maildlp.com (Postfix) with ESMTPS id 83D4940565; Thu, 24 Sep 2026 15:36:10 +0800 (CST) Received: from octopus.huawei.com (10.67.174.191) by whupemk100010.china.huawei.com (7.152.184.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Thu, 24 Sep 2026 15:36:08 +0800 From: Cai Xinchen To: , , , , , , , , , , , , , , , , , , , , CC: , , , , , , , , Subject: [PATCH RFC -next 3/5] mptcp: sync memcg budget and drop backlog page compensation Date: Thu, 24 Sep 2026 16:02:17 +0800 Message-ID: <20260924080219.1036588-4-caixinchen1@huawei.com> X-Mailer: git-send-email 2.18.0.huawei.25 In-Reply-To: <20260924080219.1036588-1-caixinchen1@huawei.com> References: <20260924080219.1036588-1-caixinchen1@huawei.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To whupemk100010.china.huawei.com (7.152.184.41) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Sync the memcg budget tracker at the MPTCP budget writes: mptcp_init_sock() (per-net rcvbuf default), mptcp_rcvbuf_grow(), __mptcp_sync_sndbuf(), __mptcp_subflow_set_rcvbuf() and mptcp_set_rcvlowat() (both on the msk and on each subflow). __mptcp_inherit_memcg() moves a subflow to the msk's memcg: refund the budget the subflow still holds in the old memcg with sk_memcg_budget_release() before the switch; the new memcg side re-charges it through __sk_charge(). With budgets charged upfront, the pages an MPTCP socket spooled into its backlog before being accepted are covered by the full budget that __sk_charge() charges at accept time, so the old per-page compensation in mptcp_graft_subflows() (fed by mptcp_sock->backlog_unaccounted) would now double-charge. Remove the counter, its accumulation in __mptcp_add_backlog() and the compensation charge. Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen --- net/mptcp/protocol.c | 42 ++++++------------------------------------ net/mptcp/protocol.h | 2 +- net/mptcp/sockopt.c | 3 +++ net/mptcp/subflow.c | 5 +++++ 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 0098e2830931..3c8b885d8298 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -238,6 +238,7 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newv= al) rcvbuf =3D min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); if (rcvbuf > sk->sk_rcvbuf) { WRITE_ONCE(sk->sk_rcvbuf, rcvbuf); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); return true; } return false; @@ -791,12 +792,6 @@ static void __mptcp_add_backlog(struct sock *sk, =20 account: WRITE_ONCE(msk->backlog_len, msk->backlog_len + delta); - - /* Possibly not accept()ed yet, keep track of memory not CG - * accounted, mptcp_graft_subflows() will handle it. - */ - if (!mem_cgroup_from_sk(ssk)) - msk->backlog_unaccounted +=3D delta; } =20 static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, @@ -2315,12 +2310,6 @@ static bool mptcp_can_spool_backlog(struct sock *sk,= struct list_head *skbs) { struct mptcp_sock *msk =3D mptcp_sk(sk); =20 - /* After CG initialization, subflows should never add skb before - * gaining the CG themself. - */ - DEBUG_NET_WARN_ON_ONCE(msk->backlog_unaccounted && sk->sk_socket && - mem_cgroup_from_sk(sk)); - if (list_empty(&msk->backlog_list)) return false; =20 @@ -3252,6 +3241,10 @@ static int mptcp_init_sock(struct sock *sk) sk_sockets_allocated_inc(sk); sk->sk_rcvbuf =3D READ_ONCE(net->ipv4.sysctl_tcp_rmem[1]); sk->sk_sndbuf =3D READ_ONCE(net->ipv4.sysctl_tcp_wmem[1]); + /* The default buffers grew from the generic sock_init_data() + * values: charge the difference to the memcg. + */ + sk_memcg_budget_sync(sk, gfp_memcg_charge()); sk->sk_write_space =3D sk_stream_write_space; =20 return 0; @@ -4329,10 +4322,7 @@ static void mptcp_graft_subflows(struct sock *sk) LIST_HEAD(join_list); =20 /* Subflows joining after __inet_accept() will get the - * mem CG properly initialized at mptcp_finish_join() time, - * but subflows pending in join_list need explicit - * initialization before flushing `backlog_unaccounted` - * or MPTCP can later unexpectedly observe unaccounted memory. + * mem CG properly initialized at mptcp_finish_join() time. */ mptcp_data_lock(sk); list_splice_init(&msk->join_list, &join_list); @@ -4361,26 +4351,6 @@ static void mptcp_graft_subflows(struct sock *sk) unlock: release_sock(ssk); } - - if (mem_cgroup_sk_enabled(sk)) { - gfp_t gfp =3D GFP_KERNEL | __GFP_NOFAIL; - int amt; - - /* Account the backlog memory; prior accept() is aware of - * fwd and rmem only. - */ - mptcp_data_lock(sk); - amt =3D sk_mem_pages(sk->sk_forward_alloc + - msk->backlog_unaccounted + - atomic_read(&sk->sk_rmem_alloc)) - - sk_mem_pages(sk->sk_forward_alloc + - atomic_read(&sk->sk_rmem_alloc)); - msk->backlog_unaccounted =3D 0; - mptcp_data_unlock(sk); - - if (amt) - mem_cgroup_sk_charge(sk, amt, gfp); - } } =20 static int mptcp_stream_accept(struct socket *sock, struct socket *newsock, diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 2b4c27426477..aa34be38cf14 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -377,7 +377,6 @@ struct mptcp_sock { =20 struct list_head backlog_list; /* protected by the data lock */ u32 backlog_len; - u32 backlog_unaccounted; }; =20 #define mptcp_data_lock(sk) spin_lock_bh(&(sk)->sk_lock.slock) @@ -1040,6 +1039,7 @@ static inline void __mptcp_sync_sndbuf(struct sock *s= k) =20 /* the msk max wmem limit is * tcp wmem[2] */ WRITE_ONCE(sk->sk_sndbuf, new_sndbuf); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); mptcp_write_space(sk); } =20 diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index fcf6feb2a9eb..05d9dafa8c07 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -70,6 +70,7 @@ static int mptcp_get_int_option(struct mptcp_sock *msk, s= ockptr_t optval, static void __mptcp_subflow_set_rcvbuf(struct sock *ssk, int val) { WRITE_ONCE(ssk->sk_rcvbuf, val); + sk_memcg_budget_sync(ssk, gfp_memcg_charge()); tcp_set_rcvbuf(ssk, val); } =20 @@ -1656,12 +1657,14 @@ int mptcp_set_rcvlowat(struct sock *sk, int val) =20 /* propagate the rcvbuf changes to all the subflows */ WRITE_ONCE(sk->sk_rcvbuf, space); + sk_memcg_budget_sync(sk, gfp_memcg_charge()); mptcp_for_each_subflow(mptcp_sk(sk), subflow) { struct sock *ssk =3D mptcp_subflow_tcp_sock(subflow); bool slow; =20 slow =3D lock_sock_fast(ssk); WRITE_ONCE(ssk->sk_rcvbuf, space); + sk_memcg_budget_sync(ssk, gfp_memcg_charge()); WRITE_ONCE(tcp_sk(ssk)->window_clamp, val); unlock_sock_fast(ssk, slow); } diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 01db7edce18a..817a98b5f40f 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -1731,6 +1731,11 @@ void __mptcp_inherit_memcg(struct sock *sk, struct s= ock *ssk, gfp_t gfp) if (!mem_cgroup_sockets_enabled || !sk->sk_socket) return; =20 + /* The subflow's budget charge went to its previous memcg: return + * it before the memcg association moves to the msk's one, the + * __sk_charge() below re-charges the budget there. + */ + sk_memcg_budget_release(ssk); mem_cgroup_sk_inherit(sk, ssk); __sk_charge(ssk, gfp); } --=20 2.18.0.huawei.25 From nobody Thu Sep 24 12:53:03 2026 Received: from canpmsgout02.his.huawei.com (canpmsgout02.his.huawei.com [113.46.200.217]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3EBC743C7B4 for ; Thu, 24 Sep 2026 07:36:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.217 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235380; cv=none; b=TfPwp68yZAnUk0aVUWtu/aUVOiQY82GQazrmLIuwRzvSJdnhleKtNhVhrN7V4+9X+Ul0M1ZOQClCFOpkVyt60OMKMGDCxKQwp3L1H6HzSEh03BLsf3TMMrwQlBRhjgHtqJGZIcnBY0NmlShlaZBJgoqGBwGOAW5KDlusnC9mhKU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235380; c=relaxed/simple; bh=NUIWn8rwRUiOu7P3B2vlF9O9uqHo2D9P/6q7nbhOVVw=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=PhHG2gL7TwdxULPm0cgWIBViKBEhaj8rxrmzifgpKHwh54MXpksNbGgtZCRkfjLpCtRBg93eV1zwWf5S5rpcQFp0X1g4x4eA6isD27XSsXf0IofG2MLI6cDC3tu37EtH1t5Zv9OzKu9aAvnRZaXYP+7V4nWvQJdmR3KBbkR/K8c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=iYrPQsXd; arc=none smtp.client-ip=113.46.200.217 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="iYrPQsXd" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=0h6IJhcmiEtJdjoFG8xXoR5aFr6QBu55uhw+svnqeNY=; b=iYrPQsXd2Wy1Wk1bSiOR7getgDZXSDXLXyQ1wc59ropjoOVpb7McqRxbpvrLoS3QGDoWOo+Gy pUXez0EL4XNgGTZ6YsHvqxuXjoF9uCU0QGFCSqvesgGux/0HPrDIKi6D0JqtPnTUEeI1boGIFhx DO3LRhjVEfQqCVYRPpA1KTc= Received: from mail.maildlp.com (unknown [172.19.162.197]) by canpmsgout02.his.huawei.com (SkyGuard) with ESMTPS id 4hr52741s1zcb4D; Thu, 24 Sep 2026 15:24:47 +0800 (CST) Received: from whupemk100010.china.huawei.com (unknown [7.152.184.41]) by mail.maildlp.com (Postfix) with ESMTPS id 7EE9F4057D; Thu, 24 Sep 2026 15:36:13 +0800 (CST) Received: from octopus.huawei.com (10.67.174.191) by whupemk100010.china.huawei.com (7.152.184.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Thu, 24 Sep 2026 15:36:11 +0800 From: Cai Xinchen To: , , , , , , , , , , , , , , , , , , , , CC: , , , , , , , , Subject: [PATCH RFC -next 4/5] Docs/admin-guide/cgroup-v2: document upfront socket budget charging Date: Thu, 24 Sep 2026 16:02:18 +0800 Message-ID: <20260924080219.1036588-5-caixinchen1@huawei.com> X-Mailer: git-send-email 2.18.0.huawei.25 In-Reply-To: <20260924080219.1036588-1-caixinchen1@huawei.com> References: <20260924080219.1036588-1-caixinchen1@huawei.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To whupemk100010.china.huawei.com (7.152.184.41) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The "TCP socket buffers" entry in the memory.stat description leaves the impression that the charge tracks in-flight packet buffers. Since the budget-charging rework, a socket is instead charged its full established memory budget (send buffer, receive buffer and SO_RESERVE_MEM reservation) when the budget is created or resized, and the charge is returned when the budget shrinks or the socket is closed. Say so. Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen --- Documentation/admin-guide/cgroup-v2.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-= guide/cgroup-v2.rst index 8d2603751c51..4f26a459cfdc 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1309,7 +1309,11 @@ following types of memory usages are tracked. =20 - Kernel data structures such as dentries and inodes. =20 -- TCP socket buffers. +- TCP socket buffers. These are charged upfront for the socket's + established memory budget (send buffer, receive buffer and + SO_RESERVE_MEM reservation) when the budget is created or resized, + and returned when the budget shrinks or the socket is closed; the + charge is not tied to per-packet buffer usage. =20 The above list may expand in the future for better coverage. =20 --=20 2.18.0.huawei.25 From nobody Thu Sep 24 12:53:03 2026 Received: from canpmsgout04.his.huawei.com (canpmsgout04.his.huawei.com [113.46.200.219]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A2B0C43711B for ; Thu, 24 Sep 2026 07:36:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.219 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235390; cv=none; b=I2w5+h3rE71gyoNQiP2tDCQDPNVod2Wxez05GqA7f+2+0HARNT3bdZGWOwPe3/VFwizlQf6ebI8VycQ1PUwPhMPr/eGxcYubRlF3RClMJXMOniOeqFJ94rpncY5ROJnX9LaISeZ0Bos7mV7PcdRiO7vQ/cZZ4X7Jada8msJ5c+E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790235390; c=relaxed/simple; bh=I00N0sC6N8z/NihuWASPNcr6BoeUuPaC8rphpErkLeI=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=uCUpej9BYMG2ORjMQ5fe64MgrF8GTBpOmPH4WcGjJukGLZIDKLCD03353YQ2jmHcpL6E7fViPm5wNZvFsoBCmJ/7We5SuaUFKGu6Odn2gQf5FUOxzo85CGY6sgoPrzIJxTOt3RFaxiibHKB8GJeXOLlR14OB48d7e/dGE091Ips= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=xo4e0qcW; arc=none smtp.client-ip=113.46.200.219 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="xo4e0qcW" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=AM/1COYI+X2cxZahYdg2nMaQ9CPWb/Pi9FF0HZq9dI8=; b=xo4e0qcWYUKb+XSdoEELGMTI9wjW82TMlWcj31whkd2V3Qjx9MHato7sp5L3DCl4rrjFgXAk2 l+apV22HWtkX2LDdzIWioU1kaq2lpBRU81qUybOOqT721sSCyjyEdMe63VVppSohvA0QfMgfApF wvtZ8BublnUnhUv4ijDY2XM= Received: from mail.maildlp.com (unknown [172.19.163.0]) by canpmsgout04.his.huawei.com (SkyGuard) with ESMTPS id 4hr51N5Gw1z1prLc; Thu, 24 Sep 2026 15:24:08 +0800 (CST) Received: from whupemk100010.china.huawei.com (unknown [7.152.184.41]) by mail.maildlp.com (Postfix) with ESMTPS id 6D41C40537; Thu, 24 Sep 2026 15:36:16 +0800 (CST) Received: from octopus.huawei.com (10.67.174.191) by whupemk100010.china.huawei.com (7.152.184.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Thu, 24 Sep 2026 15:36:14 +0800 From: Cai Xinchen To: , , , , , , , , , , , , , , , , , , , , CC: , , , , , , , , Subject: [PATCH RFC -next 5/5] selftests/cgroup: compare socket memory deltas in test_memcg_sock Date: Thu, 24 Sep 2026 16:02:19 +0800 Message-ID: <20260924080219.1036588-6-caixinchen1@huawei.com> X-Mailer: git-send-email 2.18.0.huawei.25 In-Reply-To: <20260924080219.1036588-1-caixinchen1@huawei.com> References: <20260924080219.1036588-1-caixinchen1@huawei.com> Precedence: bulk X-Mailing-List: mptcp@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To whupemk100010.china.huawei.com (7.152.184.41) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" test_memcg_sock() baselines memory.current and then requires the growth of memory.current to match the absolute value of the "sock" counter. That relies on "sock" being zero at the baseline: with an idle listener it used to be, because socket memory was only charged for in-flight buffers. With upfront budget charging, a listening socket is charged its full buffer budget as soon as it exists, so "sock" is non-zero at the baseline and the absolute comparison breaks. Baseline the "sock" counter as well and compare the growth of both counters; this is valid under both the old and the new accounting model. Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen --- .../testing/selftests/cgroup/test_memcontrol.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testi= ng/selftests/cgroup/test_memcontrol.c index 3a84d068fbf3..54dae37291c3 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -1335,9 +1335,12 @@ static int tcp_client(const char *cgroup, unsigned s= hort port) char servport[6]; int retries =3D 0x10; /* nice round number */ int sk, ret; - long allocated; + long allocated, sock_pre; =20 allocated =3D cg_read_long(cgroup, "memory.current"); + sock_pre =3D cg_read_key_long(cgroup, "memory.stat", "sock "); + if (sock_pre < 0) + return KSFT_FAIL; snprintf(servport, sizeof(servport), "%hd", port); ret =3D getaddrinfo(server, servport, NULL, &ai); if (ret) @@ -1365,8 +1368,12 @@ static int tcp_client(const char *cgroup, unsigned s= hort port) if (current < 0 || sock < 0) goto close_sk; =20 - /* exclude the memory not related to socket connection */ - if (values_close(current - allocated, sock, 10)) { + /* exclude the memory not related to socket connection; + * compare the growth of both counters since the baseline, + * as the listening socket may already hold socket memory + * (e.g. its pre-charged memory budget) at the baseline. + */ + if (values_close(current - allocated, sock - sock_pre, 10)) { ret =3D KSFT_PASS; break; } @@ -1384,8 +1391,9 @@ static int tcp_client(const char *cgroup, unsigned sh= ort port) * The test forks a TCP server listens on a random port between 1000 * and 61000. Once it gets a client connection, it starts writing to * its socket. - * The TCP client interleaves reads from the socket with check whether - * memory.current and memory.stat.sock are similar. + * The TCP client interleaves reads from the socket with checking whether + * the growth of memory.current and memory.stat.sock since the baseline + * are similar. */ static int test_memcg_sock(const char *root) { --=20 2.18.0.huawei.25