Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o freezer.o
obj-$(CONFIG_CGROUP_FREEZER) += legacy_freezer.o
obj-$(CONFIG_CGROUP_PIDS) += pids.o
obj-$(CONFIG_CGROUP_RDMA) += rdma.o
obj-$(CONFIG_CPUSETS) += cpuset.o
obj-$(CONFIG_CPUSETS_V1) += cpuset-v1.o
obj-$(CONFIG_CGROUP_MISC) += misc.o
obj-$(CONFIG_CGROUP_DMEM) += dmem.o
obj-$(CONFIG_CGROUP_DEBUG) += debug.o
+296
View File
@@ -0,0 +1,296 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __CGROUP_INTERNAL_H
#define __CGROUP_INTERNAL_H
#include <linux/cgroup.h>
#include <linux/kernfs.h>
#include <linux/workqueue.h>
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/fs_parser.h>
#define TRACE_CGROUP_PATH_LEN 1024
extern spinlock_t trace_cgroup_path_lock;
extern char trace_cgroup_path[TRACE_CGROUP_PATH_LEN];
extern void __init enable_debug_cgroup(void);
/*
* cgroup_path() takes a spin lock. It is good practice not to take
* spin locks within trace point handlers, as they are mostly hidden
* from normal view. As cgroup_path() can take the kernfs_rename_lock
* spin lock, it is best to not call that function from the trace event
* handler.
*
* Note: trace_cgroup_##type##_enabled() is a static branch that will only
* be set when the trace event is enabled.
*/
#define TRACE_CGROUP_PATH(type, cgrp, ...) \
do { \
if (trace_cgroup_##type##_enabled()) { \
unsigned long flags; \
spin_lock_irqsave(&trace_cgroup_path_lock, \
flags); \
cgroup_path(cgrp, trace_cgroup_path, \
TRACE_CGROUP_PATH_LEN); \
trace_cgroup_##type(cgrp, trace_cgroup_path, \
##__VA_ARGS__); \
spin_unlock_irqrestore(&trace_cgroup_path_lock, \
flags); \
} \
} while (0)
/*
* The cgroup filesystem superblock creation/mount context.
*/
struct cgroup_fs_context {
struct kernfs_fs_context kfc;
struct cgroup_root *root;
struct cgroup_namespace *ns;
unsigned int flags; /* CGRP_ROOT_* flags */
/* cgroup1 bits */
bool cpuset_clone_children;
bool none; /* User explicitly requested empty subsystem */
bool all_ss; /* Seen 'all' option */
u32 subsys_mask; /* Selected subsystems */
char *name; /* Hierarchy name */
char *release_agent; /* Path for release notifications */
};
static inline struct cgroup_fs_context *cgroup_fc2context(struct fs_context *fc)
{
struct kernfs_fs_context *kfc = fc->fs_private;
return container_of(kfc, struct cgroup_fs_context, kfc);
}
struct cgroup_pidlist;
struct cgroup_file_ctx {
struct cgroup_namespace *ns;
struct {
void *trigger;
} psi;
struct {
bool started;
struct css_task_iter iter;
} procs;
struct {
struct cgroup_pidlist *pidlist;
} procs1;
struct cgroup_of_peak peak;
};
/*
* A cgroup can be associated with multiple css_sets as different tasks may
* belong to different cgroups on different hierarchies. In the other
* direction, a css_set is naturally associated with multiple cgroups.
* This M:N relationship is represented by the following link structure
* which exists for each association and allows traversing the associations
* from both sides.
*/
struct cgrp_cset_link {
/* the cgroup and css_set this link associates */
struct cgroup *cgrp;
struct css_set *cset;
/* list of cgrp_cset_links anchored at cgrp->cset_links */
struct list_head cset_link;
/* list of cgrp_cset_links anchored at css_set->cgrp_links */
struct list_head cgrp_link;
};
/* used to track tasks and csets during migration */
struct cgroup_taskset {
/* the src and dst cset list running through cset->mg_node */
struct list_head src_csets;
struct list_head dst_csets;
/* the number of tasks in the set */
int nr_tasks;
/* the subsys currently being processed */
int ssid;
/*
* Fields for cgroup_taskset_*() iteration.
*
* Before migration is committed, the target migration tasks are on
* ->mg_tasks of the csets on ->src_csets. After, on ->mg_tasks of
* the csets on ->dst_csets. ->csets point to either ->src_csets
* or ->dst_csets depending on whether migration is committed.
*
* ->cur_csets and ->cur_task point to the current task position
* during iteration.
*/
struct list_head *csets;
struct css_set *cur_cset;
struct task_struct *cur_task;
};
/* migration context also tracks preloading */
struct cgroup_mgctx {
/*
* Preloaded source and destination csets. Used to guarantee
* atomic success or failure on actual migration.
*/
struct list_head preloaded_src_csets;
struct list_head preloaded_dst_csets;
/* tasks and csets to migrate */
struct cgroup_taskset tset;
/* subsystems affected by migration */
u32 ss_mask;
};
#define CGROUP_TASKSET_INIT(tset) \
{ \
.src_csets = LIST_HEAD_INIT(tset.src_csets), \
.dst_csets = LIST_HEAD_INIT(tset.dst_csets), \
.csets = &tset.src_csets, \
}
#define CGROUP_MGCTX_INIT(name) \
{ \
LIST_HEAD_INIT(name.preloaded_src_csets), \
LIST_HEAD_INIT(name.preloaded_dst_csets), \
CGROUP_TASKSET_INIT(name.tset), \
}
#define DEFINE_CGROUP_MGCTX(name) \
struct cgroup_mgctx name = CGROUP_MGCTX_INIT(name)
extern struct cgroup_subsys *cgroup_subsys[];
extern struct list_head cgroup_roots;
extern bool cgrp_dfl_visible;
/* iterate across the hierarchies */
#define for_each_root(root) \
list_for_each_entry_rcu((root), &cgroup_roots, root_list, \
lockdep_is_held(&cgroup_mutex))
/**
* for_each_subsys - iterate all enabled cgroup subsystems
* @ss: the iteration cursor
* @ssid: the index of @ss, CGROUP_SUBSYS_COUNT after reaching the end
*/
#define for_each_subsys(ss, ssid) \
for ((ssid) = 0; (ssid) < CGROUP_SUBSYS_COUNT && \
(((ss) = cgroup_subsys[ssid]) || true); (ssid)++)
static inline bool notify_on_release(const struct cgroup *cgrp)
{
return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
void put_css_set_locked(struct css_set *cset);
static inline void put_css_set(struct css_set *cset)
{
unsigned long flags;
/*
* Ensure that the refcount doesn't hit zero while any readers
* can see it. Similar to atomic_dec_and_lock(), but for an
* rwlock
*/
if (refcount_dec_not_one(&cset->refcount))
return;
spin_lock_irqsave(&css_set_lock, flags);
put_css_set_locked(cset);
spin_unlock_irqrestore(&css_set_lock, flags);
}
/*
* refcounted get/put for css_set objects
*/
static inline void get_css_set(struct css_set *cset)
{
refcount_inc(&cset->refcount);
}
bool cgroup_ssid_enabled(int ssid);
struct cgroup_root *cgroup_root_from_kf(struct kernfs_root *kf_root);
struct cgroup *task_cgroup_from_root(struct task_struct *task,
struct cgroup_root *root);
struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline);
void cgroup_kn_unlock(struct kernfs_node *kn);
int cgroup_path_ns_locked(struct cgroup *cgrp, char *buf, size_t buflen,
struct cgroup_namespace *ns);
void cgroup_favor_dynmods(struct cgroup_root *root, bool favor);
void cgroup_free_root(struct cgroup_root *root);
void init_cgroup_root(struct cgroup_fs_context *ctx);
int cgroup_setup_root(struct cgroup_root *root, u32 ss_mask);
int rebind_subsystems(struct cgroup_root *dst_root, u32 ss_mask);
int cgroup_do_get_tree(struct fs_context *fc);
int cgroup_migrate_vet_dst(struct cgroup *dst_cgrp);
void cgroup_migrate_finish(struct cgroup_mgctx *mgctx);
void cgroup_migrate_add_src(struct css_set *src_cset, struct cgroup *dst_cgrp,
struct cgroup_mgctx *mgctx);
int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx);
int cgroup_migrate(struct task_struct *leader, bool threadgroup,
struct cgroup_mgctx *mgctx);
int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
bool threadgroup);
void cgroup_attach_lock(enum cgroup_attach_lock_mode lock_mode,
struct task_struct *tsk);
void cgroup_attach_unlock(enum cgroup_attach_lock_mode lock_mode,
struct task_struct *tsk);
struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup,
enum cgroup_attach_lock_mode *lock_mode)
__acquires(&cgroup_threadgroup_rwsem);
void cgroup_procs_write_finish(struct task_struct *task,
enum cgroup_attach_lock_mode lock_mode)
__releases(&cgroup_threadgroup_rwsem);
void cgroup_lock_and_drain_offline(struct cgroup *cgrp);
int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode);
int cgroup_rmdir(struct kernfs_node *kn);
int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
struct kernfs_root *kf_root);
int __cgroup_task_count(const struct cgroup *cgrp);
int cgroup_task_count(const struct cgroup *cgrp);
/*
* rstat.c
*/
int css_rstat_init(struct cgroup_subsys_state *css);
void css_rstat_exit(struct cgroup_subsys_state *css);
int ss_rstat_init(struct cgroup_subsys *ss);
void cgroup_base_stat_cputime_show(struct seq_file *seq);
/*
* namespace.c
*/
extern const struct proc_ns_operations cgroupns_operations;
/*
* cgroup-v1.c
*/
extern struct cftype cgroup1_base_files[];
extern struct kernfs_syscall_ops cgroup1_kf_syscall_ops;
extern const struct fs_parameter_spec cgroup1_fs_parameters[];
int proc_cgroupstats_show(struct seq_file *m, void *v);
bool cgroup1_ssid_disabled(int ssid);
void cgroup1_pidlist_destroy_all(struct cgroup *cgrp);
void cgroup1_release_agent(struct work_struct *work);
void cgroup1_check_for_release(struct cgroup *cgrp);
int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param);
int cgroup1_get_tree(struct fs_context *fc);
int cgroup1_reconfigure(struct fs_context *ctx);
#endif /* __CGROUP_INTERNAL_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+354
View File
@@ -0,0 +1,354 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __CPUSET_INTERNAL_H
#define __CPUSET_INTERNAL_H
#include <linux/cgroup.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/cpuset.h>
#include <linux/spinlock.h>
#include <linux/union_find.h>
#include <linux/sched/isolation.h>
/* See "Frequency meter" comments, below. */
struct fmeter {
int cnt; /* unprocessed events count */
int val; /* most recent output value */
time64_t time; /* clock (secs) when val computed */
spinlock_t lock; /* guards read or write of above */
};
/*
* Invalid partition error code
*/
enum prs_errcode {
PERR_NONE = 0,
PERR_INVCPUS,
PERR_INVPARENT,
PERR_NOTPART,
PERR_NOTEXCL,
PERR_NOCPUS,
PERR_HOTPLUG,
PERR_CPUSEMPTY,
PERR_HKEEPING,
PERR_ACCESS,
PERR_REMOTE,
};
/* bits in struct cpuset flags field */
typedef enum {
CS_CPU_EXCLUSIVE,
CS_MEM_EXCLUSIVE,
CS_MEM_HARDWALL,
CS_MEMORY_MIGRATE,
CS_SCHED_LOAD_BALANCE,
CS_SPREAD_PAGE,
CS_SPREAD_SLAB,
} cpuset_flagbits_t;
/* The various types of files and directories in a cpuset file system */
typedef enum {
FILE_MEMORY_MIGRATE,
FILE_CPULIST,
FILE_MEMLIST,
FILE_EFFECTIVE_CPULIST,
FILE_EFFECTIVE_MEMLIST,
FILE_SUBPARTS_CPULIST,
FILE_EXCLUSIVE_CPULIST,
FILE_EFFECTIVE_XCPULIST,
FILE_ISOLATED_CPULIST,
FILE_CPU_EXCLUSIVE,
FILE_MEM_EXCLUSIVE,
FILE_MEM_HARDWALL,
FILE_SCHED_LOAD_BALANCE,
FILE_PARTITION_ROOT,
FILE_SCHED_RELAX_DOMAIN_LEVEL,
FILE_MEMORY_PRESSURE_ENABLED,
FILE_MEMORY_PRESSURE,
FILE_SPREAD_PAGE,
FILE_SPREAD_SLAB,
} cpuset_filetype_t;
struct cpuset {
struct cgroup_subsys_state css;
unsigned long flags; /* "unsigned long" so bitops work */
/*
* On default hierarchy:
*
* The user-configured masks can only be changed by writing to
* cpuset.cpus and cpuset.mems, and won't be limited by the
* parent masks.
*
* The effective masks is the real masks that apply to the tasks
* in the cpuset. They may be changed if the configured masks are
* changed or hotplug happens.
*
* effective_mask == configured_mask & parent's effective_mask,
* and if it ends up empty, it will inherit the parent's mask.
*
*
* On legacy hierarchy:
*
* The user-configured masks are always the same with effective masks.
*/
/* user-configured CPUs and Memory Nodes allow to tasks */
cpumask_var_t cpus_allowed;
nodemask_t mems_allowed;
/* effective CPUs and Memory Nodes allow to tasks */
cpumask_var_t effective_cpus;
nodemask_t effective_mems;
/*
* Exclusive CPUs dedicated to current cgroup (default hierarchy only)
*
* The effective_cpus of a valid partition root comes solely from its
* effective_xcpus and some of the effective_xcpus may be distributed
* to sub-partitions below & hence excluded from its effective_cpus.
* For a valid partition root, its effective_cpus have no relationship
* with cpus_allowed unless its exclusive_cpus isn't set.
*
* This value will only be set if either exclusive_cpus is set or
* when this cpuset becomes a local partition root.
*/
cpumask_var_t effective_xcpus;
/*
* Exclusive CPUs as requested by the user (default hierarchy only)
*
* Its value is independent of cpus_allowed and designates the set of
* CPUs that can be granted to the current cpuset or its children when
* it becomes a valid partition root. The effective set of exclusive
* CPUs granted (effective_xcpus) depends on whether those exclusive
* CPUs are passed down by its ancestors and not yet taken up by
* another sibling partition root along the way.
*
* If its value isn't set, it defaults to cpus_allowed.
*/
cpumask_var_t exclusive_cpus;
/*
* This is old Memory Nodes tasks took on.
*
* - top_cpuset.old_mems_allowed is initialized to mems_allowed.
* - A new cpuset's old_mems_allowed is initialized when some
* task is moved into it.
* - old_mems_allowed is used in cpuset_migrate_mm() when we change
* cpuset.mems_allowed and have tasks' nodemask updated, and
* then old_mems_allowed is updated to mems_allowed.
*/
nodemask_t old_mems_allowed;
/*
* Tasks are being attached to this cpuset. Used to prevent
* zeroing cpus/mems_allowed between ->can_attach() and ->attach().
*/
int attach_in_progress;
/* partition root state */
int partition_root_state;
/*
* Whether cpuset is a remote partition.
* It used to be a list anchoring all remote partitions — we can switch back
* to a list if we need to iterate over the remote partitions.
*/
bool remote_partition;
/*
* number of SCHED_DEADLINE tasks attached to this cpuset, so that we
* know when to rebuild associated root domain bandwidth information.
*/
int nr_deadline_tasks;
int nr_migrate_dl_tasks;
/* DL bandwidth that needs destination reservation for this attach. */
u64 sum_migrate_dl_bw;
/*
* CPU used for temporary DL bandwidth allocation during attach;
* -1 if no DL bandwidth was allocated in the current attach.
*/
int dl_bw_cpu;
/* Invalid partition error code, not lock protected */
enum prs_errcode prs_err;
/* Handle for cpuset.cpus.partition */
struct cgroup_file partition_file;
#ifdef CONFIG_CPUSETS_V1
struct fmeter fmeter; /* memory_pressure filter */
/* for custom sched domain */
int relax_domain_level;
/* Used to merge intersecting subsets for generate_sched_domains */
struct uf_node node;
#endif
};
extern struct cpuset top_cpuset;
static inline struct cpuset *css_cs(struct cgroup_subsys_state *css)
{
return css ? container_of(css, struct cpuset, css) : NULL;
}
/* Retrieve the cpuset for a task */
static inline struct cpuset *task_cs(struct task_struct *task)
{
return css_cs(task_css(task, cpuset_cgrp_id));
}
static inline struct cpuset *parent_cs(struct cpuset *cs)
{
return css_cs(cs->css.parent);
}
/* convenient tests for these bits */
static inline bool is_cpuset_online(struct cpuset *cs)
{
return css_is_online(&cs->css) && !css_is_dying(&cs->css);
}
static inline int is_cpu_exclusive(const struct cpuset *cs)
{
return test_bit(CS_CPU_EXCLUSIVE, &cs->flags);
}
static inline int is_mem_exclusive(const struct cpuset *cs)
{
return test_bit(CS_MEM_EXCLUSIVE, &cs->flags);
}
static inline int is_mem_hardwall(const struct cpuset *cs)
{
return test_bit(CS_MEM_HARDWALL, &cs->flags);
}
static inline int is_sched_load_balance(const struct cpuset *cs)
{
return test_bit(CS_SCHED_LOAD_BALANCE, &cs->flags);
}
static inline int is_memory_migrate(const struct cpuset *cs)
{
return test_bit(CS_MEMORY_MIGRATE, &cs->flags);
}
static inline int is_spread_page(const struct cpuset *cs)
{
return test_bit(CS_SPREAD_PAGE, &cs->flags);
}
static inline int is_spread_slab(const struct cpuset *cs)
{
return test_bit(CS_SPREAD_SLAB, &cs->flags);
}
/*
* Helper routine for generate_sched_domains().
* Do cpusets a, b have overlapping effective cpus_allowed masks?
*/
static inline int cpusets_overlap(struct cpuset *a, struct cpuset *b)
{
return cpumask_intersects(a->effective_cpus, b->effective_cpus);
}
static inline int nr_cpusets(void)
{
/* jump label reference count + the top-level cpuset */
return static_key_count(&cpusets_enabled_key.key) + 1;
}
static inline bool cpuset_is_populated(struct cpuset *cs)
{
lockdep_assert_cpuset_lock_held();
/* Cpusets in the process of attaching should be considered as populated */
return cgroup_is_populated(cs->css.cgroup) ||
cs->attach_in_progress;
}
/**
* cpuset_for_each_child - traverse online children of a cpuset
* @child_cs: loop cursor pointing to the current child
* @pos_css: used for iteration
* @parent_cs: target cpuset to walk children of
*
* Walk @child_cs through the online children of @parent_cs. Must be used
* with RCU read locked.
*/
#define cpuset_for_each_child(child_cs, pos_css, parent_cs) \
css_for_each_child((pos_css), &(parent_cs)->css) \
if (is_cpuset_online(((child_cs) = css_cs((pos_css)))))
/**
* cpuset_for_each_descendant_pre - pre-order walk of a cpuset's descendants
* @des_cs: loop cursor pointing to the current descendant
* @pos_css: used for iteration
* @root_cs: target cpuset to walk ancestor of
*
* Walk @des_cs through the online descendants of @root_cs. Must be used
* with RCU read locked. The caller may modify @pos_css by calling
* css_rightmost_descendant() to skip subtree. @root_cs is included in the
* iteration and the first node to be visited.
*/
#define cpuset_for_each_descendant_pre(des_cs, pos_css, root_cs) \
css_for_each_descendant_pre((pos_css), &(root_cs)->css) \
if (is_cpuset_online(((des_cs) = css_cs((pos_css)))))
void rebuild_sched_domains_locked(void);
void cpuset_callback_lock_irq(void);
void cpuset_callback_unlock_irq(void);
void cpuset_update_tasks_cpumask(struct cpuset *cs, struct cpumask *new_cpus);
void cpuset_update_tasks_nodemask(struct cpuset *cs);
int cpuset_update_flag(cpuset_flagbits_t bit, struct cpuset *cs, int turning_on);
ssize_t cpuset_write_resmask(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off);
int cpuset_common_seq_show(struct seq_file *sf, void *v);
void cpuset_full_lock(void);
void cpuset_full_unlock(void);
/*
* cpuset-v1.c
*/
#ifdef CONFIG_CPUSETS_V1
extern struct cftype cpuset1_files[];
void cpuset1_update_task_spread_flags(struct cpuset *cs,
struct task_struct *tsk);
void cpuset1_update_tasks_flags(struct cpuset *cs);
void cpuset1_hotplug_update_tasks(struct cpuset *cs,
struct cpumask *new_cpus, nodemask_t *new_mems,
bool cpus_updated, bool mems_updated);
int cpuset1_validate_change(struct cpuset *cur, struct cpuset *trial);
bool cpuset1_cpus_excl_conflict(struct cpuset *cs1, struct cpuset *cs2);
void cpuset1_init(struct cpuset *cs);
void cpuset1_online_css(struct cgroup_subsys_state *css);
int cpuset1_generate_sched_domains(cpumask_var_t **domains,
struct sched_domain_attr **attributes);
#else
static inline void cpuset1_update_task_spread_flags(struct cpuset *cs,
struct task_struct *tsk) {}
static inline void cpuset1_update_tasks_flags(struct cpuset *cs) {}
static inline void cpuset1_hotplug_update_tasks(struct cpuset *cs,
struct cpumask *new_cpus, nodemask_t *new_mems,
bool cpus_updated, bool mems_updated) {}
static inline int cpuset1_validate_change(struct cpuset *cur,
struct cpuset *trial) { return 0; }
static inline bool cpuset1_cpus_excl_conflict(struct cpuset *cs1,
struct cpuset *cs2) { return false; }
static inline void cpuset1_init(struct cpuset *cs) {}
static inline void cpuset1_online_css(struct cgroup_subsys_state *css) {}
static inline int cpuset1_generate_sched_domains(cpumask_var_t **domains,
struct sched_domain_attr **attributes) { return 0; };
#endif /* CONFIG_CPUSETS_V1 */
#endif /* __CPUSET_INTERNAL_H */
+875
View File
@@ -0,0 +1,875 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "cgroup-internal.h"
#include "cpuset-internal.h"
/*
* Legacy hierarchy call to cgroup_transfer_tasks() is handled asynchrously
*/
struct cpuset_remove_tasks_struct {
struct work_struct work;
struct cpuset *cs;
};
/*
* Frequency meter - How fast is some event occurring?
*
* These routines manage a digitally filtered, constant time based,
* event frequency meter. There are four routines:
* fmeter_init() - initialize a frequency meter.
* fmeter_markevent() - called each time the event happens.
* fmeter_getrate() - returns the recent rate of such events.
* fmeter_update() - internal routine used to update fmeter.
*
* A common data structure is passed to each of these routines,
* which is used to keep track of the state required to manage the
* frequency meter and its digital filter.
*
* The filter works on the number of events marked per unit time.
* The filter is single-pole low-pass recursive (IIR). The time unit
* is 1 second. Arithmetic is done using 32-bit integers scaled to
* simulate 3 decimal digits of precision (multiplied by 1000).
*
* With an FM_COEF of 933, and a time base of 1 second, the filter
* has a half-life of 10 seconds, meaning that if the events quit
* happening, then the rate returned from the fmeter_getrate()
* will be cut in half each 10 seconds, until it converges to zero.
*
* It is not worth doing a real infinitely recursive filter. If more
* than FM_MAXTICKS ticks have elapsed since the last filter event,
* just compute FM_MAXTICKS ticks worth, by which point the level
* will be stable.
*
* Limit the count of unprocessed events to FM_MAXCNT, so as to avoid
* arithmetic overflow in the fmeter_update() routine.
*
* Given the simple 32 bit integer arithmetic used, this meter works
* best for reporting rates between one per millisecond (msec) and
* one per 32 (approx) seconds. At constant rates faster than one
* per msec it maxes out at values just under 1,000,000. At constant
* rates between one per msec, and one per second it will stabilize
* to a value N*1000, where N is the rate of events per second.
* At constant rates between one per second and one per 32 seconds,
* it will be choppy, moving up on the seconds that have an event,
* and then decaying until the next event. At rates slower than
* about one in 32 seconds, it decays all the way back to zero between
* each event.
*/
#define FM_COEF 933 /* coefficient for half-life of 10 secs */
#define FM_MAXTICKS ((u32)99) /* useless computing more ticks than this */
#define FM_MAXCNT 1000000 /* limit cnt to avoid overflow */
#define FM_SCALE 1000 /* faux fixed point scale */
/* Initialize a frequency meter */
static void fmeter_init(struct fmeter *fmp)
{
fmp->cnt = 0;
fmp->val = 0;
fmp->time = 0;
spin_lock_init(&fmp->lock);
}
/* Internal meter update - process cnt events and update value */
static void fmeter_update(struct fmeter *fmp)
{
time64_t now;
u32 ticks;
now = ktime_get_seconds();
ticks = now - fmp->time;
if (ticks == 0)
return;
ticks = min(FM_MAXTICKS, ticks);
while (ticks-- > 0)
fmp->val = (FM_COEF * fmp->val) / FM_SCALE;
fmp->time = now;
fmp->val += ((FM_SCALE - FM_COEF) * fmp->cnt) / FM_SCALE;
fmp->cnt = 0;
}
/* Process any previous ticks, then bump cnt by one (times scale). */
static void fmeter_markevent(struct fmeter *fmp)
{
spin_lock(&fmp->lock);
fmeter_update(fmp);
fmp->cnt = min(FM_MAXCNT, fmp->cnt + FM_SCALE);
spin_unlock(&fmp->lock);
}
/* Process any previous ticks, then return current value. */
static int fmeter_getrate(struct fmeter *fmp)
{
int val;
spin_lock(&fmp->lock);
fmeter_update(fmp);
val = fmp->val;
spin_unlock(&fmp->lock);
return val;
}
/*
* Collection of memory_pressure is suppressed unless
* this flag is enabled by writing "1" to the special
* cpuset file 'memory_pressure_enabled' in the root cpuset.
*/
int cpuset_memory_pressure_enabled __read_mostly;
/*
* __cpuset_memory_pressure_bump - keep stats of per-cpuset reclaims.
*
* Keep a running average of the rate of synchronous (direct)
* page reclaim efforts initiated by tasks in each cpuset.
*
* This represents the rate at which some task in the cpuset
* ran low on memory on all nodes it was allowed to use, and
* had to enter the kernels page reclaim code in an effort to
* create more free memory by tossing clean pages or swapping
* or writing dirty pages.
*
* Display to user space in the per-cpuset read-only file
* "memory_pressure". Value displayed is an integer
* representing the recent rate of entry into the synchronous
* (direct) page reclaim by any task attached to the cpuset.
*/
void __cpuset_memory_pressure_bump(void)
{
rcu_read_lock();
fmeter_markevent(&task_cs(current)->fmeter);
rcu_read_unlock();
}
static int update_relax_domain_level(struct cpuset *cs, s64 val)
{
#ifdef CONFIG_SMP
if (val < -1 || val > sched_domain_level_max + 1)
return -EINVAL;
#endif
if (val != cs->relax_domain_level) {
cs->relax_domain_level = val;
if (!cpumask_empty(cs->cpus_allowed) &&
is_sched_load_balance(cs))
rebuild_sched_domains_locked();
}
return 0;
}
static int cpuset_write_s64(struct cgroup_subsys_state *css, struct cftype *cft,
s64 val)
{
struct cpuset *cs = css_cs(css);
cpuset_filetype_t type = cft->private;
int retval = -ENODEV;
cpuset_full_lock();
if (!is_cpuset_online(cs))
goto out_unlock;
switch (type) {
case FILE_SCHED_RELAX_DOMAIN_LEVEL:
pr_info_once("cpuset.%s is deprecated\n", cft->name);
retval = update_relax_domain_level(cs, val);
break;
default:
retval = -EINVAL;
break;
}
out_unlock:
cpuset_full_unlock();
return retval;
}
static s64 cpuset_read_s64(struct cgroup_subsys_state *css, struct cftype *cft)
{
struct cpuset *cs = css_cs(css);
cpuset_filetype_t type = cft->private;
switch (type) {
case FILE_SCHED_RELAX_DOMAIN_LEVEL:
return cs->relax_domain_level;
default:
BUG();
}
/* Unreachable but makes gcc happy */
return 0;
}
/*
* update task's spread flag if cpuset's page/slab spread flag is set
*
* Call with callback_lock or cpuset_mutex held. The check can be skipped
* if on default hierarchy.
*/
void cpuset1_update_task_spread_flags(struct cpuset *cs,
struct task_struct *tsk)
{
if (cgroup_subsys_on_dfl(cpuset_cgrp_subsys))
return;
if (is_spread_page(cs))
task_set_spread_page(tsk);
else
task_clear_spread_page(tsk);
if (is_spread_slab(cs))
task_set_spread_slab(tsk);
else
task_clear_spread_slab(tsk);
}
/**
* cpuset1_update_tasks_flags - update the spread flags of tasks in the cpuset.
* @cs: the cpuset in which each task's spread flags needs to be changed
*
* Iterate through each task of @cs updating its spread flags. As this
* function is called with cpuset_mutex held, cpuset membership stays
* stable.
*/
void cpuset1_update_tasks_flags(struct cpuset *cs)
{
struct css_task_iter it;
struct task_struct *task;
css_task_iter_start(&cs->css, 0, &it);
while ((task = css_task_iter_next(&it)))
cpuset1_update_task_spread_flags(cs, task);
css_task_iter_end(&it);
}
/*
* If CPU and/or memory hotplug handlers, below, unplug any CPUs
* or memory nodes, we need to walk over the cpuset hierarchy,
* removing that CPU or node from all cpusets. If this removes the
* last CPU or node from a cpuset, then move the tasks in the empty
* cpuset to its next-highest non-empty parent.
*/
static void remove_tasks_in_empty_cpuset(struct cpuset *cs)
{
struct cpuset *parent;
/*
* Find its next-highest non-empty parent, (top cpuset
* has online cpus, so can't be empty).
*/
parent = parent_cs(cs);
while (cpumask_empty(parent->cpus_allowed) ||
nodes_empty(parent->mems_allowed))
parent = parent_cs(parent);
if (cgroup_transfer_tasks(parent->css.cgroup, cs->css.cgroup)) {
pr_err("cpuset: failed to transfer tasks out of empty cpuset ");
pr_cont_cgroup_name(cs->css.cgroup);
pr_cont("\n");
}
}
static void cpuset_migrate_tasks_workfn(struct work_struct *work)
{
struct cpuset_remove_tasks_struct *s;
s = container_of(work, struct cpuset_remove_tasks_struct, work);
remove_tasks_in_empty_cpuset(s->cs);
css_put(&s->cs->css);
kfree(s);
}
void cpuset1_hotplug_update_tasks(struct cpuset *cs,
struct cpumask *new_cpus, nodemask_t *new_mems,
bool cpus_updated, bool mems_updated)
{
bool is_empty;
cpuset_callback_lock_irq();
cpumask_copy(cs->cpus_allowed, new_cpus);
cpumask_copy(cs->effective_cpus, new_cpus);
cs->mems_allowed = *new_mems;
cs->effective_mems = *new_mems;
cpuset_callback_unlock_irq();
/*
* Don't call cpuset_update_tasks_cpumask() if the cpuset becomes empty,
* as the tasks will be migrated to an ancestor.
*/
if (cpus_updated && !cpumask_empty(cs->cpus_allowed))
cpuset_update_tasks_cpumask(cs, new_cpus);
if (mems_updated && !nodes_empty(cs->mems_allowed))
cpuset_update_tasks_nodemask(cs);
is_empty = cpumask_empty(cs->cpus_allowed) ||
nodes_empty(cs->mems_allowed);
/*
* Move tasks to the nearest ancestor with execution resources,
* This is full cgroup operation which will also call back into
* cpuset. Execute it asynchronously using workqueue.
*/
if (is_empty && cgroup_has_tasks(cs->css.cgroup) &&
css_tryget_online(&cs->css)) {
struct cpuset_remove_tasks_struct *s;
s = kzalloc_obj(*s);
if (WARN_ON_ONCE(!s)) {
css_put(&cs->css);
return;
}
s->cs = cs;
INIT_WORK(&s->work, cpuset_migrate_tasks_workfn);
schedule_work(&s->work);
}
}
/*
* is_cpuset_subset(p, q) - Is cpuset p a subset of cpuset q?
*
* One cpuset is a subset of another if all its allowed CPUs and
* Memory Nodes are a subset of the other, and its exclusive flags
* are only set if the other's are set. Call holding cpuset_mutex.
*/
static int is_cpuset_subset(const struct cpuset *p, const struct cpuset *q)
{
return cpumask_subset(p->cpus_allowed, q->cpus_allowed) &&
nodes_subset(p->mems_allowed, q->mems_allowed) &&
is_cpu_exclusive(p) <= is_cpu_exclusive(q) &&
is_mem_exclusive(p) <= is_mem_exclusive(q);
}
/*
* cpuset1_validate_change() - Validate conditions specific to legacy (v1)
* behavior.
*/
int cpuset1_validate_change(struct cpuset *cur, struct cpuset *trial)
{
struct cgroup_subsys_state *css;
struct cpuset *c, *par;
int ret;
WARN_ON_ONCE(!rcu_read_lock_held());
/* Each of our child cpusets must be a subset of us */
ret = -EBUSY;
cpuset_for_each_child(c, css, cur)
if (!is_cpuset_subset(c, trial))
goto out;
/* On legacy hierarchy, we must be a subset of our parent cpuset. */
ret = -EACCES;
par = parent_cs(cur);
if (par && !is_cpuset_subset(trial, par))
goto out;
/*
* Cpusets with tasks - existing or newly being attached - can't
* be changed to have empty cpus_allowed or mems_allowed.
*/
ret = -ENOSPC;
if (cpuset_is_populated(cur)) {
if (!cpumask_empty(cur->cpus_allowed) &&
cpumask_empty(trial->cpus_allowed))
goto out;
if (!nodes_empty(cur->mems_allowed) &&
nodes_empty(trial->mems_allowed))
goto out;
}
ret = 0;
out:
return ret;
}
/*
* cpuset1_cpus_excl_conflict() - Check if two cpusets have exclusive CPU conflicts
* to legacy (v1)
* @cs1: first cpuset to check
* @cs2: second cpuset to check
*
* Returns: true if CPU exclusivity conflict exists, false otherwise
*
* If either cpuset is CPU exclusive, their allowed CPUs cannot intersect.
*/
bool cpuset1_cpus_excl_conflict(struct cpuset *cs1, struct cpuset *cs2)
{
if (is_cpu_exclusive(cs1) || is_cpu_exclusive(cs2))
return cpumask_intersects(cs1->cpus_allowed,
cs2->cpus_allowed);
return false;
}
#ifdef CONFIG_PROC_PID_CPUSET
/*
* proc_cpuset_show()
* - Print tasks cpuset path into seq_file.
* - Used for /proc/<pid>/cpuset.
*/
int proc_cpuset_show(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *tsk)
{
char *buf;
struct cgroup_subsys_state *css;
int retval;
retval = -ENOMEM;
buf = kmalloc(PATH_MAX, GFP_KERNEL);
if (!buf)
goto out;
rcu_read_lock();
spin_lock_irq(&css_set_lock);
css = task_css(tsk, cpuset_cgrp_id);
retval = cgroup_path_ns_locked(css->cgroup, buf, PATH_MAX,
current->nsproxy->cgroup_ns);
spin_unlock_irq(&css_set_lock);
rcu_read_unlock();
if (retval == -E2BIG)
retval = -ENAMETOOLONG;
if (retval < 0)
goto out_free;
seq_puts(m, buf);
seq_putc(m, '\n');
retval = 0;
out_free:
kfree(buf);
out:
return retval;
}
#endif /* CONFIG_PROC_PID_CPUSET */
static u64 cpuset_read_u64(struct cgroup_subsys_state *css, struct cftype *cft)
{
struct cpuset *cs = css_cs(css);
cpuset_filetype_t type = cft->private;
switch (type) {
case FILE_CPU_EXCLUSIVE:
return is_cpu_exclusive(cs);
case FILE_MEM_EXCLUSIVE:
return is_mem_exclusive(cs);
case FILE_MEM_HARDWALL:
return is_mem_hardwall(cs);
case FILE_SCHED_LOAD_BALANCE:
return is_sched_load_balance(cs);
case FILE_MEMORY_MIGRATE:
return is_memory_migrate(cs);
case FILE_MEMORY_PRESSURE_ENABLED:
return cpuset_memory_pressure_enabled;
case FILE_MEMORY_PRESSURE:
return fmeter_getrate(&cs->fmeter);
case FILE_SPREAD_PAGE:
return is_spread_page(cs);
case FILE_SPREAD_SLAB:
return is_spread_slab(cs);
default:
BUG();
}
/* Unreachable but makes gcc happy */
return 0;
}
static int cpuset_write_u64(struct cgroup_subsys_state *css, struct cftype *cft,
u64 val)
{
struct cpuset *cs = css_cs(css);
cpuset_filetype_t type = cft->private;
int retval = 0;
cpuset_full_lock();
if (!is_cpuset_online(cs)) {
retval = -ENODEV;
goto out_unlock;
}
switch (type) {
case FILE_CPU_EXCLUSIVE:
retval = cpuset_update_flag(CS_CPU_EXCLUSIVE, cs, val);
break;
case FILE_MEM_EXCLUSIVE:
pr_info_once("cpuset.%s is deprecated\n", cft->name);
retval = cpuset_update_flag(CS_MEM_EXCLUSIVE, cs, val);
break;
case FILE_MEM_HARDWALL:
pr_info_once("cpuset.%s is deprecated\n", cft->name);
retval = cpuset_update_flag(CS_MEM_HARDWALL, cs, val);
break;
case FILE_SCHED_LOAD_BALANCE:
pr_info_once("cpuset.%s is deprecated, use cpuset.cpus.partition instead\n", cft->name);
retval = cpuset_update_flag(CS_SCHED_LOAD_BALANCE, cs, val);
break;
case FILE_MEMORY_MIGRATE:
pr_info_once("cpuset.%s is deprecated\n", cft->name);
retval = cpuset_update_flag(CS_MEMORY_MIGRATE, cs, val);
break;
case FILE_MEMORY_PRESSURE_ENABLED:
pr_info_once("cpuset.%s is deprecated, use memory.pressure with CONFIG_PSI instead\n", cft->name);
cpuset_memory_pressure_enabled = !!val;
break;
case FILE_SPREAD_PAGE:
pr_info_once("cpuset.%s is deprecated\n", cft->name);
retval = cpuset_update_flag(CS_SPREAD_PAGE, cs, val);
break;
case FILE_SPREAD_SLAB:
pr_warn_once("cpuset.%s is deprecated\n", cft->name);
retval = cpuset_update_flag(CS_SPREAD_SLAB, cs, val);
break;
default:
retval = -EINVAL;
break;
}
out_unlock:
cpuset_full_unlock();
return retval;
}
void cpuset1_init(struct cpuset *cs)
{
fmeter_init(&cs->fmeter);
cs->relax_domain_level = -1;
}
void cpuset1_online_css(struct cgroup_subsys_state *css)
{
struct cpuset *tmp_cs;
struct cgroup_subsys_state *pos_css;
struct cpuset *cs = css_cs(css);
struct cpuset *parent = parent_cs(cs);
lockdep_assert_cpus_held();
lockdep_assert_cpuset_lock_held();
if (is_spread_page(parent))
set_bit(CS_SPREAD_PAGE, &cs->flags);
if (is_spread_slab(parent))
set_bit(CS_SPREAD_SLAB, &cs->flags);
if (!test_bit(CGRP_CPUSET_CLONE_CHILDREN, &css->cgroup->flags))
return;
/*
* Clone @parent's configuration if CGRP_CPUSET_CLONE_CHILDREN is
* set. This flag handling is implemented in cgroup core for
* historical reasons - the flag may be specified during mount.
*
* Currently, if any sibling cpusets have exclusive cpus or mem, we
* refuse to clone the configuration - thereby refusing the task to
* be entered, and as a result refusing the sys_unshare() or
* clone() which initiated it. If this becomes a problem for some
* users who wish to allow that scenario, then this could be
* changed to grant parent->cpus_allowed-sibling_cpus_exclusive
* (and likewise for mems) to the new cgroup.
*/
rcu_read_lock();
cpuset_for_each_child(tmp_cs, pos_css, parent) {
if (is_mem_exclusive(tmp_cs) || is_cpu_exclusive(tmp_cs)) {
rcu_read_unlock();
return;
}
}
rcu_read_unlock();
cpuset_callback_lock_irq();
cs->mems_allowed = parent->mems_allowed;
cs->effective_mems = parent->mems_allowed;
cpumask_copy(cs->cpus_allowed, parent->cpus_allowed);
cpumask_copy(cs->effective_cpus, parent->cpus_allowed);
cpuset_callback_unlock_irq();
}
static void
update_domain_attr(struct sched_domain_attr *dattr, struct cpuset *c)
{
if (dattr->relax_domain_level < c->relax_domain_level)
dattr->relax_domain_level = c->relax_domain_level;
}
static void update_domain_attr_tree(struct sched_domain_attr *dattr,
struct cpuset *root_cs)
{
struct cpuset *cp;
struct cgroup_subsys_state *pos_css;
rcu_read_lock();
cpuset_for_each_descendant_pre(cp, pos_css, root_cs) {
/* skip the whole subtree if @cp doesn't have any CPU */
if (cpumask_empty(cp->cpus_allowed)) {
pos_css = css_rightmost_descendant(pos_css);
continue;
}
if (is_sched_load_balance(cp))
update_domain_attr(dattr, cp);
}
rcu_read_unlock();
}
/*
* cpuset1_generate_sched_domains()
*
* Finding the best partition (set of domains):
* The double nested loops below over i, j scan over the load
* balanced cpusets (using the array of cpuset pointers in csa[])
* looking for pairs of cpusets that have overlapping cpus_allowed
* and merging them using a union-find algorithm.
*
* The union of the cpus_allowed masks from the set of all cpusets
* having the same root then form the one element of the partition
* (one sched domain) to be passed to partition_sched_domains().
*/
int cpuset1_generate_sched_domains(cpumask_var_t **domains,
struct sched_domain_attr **attributes)
{
struct cpuset *cp; /* top-down scan of cpusets */
struct cpuset **csa; /* array of all cpuset ptrs */
int csn; /* how many cpuset ptrs in csa so far */
int i, j; /* indices for partition finding loops */
cpumask_var_t *doms; /* resulting partition; i.e. sched domains */
struct sched_domain_attr *dattr; /* attributes for custom domains */
int ndoms = 0; /* number of sched domains in result */
int nslot; /* next empty doms[] struct cpumask slot */
struct cgroup_subsys_state *pos_css;
int nslot_update;
lockdep_assert_cpuset_lock_held();
doms = NULL;
dattr = NULL;
csa = NULL;
/* Special case for the 99% of systems with one, full, sched domain */
if (is_sched_load_balance(&top_cpuset)) {
ndoms = 1;
doms = alloc_sched_domains(ndoms);
if (!doms)
goto done;
dattr = kmalloc_obj(struct sched_domain_attr);
if (dattr) {
*dattr = SD_ATTR_INIT;
update_domain_attr_tree(dattr, &top_cpuset);
}
cpumask_and(doms[0], top_cpuset.effective_cpus,
housekeeping_cpumask(HK_TYPE_DOMAIN));
goto done;
}
csa = kmalloc_objs(cp, nr_cpusets());
if (!csa)
goto done;
csn = 0;
rcu_read_lock();
cpuset_for_each_descendant_pre(cp, pos_css, &top_cpuset) {
if (cp == &top_cpuset)
continue;
/*
* Continue traversing beyond @cp iff @cp has some CPUs and
* isn't load balancing. The former is obvious. The
* latter: All child cpusets contain a subset of the
* parent's cpus, so just skip them, and then we call
* update_domain_attr_tree() to calc relax_domain_level of
* the corresponding sched domain.
*/
if (!cpumask_empty(cp->cpus_allowed) &&
!(is_sched_load_balance(cp) &&
cpumask_intersects(cp->cpus_allowed,
housekeeping_cpumask(HK_TYPE_DOMAIN))))
continue;
if (is_sched_load_balance(cp) &&
!cpumask_empty(cp->effective_cpus))
csa[csn++] = cp;
/* skip @cp's subtree */
pos_css = css_rightmost_descendant(pos_css);
continue;
}
rcu_read_unlock();
for (i = 0; i < csn; i++)
uf_node_init(&csa[i]->node);
/* Merge overlapping cpusets */
for (i = 0; i < csn; i++) {
for (j = i + 1; j < csn; j++) {
if (cpusets_overlap(csa[i], csa[j]))
uf_union(&csa[i]->node, &csa[j]->node);
}
}
/* Count the total number of domains */
for (i = 0; i < csn; i++) {
if (uf_find(&csa[i]->node) == &csa[i]->node)
ndoms++;
}
/*
* Now we know how many domains to create.
* Convert <csn, csa> to <ndoms, doms> and populate cpu masks.
*/
doms = alloc_sched_domains(ndoms);
if (!doms)
goto done;
/*
* The rest of the code, including the scheduler, can deal with
* dattr==NULL case. No need to abort if alloc fails.
*/
dattr = kmalloc_objs(struct sched_domain_attr, ndoms);
for (nslot = 0, i = 0; i < csn; i++) {
nslot_update = 0;
for (j = i; j < csn; j++) {
if (uf_find(&csa[j]->node) == &csa[i]->node) {
struct cpumask *dp = doms[nslot];
if (i == j) {
nslot_update = 1;
cpumask_clear(dp);
if (dattr)
*(dattr + nslot) = SD_ATTR_INIT;
}
cpumask_or(dp, dp, csa[j]->effective_cpus);
cpumask_and(dp, dp, housekeeping_cpumask(HK_TYPE_DOMAIN));
if (dattr)
update_domain_attr_tree(dattr + nslot, csa[j]);
}
}
if (nslot_update)
nslot++;
}
BUG_ON(nslot != ndoms);
done:
kfree(csa);
/*
* Fallback to the default domain if kmalloc() failed.
* See comments in partition_sched_domains().
*/
if (doms == NULL)
ndoms = 1;
*domains = doms;
*attributes = dattr;
return ndoms;
}
/*
* for the common functions, 'private' gives the type of file
*/
struct cftype cpuset1_files[] = {
{
.name = "cpus",
.seq_show = cpuset_common_seq_show,
.write = cpuset_write_resmask,
.max_write_len = (100U + 6 * NR_CPUS),
.private = FILE_CPULIST,
},
{
.name = "mems",
.seq_show = cpuset_common_seq_show,
.write = cpuset_write_resmask,
.max_write_len = (100U + 6 * MAX_NUMNODES),
.private = FILE_MEMLIST,
},
{
.name = "effective_cpus",
.seq_show = cpuset_common_seq_show,
.private = FILE_EFFECTIVE_CPULIST,
},
{
.name = "effective_mems",
.seq_show = cpuset_common_seq_show,
.private = FILE_EFFECTIVE_MEMLIST,
},
{
.name = "cpu_exclusive",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_CPU_EXCLUSIVE,
},
{
.name = "mem_exclusive",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_MEM_EXCLUSIVE,
},
{
.name = "mem_hardwall",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_MEM_HARDWALL,
},
{
.name = "sched_load_balance",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_SCHED_LOAD_BALANCE,
},
{
.name = "sched_relax_domain_level",
.read_s64 = cpuset_read_s64,
.write_s64 = cpuset_write_s64,
.private = FILE_SCHED_RELAX_DOMAIN_LEVEL,
},
{
.name = "memory_migrate",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_MEMORY_MIGRATE,
},
{
.name = "memory_pressure",
.read_u64 = cpuset_read_u64,
.private = FILE_MEMORY_PRESSURE,
},
{
.name = "memory_spread_page",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_SPREAD_PAGE,
},
{
/* obsolete, may be removed in the future */
.name = "memory_spread_slab",
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_SPREAD_SLAB,
},
{
.name = "memory_pressure_enabled",
.flags = CFTYPE_ONLY_ON_ROOT,
.read_u64 = cpuset_read_u64,
.write_u64 = cpuset_write_u64,
.private = FILE_MEMORY_PRESSURE_ENABLED,
},
{ } /* terminate */
};
File diff suppressed because it is too large Load Diff
+377
View File
@@ -0,0 +1,377 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Debug controller
*
* WARNING: This controller is for cgroup core debugging only.
* Its interfaces are unstable and subject to changes at any time.
*/
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include "cgroup-internal.h"
static struct cgroup_subsys_state *
debug_css_alloc(struct cgroup_subsys_state *parent_css)
{
struct cgroup_subsys_state *css = kzalloc_obj(*css);
if (!css)
return ERR_PTR(-ENOMEM);
return css;
}
static void debug_css_free(struct cgroup_subsys_state *css)
{
kfree(css);
}
/*
* debug_taskcount_read - return the number of tasks in a cgroup.
* @cgrp: the cgroup in question
*/
static u64 debug_taskcount_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
return cgroup_task_count(css->cgroup);
}
static int current_css_set_read(struct seq_file *seq, void *v)
{
struct kernfs_open_file *of = seq->private;
struct css_set *cset;
struct cgroup_subsys *ss;
struct cgroup_subsys_state *css;
int i, refcnt;
if (!cgroup_kn_lock_live(of->kn, false))
return -ENODEV;
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
refcnt = refcount_read(&cset->refcount);
seq_printf(seq, "css_set %pK %d", cset, refcnt);
if (refcnt > cset->nr_tasks)
seq_printf(seq, " +%d", refcnt - cset->nr_tasks);
seq_puts(seq, "\n");
/*
* Print the css'es stored in the current css_set.
*/
for_each_subsys(ss, i) {
css = cset->subsys[ss->id];
if (!css)
continue;
seq_printf(seq, "%2d: %-4s\t- %p[%d]\n", ss->id, ss->name,
css, css->id);
}
spin_unlock_irq(&css_set_lock);
cgroup_kn_unlock(of->kn);
return 0;
}
static u64 current_css_set_refcount_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
u64 count;
rcu_read_lock();
count = refcount_read(&task_css_set(current)->refcount);
rcu_read_unlock();
return count;
}
static int current_css_set_cg_links_read(struct seq_file *seq, void *v)
{
struct cgrp_cset_link *link;
struct css_set *cset;
char *name_buf;
name_buf = kmalloc(NAME_MAX + 1, GFP_KERNEL);
if (!name_buf)
return -ENOMEM;
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
list_for_each_entry(link, &cset->cgrp_links, cgrp_link) {
struct cgroup *c = link->cgrp;
cgroup_name(c, name_buf, NAME_MAX + 1);
seq_printf(seq, "Root %d group %s\n",
c->root->hierarchy_id, name_buf);
}
spin_unlock_irq(&css_set_lock);
kfree(name_buf);
return 0;
}
#define MAX_TASKS_SHOWN_PER_CSS 25
static int cgroup_css_links_read(struct seq_file *seq, void *v)
{
struct cgroup_subsys_state *css = seq_css(seq);
struct cgrp_cset_link *link;
int dead_cnt = 0, extra_refs = 0, threaded_csets = 0;
spin_lock_irq(&css_set_lock);
list_for_each_entry(link, &css->cgroup->cset_links, cset_link) {
struct css_set *cset = link->cset;
struct task_struct *task;
int count = 0;
int refcnt = refcount_read(&cset->refcount);
/*
* Print out the proc_cset and threaded_cset relationship
* and highlight difference between refcount and task_count.
*/
seq_printf(seq, "css_set %pK", cset);
if (rcu_dereference_protected(cset->dom_cset, 1) != cset) {
threaded_csets++;
seq_printf(seq, "=>%pK", cset->dom_cset);
}
if (!list_empty(&cset->threaded_csets)) {
struct css_set *tcset;
int idx = 0;
list_for_each_entry(tcset, &cset->threaded_csets,
threaded_csets_node) {
seq_puts(seq, idx ? "," : "<=");
seq_printf(seq, "%pK", tcset);
idx++;
}
} else {
seq_printf(seq, " %d", refcnt);
if (refcnt - cset->nr_tasks > 0) {
int extra = refcnt - cset->nr_tasks;
seq_printf(seq, " +%d", extra);
/*
* Take out the one additional reference in
* init_css_set.
*/
if (cset == &init_css_set)
extra--;
extra_refs += extra;
}
}
seq_puts(seq, "\n");
list_for_each_entry(task, &cset->tasks, cg_list) {
if (count++ <= MAX_TASKS_SHOWN_PER_CSS)
seq_printf(seq, " task %d\n",
task_pid_vnr(task));
}
list_for_each_entry(task, &cset->mg_tasks, cg_list) {
if (count++ <= MAX_TASKS_SHOWN_PER_CSS)
seq_printf(seq, " task %d\n",
task_pid_vnr(task));
}
/* show # of overflowed tasks */
if (count > MAX_TASKS_SHOWN_PER_CSS)
seq_printf(seq, " ... (%d)\n",
count - MAX_TASKS_SHOWN_PER_CSS);
if (cset->dead) {
seq_puts(seq, " [dead]\n");
dead_cnt++;
}
WARN_ON(count != cset->nr_tasks);
}
spin_unlock_irq(&css_set_lock);
if (!dead_cnt && !extra_refs && !threaded_csets)
return 0;
seq_puts(seq, "\n");
if (threaded_csets)
seq_printf(seq, "threaded css_sets = %d\n", threaded_csets);
if (extra_refs)
seq_printf(seq, "extra references = %d\n", extra_refs);
if (dead_cnt)
seq_printf(seq, "dead css_sets = %d\n", dead_cnt);
return 0;
}
static int cgroup_subsys_states_read(struct seq_file *seq, void *v)
{
struct kernfs_open_file *of = seq->private;
struct cgroup *cgrp;
struct cgroup_subsys *ss;
struct cgroup_subsys_state *css;
char pbuf[16];
int i;
cgrp = cgroup_kn_lock_live(of->kn, false);
if (!cgrp)
return -ENODEV;
for_each_subsys(ss, i) {
css = rcu_dereference_check(cgrp->subsys[ss->id], true);
if (!css)
continue;
pbuf[0] = '\0';
/* Show the parent CSS if applicable*/
if (css->parent)
snprintf(pbuf, sizeof(pbuf) - 1, " P=%d",
css->parent->id);
seq_printf(seq, "%2d: %-4s\t- %p[%d] %d%s\n", ss->id, ss->name,
css, css->id,
atomic_read(&css->online_cnt), pbuf);
}
cgroup_kn_unlock(of->kn);
return 0;
}
static void cgroup_masks_read_one(struct seq_file *seq, const char *name,
u32 mask)
{
struct cgroup_subsys *ss;
int ssid;
bool first = true;
seq_printf(seq, "%-17s: ", name);
for_each_subsys(ss, ssid) {
if (!(mask & (1 << ssid)))
continue;
if (!first)
seq_puts(seq, ", ");
seq_puts(seq, ss->name);
first = false;
}
seq_putc(seq, '\n');
}
static int cgroup_masks_read(struct seq_file *seq, void *v)
{
struct kernfs_open_file *of = seq->private;
struct cgroup *cgrp;
cgrp = cgroup_kn_lock_live(of->kn, false);
if (!cgrp)
return -ENODEV;
cgroup_masks_read_one(seq, "subtree_control", cgrp->subtree_control);
cgroup_masks_read_one(seq, "subtree_ss_mask", cgrp->subtree_ss_mask);
cgroup_kn_unlock(of->kn);
return 0;
}
static u64 releasable_read(struct cgroup_subsys_state *css, struct cftype *cft)
{
return (!cgroup_is_populated(css->cgroup) &&
!css_has_online_children(&css->cgroup->self));
}
static struct cftype debug_legacy_files[] = {
{
.name = "taskcount",
.read_u64 = debug_taskcount_read,
},
{
.name = "current_css_set",
.seq_show = current_css_set_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "current_css_set_cg_links",
.seq_show = current_css_set_cg_links_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "cgroup_css_links",
.seq_show = cgroup_css_links_read,
},
{
.name = "cgroup_subsys_states",
.seq_show = cgroup_subsys_states_read,
},
{
.name = "cgroup_masks",
.seq_show = cgroup_masks_read,
},
{
.name = "releasable",
.read_u64 = releasable_read,
},
{ } /* terminate */
};
static struct cftype debug_files[] = {
{
.name = "taskcount",
.read_u64 = debug_taskcount_read,
},
{
.name = "current_css_set",
.seq_show = current_css_set_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "current_css_set_cg_links",
.seq_show = current_css_set_cg_links_read,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "css_links",
.seq_show = cgroup_css_links_read,
},
{
.name = "csses",
.seq_show = cgroup_subsys_states_read,
},
{
.name = "masks",
.seq_show = cgroup_masks_read,
},
{ } /* terminate */
};
struct cgroup_subsys debug_cgrp_subsys = {
.css_alloc = debug_css_alloc,
.css_free = debug_css_free,
.legacy_cftypes = debug_legacy_files,
};
/*
* On v2, debug is an implicit controller enabled by "cgroup_debug" boot
* parameter.
*/
void __init enable_debug_cgroup(void)
{
debug_cgrp_subsys.dfl_cftypes = debug_files;
debug_cgrp_subsys.implicit_on_dfl = true;
debug_cgrp_subsys.threaded = true;
}
+886
View File
@@ -0,0 +1,886 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2023-2024 Intel Corporation (Maarten Lankhorst <dev@lankhorst.se>)
* Copyright 2024 Red Hat (Maxime Ripard <mripard@kernel.org>)
* Partially based on the rdma and misc controllers, which bear the following copyrights:
*
* Copyright 2020 Google LLC
* Copyright (C) 2016 Parav Pandit <pandit.parav@gmail.com>
*/
#include <linux/cgroup.h>
#include <linux/cgroup_dmem.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/page_counter.h>
#include <linux/parser.h>
#include <linux/refcount.h>
#include <linux/rculist.h>
#include <linux/slab.h>
struct dmem_cgroup_region {
/**
* @ref: References keeping the region alive.
* Keeps the region reference alive after a succesful RCU lookup.
*/
struct kref ref;
/** @rcu: RCU head for freeing */
struct rcu_head rcu;
/**
* @region_node: Linked into &dmem_cgroup_regions list.
* Protected by RCU and global spinlock.
*/
struct list_head region_node;
/**
* @pools: List of pools linked to this region.
* Protected by global spinlock only
*/
struct list_head pools;
/** @size: Size of region, in bytes */
u64 size;
/** @name: Name describing the node, set by dmem_cgroup_register_region */
char *name;
/**
* @unregistered: Whether the region is unregistered by its caller.
* No new pools should be added to the region afterwards.
*/
bool unregistered;
};
struct dmemcg_state {
struct cgroup_subsys_state css;
struct list_head pools;
};
struct dmem_cgroup_pool_state {
struct dmem_cgroup_region *region;
struct dmemcg_state *cs;
/* css node, RCU protected against region teardown */
struct list_head css_node;
/* dev node, no RCU protection required */
struct list_head region_node;
struct rcu_head rcu;
struct page_counter cnt;
struct dmem_cgroup_pool_state *parent;
refcount_t ref;
bool inited;
};
/*
* 3 operations require locking protection:
* - Registering and unregistering region to/from list, requires global lock.
* - Adding a dmem_cgroup_pool_state to a CSS, removing when CSS is freed.
* - Adding a dmem_cgroup_pool_state to a region list.
*
* Since for the most common operations RCU provides enough protection, I
* do not think more granular locking makes sense. Most protection is offered
* by RCU and the lockless operating page_counter.
*/
static DEFINE_SPINLOCK(dmemcg_lock);
static LIST_HEAD(dmem_cgroup_regions);
static void dmemcg_free_region(struct kref *ref);
static void dmemcg_pool_free_rcu(struct rcu_head *rcu);
static inline struct dmemcg_state *
css_to_dmemcs(struct cgroup_subsys_state *css)
{
return container_of(css, struct dmemcg_state, css);
}
static inline struct dmemcg_state *get_current_dmemcs(void)
{
return css_to_dmemcs(task_get_css(current, dmem_cgrp_id));
}
static struct dmemcg_state *parent_dmemcs(struct dmemcg_state *cg)
{
return cg->css.parent ? css_to_dmemcs(cg->css.parent) : NULL;
}
static void dmemcg_pool_get(struct dmem_cgroup_pool_state *pool)
{
refcount_inc(&pool->ref);
}
static bool dmemcg_pool_tryget(struct dmem_cgroup_pool_state *pool)
{
return refcount_inc_not_zero(&pool->ref);
}
static void dmemcg_pool_put(struct dmem_cgroup_pool_state *pool)
{
if (!refcount_dec_and_test(&pool->ref))
return;
call_rcu(&pool->rcu, dmemcg_pool_free_rcu);
}
static void dmemcg_pool_free_rcu(struct rcu_head *rcu)
{
struct dmem_cgroup_pool_state *pool = container_of(rcu, typeof(*pool), rcu);
if (pool->parent)
dmemcg_pool_put(pool->parent);
kref_put(&pool->region->ref, dmemcg_free_region);
kfree(pool);
}
static void free_cg_pool(struct dmem_cgroup_pool_state *pool)
{
list_del(&pool->region_node);
dmemcg_pool_put(pool);
}
static void
set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val)
{
page_counter_set_min(&pool->cnt, val);
}
static void
set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val)
{
page_counter_set_low(&pool->cnt, val);
}
static void
set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val)
{
page_counter_set_max(&pool->cnt, val);
}
static u64 get_resource_low(struct dmem_cgroup_pool_state *pool)
{
return pool ? READ_ONCE(pool->cnt.low) : 0;
}
static u64 get_resource_min(struct dmem_cgroup_pool_state *pool)
{
return pool ? READ_ONCE(pool->cnt.min) : 0;
}
static u64 get_resource_max(struct dmem_cgroup_pool_state *pool)
{
return pool ? READ_ONCE(pool->cnt.max) : PAGE_COUNTER_MAX;
}
static u64 get_resource_current(struct dmem_cgroup_pool_state *pool)
{
return pool ? page_counter_read(&pool->cnt) : 0;
}
static void reset_all_resource_limits(struct dmem_cgroup_pool_state *rpool)
{
set_resource_min(rpool, 0);
set_resource_low(rpool, 0);
set_resource_max(rpool, PAGE_COUNTER_MAX);
}
static void dmemcs_offline(struct cgroup_subsys_state *css)
{
struct dmemcg_state *dmemcs = css_to_dmemcs(css);
struct dmem_cgroup_pool_state *pool;
rcu_read_lock();
list_for_each_entry_rcu(pool, &dmemcs->pools, css_node)
reset_all_resource_limits(pool);
rcu_read_unlock();
}
static void dmemcs_free(struct cgroup_subsys_state *css)
{
struct dmemcg_state *dmemcs = css_to_dmemcs(css);
struct dmem_cgroup_pool_state *pool, *next;
spin_lock(&dmemcg_lock);
list_for_each_entry_safe(pool, next, &dmemcs->pools, css_node) {
/*
*The pool is dead and all references are 0,
* no need for RCU protection with list_del_rcu or freeing.
*/
list_del(&pool->css_node);
free_cg_pool(pool);
}
spin_unlock(&dmemcg_lock);
kfree(dmemcs);
}
static struct cgroup_subsys_state *
dmemcs_alloc(struct cgroup_subsys_state *parent_css)
{
struct dmemcg_state *dmemcs = kzalloc_obj(*dmemcs);
if (!dmemcs)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&dmemcs->pools);
return &dmemcs->css;
}
static struct dmem_cgroup_pool_state *
find_cg_pool_locked(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region)
{
struct dmem_cgroup_pool_state *pool;
list_for_each_entry_rcu(pool, &dmemcs->pools, css_node, spin_is_locked(&dmemcg_lock))
if (pool->region == region)
return pool;
return NULL;
}
static struct dmem_cgroup_pool_state *pool_parent(struct dmem_cgroup_pool_state *pool)
{
if (!pool->cnt.parent)
return NULL;
return container_of(pool->cnt.parent, typeof(*pool), cnt);
}
static void
dmem_cgroup_calculate_protection(struct dmem_cgroup_pool_state *limit_pool,
struct dmem_cgroup_pool_state *test_pool)
{
struct page_counter *climit;
struct cgroup_subsys_state *css;
struct dmemcg_state *dmemcg_iter;
struct dmem_cgroup_pool_state *pool, *found_pool;
climit = &limit_pool->cnt;
rcu_read_lock();
css_for_each_descendant_pre(css, &limit_pool->cs->css) {
dmemcg_iter = container_of(css, struct dmemcg_state, css);
found_pool = NULL;
list_for_each_entry_rcu(pool, &dmemcg_iter->pools, css_node) {
if (pool->region == limit_pool->region) {
found_pool = pool;
break;
}
}
if (!found_pool)
continue;
page_counter_calculate_protection(
climit, &found_pool->cnt, true);
if (found_pool == test_pool)
break;
}
rcu_read_unlock();
}
/**
* dmem_cgroup_state_evict_valuable() - Check if we should evict from test_pool
* @limit_pool: The pool for which we hit limits
* @test_pool: The pool for which to test
* @ignore_low: Whether we have to respect low watermarks.
* @ret_hit_low: Pointer to whether it makes sense to consider low watermark.
*
* This function returns true if we can evict from @test_pool, false if not.
* When returning false and @ignore_low is false, @ret_hit_low may
* be set to true to indicate this function can be retried with @ignore_low
* set to true.
*
* Return: bool
*/
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)
{
struct dmem_cgroup_pool_state *pool = test_pool;
struct page_counter *ctest;
u64 used, min, low;
/* Can always evict from current pool, despite limits */
if (limit_pool == test_pool)
return true;
if (limit_pool) {
if (!parent_dmemcs(limit_pool->cs))
return true;
for (pool = test_pool; pool && limit_pool != pool; pool = pool_parent(pool))
{}
if (!pool)
return false;
} else {
/*
* If there is no cgroup limiting memory usage, use the root
* cgroup instead for limit calculations.
*/
for (limit_pool = test_pool; pool_parent(limit_pool); limit_pool = pool_parent(limit_pool))
{}
}
ctest = &test_pool->cnt;
dmem_cgroup_calculate_protection(limit_pool, test_pool);
used = page_counter_read(ctest);
min = READ_ONCE(ctest->emin);
if (used <= min)
return false;
if (!ignore_low) {
low = READ_ONCE(ctest->elow);
if (used > low)
return true;
*ret_hit_low = true;
return false;
}
return true;
}
EXPORT_SYMBOL_GPL(dmem_cgroup_state_evict_valuable);
static struct dmem_cgroup_pool_state *
alloc_pool_single(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region,
struct dmem_cgroup_pool_state **allocpool)
{
struct dmemcg_state *parent = parent_dmemcs(dmemcs);
struct dmem_cgroup_pool_state *pool, *ppool = NULL;
if (!*allocpool) {
pool = kzalloc_obj(*pool, GFP_NOWAIT);
if (!pool)
return ERR_PTR(-ENOMEM);
} else {
pool = *allocpool;
*allocpool = NULL;
}
pool->region = region;
pool->cs = dmemcs;
if (parent)
ppool = find_cg_pool_locked(parent, region);
page_counter_init(&pool->cnt,
ppool ? &ppool->cnt : NULL, true);
reset_all_resource_limits(pool);
refcount_set(&pool->ref, 1);
kref_get(&region->ref);
if (ppool && !pool->parent) {
pool->parent = ppool;
dmemcg_pool_get(ppool);
}
list_add_tail_rcu(&pool->css_node, &dmemcs->pools);
list_add_tail(&pool->region_node, &region->pools);
if (!parent)
pool->inited = true;
else
pool->inited = ppool ? ppool->inited : false;
return pool;
}
static struct dmem_cgroup_pool_state *
get_cg_pool_locked(struct dmemcg_state *dmemcs, struct dmem_cgroup_region *region,
struct dmem_cgroup_pool_state **allocpool)
{
struct dmem_cgroup_pool_state *pool, *ppool, *retpool;
struct dmemcg_state *p, *pp;
/*
* Recursively create pool, we may not initialize yet on
* recursion, this is done as a separate step.
*/
for (p = dmemcs; p; p = parent_dmemcs(p)) {
pool = find_cg_pool_locked(p, region);
if (!pool)
pool = alloc_pool_single(p, region, allocpool);
if (IS_ERR(pool))
return pool;
if (p == dmemcs && pool->inited)
return pool;
if (pool->inited)
break;
}
retpool = pool = find_cg_pool_locked(dmemcs, region);
for (p = dmemcs, pp = parent_dmemcs(dmemcs); pp; p = pp, pp = parent_dmemcs(p)) {
if (pool->inited)
break;
/* ppool was created if it didn't exist by above loop. */
ppool = find_cg_pool_locked(pp, region);
/* Fix up parent links, mark as inited. */
pool->cnt.parent = &ppool->cnt;
if (ppool && !pool->parent) {
pool->parent = ppool;
dmemcg_pool_get(ppool);
}
pool->inited = true;
pool = ppool;
}
return retpool;
}
static void dmemcg_free_rcu(struct rcu_head *rcu)
{
struct dmem_cgroup_region *region = container_of(rcu, typeof(*region), rcu);
struct dmem_cgroup_pool_state *pool, *next;
list_for_each_entry_safe(pool, next, &region->pools, region_node)
free_cg_pool(pool);
kfree(region->name);
kfree(region);
}
static void dmemcg_free_region(struct kref *ref)
{
struct dmem_cgroup_region *cgregion = container_of(ref, typeof(*cgregion), ref);
call_rcu(&cgregion->rcu, dmemcg_free_rcu);
}
/**
* dmem_cgroup_unregister_region() - Unregister a previously registered region.
* @region: The region to unregister.
*
* This function undoes dmem_cgroup_register_region.
*/
void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region)
{
struct dmem_cgroup_pool_state *pool, *next;
if (!region)
return;
spin_lock(&dmemcg_lock);
/* Remove from global region list */
list_del_rcu(&region->region_node);
list_for_each_entry_safe(pool, next, &region->pools, region_node) {
list_del_rcu(&pool->css_node);
list_del(&pool->region_node);
dmemcg_pool_put(pool);
}
/*
* Ensure any RCU based lookups fail. Additionally,
* no new pools should be added to the dead region
* by get_cg_pool_unlocked.
*/
region->unregistered = true;
spin_unlock(&dmemcg_lock);
kref_put(&region->ref, dmemcg_free_region);
}
EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region);
/**
* dmem_cgroup_register_region() - Register a regions for dev cgroup.
* @size: Size of region to register, in bytes.
* @fmt: Region parameters to register
*
* This function registers a node in the dmem cgroup with the
* name given. After calling this function, the region can be
* used for allocations.
*
* Return: NULL or a struct on success, PTR_ERR on failure.
*/
struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt, ...)
{
struct dmem_cgroup_region *ret;
char *region_name;
va_list ap;
if (!size)
return NULL;
va_start(ap, fmt);
region_name = kvasprintf(GFP_KERNEL, fmt, ap);
va_end(ap);
if (!region_name)
return ERR_PTR(-ENOMEM);
ret = kzalloc_obj(*ret);
if (!ret) {
kfree(region_name);
return ERR_PTR(-ENOMEM);
}
INIT_LIST_HEAD(&ret->pools);
ret->name = region_name;
ret->size = size;
kref_init(&ret->ref);
spin_lock(&dmemcg_lock);
list_add_tail_rcu(&ret->region_node, &dmem_cgroup_regions);
spin_unlock(&dmemcg_lock);
return ret;
}
EXPORT_SYMBOL_GPL(dmem_cgroup_register_region);
static struct dmem_cgroup_region *dmemcg_get_region_by_name(const char *name)
{
struct dmem_cgroup_region *region;
list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node, spin_is_locked(&dmemcg_lock))
if (!strcmp(name, region->name) &&
kref_get_unless_zero(&region->ref))
return region;
return NULL;
}
/**
* dmem_cgroup_pool_state_put() - Drop a reference to a dmem_cgroup_pool_state
* @pool: &dmem_cgroup_pool_state
*
* Called to drop a reference to the limiting pool returned by
* dmem_cgroup_try_charge().
*/
void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool)
{
if (pool) {
css_put(&pool->cs->css);
dmemcg_pool_put(pool);
}
}
EXPORT_SYMBOL_GPL(dmem_cgroup_pool_state_put);
static struct dmem_cgroup_pool_state *
get_cg_pool_unlocked(struct dmemcg_state *cg, struct dmem_cgroup_region *region)
{
struct dmem_cgroup_pool_state *pool, *allocpool = NULL;
/* fastpath lookup? */
rcu_read_lock();
pool = find_cg_pool_locked(cg, region);
if (pool && !READ_ONCE(pool->inited))
pool = NULL;
if (pool && !dmemcg_pool_tryget(pool))
pool = NULL;
rcu_read_unlock();
while (!pool) {
spin_lock(&dmemcg_lock);
if (!region->unregistered)
pool = get_cg_pool_locked(cg, region, &allocpool);
else
pool = ERR_PTR(-ENODEV);
if (!IS_ERR(pool))
dmemcg_pool_get(pool);
spin_unlock(&dmemcg_lock);
if (pool == ERR_PTR(-ENOMEM)) {
pool = NULL;
if (WARN_ON(allocpool))
continue;
allocpool = kzalloc_obj(*allocpool);
if (allocpool) {
pool = NULL;
continue;
}
pool = ERR_PTR(-ENOMEM);
}
}
kfree(allocpool);
return pool;
}
/**
* dmem_cgroup_uncharge() - Uncharge a pool.
* @pool: Pool to uncharge.
* @size: Size to uncharge.
*
* Undoes the effects of dmem_cgroup_try_charge.
* Must be called with the returned pool as argument,
* and same @index and @size.
*/
void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size)
{
if (!pool)
return;
page_counter_uncharge(&pool->cnt, size);
css_put(&pool->cs->css);
dmemcg_pool_put(pool);
}
EXPORT_SYMBOL_GPL(dmem_cgroup_uncharge);
/**
* dmem_cgroup_try_charge() - Try charging a new allocation to a region.
* @region: dmem region to charge
* @size: Size (in bytes) to charge.
* @ret_pool: On succesfull allocation, the pool that is charged.
* @ret_limit_pool: On a failed allocation, the limiting pool.
*
* This function charges the @region region for a size of @size bytes.
*
* If the function succeeds, @ret_pool is set, which must be passed to
* dmem_cgroup_uncharge() when undoing the allocation.
*
* When this function fails with -EAGAIN and @ret_limit_pool is non-null, it
* will be set to the pool for which the limit is hit. This can be used for
* eviction as argument to dmem_cgroup_evict_valuable(). This reference must be freed
* with @dmem_cgroup_pool_state_put().
*
* Return: 0 on success, -EAGAIN on hitting a limit, or a negative errno on failure.
*/
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)
{
struct dmemcg_state *cg;
struct dmem_cgroup_pool_state *pool;
struct page_counter *fail;
int ret;
*ret_pool = NULL;
if (ret_limit_pool)
*ret_limit_pool = NULL;
/*
* hold on to css, as cgroup can be removed but resource
* accounting happens on css.
*/
cg = get_current_dmemcs();
pool = get_cg_pool_unlocked(cg, region);
if (IS_ERR(pool)) {
ret = PTR_ERR(pool);
goto err;
}
if (!page_counter_try_charge(&pool->cnt, size, &fail)) {
if (ret_limit_pool) {
*ret_limit_pool = container_of(fail, struct dmem_cgroup_pool_state, cnt);
css_get(&(*ret_limit_pool)->cs->css);
dmemcg_pool_get(*ret_limit_pool);
}
dmemcg_pool_put(pool);
ret = -EAGAIN;
goto err;
}
/* On success, reference from get_current_dmemcs is transferred to *ret_pool */
*ret_pool = pool;
return 0;
err:
css_put(&cg->css);
return ret;
}
EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge);
static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v)
{
struct dmem_cgroup_region *region;
rcu_read_lock();
list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node) {
seq_puts(sf, region->name);
seq_printf(sf, " %llu\n", region->size);
}
rcu_read_unlock();
return 0;
}
static int dmemcg_parse_limit(char *options, u64 *new_limit)
{
char *end;
if (!strcmp(options, "max")) {
*new_limit = PAGE_COUNTER_MAX;
return 0;
}
*new_limit = memparse(options, &end);
if (*end != '\0')
return -EINVAL;
return 0;
}
static ssize_t dmemcg_limit_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off,
void (*apply)(struct dmem_cgroup_pool_state *, u64))
{
struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of));
int err = 0;
while (buf && !err) {
struct dmem_cgroup_pool_state *pool = NULL;
char *options, *region_name;
struct dmem_cgroup_region *region;
u64 new_limit;
options = buf;
buf = strchr(buf, '\n');
if (buf)
*buf++ = '\0';
options = strstrip(options);
/* eat empty lines */
if (!options[0])
continue;
region_name = strsep(&options, " \t");
if (!region_name[0])
continue;
if (!options || !*options)
return -EINVAL;
rcu_read_lock();
region = dmemcg_get_region_by_name(region_name);
rcu_read_unlock();
if (!region)
return -EINVAL;
err = dmemcg_parse_limit(options, &new_limit);
if (err < 0)
goto out_put;
pool = get_cg_pool_unlocked(dmemcs, region);
if (IS_ERR(pool)) {
err = PTR_ERR(pool);
goto out_put;
}
/* And commit */
apply(pool, new_limit);
dmemcg_pool_put(pool);
out_put:
kref_put(&region->ref, dmemcg_free_region);
}
return err ?: nbytes;
}
static int dmemcg_limit_show(struct seq_file *sf, void *v,
u64 (*fn)(struct dmem_cgroup_pool_state *))
{
struct dmemcg_state *dmemcs = css_to_dmemcs(seq_css(sf));
struct dmem_cgroup_region *region;
rcu_read_lock();
list_for_each_entry_rcu(region, &dmem_cgroup_regions, region_node) {
struct dmem_cgroup_pool_state *pool = find_cg_pool_locked(dmemcs, region);
u64 val;
seq_puts(sf, region->name);
val = fn(pool);
if (val < PAGE_COUNTER_MAX)
seq_printf(sf, " %lld\n", val);
else
seq_puts(sf, " max\n");
}
rcu_read_unlock();
return 0;
}
static int dmem_cgroup_region_current_show(struct seq_file *sf, void *v)
{
return dmemcg_limit_show(sf, v, get_resource_current);
}
static int dmem_cgroup_region_min_show(struct seq_file *sf, void *v)
{
return dmemcg_limit_show(sf, v, get_resource_min);
}
static ssize_t dmem_cgroup_region_min_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
return dmemcg_limit_write(of, buf, nbytes, off, set_resource_min);
}
static int dmem_cgroup_region_low_show(struct seq_file *sf, void *v)
{
return dmemcg_limit_show(sf, v, get_resource_low);
}
static ssize_t dmem_cgroup_region_low_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
return dmemcg_limit_write(of, buf, nbytes, off, set_resource_low);
}
static int dmem_cgroup_region_max_show(struct seq_file *sf, void *v)
{
return dmemcg_limit_show(sf, v, get_resource_max);
}
static ssize_t dmem_cgroup_region_max_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
return dmemcg_limit_write(of, buf, nbytes, off, set_resource_max);
}
static struct cftype files[] = {
{
.name = "capacity",
.seq_show = dmem_cgroup_region_capacity_show,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "current",
.seq_show = dmem_cgroup_region_current_show,
},
{
.name = "min",
.write = dmem_cgroup_region_min_write,
.seq_show = dmem_cgroup_region_min_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "low",
.write = dmem_cgroup_region_low_write,
.seq_show = dmem_cgroup_region_low_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "max",
.write = dmem_cgroup_region_max_write,
.seq_show = dmem_cgroup_region_max_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{ } /* Zero entry terminates. */
};
struct cgroup_subsys dmem_cgrp_subsys = {
.css_alloc = dmemcs_alloc,
.css_free = dmemcs_free,
.css_offline = dmemcs_offline,
.legacy_cftypes = files,
.dfl_cftypes = files,
};
+326
View File
@@ -0,0 +1,326 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/cgroup.h>
#include <linux/sched.h>
#include <linux/sched/task.h>
#include <linux/sched/signal.h>
#include "cgroup-internal.h"
#include <trace/events/cgroup.h>
/*
* Update CGRP_FROZEN of cgroup.flag
* Return true if flags is updated; false if flags has no change
*/
static bool cgroup_update_frozen_flag(struct cgroup *cgrp, bool frozen)
{
lockdep_assert_held(&css_set_lock);
/* Already there? */
if (test_bit(CGRP_FROZEN, &cgrp->flags) == frozen)
return false;
if (frozen)
set_bit(CGRP_FROZEN, &cgrp->flags);
else
clear_bit(CGRP_FROZEN, &cgrp->flags);
cgroup_file_notify(&cgrp->events_file);
TRACE_CGROUP_PATH(notify_frozen, cgrp, frozen);
return true;
}
/*
* Propagate the cgroup frozen state upwards by the cgroup tree.
*/
static void cgroup_propagate_frozen(struct cgroup *cgrp, bool frozen)
{
int desc = 1;
/*
* If the new state is frozen, some freezing ancestor cgroups may change
* their state too, depending on if all their descendants are frozen.
*
* Otherwise, all ancestor cgroups are forced into the non-frozen state.
*/
while ((cgrp = cgroup_parent(cgrp))) {
if (frozen) {
cgrp->freezer.nr_frozen_descendants += desc;
if (!test_bit(CGRP_FREEZE, &cgrp->flags) ||
(cgrp->freezer.nr_frozen_descendants !=
cgrp->nr_descendants))
continue;
} else {
cgrp->freezer.nr_frozen_descendants -= desc;
}
if (cgroup_update_frozen_flag(cgrp, frozen))
desc++;
}
}
/*
* Revisit the cgroup frozen state.
* Checks if the cgroup is really frozen and perform all state transitions.
*/
void cgroup_update_frozen(struct cgroup *cgrp)
{
bool frozen;
/*
* If the cgroup has to be frozen (CGRP_FREEZE bit set),
* and all tasks are frozen and/or stopped, let's consider
* the cgroup frozen. Otherwise it's not frozen.
*/
frozen = test_bit(CGRP_FREEZE, &cgrp->flags) &&
cgrp->freezer.nr_frozen_tasks == __cgroup_task_count(cgrp);
/* If flags is updated, update the state of ancestor cgroups. */
if (cgroup_update_frozen_flag(cgrp, frozen))
cgroup_propagate_frozen(cgrp, frozen);
}
/*
* Increment cgroup's nr_frozen_tasks.
*/
static void cgroup_inc_frozen_cnt(struct cgroup *cgrp)
{
cgrp->freezer.nr_frozen_tasks++;
}
/*
* Decrement cgroup's nr_frozen_tasks.
*/
static void cgroup_dec_frozen_cnt(struct cgroup *cgrp)
{
cgrp->freezer.nr_frozen_tasks--;
WARN_ON_ONCE(cgrp->freezer.nr_frozen_tasks < 0);
}
/*
* Enter frozen/stopped state, if not yet there. Update cgroup's counters,
* and revisit the state of the cgroup, if necessary.
*/
void cgroup_enter_frozen(void)
{
struct cgroup *cgrp;
if (current->frozen)
return;
spin_lock_irq(&css_set_lock);
current->frozen = true;
cgrp = task_dfl_cgroup(current);
cgroup_inc_frozen_cnt(cgrp);
cgroup_update_frozen(cgrp);
spin_unlock_irq(&css_set_lock);
}
/*
* Conditionally leave frozen/stopped state. Update cgroup's counters,
* and revisit the state of the cgroup, if necessary.
*
* If always_leave is not set, and the cgroup is freezing,
* we're racing with the cgroup freezing. In this case, we don't
* drop the frozen counter to avoid a transient switch to
* the unfrozen state.
*/
void cgroup_leave_frozen(bool always_leave)
{
struct cgroup *cgrp;
spin_lock_irq(&css_set_lock);
cgrp = task_dfl_cgroup(current);
if (always_leave || !test_bit(CGRP_FREEZE, &cgrp->flags)) {
cgroup_dec_frozen_cnt(cgrp);
cgroup_update_frozen(cgrp);
WARN_ON_ONCE(!current->frozen);
current->frozen = false;
} else if (!(current->jobctl & JOBCTL_TRAP_FREEZE)) {
spin_lock(&current->sighand->siglock);
current->jobctl |= JOBCTL_TRAP_FREEZE;
set_thread_flag(TIF_SIGPENDING);
spin_unlock(&current->sighand->siglock);
}
spin_unlock_irq(&css_set_lock);
}
/*
* Freeze or unfreeze the task by setting or clearing the JOBCTL_TRAP_FREEZE
* jobctl bit.
*/
static void cgroup_freeze_task(struct task_struct *task, bool freeze)
{
unsigned long flags;
/* If the task is about to die, don't bother with freezing it. */
if (!lock_task_sighand(task, &flags))
return;
if (freeze) {
task->jobctl |= JOBCTL_TRAP_FREEZE;
signal_wake_up(task, false);
} else {
task->jobctl &= ~JOBCTL_TRAP_FREEZE;
wake_up_process(task);
}
unlock_task_sighand(task, &flags);
}
/*
* Freeze or unfreeze all tasks in the given cgroup.
*/
static void cgroup_do_freeze(struct cgroup *cgrp, bool freeze, u64 ts_nsec)
{
struct css_task_iter it;
struct task_struct *task;
lockdep_assert_held(&cgroup_mutex);
spin_lock_irq(&css_set_lock);
write_seqcount_begin(&cgrp->freezer.freeze_seq);
if (freeze) {
set_bit(CGRP_FREEZE, &cgrp->flags);
cgrp->freezer.freeze_start_nsec = ts_nsec;
} else {
clear_bit(CGRP_FREEZE, &cgrp->flags);
cgrp->freezer.frozen_nsec += (ts_nsec -
cgrp->freezer.freeze_start_nsec);
}
write_seqcount_end(&cgrp->freezer.freeze_seq);
spin_unlock_irq(&css_set_lock);
if (freeze)
TRACE_CGROUP_PATH(freeze, cgrp);
else
TRACE_CGROUP_PATH(unfreeze, cgrp);
css_task_iter_start(&cgrp->self, 0, &it);
while ((task = css_task_iter_next(&it))) {
/*
* Ignore kernel threads here. Freezing cgroups containing
* kthreads isn't supported.
*/
if (task->flags & PF_KTHREAD)
continue;
cgroup_freeze_task(task, freeze);
}
css_task_iter_end(&it);
/*
* Cgroup state should be revisited here to cover empty leaf cgroups
* and cgroups which descendants are already in the desired state.
*/
spin_lock_irq(&css_set_lock);
if (cgrp->nr_descendants == cgrp->freezer.nr_frozen_descendants)
cgroup_update_frozen(cgrp);
spin_unlock_irq(&css_set_lock);
}
/*
* Adjust the task state (freeze or unfreeze) and revisit the state of
* source and destination cgroups.
*/
void cgroup_freezer_migrate_task(struct task_struct *task,
struct cgroup *src, struct cgroup *dst)
{
lockdep_assert_held(&css_set_lock);
/*
* Kernel threads are not supposed to be frozen at all.
*/
if (task->flags & PF_KTHREAD)
return;
/*
* It's not necessary to do changes if both of the src and dst cgroups
* are not freezing and task is not frozen.
*/
if (!test_bit(CGRP_FREEZE, &src->flags) &&
!test_bit(CGRP_FREEZE, &dst->flags) &&
!task->frozen)
return;
/*
* Adjust counters of freezing and frozen tasks.
* Note, that if the task is frozen, but the destination cgroup is not
* frozen, we bump both counters to keep them balanced.
*/
if (task->frozen) {
cgroup_inc_frozen_cnt(dst);
cgroup_dec_frozen_cnt(src);
}
cgroup_update_frozen(dst);
cgroup_update_frozen(src);
/*
* Force the task to the desired state.
*/
cgroup_freeze_task(task, test_bit(CGRP_FREEZE, &dst->flags));
}
void cgroup_freeze(struct cgroup *cgrp, bool freeze)
{
struct cgroup_subsys_state *css;
struct cgroup *parent;
struct cgroup *dsct;
bool applied = false;
u64 ts_nsec;
bool old_e;
lockdep_assert_held(&cgroup_mutex);
/*
* Nothing changed? Just exit.
*/
if (cgrp->freezer.freeze == freeze)
return;
cgrp->freezer.freeze = freeze;
ts_nsec = ktime_get_ns();
/*
* Propagate changes downwards the cgroup tree.
*/
css_for_each_descendant_pre(css, &cgrp->self) {
dsct = css->cgroup;
if (cgroup_is_dead(dsct))
continue;
/*
* e_freeze is affected by parent's e_freeze and dst's freeze.
* If old e_freeze eq new e_freeze, no change, its children
* will not be affected. So do nothing and skip the subtree
*/
old_e = dsct->freezer.e_freeze;
parent = cgroup_parent(dsct);
dsct->freezer.e_freeze = (dsct->freezer.freeze ||
parent->freezer.e_freeze);
if (dsct->freezer.e_freeze == old_e) {
css = css_rightmost_descendant(css);
continue;
}
/*
* Do change actual state: freeze or unfreeze.
*/
cgroup_do_freeze(dsct, freeze, ts_nsec);
applied = true;
}
/*
* Even if the actual state hasn't changed, let's notify a user.
* The state can be enforced by an ancestor cgroup: the cgroup
* can already be in the desired state or it can be locked in the
* opposite state, so that the transition will never happen.
* In both cases it's better to notify a user, that there is
* nothing to wait for.
*/
if (!applied) {
TRACE_CGROUP_PATH(notify_frozen, cgrp,
test_bit(CGRP_FROZEN, &cgrp->flags));
cgroup_file_notify(&cgrp->events_file);
}
}
+474
View File
@@ -0,0 +1,474 @@
// SPDX-License-Identifier: LGPL-2.1
/*
* cgroup_freezer.c - control group freezer subsystem
*
* Copyright IBM Corporation, 2007
*
* Author : Cedric Le Goater <clg@fr.ibm.com>
*/
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/cgroup.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/freezer.h>
#include <linux/seq_file.h>
#include <linux/mutex.h>
#include <linux/cpu.h>
/*
* A cgroup is freezing if any FREEZING flags are set. FREEZING_SELF is
* set if "FROZEN" is written to freezer.state cgroupfs file, and cleared
* for "THAWED". FREEZING_PARENT is set if the parent freezer is FREEZING
* for whatever reason. IOW, a cgroup has FREEZING_PARENT set if one of
* its ancestors has FREEZING_SELF set.
*/
enum freezer_state_flags {
CGROUP_FREEZER_ONLINE = (1 << 0), /* freezer is fully online */
CGROUP_FREEZING_SELF = (1 << 1), /* this freezer is freezing */
CGROUP_FREEZING_PARENT = (1 << 2), /* the parent freezer is freezing */
CGROUP_FROZEN = (1 << 3), /* this and its descendants frozen */
/* mask for all FREEZING flags */
CGROUP_FREEZING = CGROUP_FREEZING_SELF | CGROUP_FREEZING_PARENT,
};
struct freezer {
struct cgroup_subsys_state css;
unsigned int state;
};
static DEFINE_MUTEX(freezer_mutex);
static inline struct freezer *css_freezer(struct cgroup_subsys_state *css)
{
return css ? container_of(css, struct freezer, css) : NULL;
}
static inline struct freezer *task_freezer(struct task_struct *task)
{
return css_freezer(task_css(task, freezer_cgrp_id));
}
static struct freezer *parent_freezer(struct freezer *freezer)
{
return css_freezer(freezer->css.parent);
}
bool cgroup1_freezing(struct task_struct *task)
{
bool ret;
rcu_read_lock();
ret = task_freezer(task)->state & CGROUP_FREEZING;
rcu_read_unlock();
return ret;
}
static const char *freezer_state_strs(unsigned int state)
{
if (state & CGROUP_FROZEN)
return "FROZEN";
if (state & CGROUP_FREEZING)
return "FREEZING";
return "THAWED";
};
static struct cgroup_subsys_state *
freezer_css_alloc(struct cgroup_subsys_state *parent_css)
{
struct freezer *freezer;
freezer = kzalloc_obj(struct freezer);
if (!freezer)
return ERR_PTR(-ENOMEM);
return &freezer->css;
}
/**
* freezer_css_online - commit creation of a freezer css
* @css: css being created
*
* We're committing to creation of @css. Mark it online and inherit
* parent's freezing state while holding cpus read lock and freezer_mutex.
*/
static int freezer_css_online(struct cgroup_subsys_state *css)
{
struct freezer *freezer = css_freezer(css);
struct freezer *parent = parent_freezer(freezer);
cpus_read_lock();
mutex_lock(&freezer_mutex);
freezer->state |= CGROUP_FREEZER_ONLINE;
if (parent && (parent->state & CGROUP_FREEZING)) {
freezer->state |= CGROUP_FREEZING_PARENT | CGROUP_FROZEN;
static_branch_inc_cpuslocked(&freezer_active);
}
mutex_unlock(&freezer_mutex);
cpus_read_unlock();
return 0;
}
/**
* freezer_css_offline - initiate destruction of a freezer css
* @css: css being destroyed
*
* @css is going away. Mark it dead and decrement freezer_active if
* it was holding one.
*/
static void freezer_css_offline(struct cgroup_subsys_state *css)
{
struct freezer *freezer = css_freezer(css);
cpus_read_lock();
mutex_lock(&freezer_mutex);
if (freezer->state & CGROUP_FREEZING)
static_branch_dec_cpuslocked(&freezer_active);
freezer->state = 0;
mutex_unlock(&freezer_mutex);
cpus_read_unlock();
}
static void freezer_css_free(struct cgroup_subsys_state *css)
{
kfree(css_freezer(css));
}
/*
* Tasks can be migrated into a different freezer anytime regardless of its
* current state. freezer_attach() is responsible for making new tasks
* conform to the current state.
*
* Freezer state changes and task migration are synchronized via
* @freezer->lock. freezer_attach() makes the new tasks conform to the
* current state and all following state changes can see the new tasks.
*/
static void freezer_attach(struct cgroup_taskset *tset)
{
struct task_struct *task;
struct cgroup_subsys_state *new_css;
mutex_lock(&freezer_mutex);
/*
* Make the new tasks conform to the current state of @new_css.
* For simplicity, when migrating any task to a FROZEN cgroup, we
* revert it to FREEZING and let update_if_frozen() determine the
* correct state later.
*
* Tasks in @tset are on @new_css but may not conform to its
* current state before executing the following - !frozen tasks may
* be visible in a FROZEN cgroup and frozen tasks in a THAWED one.
*/
cgroup_taskset_for_each(task, new_css, tset) {
struct freezer *freezer = css_freezer(new_css);
if (!(freezer->state & CGROUP_FREEZING)) {
__thaw_task(task);
} else {
/* clear FROZEN and propagate upwards */
while (freezer && (freezer->state & CGROUP_FROZEN)) {
freezer->state &= ~CGROUP_FROZEN;
freezer = parent_freezer(freezer);
}
freeze_task(task);
}
}
mutex_unlock(&freezer_mutex);
}
/**
* freezer_fork - cgroup post fork callback
* @task: a task which has just been forked
*
* @task has just been created and should conform to the current state of
* the cgroup_freezer it belongs to. This function may race against
* freezer_attach(). Losing to freezer_attach() means that we don't have
* to do anything as freezer_attach() will put @task into the appropriate
* state.
*/
static void freezer_fork(struct task_struct *task)
{
struct freezer *freezer;
/*
* The root cgroup is non-freezable, so we can skip locking the
* freezer. This is safe regardless of race with task migration.
* If we didn't race or won, skipping is obviously the right thing
* to do. If we lost and root is the new cgroup, noop is still the
* right thing to do.
*/
if (task_css_is_root(task, freezer_cgrp_id))
return;
mutex_lock(&freezer_mutex);
rcu_read_lock();
freezer = task_freezer(task);
if (freezer->state & CGROUP_FREEZING)
freeze_task(task);
rcu_read_unlock();
mutex_unlock(&freezer_mutex);
}
/**
* update_if_frozen - update whether a cgroup finished freezing
* @css: css of interest
*
* Once FREEZING is initiated, transition to FROZEN is lazily updated by
* calling this function. If the current state is FREEZING but not FROZEN,
* this function checks whether all tasks of this cgroup and the descendant
* cgroups finished freezing and, if so, sets FROZEN.
*
* The caller is responsible for grabbing RCU read lock and calling
* update_if_frozen() on all descendants prior to invoking this function.
*
* Task states and freezer state might disagree while tasks are being
* migrated into or out of @css, so we can't verify task states against
* @freezer state here. See freezer_attach() for details.
*/
static void update_if_frozen(struct cgroup_subsys_state *css)
{
struct freezer *freezer = css_freezer(css);
struct cgroup_subsys_state *pos;
struct css_task_iter it;
struct task_struct *task;
lockdep_assert_held(&freezer_mutex);
if (!(freezer->state & CGROUP_FREEZING) ||
(freezer->state & CGROUP_FROZEN))
return;
/* are all (live) children frozen? */
rcu_read_lock();
css_for_each_child(pos, css) {
struct freezer *child = css_freezer(pos);
if ((child->state & CGROUP_FREEZER_ONLINE) &&
!(child->state & CGROUP_FROZEN)) {
rcu_read_unlock();
return;
}
}
rcu_read_unlock();
/* are all tasks frozen? */
css_task_iter_start(css, 0, &it);
while ((task = css_task_iter_next(&it))) {
if (freezing(task) && !frozen(task))
goto out_iter_end;
}
freezer->state |= CGROUP_FROZEN;
out_iter_end:
css_task_iter_end(&it);
}
static int freezer_read(struct seq_file *m, void *v)
{
struct cgroup_subsys_state *css = seq_css(m), *pos;
mutex_lock(&freezer_mutex);
rcu_read_lock();
/* update states bottom-up */
css_for_each_descendant_post(pos, css) {
if (!css_tryget_online(pos))
continue;
rcu_read_unlock();
update_if_frozen(pos);
rcu_read_lock();
css_put(pos);
}
rcu_read_unlock();
mutex_unlock(&freezer_mutex);
seq_puts(m, freezer_state_strs(css_freezer(css)->state));
seq_putc(m, '\n');
return 0;
}
static void freeze_cgroup(struct freezer *freezer)
{
struct css_task_iter it;
struct task_struct *task;
css_task_iter_start(&freezer->css, 0, &it);
while ((task = css_task_iter_next(&it)))
freeze_task(task);
css_task_iter_end(&it);
}
static void unfreeze_cgroup(struct freezer *freezer)
{
struct css_task_iter it;
struct task_struct *task;
css_task_iter_start(&freezer->css, 0, &it);
while ((task = css_task_iter_next(&it)))
__thaw_task(task);
css_task_iter_end(&it);
}
/**
* freezer_apply_state - apply state change to a single cgroup_freezer
* @freezer: freezer to apply state change to
* @freeze: whether to freeze or unfreeze
* @state: CGROUP_FREEZING_* flag to set or clear
*
* Set or clear @state on @cgroup according to @freeze, and perform
* freezing or thawing as necessary.
*/
static void freezer_apply_state(struct freezer *freezer, bool freeze,
unsigned int state)
{
/* also synchronizes against task migration, see freezer_attach() */
lockdep_assert_held(&freezer_mutex);
if (!(freezer->state & CGROUP_FREEZER_ONLINE))
return;
if (freeze) {
if (!(freezer->state & CGROUP_FREEZING))
static_branch_inc_cpuslocked(&freezer_active);
freezer->state |= state;
freeze_cgroup(freezer);
} else {
bool was_freezing = freezer->state & CGROUP_FREEZING;
freezer->state &= ~state;
if (!(freezer->state & CGROUP_FREEZING)) {
freezer->state &= ~CGROUP_FROZEN;
if (was_freezing)
static_branch_dec_cpuslocked(&freezer_active);
unfreeze_cgroup(freezer);
}
}
}
/**
* freezer_change_state - change the freezing state of a cgroup_freezer
* @freezer: freezer of interest
* @freeze: whether to freeze or thaw
*
* Freeze or thaw @freezer according to @freeze. The operations are
* recursive - all descendants of @freezer will be affected.
*/
static void freezer_change_state(struct freezer *freezer, bool freeze)
{
struct cgroup_subsys_state *pos;
cpus_read_lock();
/*
* Update all its descendants in pre-order traversal. Each
* descendant will try to inherit its parent's FREEZING state as
* CGROUP_FREEZING_PARENT.
*/
mutex_lock(&freezer_mutex);
rcu_read_lock();
css_for_each_descendant_pre(pos, &freezer->css) {
struct freezer *pos_f = css_freezer(pos);
struct freezer *parent = parent_freezer(pos_f);
if (!css_tryget_online(pos))
continue;
rcu_read_unlock();
if (pos_f == freezer)
freezer_apply_state(pos_f, freeze,
CGROUP_FREEZING_SELF);
else
freezer_apply_state(pos_f,
parent->state & CGROUP_FREEZING,
CGROUP_FREEZING_PARENT);
rcu_read_lock();
css_put(pos);
}
rcu_read_unlock();
mutex_unlock(&freezer_mutex);
cpus_read_unlock();
}
static ssize_t freezer_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
bool freeze;
buf = strstrip(buf);
if (strcmp(buf, freezer_state_strs(0)) == 0)
freeze = false;
else if (strcmp(buf, freezer_state_strs(CGROUP_FROZEN)) == 0) {
pr_info_once("Freezing with imperfect legacy cgroup freezer. "
"See cgroup.freeze of cgroup v2\n");
freeze = true;
} else
return -EINVAL;
freezer_change_state(css_freezer(of_css(of)), freeze);
return nbytes;
}
static u64 freezer_self_freezing_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
struct freezer *freezer = css_freezer(css);
return (bool)(freezer->state & CGROUP_FREEZING_SELF);
}
static u64 freezer_parent_freezing_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
struct freezer *freezer = css_freezer(css);
return (bool)(freezer->state & CGROUP_FREEZING_PARENT);
}
static struct cftype files[] = {
{
.name = "state",
.flags = CFTYPE_NOT_ON_ROOT,
.seq_show = freezer_read,
.write = freezer_write,
},
{
.name = "self_freezing",
.flags = CFTYPE_NOT_ON_ROOT,
.read_u64 = freezer_self_freezing_read,
},
{
.name = "parent_freezing",
.flags = CFTYPE_NOT_ON_ROOT,
.read_u64 = freezer_parent_freezing_read,
},
{ } /* terminate */
};
struct cgroup_subsys freezer_cgrp_subsys = {
.css_alloc = freezer_css_alloc,
.css_online = freezer_css_online,
.css_offline = freezer_css_offline,
.css_free = freezer_css_free,
.attach = freezer_attach,
.fork = freezer_fork,
.legacy_cftypes = files,
};
+478
View File
@@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Miscellaneous cgroup controller
*
* Copyright 2020 Google LLC
* Author: Vipin Sharma <vipinsh@google.com>
*/
#include <linux/limits.h>
#include <linux/cgroup.h>
#include <linux/errno.h>
#include <linux/atomic.h>
#include <linux/slab.h>
#include <linux/misc_cgroup.h>
#define MAX_STR "max"
#define MAX_NUM U64_MAX
/* Miscellaneous res name, keep it in sync with enum misc_res_type */
static const char *const misc_res_name[] = {
#ifdef CONFIG_KVM_AMD_SEV
/* AMD SEV ASIDs resource */
"sev",
/* AMD SEV-ES ASIDs resource */
"sev_es",
#endif
#ifdef CONFIG_INTEL_TDX_HOST
/* Intel TDX HKIDs resource */
"tdx",
#endif
};
/* Root misc cgroup */
static struct misc_cg root_cg;
/*
* Miscellaneous resources capacity for the entire machine. 0 capacity means
* resource is not initialized or not present in the host.
*
* root_cg.max and capacity are independent of each other. root_cg.max can be
* more than the actual capacity. We are using Limits resource distribution
* model of cgroup for miscellaneous controller.
*/
static u64 misc_res_capacity[MISC_CG_RES_TYPES];
/**
* parent_misc() - Get the parent of the passed misc cgroup.
* @cgroup: cgroup whose parent needs to be fetched.
*
* Context: Any context.
* Return:
* * struct misc_cg* - Parent of the @cgroup.
* * %NULL - If @cgroup is null or the passed cgroup does not have a parent.
*/
static struct misc_cg *parent_misc(struct misc_cg *cgroup)
{
return cgroup ? css_misc(cgroup->css.parent) : NULL;
}
/**
* valid_type() - Check if @type is valid or not.
* @type: misc res type.
*
* Context: Any context.
* Return:
* * true - If valid type.
* * false - If not valid type.
*/
static inline bool valid_type(enum misc_res_type type)
{
return type >= 0 && type < MISC_CG_RES_TYPES;
}
/**
* misc_cg_set_capacity() - Set the capacity of the misc cgroup res.
* @type: Type of the misc res.
* @capacity: Supported capacity of the misc res on the host.
*
* If capacity is 0 then the charging a misc cgroup fails for that type.
*
* Context: Any context.
* Return:
* * %0 - Successfully registered the capacity.
* * %-EINVAL - If @type is invalid.
*/
int misc_cg_set_capacity(enum misc_res_type type, u64 capacity)
{
if (!valid_type(type))
return -EINVAL;
WRITE_ONCE(misc_res_capacity[type], capacity);
return 0;
}
EXPORT_SYMBOL_GPL(misc_cg_set_capacity);
/**
* misc_cg_cancel_charge() - Cancel the charge from the misc cgroup.
* @type: Misc res type in misc cg to cancel the charge from.
* @cg: Misc cgroup to cancel charge from.
* @amount: Amount to cancel.
*
* Context: Any context.
*/
static void misc_cg_cancel_charge(enum misc_res_type type, struct misc_cg *cg,
u64 amount)
{
WARN_ONCE(atomic64_add_negative(-amount, &cg->res[type].usage),
"misc cgroup resource %s became less than 0",
misc_res_name[type]);
}
static void misc_cg_update_watermark(struct misc_res *res, u64 new_usage)
{
u64 old;
while (true) {
old = atomic64_read(&res->watermark);
if (new_usage <= old)
break;
if (atomic64_cmpxchg(&res->watermark, old, new_usage) == old)
break;
}
}
static void misc_cg_event(enum misc_res_type type, struct misc_cg *cg)
{
atomic64_inc(&cg->res[type].events_local);
cgroup_file_notify(&cg->events_local_file);
for (; parent_misc(cg); cg = parent_misc(cg)) {
atomic64_inc(&cg->res[type].events);
cgroup_file_notify(&cg->events_file);
}
}
/**
* misc_cg_try_charge() - Try charging the misc cgroup.
* @type: Misc res type to charge.
* @cg: Misc cgroup which will be charged.
* @amount: Amount to charge.
*
* Charge @amount to the misc cgroup. Caller must use the same cgroup during
* the uncharge call.
*
* Context: Any context.
* Return:
* * %0 - If successfully charged.
* * -EINVAL - If @type is invalid or misc res has 0 capacity.
* * -EBUSY - If max limit will be crossed or total usage will be more than the
* capacity.
*/
int misc_cg_try_charge(enum misc_res_type type, struct misc_cg *cg, u64 amount)
{
struct misc_cg *i, *j;
int ret;
struct misc_res *res;
u64 new_usage;
if (!(valid_type(type) && cg && READ_ONCE(misc_res_capacity[type])))
return -EINVAL;
if (!amount)
return 0;
for (i = cg; i; i = parent_misc(i)) {
res = &i->res[type];
new_usage = atomic64_add_return(amount, &res->usage);
if (new_usage > READ_ONCE(res->max) ||
new_usage > READ_ONCE(misc_res_capacity[type])) {
ret = -EBUSY;
goto err_charge;
}
misc_cg_update_watermark(res, new_usage);
}
return 0;
err_charge:
misc_cg_event(type, i);
for (j = cg; j != i; j = parent_misc(j))
misc_cg_cancel_charge(type, j, amount);
misc_cg_cancel_charge(type, i, amount);
return ret;
}
EXPORT_SYMBOL_GPL(misc_cg_try_charge);
/**
* misc_cg_uncharge() - Uncharge the misc cgroup.
* @type: Misc res type which was charged.
* @cg: Misc cgroup which will be uncharged.
* @amount: Charged amount.
*
* Context: Any context.
*/
void misc_cg_uncharge(enum misc_res_type type, struct misc_cg *cg, u64 amount)
{
struct misc_cg *i;
if (!(amount && valid_type(type) && cg))
return;
for (i = cg; i; i = parent_misc(i))
misc_cg_cancel_charge(type, i, amount);
}
EXPORT_SYMBOL_GPL(misc_cg_uncharge);
/**
* misc_cg_max_show() - Show the misc cgroup max limit.
* @sf: Interface file
* @v: Arguments passed
*
* Context: Any context.
* Return: 0 to denote successful print.
*/
static int misc_cg_max_show(struct seq_file *sf, void *v)
{
int i;
struct misc_cg *cg = css_misc(seq_css(sf));
u64 max;
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
if (READ_ONCE(misc_res_capacity[i])) {
max = READ_ONCE(cg->res[i].max);
if (max == MAX_NUM)
seq_printf(sf, "%s max\n", misc_res_name[i]);
else
seq_printf(sf, "%s %llu\n", misc_res_name[i],
max);
}
}
return 0;
}
/**
* misc_cg_max_write() - Update the maximum limit of the cgroup.
* @of: Handler for the file.
* @buf: Data from the user. It should be either "max", 0, or a positive
* integer.
* @nbytes: Number of bytes of the data.
* @off: Offset in the file.
*
* User can pass data like:
* echo sev 23 > misc.max, OR
* echo sev max > misc.max
*
* Context: Any context.
* Return:
* * >= 0 - Number of bytes processed in the input.
* * -EINVAL - If buf is not valid.
* * -ERANGE - If number is bigger than the u64 capacity.
*/
static ssize_t misc_cg_max_write(struct kernfs_open_file *of, char *buf,
size_t nbytes, loff_t off)
{
struct misc_cg *cg;
u64 max;
int ret = 0, i;
enum misc_res_type type = MISC_CG_RES_TYPES;
char *token;
buf = strstrip(buf);
token = strsep(&buf, " ");
if (!token || !buf)
return -EINVAL;
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
if (!strcmp(misc_res_name[i], token)) {
type = i;
break;
}
}
if (type == MISC_CG_RES_TYPES)
return -EINVAL;
if (!strcmp(MAX_STR, buf)) {
max = MAX_NUM;
} else {
ret = kstrtou64(buf, 0, &max);
if (ret)
return ret;
}
cg = css_misc(of_css(of));
if (READ_ONCE(misc_res_capacity[type]))
WRITE_ONCE(cg->res[type].max, max);
else
ret = -EINVAL;
return ret ? ret : nbytes;
}
/**
* misc_cg_current_show() - Show the current usage of the misc cgroup.
* @sf: Interface file
* @v: Arguments passed
*
* Context: Any context.
* Return: 0 to denote successful print.
*/
static int misc_cg_current_show(struct seq_file *sf, void *v)
{
int i;
u64 usage;
struct misc_cg *cg = css_misc(seq_css(sf));
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
usage = atomic64_read(&cg->res[i].usage);
if (READ_ONCE(misc_res_capacity[i]) || usage)
seq_printf(sf, "%s %llu\n", misc_res_name[i], usage);
}
return 0;
}
/**
* misc_cg_peak_show() - Show the peak usage of the misc cgroup.
* @sf: Interface file
* @v: Arguments passed
*
* Context: Any context.
* Return: 0 to denote successful print.
*/
static int misc_cg_peak_show(struct seq_file *sf, void *v)
{
int i;
u64 watermark;
struct misc_cg *cg = css_misc(seq_css(sf));
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
watermark = atomic64_read(&cg->res[i].watermark);
if (READ_ONCE(misc_res_capacity[i]) || watermark)
seq_printf(sf, "%s %llu\n", misc_res_name[i], watermark);
}
return 0;
}
/**
* misc_cg_capacity_show() - Show the total capacity of misc res on the host.
* @sf: Interface file
* @v: Arguments passed
*
* Only present in the root cgroup directory.
*
* Context: Any context.
* Return: 0 to denote successful print.
*/
static int misc_cg_capacity_show(struct seq_file *sf, void *v)
{
int i;
u64 cap;
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
cap = READ_ONCE(misc_res_capacity[i]);
if (cap)
seq_printf(sf, "%s %llu\n", misc_res_name[i], cap);
}
return 0;
}
static int __misc_events_show(struct seq_file *sf, bool local)
{
struct misc_cg *cg = css_misc(seq_css(sf));
u64 events;
int i;
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
if (local)
events = atomic64_read(&cg->res[i].events_local);
else
events = atomic64_read(&cg->res[i].events);
if (READ_ONCE(misc_res_capacity[i]) || events)
seq_printf(sf, "%s.max %llu\n", misc_res_name[i], events);
}
return 0;
}
static int misc_events_show(struct seq_file *sf, void *v)
{
return __misc_events_show(sf, false);
}
static int misc_events_local_show(struct seq_file *sf, void *v)
{
return __misc_events_show(sf, true);
}
/* Misc cgroup interface files */
static struct cftype misc_cg_files[] = {
{
.name = "max",
.write = misc_cg_max_write,
.seq_show = misc_cg_max_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "current",
.seq_show = misc_cg_current_show,
},
{
.name = "peak",
.seq_show = misc_cg_peak_show,
},
{
.name = "capacity",
.seq_show = misc_cg_capacity_show,
.flags = CFTYPE_ONLY_ON_ROOT,
},
{
.name = "events",
.flags = CFTYPE_NOT_ON_ROOT,
.file_offset = offsetof(struct misc_cg, events_file),
.seq_show = misc_events_show,
},
{
.name = "events.local",
.flags = CFTYPE_NOT_ON_ROOT,
.file_offset = offsetof(struct misc_cg, events_local_file),
.seq_show = misc_events_local_show,
},
{}
};
/**
* misc_cg_alloc() - Allocate misc cgroup.
* @parent_css: Parent cgroup.
*
* Context: Process context.
* Return:
* * struct cgroup_subsys_state* - css of the allocated cgroup.
* * ERR_PTR(-ENOMEM) - No memory available to allocate.
*/
static struct cgroup_subsys_state *
misc_cg_alloc(struct cgroup_subsys_state *parent_css)
{
enum misc_res_type i;
struct misc_cg *cg;
if (!parent_css) {
cg = &root_cg;
} else {
cg = kzalloc_obj(*cg);
if (!cg)
return ERR_PTR(-ENOMEM);
}
for (i = 0; i < MISC_CG_RES_TYPES; i++) {
WRITE_ONCE(cg->res[i].max, MAX_NUM);
atomic64_set(&cg->res[i].usage, 0);
}
return &cg->css;
}
/**
* misc_cg_free() - Free the misc cgroup.
* @css: cgroup subsys object.
*
* Context: Any context.
*/
static void misc_cg_free(struct cgroup_subsys_state *css)
{
kfree(css_misc(css));
}
/* Cgroup controller callbacks */
struct cgroup_subsys misc_cgrp_subsys = {
.css_alloc = misc_cg_alloc,
.css_free = misc_cg_free,
.legacy_cftypes = misc_cg_files,
.dfl_cftypes = misc_cg_files,
};
+144
View File
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
#include "cgroup-internal.h"
#include <linux/sched/task.h>
#include <linux/slab.h>
#include <linux/nsproxy.h>
#include <linux/proc_ns.h>
#include <linux/nstree.h>
/* cgroup namespaces */
static struct ucounts *inc_cgroup_namespaces(struct user_namespace *ns)
{
return inc_ucount(ns, current_euid(), UCOUNT_CGROUP_NAMESPACES);
}
static void dec_cgroup_namespaces(struct ucounts *ucounts)
{
dec_ucount(ucounts, UCOUNT_CGROUP_NAMESPACES);
}
static struct cgroup_namespace *alloc_cgroup_ns(void)
{
struct cgroup_namespace *new_ns __free(kfree) = NULL;
int ret;
new_ns = kzalloc_obj(struct cgroup_namespace, GFP_KERNEL_ACCOUNT);
if (!new_ns)
return ERR_PTR(-ENOMEM);
ret = ns_common_init(new_ns);
if (ret)
return ERR_PTR(ret);
return no_free_ptr(new_ns);
}
void free_cgroup_ns(struct cgroup_namespace *ns)
{
ns_tree_remove(ns);
put_css_set(ns->root_cset);
dec_cgroup_namespaces(ns->ucounts);
put_user_ns(ns->user_ns);
ns_common_free(ns);
/* Concurrent nstree traversal depends on a grace period. */
kfree_rcu(ns, ns.ns_rcu);
}
EXPORT_SYMBOL(free_cgroup_ns);
struct cgroup_namespace *copy_cgroup_ns(u64 flags,
struct user_namespace *user_ns,
struct cgroup_namespace *old_ns)
{
struct cgroup_namespace *new_ns;
struct ucounts *ucounts;
struct css_set *cset;
BUG_ON(!old_ns);
if (!(flags & CLONE_NEWCGROUP)) {
get_cgroup_ns(old_ns);
return old_ns;
}
/* Allow only sysadmin to create cgroup namespace. */
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
return ERR_PTR(-EPERM);
ucounts = inc_cgroup_namespaces(user_ns);
if (!ucounts)
return ERR_PTR(-ENOSPC);
/* It is not safe to take cgroup_mutex here */
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
get_css_set(cset);
spin_unlock_irq(&css_set_lock);
new_ns = alloc_cgroup_ns();
if (IS_ERR(new_ns)) {
put_css_set(cset);
dec_cgroup_namespaces(ucounts);
return new_ns;
}
new_ns->user_ns = get_user_ns(user_ns);
new_ns->ucounts = ucounts;
new_ns->root_cset = cset;
ns_tree_add(new_ns);
return new_ns;
}
static int cgroupns_install(struct nsset *nsset, struct ns_common *ns)
{
struct nsproxy *nsproxy = nsset->nsproxy;
struct cgroup_namespace *cgroup_ns = to_cg_ns(ns);
if (!ns_capable(nsset->cred->user_ns, CAP_SYS_ADMIN) ||
!ns_capable(cgroup_ns->user_ns, CAP_SYS_ADMIN))
return -EPERM;
/* Don't need to do anything if we are attaching to our own cgroupns. */
if (cgroup_ns == nsproxy->cgroup_ns)
return 0;
get_cgroup_ns(cgroup_ns);
put_cgroup_ns(nsproxy->cgroup_ns);
nsproxy->cgroup_ns = cgroup_ns;
return 0;
}
static struct ns_common *cgroupns_get(struct task_struct *task)
{
struct cgroup_namespace *ns = NULL;
struct nsproxy *nsproxy;
task_lock(task);
nsproxy = task->nsproxy;
if (nsproxy) {
ns = nsproxy->cgroup_ns;
get_cgroup_ns(ns);
}
task_unlock(task);
return ns ? &ns->ns : NULL;
}
static void cgroupns_put(struct ns_common *ns)
{
put_cgroup_ns(to_cg_ns(ns));
}
static struct user_namespace *cgroupns_owner(struct ns_common *ns)
{
return to_cg_ns(ns)->user_ns;
}
const struct proc_ns_operations cgroupns_operations = {
.name = "cgroup",
.get = cgroupns_get,
.put = cgroupns_put,
.install = cgroupns_install,
.owner = cgroupns_owner,
};
+460
View File
@@ -0,0 +1,460 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Process number limiting controller for cgroups.
*
* Used to allow a cgroup hierarchy to stop any new processes from fork()ing
* after a certain limit is reached.
*
* Since it is trivial to hit the task limit without hitting any kmemcg limits
* in place, PIDs are a fundamental resource. As such, PID exhaustion must be
* preventable in the scope of a cgroup hierarchy by allowing resource limiting
* of the number of tasks in a cgroup.
*
* In order to use the `pids` controller, set the maximum number of tasks in
* pids.max (this is not available in the root cgroup for obvious reasons). The
* number of processes currently in the cgroup is given by pids.current.
* Organisational operations are not blocked by cgroup policies, so it is
* possible to have pids.current > pids.max. However, it is not possible to
* violate a cgroup policy through fork(). fork() will return -EAGAIN if forking
* would cause a cgroup policy to be violated.
*
* To set a cgroup to have no limit, set pids.max to "max". This is the default
* for all new cgroups (N.B. that PID limits are hierarchical, so the most
* stringent limit in the hierarchy is followed).
*
* pids.current tracks all child cgroup hierarchies, so parent/pids.current is
* a superset of parent/child/pids.current.
*
* Copyright (C) 2015 Aleksa Sarai <cyphar@cyphar.com>
*/
#include <linux/kernel.h>
#include <linux/threads.h>
#include <linux/atomic.h>
#include <linux/cgroup.h>
#include <linux/slab.h>
#include <linux/sched/task.h>
#define PIDS_MAX (PID_MAX_LIMIT + 1ULL)
#define PIDS_MAX_STR "max"
enum pidcg_event {
/* Fork failed in subtree because this pids_cgroup limit was hit. */
PIDCG_MAX,
/* Fork failed in this pids_cgroup because ancestor limit was hit. */
PIDCG_FORKFAIL,
NR_PIDCG_EVENTS,
};
struct pids_cgroup {
struct cgroup_subsys_state css;
/*
* Use 64-bit types so that we can safely represent "max" as
* %PIDS_MAX = (%PID_MAX_LIMIT + 1).
*/
atomic64_t counter;
atomic64_t limit;
int64_t watermark;
/* Handles for pids.events[.local] */
struct cgroup_file events_file;
struct cgroup_file events_local_file;
atomic64_t events[NR_PIDCG_EVENTS];
atomic64_t events_local[NR_PIDCG_EVENTS];
};
static struct pids_cgroup *css_pids(struct cgroup_subsys_state *css)
{
return container_of(css, struct pids_cgroup, css);
}
static struct pids_cgroup *parent_pids(struct pids_cgroup *pids)
{
return css_pids(pids->css.parent);
}
static struct cgroup_subsys_state *
pids_css_alloc(struct cgroup_subsys_state *parent)
{
struct pids_cgroup *pids;
pids = kzalloc_obj(struct pids_cgroup);
if (!pids)
return ERR_PTR(-ENOMEM);
atomic64_set(&pids->limit, PIDS_MAX);
return &pids->css;
}
static void pids_css_free(struct cgroup_subsys_state *css)
{
kfree(css_pids(css));
}
static void pids_update_watermark(struct pids_cgroup *p, int64_t nr_pids)
{
/*
* This is racy, but we don't need perfectly accurate tallying of
* the watermark, and this lets us avoid extra atomic overhead.
*/
if (nr_pids > READ_ONCE(p->watermark))
WRITE_ONCE(p->watermark, nr_pids);
}
/**
* pids_cancel - uncharge the local pid count
* @pids: the pid cgroup state
* @num: the number of pids to cancel
*
* This function will WARN if the pid count goes under 0, because such a case is
* a bug in the pids controller proper.
*/
static void pids_cancel(struct pids_cgroup *pids, int num)
{
/*
* A negative count (or overflow for that matter) is invalid,
* and indicates a bug in the `pids` controller proper.
*/
WARN_ON_ONCE(atomic64_add_negative(-num, &pids->counter));
}
/**
* pids_uncharge - hierarchically uncharge the pid count
* @pids: the pid cgroup state
* @num: the number of pids to uncharge
*/
static void pids_uncharge(struct pids_cgroup *pids, int num)
{
struct pids_cgroup *p;
for (p = pids; parent_pids(p); p = parent_pids(p))
pids_cancel(p, num);
}
/**
* pids_charge - hierarchically charge the pid count
* @pids: the pid cgroup state
* @num: the number of pids to charge
*
* This function does *not* follow the pid limit set. It cannot fail and the new
* pid count may exceed the limit. This is only used for reverting failed
* attaches, where there is no other way out than violating the limit.
*/
static void pids_charge(struct pids_cgroup *pids, int num)
{
struct pids_cgroup *p;
for (p = pids; parent_pids(p); p = parent_pids(p)) {
int64_t new = atomic64_add_return(num, &p->counter);
pids_update_watermark(p, new);
}
}
/**
* pids_try_charge - hierarchically try to charge the pid count
* @pids: the pid cgroup state
* @num: the number of pids to charge
* @fail: storage of pid cgroup causing the fail
*
* This function follows the set limit. It will fail if the charge would cause
* the new value to exceed the hierarchical limit. Returns 0 if the charge
* succeeded, otherwise -EAGAIN.
*/
static int pids_try_charge(struct pids_cgroup *pids, int num, struct pids_cgroup **fail)
{
struct pids_cgroup *p, *q;
for (p = pids; parent_pids(p); p = parent_pids(p)) {
int64_t new = atomic64_add_return(num, &p->counter);
int64_t limit = atomic64_read(&p->limit);
/*
* Since new is capped to the maximum number of pid_t, if
* p->limit is %PIDS_MAX then we know that this test will never
* fail.
*/
if (new > limit) {
*fail = p;
goto revert;
}
/*
* Not technically accurate if we go over limit somewhere up
* the hierarchy, but that's tolerable for the watermark.
*/
pids_update_watermark(p, new);
}
return 0;
revert:
for (q = pids; q != p; q = parent_pids(q))
pids_cancel(q, num);
pids_cancel(p, num);
return -EAGAIN;
}
static int pids_can_attach(struct cgroup_taskset *tset)
{
struct task_struct *task;
struct cgroup_subsys_state *dst_css;
cgroup_taskset_for_each(task, dst_css, tset) {
struct pids_cgroup *pids = css_pids(dst_css);
struct cgroup_subsys_state *old_css;
struct pids_cgroup *old_pids;
/*
* No need to pin @old_css between here and cancel_attach()
* because cgroup core protects it from being freed before
* the migration completes or fails.
*/
old_css = task_css(task, pids_cgrp_id);
old_pids = css_pids(old_css);
pids_charge(pids, 1);
pids_uncharge(old_pids, 1);
}
return 0;
}
static void pids_cancel_attach(struct cgroup_taskset *tset)
{
struct task_struct *task;
struct cgroup_subsys_state *dst_css;
cgroup_taskset_for_each(task, dst_css, tset) {
struct pids_cgroup *pids = css_pids(dst_css);
struct cgroup_subsys_state *old_css;
struct pids_cgroup *old_pids;
old_css = task_css(task, pids_cgrp_id);
old_pids = css_pids(old_css);
pids_charge(old_pids, 1);
pids_uncharge(pids, 1);
}
}
static void pids_event(struct pids_cgroup *pids_forking,
struct pids_cgroup *pids_over_limit)
{
struct pids_cgroup *p = pids_forking;
/* Only log the first time limit is hit. */
if (atomic64_inc_return(&p->events_local[PIDCG_FORKFAIL]) == 1) {
pr_info("cgroup: fork rejected by pids controller in ");
pr_cont_cgroup_path(p->css.cgroup);
pr_cont("\n");
}
if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) ||
cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) {
cgroup_file_notify(&p->events_local_file);
return;
}
atomic64_inc(&pids_over_limit->events_local[PIDCG_MAX]);
cgroup_file_notify(&pids_over_limit->events_local_file);
for (p = pids_over_limit; parent_pids(p); p = parent_pids(p)) {
atomic64_inc(&p->events[PIDCG_MAX]);
cgroup_file_notify(&p->events_file);
}
}
/*
* task_css_check(true) in pids_can_fork() and pids_cancel_fork() relies
* on cgroup_threadgroup_change_begin() held by the copy_process().
*/
static int pids_can_fork(struct task_struct *task, struct css_set *cset)
{
struct pids_cgroup *pids, *pids_over_limit;
int err;
pids = css_pids(cset->subsys[pids_cgrp_id]);
err = pids_try_charge(pids, 1, &pids_over_limit);
if (err)
pids_event(pids, pids_over_limit);
return err;
}
static void pids_cancel_fork(struct task_struct *task, struct css_set *cset)
{
struct pids_cgroup *pids;
pids = css_pids(cset->subsys[pids_cgrp_id]);
pids_uncharge(pids, 1);
}
static void pids_release(struct task_struct *task)
{
struct pids_cgroup *pids = css_pids(task_css(task, pids_cgrp_id));
pids_uncharge(pids, 1);
}
static ssize_t pids_max_write(struct kernfs_open_file *of, char *buf,
size_t nbytes, loff_t off)
{
struct cgroup_subsys_state *css = of_css(of);
struct pids_cgroup *pids = css_pids(css);
int64_t limit;
int err;
buf = strstrip(buf);
if (!strcmp(buf, PIDS_MAX_STR)) {
limit = PIDS_MAX;
goto set_limit;
}
err = kstrtoll(buf, 0, &limit);
if (err)
return err;
if (limit < 0 || limit >= PIDS_MAX)
return -EINVAL;
set_limit:
/*
* Limit updates don't need to be mutex'd, since it isn't
* critical that any racing fork()s follow the new limit.
*/
atomic64_set(&pids->limit, limit);
return nbytes;
}
static int pids_max_show(struct seq_file *sf, void *v)
{
struct cgroup_subsys_state *css = seq_css(sf);
struct pids_cgroup *pids = css_pids(css);
int64_t limit = atomic64_read(&pids->limit);
if (limit >= PIDS_MAX)
seq_printf(sf, "%s\n", PIDS_MAX_STR);
else
seq_printf(sf, "%lld\n", limit);
return 0;
}
static s64 pids_current_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
struct pids_cgroup *pids = css_pids(css);
return atomic64_read(&pids->counter);
}
static s64 pids_peak_read(struct cgroup_subsys_state *css,
struct cftype *cft)
{
struct pids_cgroup *pids = css_pids(css);
return READ_ONCE(pids->watermark);
}
static int __pids_events_show(struct seq_file *sf, bool local)
{
struct pids_cgroup *pids = css_pids(seq_css(sf));
enum pidcg_event pe = PIDCG_MAX;
atomic64_t *events;
if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) ||
cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) {
pe = PIDCG_FORKFAIL;
local = true;
}
events = local ? pids->events_local : pids->events;
seq_printf(sf, "max %lld\n", (s64)atomic64_read(&events[pe]));
return 0;
}
static int pids_events_show(struct seq_file *sf, void *v)
{
__pids_events_show(sf, false);
return 0;
}
static int pids_events_local_show(struct seq_file *sf, void *v)
{
__pids_events_show(sf, true);
return 0;
}
static struct cftype pids_files[] = {
{
.name = "max",
.write = pids_max_write,
.seq_show = pids_max_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "current",
.read_s64 = pids_current_read,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "peak",
.flags = CFTYPE_NOT_ON_ROOT,
.read_s64 = pids_peak_read,
},
{
.name = "events",
.seq_show = pids_events_show,
.file_offset = offsetof(struct pids_cgroup, events_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "events.local",
.seq_show = pids_events_local_show,
.file_offset = offsetof(struct pids_cgroup, events_local_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{ } /* terminate */
};
static struct cftype pids_files_legacy[] = {
{
.name = "max",
.write = pids_max_write,
.seq_show = pids_max_show,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "current",
.read_s64 = pids_current_read,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "peak",
.flags = CFTYPE_NOT_ON_ROOT,
.read_s64 = pids_peak_read,
},
{
.name = "events",
.seq_show = pids_events_show,
.file_offset = offsetof(struct pids_cgroup, events_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{ } /* terminate */
};
struct cgroup_subsys pids_cgrp_subsys = {
.css_alloc = pids_css_alloc,
.css_free = pids_css_free,
.can_attach = pids_can_attach,
.cancel_attach = pids_cancel_attach,
.can_fork = pids_can_fork,
.cancel_fork = pids_cancel_fork,
.release = pids_release,
.legacy_cftypes = pids_files_legacy,
.dfl_cftypes = pids_files,
.threaded = true,
};
+789
View File
@@ -0,0 +1,789 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* RDMA resource limiting controller for cgroups.
*
* Used to allow a cgroup hierarchy to stop processes from consuming
* additional RDMA resources after a certain limit is reached.
*
* Copyright (C) 2016 Parav Pandit <pandit.parav@gmail.com>
*/
#include <linux/bitops.h>
#include <linux/limits.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/cgroup.h>
#include <linux/parser.h>
#include <linux/cgroup_rdma.h>
#define RDMACG_MAX_STR "max"
enum rdmacg_limit_tokens {
RDMACG_HCA_HANDLE_VAL,
RDMACG_HCA_HANDLE_MAX,
RDMACG_HCA_OBJECT_VAL,
RDMACG_HCA_OBJECT_MAX,
NR_RDMACG_LIMIT_TOKENS,
};
static const match_table_t rdmacg_limit_tokens = {
{ RDMACG_HCA_HANDLE_VAL, "hca_handle=%d" },
{ RDMACG_HCA_HANDLE_MAX, "hca_handle=max" },
{ RDMACG_HCA_OBJECT_VAL, "hca_object=%d" },
{ RDMACG_HCA_OBJECT_MAX, "hca_object=max" },
{ NR_RDMACG_LIMIT_TOKENS, NULL },
};
/*
* Protects list of resource pools maintained on per cgroup basis
* and rdma device list.
*/
static DEFINE_MUTEX(rdmacg_mutex);
static LIST_HEAD(rdmacg_devices);
enum rdmacg_file_type {
RDMACG_RESOURCE_TYPE_MAX,
RDMACG_RESOURCE_TYPE_STAT,
RDMACG_RESOURCE_TYPE_PEAK,
};
/*
* resource table definition as to be seen by the user.
* Need to add entries to it when more resources are
* added/defined at IB verb/core layer.
*/
static char const *rdmacg_resource_names[] = {
[RDMACG_RESOURCE_HCA_HANDLE] = "hca_handle",
[RDMACG_RESOURCE_HCA_OBJECT] = "hca_object",
};
/* resource tracker for each resource of rdma cgroup */
struct rdmacg_resource {
int max;
int usage;
int peak;
};
/*
* resource pool object which represents per cgroup, per device
* resources. There are multiple instances of this object per cgroup,
* therefore it cannot be embedded within rdma_cgroup structure. It
* is maintained as list.
*/
struct rdmacg_resource_pool {
struct rdmacg_device *device;
struct rdmacg_resource resources[RDMACG_RESOURCE_MAX];
struct list_head cg_node;
struct list_head dev_node;
/* count active user tasks of this pool */
u64 usage_sum;
/* total number counts which are set to max */
int num_max_cnt;
/* per-resource event counters */
u64 events_max[RDMACG_RESOURCE_MAX];
u64 events_alloc_fail[RDMACG_RESOURCE_MAX];
u64 events_local_max[RDMACG_RESOURCE_MAX];
u64 events_local_alloc_fail[RDMACG_RESOURCE_MAX];
};
static struct rdma_cgroup *css_rdmacg(struct cgroup_subsys_state *css)
{
return container_of(css, struct rdma_cgroup, css);
}
static struct rdma_cgroup *parent_rdmacg(struct rdma_cgroup *cg)
{
return css_rdmacg(cg->css.parent);
}
static inline struct rdma_cgroup *get_current_rdmacg(void)
{
return css_rdmacg(task_get_css(current, rdma_cgrp_id));
}
static void set_resource_limit(struct rdmacg_resource_pool *rpool,
int index, int new_max)
{
if (new_max == S32_MAX) {
if (rpool->resources[index].max != S32_MAX)
rpool->num_max_cnt++;
} else {
if (rpool->resources[index].max == S32_MAX)
rpool->num_max_cnt--;
}
rpool->resources[index].max = new_max;
}
static void set_all_resource_max_limit(struct rdmacg_resource_pool *rpool)
{
int i;
for (i = 0; i < RDMACG_RESOURCE_MAX; i++)
set_resource_limit(rpool, i, S32_MAX);
}
static void free_cg_rpool_locked(struct rdmacg_resource_pool *rpool)
{
lockdep_assert_held(&rdmacg_mutex);
list_del(&rpool->cg_node);
list_del(&rpool->dev_node);
kfree(rpool);
}
static bool rpool_has_persistent_state(struct rdmacg_resource_pool *rpool)
{
int i;
/*
* Keep the rpool alive if any peak value is non-zero,
* so that rdma.peak persists as a historical high-
* watermark even after all resources are freed.
*/
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
if (rpool->resources[i].peak ||
rpool->events_max[i] ||
rpool->events_local_max[i] ||
rpool->events_alloc_fail[i] ||
rpool->events_local_alloc_fail[i])
return true;
}
return false;
}
static struct rdmacg_resource_pool *
find_cg_rpool_locked(struct rdma_cgroup *cg,
struct rdmacg_device *device)
{
struct rdmacg_resource_pool *pool;
lockdep_assert_held(&rdmacg_mutex);
list_for_each_entry(pool, &cg->rpools, cg_node)
if (pool->device == device)
return pool;
return NULL;
}
static struct rdmacg_resource_pool *
get_cg_rpool_locked(struct rdma_cgroup *cg, struct rdmacg_device *device)
{
struct rdmacg_resource_pool *rpool;
rpool = find_cg_rpool_locked(cg, device);
if (rpool)
return rpool;
rpool = kzalloc_obj(*rpool);
if (!rpool)
return ERR_PTR(-ENOMEM);
rpool->device = device;
set_all_resource_max_limit(rpool);
INIT_LIST_HEAD(&rpool->cg_node);
INIT_LIST_HEAD(&rpool->dev_node);
list_add_tail(&rpool->cg_node, &cg->rpools);
list_add_tail(&rpool->dev_node, &device->rpools);
return rpool;
}
/**
* uncharge_cg_locked - uncharge resource for rdma cgroup
* @cg: pointer to cg to uncharge and all parents in hierarchy
* @device: pointer to rdmacg device
* @index: index of the resource to uncharge in cg (resource pool)
*
* It also frees the resource pool which was created as part of
* charging operation when there are no resources attached to
* resource pool.
*/
static void
uncharge_cg_locked(struct rdma_cgroup *cg,
struct rdmacg_device *device,
enum rdmacg_resource_type index)
{
struct rdmacg_resource_pool *rpool;
rpool = find_cg_rpool_locked(cg, device);
/*
* rpool cannot be null at this stage. Let kernel operate in case
* if there a bug in IB stack or rdma controller, instead of crashing
* the system.
*/
if (unlikely(!rpool)) {
pr_warn("Invalid device %p or rdma cgroup %p\n", device, cg);
return;
}
rpool->resources[index].usage--;
/*
* A negative count (or overflow) is invalid,
* it indicates a bug in the rdma controller.
*/
WARN_ON_ONCE(rpool->resources[index].usage < 0);
rpool->usage_sum--;
if (rpool->usage_sum == 0 &&
rpool->num_max_cnt == RDMACG_RESOURCE_MAX) {
if (!rpool_has_persistent_state(rpool)) {
/*
* No user of the rpool and all entries are set to max, so
* safe to delete this rpool.
*/
free_cg_rpool_locked(rpool);
}
}
}
/**
* rdmacg_event_locked - fire event when resource allocation exceeds limit
* @cg: requesting cgroup
* @over_cg: cgroup whose limit was exceeded
* @device: rdma device
* @index: resource type index
*
* Must be called under rdmacg_mutex. Updates event counters in the
* resource pools of @cg and @over_cg, propagates hierarchical max
* events from @over_cg (including itself) upward, and notifies
* userspace via cgroup_file_notify().
*/
static void rdmacg_event_locked(struct rdma_cgroup *cg,
struct rdma_cgroup *over_cg,
struct rdmacg_device *device,
enum rdmacg_resource_type index)
{
struct rdmacg_resource_pool *rpool;
struct rdma_cgroup *p;
lockdep_assert_held(&rdmacg_mutex);
/* Increment local alloc_fail in requesting cgroup */
rpool = find_cg_rpool_locked(cg, device);
if (rpool) {
rpool->events_local_alloc_fail[index]++;
cgroup_file_notify(&cg->events_local_file);
}
/* Increment local max in the over-limit cgroup */
rpool = find_cg_rpool_locked(over_cg, device);
if (rpool) {
rpool->events_local_max[index]++;
cgroup_file_notify(&over_cg->events_local_file);
}
/* Propagate hierarchical max events upward */
for (p = over_cg; parent_rdmacg(p); p = parent_rdmacg(p)) {
rpool = get_cg_rpool_locked(p, device);
if (!IS_ERR(rpool)) {
rpool->events_max[index]++;
cgroup_file_notify(&p->events_file);
}
}
/* Propagate hierarchical alloc_fail from requesting cgroup upward */
for (p = cg; parent_rdmacg(p); p = parent_rdmacg(p)) {
rpool = get_cg_rpool_locked(p, device);
if (!IS_ERR(rpool)) {
rpool->events_alloc_fail[index]++;
cgroup_file_notify(&p->events_file);
}
}
}
/**
* rdmacg_uncharge_hierarchy - hierarchically uncharge rdma resource count
* @cg: pointer to cg to uncharge and all parents in hierarchy
* @device: pointer to rdmacg device
* @stop_cg: while traversing hirerchy, when meet with stop_cg cgroup
* stop uncharging
* @index: index of the resource to uncharge in cg in given resource pool
*/
static void rdmacg_uncharge_hierarchy(struct rdma_cgroup *cg,
struct rdmacg_device *device,
struct rdma_cgroup *stop_cg,
enum rdmacg_resource_type index)
{
struct rdma_cgroup *p;
mutex_lock(&rdmacg_mutex);
for (p = cg; p != stop_cg; p = parent_rdmacg(p))
uncharge_cg_locked(p, device, index);
mutex_unlock(&rdmacg_mutex);
css_put(&cg->css);
}
/**
* rdmacg_uncharge - hierarchically uncharge rdma resource count
* @cg: pointer to cg to uncharge and all parents in hierarchy
* @device: pointer to rdmacg device
* @index: index of the resource to uncharge in cgroup in given resource pool
*/
void rdmacg_uncharge(struct rdma_cgroup *cg,
struct rdmacg_device *device,
enum rdmacg_resource_type index)
{
if (index >= RDMACG_RESOURCE_MAX)
return;
rdmacg_uncharge_hierarchy(cg, device, NULL, index);
}
EXPORT_SYMBOL(rdmacg_uncharge);
/**
* rdmacg_try_charge - hierarchically try to charge the rdma resource
* @rdmacg: pointer to rdma cgroup which will own this resource
* @device: pointer to rdmacg device
* @index: index of the resource to charge in cgroup (resource pool)
*
* This function follows charging resource in hierarchical way.
* It will fail if the charge would cause the new value to exceed the
* hierarchical limit.
* Returns 0 if the charge succeeded, otherwise -EAGAIN, -ENOMEM or -EINVAL.
* Returns pointer to rdmacg for this resource when charging is successful.
*
* Charger needs to account resources on two criteria.
* (a) per cgroup & (b) per device resource usage.
* Per cgroup resource usage ensures that tasks of cgroup doesn't cross
* the configured limits. Per device provides granular configuration
* in multi device usage. It allocates resource pool in the hierarchy
* for each parent it come across for first resource. Later on resource
* pool will be available. Therefore it will be much faster thereon
* to charge/uncharge.
*/
int rdmacg_try_charge(struct rdma_cgroup **rdmacg,
struct rdmacg_device *device,
enum rdmacg_resource_type index)
{
struct rdma_cgroup *cg, *p;
struct rdmacg_resource_pool *rpool;
s64 new;
int ret = 0;
if (index >= RDMACG_RESOURCE_MAX)
return -EINVAL;
/*
* hold on to css, as cgroup can be removed but resource
* accounting happens on css.
*/
cg = get_current_rdmacg();
mutex_lock(&rdmacg_mutex);
for (p = cg; p; p = parent_rdmacg(p)) {
rpool = get_cg_rpool_locked(p, device);
if (IS_ERR(rpool)) {
ret = PTR_ERR(rpool);
goto err;
} else {
new = (s64)rpool->resources[index].usage + 1;
if (new > rpool->resources[index].max) {
ret = -EAGAIN;
goto err;
} else {
rpool->resources[index].usage = new;
rpool->usage_sum++;
}
}
}
/* Update peak only after all charges succeed */
for (p = cg; p; p = parent_rdmacg(p)) {
rpool = find_cg_rpool_locked(p, device);
if (rpool && rpool->resources[index].usage > rpool->resources[index].peak)
rpool->resources[index].peak = rpool->resources[index].usage;
}
mutex_unlock(&rdmacg_mutex);
*rdmacg = cg;
return 0;
err:
if (ret == -EAGAIN)
rdmacg_event_locked(cg, p, device, index);
mutex_unlock(&rdmacg_mutex);
rdmacg_uncharge_hierarchy(cg, device, p, index);
return ret;
}
EXPORT_SYMBOL(rdmacg_try_charge);
/**
* rdmacg_register_device - register rdmacg device to rdma controller.
* @device: pointer to rdmacg device whose resources need to be accounted.
*
* If IB stack wish a device to participate in rdma cgroup resource
* tracking, it must invoke this API to register with rdma cgroup before
* any user space application can start using the RDMA resources.
*/
void rdmacg_register_device(struct rdmacg_device *device)
{
INIT_LIST_HEAD(&device->dev_node);
INIT_LIST_HEAD(&device->rpools);
mutex_lock(&rdmacg_mutex);
list_add_tail(&device->dev_node, &rdmacg_devices);
mutex_unlock(&rdmacg_mutex);
}
EXPORT_SYMBOL(rdmacg_register_device);
/**
* rdmacg_unregister_device - unregister rdmacg device from rdma controller.
* @device: pointer to rdmacg device which was previously registered with rdma
* controller using rdmacg_register_device().
*
* IB stack must invoke this after all the resources of the IB device
* are destroyed and after ensuring that no more resources will be created
* when this API is invoked.
*/
void rdmacg_unregister_device(struct rdmacg_device *device)
{
struct rdmacg_resource_pool *rpool, *tmp;
/*
* Synchronize with any active resource settings,
* usage query happening via configfs.
*/
mutex_lock(&rdmacg_mutex);
list_del_init(&device->dev_node);
/*
* Now that this device is off the cgroup list, its safe to free
* all the rpool resources.
*/
list_for_each_entry_safe(rpool, tmp, &device->rpools, dev_node)
free_cg_rpool_locked(rpool);
mutex_unlock(&rdmacg_mutex);
}
EXPORT_SYMBOL(rdmacg_unregister_device);
static struct rdmacg_device *rdmacg_get_device_locked(const char *name)
{
struct rdmacg_device *device;
lockdep_assert_held(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node)
if (!strcmp(name, device->name))
return device;
return NULL;
}
static ssize_t rdmacg_resource_set_max(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
struct rdma_cgroup *cg = css_rdmacg(of_css(of));
const char *dev_name;
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
char *options = strstrip(buf);
char *p;
int *new_limits;
unsigned long enables = 0;
int i = 0, ret = 0;
/* extract the device name first */
dev_name = strsep(&options, " ");
if (!dev_name) {
ret = -EINVAL;
goto err;
}
new_limits = kzalloc_objs(int, RDMACG_RESOURCE_MAX);
if (!new_limits) {
ret = -ENOMEM;
goto err;
}
/* parse resource limit tokens */
while ((p = strsep(&options, " \t\n"))) {
substring_t args[MAX_OPT_ARGS];
int tok, intval;
if (!*p)
continue;
tok = match_token(p, rdmacg_limit_tokens, args);
switch (tok) {
case RDMACG_HCA_HANDLE_VAL:
if (match_int(&args[0], &intval) || intval < 0) {
ret = -EINVAL;
goto parse_err;
}
new_limits[RDMACG_RESOURCE_HCA_HANDLE] = intval;
enables |= BIT(RDMACG_RESOURCE_HCA_HANDLE);
break;
case RDMACG_HCA_HANDLE_MAX:
new_limits[RDMACG_RESOURCE_HCA_HANDLE] = S32_MAX;
enables |= BIT(RDMACG_RESOURCE_HCA_HANDLE);
break;
case RDMACG_HCA_OBJECT_VAL:
if (match_int(&args[0], &intval) || intval < 0) {
ret = -EINVAL;
goto parse_err;
}
new_limits[RDMACG_RESOURCE_HCA_OBJECT] = intval;
enables |= BIT(RDMACG_RESOURCE_HCA_OBJECT);
break;
case RDMACG_HCA_OBJECT_MAX:
new_limits[RDMACG_RESOURCE_HCA_OBJECT] = S32_MAX;
enables |= BIT(RDMACG_RESOURCE_HCA_OBJECT);
break;
default:
ret = -EINVAL;
goto parse_err;
}
}
/* acquire lock to synchronize with hot plug devices */
mutex_lock(&rdmacg_mutex);
device = rdmacg_get_device_locked(dev_name);
if (!device) {
ret = -ENODEV;
goto dev_err;
}
rpool = get_cg_rpool_locked(cg, device);
if (IS_ERR(rpool)) {
ret = PTR_ERR(rpool);
goto dev_err;
}
/* now set the new limits of the rpool */
for_each_set_bit(i, &enables, RDMACG_RESOURCE_MAX)
set_resource_limit(rpool, i, new_limits[i]);
if (rpool->usage_sum == 0 &&
rpool->num_max_cnt == RDMACG_RESOURCE_MAX) {
if (!rpool_has_persistent_state(rpool)) {
/*
* No user of the rpool and all entries are set to max, so
* safe to delete this rpool.
*/
free_cg_rpool_locked(rpool);
}
}
dev_err:
mutex_unlock(&rdmacg_mutex);
parse_err:
kfree(new_limits);
err:
return ret ?: nbytes;
}
static void print_rpool_values(struct seq_file *sf,
struct rdmacg_resource_pool *rpool)
{
enum rdmacg_file_type sf_type;
int i;
u32 value;
sf_type = seq_cft(sf)->private;
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_puts(sf, rdmacg_resource_names[i]);
seq_putc(sf, '=');
if (sf_type == RDMACG_RESOURCE_TYPE_MAX) {
if (rpool)
value = rpool->resources[i].max;
else
value = S32_MAX;
} else if (sf_type == RDMACG_RESOURCE_TYPE_PEAK) {
value = rpool ? rpool->resources[i].peak : 0;
} else {
if (rpool)
value = rpool->resources[i].usage;
else
value = 0;
}
if (value == S32_MAX)
seq_puts(sf, RDMACG_MAX_STR);
else
seq_printf(sf, "%d", value);
seq_putc(sf, ' ');
}
}
static int rdmacg_resource_read(struct seq_file *sf, void *v)
{
struct rdmacg_device *device;
struct rdmacg_resource_pool *rpool;
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
seq_printf(sf, "%s ", device->name);
rpool = find_cg_rpool_locked(cg, device);
print_rpool_values(sf, rpool);
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static int rdmacg_events_show(struct seq_file *sf, void *v)
{
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
int i;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
seq_printf(sf, "%s ", device->name);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
rpool ? rpool->events_max[i] : 0ULL,
rdmacg_resource_names[i],
rpool ? rpool->events_alloc_fail[i] : 0ULL);
if (i < RDMACG_RESOURCE_MAX - 1)
seq_putc(sf, ' ');
}
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static int rdmacg_events_local_show(struct seq_file *sf, void *v)
{
struct rdma_cgroup *cg = css_rdmacg(seq_css(sf));
struct rdmacg_resource_pool *rpool;
struct rdmacg_device *device;
int i;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(device, &rdmacg_devices, dev_node) {
rpool = find_cg_rpool_locked(cg, device);
seq_printf(sf, "%s ", device->name);
for (i = 0; i < RDMACG_RESOURCE_MAX; i++) {
seq_printf(sf, "%s.max=%llu %s.alloc_fail=%llu",
rdmacg_resource_names[i],
rpool ? rpool->events_local_max[i] : 0ULL,
rdmacg_resource_names[i],
rpool ? rpool->events_local_alloc_fail[i] : 0ULL);
if (i < RDMACG_RESOURCE_MAX - 1)
seq_putc(sf, ' ');
}
seq_putc(sf, '\n');
}
mutex_unlock(&rdmacg_mutex);
return 0;
}
static struct cftype rdmacg_files[] = {
{
.name = "max",
.write = rdmacg_resource_set_max,
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_MAX,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "current",
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_STAT,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "peak",
.seq_show = rdmacg_resource_read,
.private = RDMACG_RESOURCE_TYPE_PEAK,
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "events",
.seq_show = rdmacg_events_show,
.file_offset = offsetof(struct rdma_cgroup, events_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{
.name = "events.local",
.seq_show = rdmacg_events_local_show,
.file_offset = offsetof(struct rdma_cgroup, events_local_file),
.flags = CFTYPE_NOT_ON_ROOT,
},
{ } /* terminate */
};
static struct cgroup_subsys_state *
rdmacg_css_alloc(struct cgroup_subsys_state *parent)
{
struct rdma_cgroup *cg;
cg = kzalloc_obj(*cg);
if (!cg)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&cg->rpools);
return &cg->css;
}
static void rdmacg_css_free(struct cgroup_subsys_state *css)
{
struct rdma_cgroup *cg = css_rdmacg(css);
struct rdmacg_resource_pool *rpool, *tmp;
/* Clean up rpools kept alive by non-zero peak values */
mutex_lock(&rdmacg_mutex);
list_for_each_entry_safe(rpool, tmp, &cg->rpools, cg_node)
free_cg_rpool_locked(rpool);
mutex_unlock(&rdmacg_mutex);
kfree(cg);
}
/**
* rdmacg_css_offline - cgroup css_offline callback
* @css: css of interest
*
* This function is called when @css is about to go away and responsible
* for shooting down all rdmacg associated with @css. As part of that it
* marks all the resource pool entries to max value, so that when resources are
* uncharged, associated resource pool can be freed as well.
*/
static void rdmacg_css_offline(struct cgroup_subsys_state *css)
{
struct rdma_cgroup *cg = css_rdmacg(css);
struct rdmacg_resource_pool *rpool;
mutex_lock(&rdmacg_mutex);
list_for_each_entry(rpool, &cg->rpools, cg_node)
set_all_resource_max_limit(rpool);
mutex_unlock(&rdmacg_mutex);
}
struct cgroup_subsys rdma_cgrp_subsys = {
.css_alloc = rdmacg_css_alloc,
.css_free = rdmacg_css_free,
.css_offline = rdmacg_css_offline,
.legacy_cftypes = rdmacg_files,
.dfl_cftypes = rdmacg_files,
};
+771
View File
@@ -0,0 +1,771 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "cgroup-internal.h"
#include <linux/cpumask.h>
#include <linux/sched/cputime.h>
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <trace/events/cgroup.h>
static DEFINE_SPINLOCK(rstat_base_lock);
static DEFINE_PER_CPU(struct llist_head, rstat_backlog_list);
static void cgroup_base_stat_flush(struct cgroup *cgrp, int cpu);
/*
* Determines whether a given css can participate in rstat.
* css's that are cgroup::self use rstat for base stats.
* Other css's associated with a subsystem use rstat only when
* they define the ss->css_rstat_flush callback.
*/
static inline bool css_uses_rstat(struct cgroup_subsys_state *css)
{
return css_is_self(css) || css->ss->css_rstat_flush != NULL;
}
static struct css_rstat_cpu *css_rstat_cpu(
struct cgroup_subsys_state *css, int cpu)
{
return per_cpu_ptr(css->rstat_cpu, cpu);
}
static struct cgroup_rstat_base_cpu *cgroup_rstat_base_cpu(
struct cgroup *cgrp, int cpu)
{
return per_cpu_ptr(cgrp->rstat_base_cpu, cpu);
}
static spinlock_t *ss_rstat_lock(struct cgroup_subsys *ss)
{
if (ss)
return &ss->rstat_ss_lock;
return &rstat_base_lock;
}
static inline struct llist_head *ss_lhead_cpu(struct cgroup_subsys *ss, int cpu)
{
if (ss)
return per_cpu_ptr(ss->lhead, cpu);
return per_cpu_ptr(&rstat_backlog_list, cpu);
}
/**
* __css_rstat_updated - keep track of updated rstat_cpu
* @css: target cgroup subsystem state
* @cpu: cpu on which rstat_cpu was updated
*
* Atomically inserts the css in the ss's llist for the given cpu. This is
* reentrant safe i.e. safe against softirq, hardirq and nmi. The ss's llist
* will be processed at the flush time to create the update tree.
*
* NOTE: if the user needs the guarantee that the updater either add itself in
* the lockless list or the concurrent flusher flushes its updated stats, a
* memory barrier is needed before the call to __css_rstat_updated() i.e. a
* barrier after updating the per-cpu stats and before calling
* __css_rstat_updated().
*/
void __css_rstat_updated(struct cgroup_subsys_state *css, int cpu)
{
struct llist_head *lhead;
struct css_rstat_cpu *rstatc;
struct llist_node *self;
/* Prevent access to uninitialized rstat pointers. */
if (!css_uses_rstat(css))
return;
lockdep_assert_preemption_disabled();
/*
* The lockless insertion below relies on NMI-safe cmpxchg;
* bail out in NMI on archs that don't provide it.
*/
if (!IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && in_nmi())
return;
rstatc = css_rstat_cpu(css, cpu);
/*
* If already on list return. This check is racy and smp_mb() is needed
* to pair it with the smp_mb() in css_process_update_tree() if the
* guarantee that the updated stats are visible to concurrent flusher is
* needed.
*/
if (llist_on_list(&rstatc->lnode))
return;
/*
* This function can be renentered by irqs and nmis for the same cgroup
* and may try to insert the same per-cpu lnode into the llist. Note
* that llist_add() does not protect against such scenarios. In addition
* this same per-cpu lnode can be modified through init_llist_node()
* from css_rstat_flush() running on a different CPU.
*
* To protect against such stacked contexts of irqs/nmis, we use the
* fact that lnode points to itself when not on a list and then use
* try_cmpxchg() to atomically set to NULL to select the winner
* which will call llist_add(). The losers can assume the insertion is
* successful and the winner will eventually add the per-cpu lnode to
* the llist.
*
* Please note that we can not use this_cpu_cmpxchg() here as on some
* archs it is not safe against modifications from multiple CPUs.
*/
self = &rstatc->lnode;
if (!try_cmpxchg(&rstatc->lnode.next, &self, NULL))
return;
lhead = ss_lhead_cpu(css->ss, cpu);
llist_add(&rstatc->lnode, lhead);
}
/*
* BPF-facing wrapper for __css_rstat_updated(). Validate the caller-provided
* CPU before passing it to the internal rstat updater.
*/
__bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu)
{
if (unlikely(cpu < 0 || cpu >= nr_cpu_ids || !cpu_possible(cpu)))
return;
__css_rstat_updated(css, cpu);
}
static void __css_process_update_tree(struct cgroup_subsys_state *css, int cpu)
{
/* put @css and all ancestors on the corresponding updated lists */
while (true) {
struct css_rstat_cpu *rstatc = css_rstat_cpu(css, cpu);
struct cgroup_subsys_state *parent = css->parent;
struct css_rstat_cpu *prstatc;
/*
* Both additions and removals are bottom-up. If a cgroup
* is already in the tree, all ancestors are.
*/
if (rstatc->updated_next)
break;
/* Root has no parent to link it to, but mark it busy */
if (!parent) {
rstatc->updated_next = css;
break;
}
prstatc = css_rstat_cpu(parent, cpu);
rstatc->updated_next = prstatc->updated_children;
prstatc->updated_children = css;
css = parent;
}
}
static void css_process_update_tree(struct cgroup_subsys *ss, int cpu)
{
struct llist_head *lhead = ss_lhead_cpu(ss, cpu);
struct llist_node *lnode;
while ((lnode = llist_del_first_init(lhead))) {
struct css_rstat_cpu *rstatc;
/*
* smp_mb() is needed here (more specifically in between
* init_llist_node() and per-cpu stats flushing) if the
* guarantee is required by a rstat user where etiher the
* updater should add itself on the lockless list or the
* flusher flush the stats updated by the updater who have
* observed that they are already on the list. The
* corresponding barrier pair for this one should be before
* __css_rstat_updated() by the user.
*
* For now, there aren't any such user, so not adding the
* barrier here but if such a use-case arise, please add
* smp_mb() here.
*/
rstatc = container_of(lnode, struct css_rstat_cpu, lnode);
__css_process_update_tree(rstatc->owner, cpu);
}
}
/**
* css_rstat_push_children - push children css's into the given list
* @head: current head of the list (= subtree root)
* @child: first child of the root
* @cpu: target cpu
* Return: A new singly linked list of css's to be flushed
*
* Iteratively traverse down the css_rstat_cpu updated tree level by
* level and push all the parents first before their next level children
* into a singly linked list via the rstat_flush_next pointer built from the
* tail backward like "pushing" css's into a stack. The root is pushed by
* the caller.
*/
static struct cgroup_subsys_state *css_rstat_push_children(
struct cgroup_subsys_state *head,
struct cgroup_subsys_state *child, int cpu)
{
struct cgroup_subsys_state *cnext = child; /* Next head of child css level */
struct cgroup_subsys_state *ghead = NULL; /* Head of grandchild css level */
struct cgroup_subsys_state *parent, *grandchild;
struct css_rstat_cpu *crstatc;
child->rstat_flush_next = NULL;
/*
* The subsystem rstat lock must be held for the whole duration from
* here as the rstat_flush_next list is being constructed to when
* it is consumed later in css_rstat_flush().
*/
lockdep_assert_held(ss_rstat_lock(head->ss));
/*
* Notation: -> updated_next pointer
* => rstat_flush_next pointer
*
* Assuming the following sample updated_children lists:
* P: C1 -> C2 -> P
* C1: G11 -> G12 -> C1
* C2: G21 -> G22 -> C2
*
* After 1st iteration:
* head => C2 => C1 => NULL
* ghead => G21 => G11 => NULL
*
* After 2nd iteration:
* head => G12 => G11 => G22 => G21 => C2 => C1 => NULL
*/
next_level:
while (cnext) {
child = cnext;
cnext = child->rstat_flush_next;
parent = child->parent;
/* updated_next is parent cgroup terminated if !NULL */
while (child != parent) {
child->rstat_flush_next = head;
head = child;
crstatc = css_rstat_cpu(child, cpu);
grandchild = crstatc->updated_children;
if (grandchild != child) {
/* Push the grand child to the next level */
crstatc->updated_children = child;
grandchild->rstat_flush_next = ghead;
ghead = grandchild;
}
child = crstatc->updated_next;
crstatc->updated_next = NULL;
}
}
if (ghead) {
cnext = ghead;
ghead = NULL;
goto next_level;
}
return head;
}
/**
* css_rstat_updated_list - build a list of updated css's to be flushed
* @root: root of the css subtree to traverse
* @cpu: target cpu
* Return: A singly linked list of css's to be flushed
*
* Walks the updated rstat_cpu tree on @cpu from @root. During traversal,
* each returned css is unlinked from the updated tree.
*
* The only ordering guarantee is that, for a parent and a child pair
* covered by a given traversal, the child is before its parent in
* the list.
*
* Note that updated_children is self terminated and points to a list of
* child css's if not empty. Whereas updated_next is like a sibling link
* within the children list and terminated by the parent css. An exception
* here is the css root whose updated_next can be self terminated.
*/
static struct cgroup_subsys_state *css_rstat_updated_list(
struct cgroup_subsys_state *root, int cpu)
{
struct css_rstat_cpu *rstatc = css_rstat_cpu(root, cpu);
struct cgroup_subsys_state *head = NULL, *parent, *child;
css_process_update_tree(root->ss, cpu);
/* Return NULL if this subtree is not on-list */
if (!rstatc->updated_next)
return NULL;
/*
* Unlink @root from its parent. As the updated_children list is
* singly linked, we have to walk it to find the removal point.
*/
parent = root->parent;
if (parent) {
struct css_rstat_cpu *prstatc;
struct cgroup_subsys_state **nextp;
prstatc = css_rstat_cpu(parent, cpu);
nextp = &prstatc->updated_children;
while (*nextp != root) {
struct css_rstat_cpu *nrstatc;
nrstatc = css_rstat_cpu(*nextp, cpu);
WARN_ON_ONCE(*nextp == parent);
nextp = &nrstatc->updated_next;
}
*nextp = rstatc->updated_next;
}
rstatc->updated_next = NULL;
/* Push @root to the list first before pushing the children */
head = root;
root->rstat_flush_next = NULL;
child = rstatc->updated_children;
rstatc->updated_children = root;
if (child != root)
head = css_rstat_push_children(head, child, cpu);
return head;
}
/*
* A hook for bpf stat collectors to attach to and flush their stats.
* Together with providing bpf kfuncs for css_rstat_updated() and
* css_rstat_flush(), this enables a complete workflow where bpf progs that
* collect cgroup stats can integrate with rstat for efficient flushing.
*
* A static noinline declaration here could cause the compiler to optimize away
* the function. A global noinline declaration will keep the definition, but may
* optimize away the callsite. Therefore, __weak is needed to ensure that the
* call is still emitted, by telling the compiler that we don't know what the
* function might eventually be.
*/
__bpf_hook_start();
__weak noinline void bpf_rstat_flush(struct cgroup *cgrp,
struct cgroup *parent, int cpu)
{
}
__bpf_hook_end();
/*
* Helper functions for locking.
*
* This makes it easier to diagnose locking issues and contention in
* production environments. The parameter @cpu_in_loop indicate lock
* was released and re-taken when collection data from the CPUs. The
* value -1 is used when obtaining the main lock else this is the CPU
* number processed last.
*/
static inline void __css_rstat_lock(struct cgroup_subsys_state *css,
int cpu_in_loop)
__acquires(ss_rstat_lock(css->ss))
{
struct cgroup *cgrp = css->cgroup;
spinlock_t *lock;
bool contended;
lock = ss_rstat_lock(css->ss);
contended = !spin_trylock_irq(lock);
if (contended) {
trace_cgroup_rstat_lock_contended(cgrp, cpu_in_loop, contended);
spin_lock_irq(lock);
}
trace_cgroup_rstat_locked(cgrp, cpu_in_loop, contended);
}
static inline void __css_rstat_unlock(struct cgroup_subsys_state *css,
int cpu_in_loop)
__releases(ss_rstat_lock(css->ss))
{
struct cgroup *cgrp = css->cgroup;
spinlock_t *lock;
lock = ss_rstat_lock(css->ss);
trace_cgroup_rstat_unlock(cgrp, cpu_in_loop, false);
spin_unlock_irq(lock);
}
/**
* css_rstat_flush - flush stats in @css's rstat subtree
* @css: target cgroup subsystem state
*
* Collect all per-cpu stats in @css's subtree into the global counters
* and propagate them upwards. After this function returns, all rstat
* nodes in the subtree have up-to-date ->stat.
*
* This also gets all rstat nodes in the subtree including @css off the
* ->updated_children lists.
*
* This function may block.
*/
__bpf_kfunc void css_rstat_flush(struct cgroup_subsys_state *css)
{
int cpu;
bool is_self = css_is_self(css);
/*
* Since bpf programs can call this function, prevent access to
* uninitialized rstat pointers.
*/
if (!css_uses_rstat(css))
return;
might_sleep();
for_each_possible_cpu(cpu) {
struct cgroup_subsys_state *pos;
/* Reacquire for each CPU to avoid disabling IRQs too long */
__css_rstat_lock(css, cpu);
pos = css_rstat_updated_list(css, cpu);
for (; pos; pos = pos->rstat_flush_next) {
if (is_self) {
cgroup_base_stat_flush(pos->cgroup, cpu);
bpf_rstat_flush(pos->cgroup,
cgroup_parent(pos->cgroup), cpu);
} else
pos->ss->css_rstat_flush(pos, cpu);
}
__css_rstat_unlock(css, cpu);
if (!cond_resched())
cpu_relax();
}
}
int css_rstat_init(struct cgroup_subsys_state *css)
{
struct cgroup *cgrp = css->cgroup;
int cpu;
bool is_self = css_is_self(css);
if (is_self) {
/* the root cgrp has rstat_base_cpu preallocated */
if (!cgrp->rstat_base_cpu) {
cgrp->rstat_base_cpu = alloc_percpu(struct cgroup_rstat_base_cpu);
if (!cgrp->rstat_base_cpu)
return -ENOMEM;
}
} else if (css->ss->css_rstat_flush == NULL)
return 0;
/* the root cgrp's self css has rstat_cpu preallocated */
if (!css->rstat_cpu) {
css->rstat_cpu = alloc_percpu(struct css_rstat_cpu);
if (!css->rstat_cpu) {
if (is_self)
free_percpu(cgrp->rstat_base_cpu);
return -ENOMEM;
}
}
/* ->updated_children list is self terminated */
for_each_possible_cpu(cpu) {
struct css_rstat_cpu *rstatc = css_rstat_cpu(css, cpu);
rstatc->owner = rstatc->updated_children = css;
init_llist_node(&rstatc->lnode);
if (is_self) {
struct cgroup_rstat_base_cpu *rstatbc;
rstatbc = cgroup_rstat_base_cpu(cgrp, cpu);
u64_stats_init(&rstatbc->bsync);
}
}
return 0;
}
void css_rstat_exit(struct cgroup_subsys_state *css)
{
int cpu;
if (!css_uses_rstat(css))
return;
if (!css->rstat_cpu)
return;
css_rstat_flush(css);
/* sanity check */
for_each_possible_cpu(cpu) {
struct css_rstat_cpu *rstatc = css_rstat_cpu(css, cpu);
if (WARN_ON_ONCE(rstatc->updated_children != css) ||
WARN_ON_ONCE(rstatc->updated_next))
return;
}
if (css_is_self(css)) {
struct cgroup *cgrp = css->cgroup;
free_percpu(cgrp->rstat_base_cpu);
cgrp->rstat_base_cpu = NULL;
}
free_percpu(css->rstat_cpu);
css->rstat_cpu = NULL;
}
/**
* ss_rstat_init - subsystem-specific rstat initialization
* @ss: target subsystem
*
* If @ss is NULL, the static locks associated with the base stats
* are initialized. If @ss is non-NULL, the subsystem-specific locks
* are initialized.
*/
int __init ss_rstat_init(struct cgroup_subsys *ss)
{
int cpu;
if (ss) {
ss->lhead = alloc_percpu(struct llist_head);
if (!ss->lhead)
return -ENOMEM;
}
spin_lock_init(ss_rstat_lock(ss));
for_each_possible_cpu(cpu)
init_llist_head(ss_lhead_cpu(ss, cpu));
return 0;
}
/*
* Functions for cgroup basic resource statistics implemented on top of
* rstat.
*/
static void cgroup_base_stat_add(struct cgroup_base_stat *dst_bstat,
struct cgroup_base_stat *src_bstat)
{
dst_bstat->cputime.utime += src_bstat->cputime.utime;
dst_bstat->cputime.stime += src_bstat->cputime.stime;
dst_bstat->cputime.sum_exec_runtime += src_bstat->cputime.sum_exec_runtime;
#ifdef CONFIG_SCHED_CORE
dst_bstat->forceidle_sum += src_bstat->forceidle_sum;
#endif
dst_bstat->ntime += src_bstat->ntime;
}
static void cgroup_base_stat_sub(struct cgroup_base_stat *dst_bstat,
struct cgroup_base_stat *src_bstat)
{
dst_bstat->cputime.utime -= src_bstat->cputime.utime;
dst_bstat->cputime.stime -= src_bstat->cputime.stime;
dst_bstat->cputime.sum_exec_runtime -= src_bstat->cputime.sum_exec_runtime;
#ifdef CONFIG_SCHED_CORE
dst_bstat->forceidle_sum -= src_bstat->forceidle_sum;
#endif
dst_bstat->ntime -= src_bstat->ntime;
}
static void cgroup_base_stat_flush(struct cgroup *cgrp, int cpu)
{
struct cgroup_rstat_base_cpu *rstatbc = cgroup_rstat_base_cpu(cgrp, cpu);
struct cgroup *parent = cgroup_parent(cgrp);
struct cgroup_rstat_base_cpu *prstatbc;
struct cgroup_base_stat delta;
unsigned seq;
/* Root-level stats are sourced from system-wide CPU stats */
if (!parent)
return;
/* fetch the current per-cpu values */
do {
seq = __u64_stats_fetch_begin(&rstatbc->bsync);
delta = rstatbc->bstat;
} while (__u64_stats_fetch_retry(&rstatbc->bsync, seq));
/* propagate per-cpu delta to cgroup and per-cpu global statistics */
cgroup_base_stat_sub(&delta, &rstatbc->last_bstat);
cgroup_base_stat_add(&cgrp->bstat, &delta);
cgroup_base_stat_add(&rstatbc->last_bstat, &delta);
cgroup_base_stat_add(&rstatbc->subtree_bstat, &delta);
/* propagate cgroup and per-cpu global delta to parent (unless that's root) */
if (cgroup_parent(parent)) {
delta = cgrp->bstat;
cgroup_base_stat_sub(&delta, &cgrp->last_bstat);
cgroup_base_stat_add(&parent->bstat, &delta);
cgroup_base_stat_add(&cgrp->last_bstat, &delta);
delta = rstatbc->subtree_bstat;
prstatbc = cgroup_rstat_base_cpu(parent, cpu);
cgroup_base_stat_sub(&delta, &rstatbc->last_subtree_bstat);
cgroup_base_stat_add(&prstatbc->subtree_bstat, &delta);
cgroup_base_stat_add(&rstatbc->last_subtree_bstat, &delta);
}
}
static struct cgroup_rstat_base_cpu *
cgroup_base_stat_cputime_account_begin(struct cgroup *cgrp, unsigned long *flags)
{
struct cgroup_rstat_base_cpu *rstatbc;
rstatbc = get_cpu_ptr(cgrp->rstat_base_cpu);
*flags = u64_stats_update_begin_irqsave(&rstatbc->bsync);
return rstatbc;
}
static void cgroup_base_stat_cputime_account_end(struct cgroup *cgrp,
struct cgroup_rstat_base_cpu *rstatbc,
unsigned long flags)
{
u64_stats_update_end_irqrestore(&rstatbc->bsync, flags);
__css_rstat_updated(&cgrp->self, smp_processor_id());
put_cpu_ptr(rstatbc);
}
void __cgroup_account_cputime(struct cgroup *cgrp, u64 delta_exec)
{
struct cgroup_rstat_base_cpu *rstatbc;
unsigned long flags;
rstatbc = cgroup_base_stat_cputime_account_begin(cgrp, &flags);
rstatbc->bstat.cputime.sum_exec_runtime += delta_exec;
cgroup_base_stat_cputime_account_end(cgrp, rstatbc, flags);
}
void __cgroup_account_cputime_field(struct cgroup *cgrp,
enum cpu_usage_stat index, u64 delta_exec)
{
struct cgroup_rstat_base_cpu *rstatbc;
unsigned long flags;
rstatbc = cgroup_base_stat_cputime_account_begin(cgrp, &flags);
switch (index) {
case CPUTIME_NICE:
rstatbc->bstat.ntime += delta_exec;
fallthrough;
case CPUTIME_USER:
rstatbc->bstat.cputime.utime += delta_exec;
break;
case CPUTIME_SYSTEM:
case CPUTIME_IRQ:
case CPUTIME_SOFTIRQ:
rstatbc->bstat.cputime.stime += delta_exec;
break;
#ifdef CONFIG_SCHED_CORE
case CPUTIME_FORCEIDLE:
rstatbc->bstat.forceidle_sum += delta_exec;
break;
#endif
default:
break;
}
cgroup_base_stat_cputime_account_end(cgrp, rstatbc, flags);
}
/*
* compute the cputime for the root cgroup by getting the per cpu data
* at a global level, then categorizing the fields in a manner consistent
* with how it is done by __cgroup_account_cputime_field for each bit of
* cpu time attributed to a cgroup.
*/
static void root_cgroup_cputime(struct cgroup_base_stat *bstat)
{
struct task_cputime *cputime = &bstat->cputime;
int i;
memset(bstat, 0, sizeof(*bstat));
for_each_possible_cpu(i) {
struct kernel_cpustat kcpustat;
u64 *cpustat = kcpustat.cpustat;
u64 user = 0;
u64 sys = 0;
kcpustat_cpu_fetch(&kcpustat, i);
user += cpustat[CPUTIME_USER];
user += cpustat[CPUTIME_NICE];
cputime->utime += user;
sys += cpustat[CPUTIME_SYSTEM];
sys += cpustat[CPUTIME_IRQ];
sys += cpustat[CPUTIME_SOFTIRQ];
cputime->stime += sys;
cputime->sum_exec_runtime += user;
cputime->sum_exec_runtime += sys;
#ifdef CONFIG_SCHED_CORE
bstat->forceidle_sum += cpustat[CPUTIME_FORCEIDLE];
#endif
bstat->ntime += cpustat[CPUTIME_NICE];
}
}
static void cgroup_force_idle_show(struct seq_file *seq, struct cgroup_base_stat *bstat)
{
#ifdef CONFIG_SCHED_CORE
u64 forceidle_time = bstat->forceidle_sum;
do_div(forceidle_time, NSEC_PER_USEC);
seq_printf(seq, "core_sched.force_idle_usec %llu\n", forceidle_time);
#endif
}
void cgroup_base_stat_cputime_show(struct seq_file *seq)
{
struct cgroup *cgrp = seq_css(seq)->cgroup;
struct cgroup_base_stat bstat;
if (cgroup_parent(cgrp)) {
css_rstat_flush(&cgrp->self);
__css_rstat_lock(&cgrp->self, -1);
bstat = cgrp->bstat;
cputime_adjust(&cgrp->bstat.cputime, &cgrp->prev_cputime,
&bstat.cputime.utime, &bstat.cputime.stime);
__css_rstat_unlock(&cgrp->self, -1);
} else {
root_cgroup_cputime(&bstat);
}
do_div(bstat.cputime.sum_exec_runtime, NSEC_PER_USEC);
do_div(bstat.cputime.utime, NSEC_PER_USEC);
do_div(bstat.cputime.stime, NSEC_PER_USEC);
do_div(bstat.ntime, NSEC_PER_USEC);
seq_printf(seq, "usage_usec %llu\n"
"user_usec %llu\n"
"system_usec %llu\n"
"nice_usec %llu\n",
bstat.cputime.sum_exec_runtime,
bstat.cputime.utime,
bstat.cputime.stime,
bstat.ntime);
cgroup_force_idle_show(seq, &bstat);
}
/* Add bpf kfuncs for css_rstat_updated() and css_rstat_flush() */
BTF_KFUNCS_START(bpf_rstat_kfunc_ids)
BTF_ID_FLAGS(func, css_rstat_updated)
BTF_ID_FLAGS(func, css_rstat_flush, KF_SLEEPABLE)
BTF_KFUNCS_END(bpf_rstat_kfunc_ids)
static const struct btf_kfunc_id_set bpf_rstat_kfunc_set = {
.owner = THIS_MODULE,
.set = &bpf_rstat_kfunc_ids,
};
static int __init bpf_rstat_kfunc_init(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING,
&bpf_rstat_kfunc_set);
}
late_initcall(bpf_rstat_kfunc_init);