Files
linux-stable-mirror/include/linux/cgroup_dmem.h
T
Thomas Hellström 747c4bb450 cgroup/dmem: Add reclaim callback for lowering max below current usage
Add an optional reclaim callback to struct dmem_cgroup_region. When
dmem.max is set below the current usage of a cgroup pool, the new limit
is applied immediately (so that concurrent allocations are throttled
while reclaim is in progress) and then the driver is asked to evict
memory to bring usage back below the limit.

Reclaim is attempted up to a bounded number of times. No error is
returned to userspace if usage remains above the limit after reclaim,
and a pending signal will abort the reclaim loop early. This matches
the behavior of memory.max in the memory cgroup controller.

Also honor O_NONBLOCK so that if that flag is set during the
max value write, no reclaim is initiated. The idea is to avoid
charging the reclaim cost to the writer of the max value.

v2:
- Write max before reclaim is attempted (Maarten)
- Let signals abort the reclaim without error (Maarten)
- If a new max value is written with the O_NONBLOCK flag,
  reclaim is not attempted (Maarten)
- Extract region from the pool parameter rather than
  passing it explicitly to set_resource_xxx().

v3:
- Use an rw_semaphore (unregister_sem) to protect reclaim callbacks
  against concurrent region unregistration: readers (reclaim) hold the
  read side; dmem_cgroup_unregister_region() takes the write side to
  drain in-flight callbacks before returning. (Sashiko-bot)

v5:
- Rebased on the introduction of struct dmem_cgroup_init.
- Use nonblock=true in reset_all_resource_limits() to avoid sleeping
  inside rcu_read_lock() in dmemcs_offline(). (Sashiko-bot)
- Compare usage against the truncated limit value stored in cnt.max,
  not the original u64. (Sashiko-bot)
- Use a DMEM_MAX_RECLAIM_RETRIES (16) retry budget instead of 5, matching
  the memcg controller's MAX_RECLAIM_RETRIES. Only -ENOSPC (no progress)
  counts against the retry budget; other errors terminate the loop
  immediately.

v6:
- Fix dmem_cgroup_ops->reclaim docstring: -ENOSPC does not stop reclaim
  immediately but is retried up to DMEM_MAX_RECLAIM_RETRIES times; only
  other negative errors terminate the loop. (Sashiko-bot)

v7:
- Replace the per-region rw_semaphore with a static SRCU domain
  (dmemcg_srcu). SRCU is a better fit than rwsem for this use: it
  avoids the per-region lock overhead on every reclaim call, and
  synchronize_srcu() at unregister time is a rare operation. (Maarten)
- Trim in-function comments to focus on what rather than how.

Assisted-by: GitHub_Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Tested-by: Thadeu Lima de Souza Cascardo <cascardo@igalia.com>
Link: https://patch.msgid.link/20260725100036.2372-4-thomas.hellstrom@linux.intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
2026-08-06 23:38:03 +02:00

124 lines
3.9 KiB
C

/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2023-2024 Intel Corporation
*/
#ifndef _CGROUP_DMEM_H
#define _CGROUP_DMEM_H
#include <linux/types.h>
#include <linux/llist.h>
struct dmem_cgroup_pool_state;
/* Opaque definition of a cgroup region, used internally */
struct dmem_cgroup_region;
/**
* struct dmem_cgroup_ops - Operations for a dmem cgroup region.
* @reclaim: Optional callback invoked when dmem.max is set below the current
* usage of a pool. The driver should attempt to free at least
* @target_bytes from @pool. May be called multiple times if usage
* remains above the limit after returning.
*
* Return: 0 if some progress was made (even if less than
* @target_bytes was freed), -ENOSPC if no progress could be made
* (the caller will retry up to a bounded number of times), or
* another negative error code if a fatal error occurred (stops
* further reclaim attempts immediately).
*/
struct dmem_cgroup_ops {
int (*reclaim)(struct dmem_cgroup_pool_state *pool,
u64 target_bytes, void *priv);
};
/**
* struct dmem_cgroup_init - Initialization parameters for a dmem cgroup region.
* @size: Size of the region in bytes.
* @ops: Optional operations for this region. May be NULL.
* @reclaim_priv: Opaque pointer passed to @ops->reclaim. May be NULL.
*/
struct dmem_cgroup_init {
u64 size;
const struct dmem_cgroup_ops *ops;
void *reclaim_priv;
};
#if IS_ENABLED(CONFIG_CGROUP_DMEM)
struct dmem_cgroup_region *
dmem_cgroup_register_region(const struct dmem_cgroup_init *init,
const char *name_fmt, ...) __printf(2, 3);
void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region);
int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size,
struct dmem_cgroup_pool_state **ret_pool,
struct dmem_cgroup_pool_state **ret_limit_pool);
void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size);
bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool,
struct dmem_cgroup_pool_state *test_pool,
bool ignore_low, bool *ret_hit_low);
bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root,
struct dmem_cgroup_pool_state *test);
bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root,
struct dmem_cgroup_pool_state *test);
struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a,
struct dmem_cgroup_pool_state *b);
void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool);
#else
static inline __printf(2, 3) struct dmem_cgroup_region *
dmem_cgroup_register_region(const struct dmem_cgroup_init *init, const char *name_fmt, ...)
{
return NULL;
}
static inline void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region)
{ }
static inline int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size,
struct dmem_cgroup_pool_state **ret_pool,
struct dmem_cgroup_pool_state **ret_limit_pool)
{
*ret_pool = NULL;
if (ret_limit_pool)
*ret_limit_pool = NULL;
return 0;
}
static inline void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size)
{ }
static inline
bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool,
struct dmem_cgroup_pool_state *test_pool,
bool ignore_low, bool *ret_hit_low)
{
return true;
}
static inline bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root,
struct dmem_cgroup_pool_state *test)
{
return false;
}
static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root,
struct dmem_cgroup_pool_state *test)
{
return false;
}
static inline
struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a,
struct dmem_cgroup_pool_state *b)
{
return NULL;
}
static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool)
{ }
#endif
#endif /* _CGROUP_DMEM_H */