Commit abde77eb authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup

Pull cgroup updates from Tejun Heo:
 "This includes Roman's cgroup2 freezer implementation.

  It's a separate machanism from cgroup1 freezer. Instead of blocking
  user tasks in arbitrary uninterruptible sleeps, the new implementation
  extends jobctl stop - frozen tasks are trapped in jobctl stop until
  thawed and can be killed and ptraced. Lots of thanks to Oleg for
  sheperding the effort.

  Other than that, there are a few trivial changes"

* 'for-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup:
  cgroup: never call do_group_exit() with task->frozen bit set
  kernel: cgroup: fix misuse of %x
  cgroup: get rid of cgroup_freezer_frozen_exit()
  cgroup: prevent spurious transition into non-frozen state
  cgroup: Remove unused cgrp variable
  cgroup: document cgroup v2 freezer interface
  cgroup: add tracing points for cgroup v2 freezer
  cgroup: make TRACE_CGROUP_PATH irq-safe
  kselftests: cgroup: add freezer controller self-tests
  kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()
  cgroup: cgroup v2 freezer
  cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
  cgroup: implement __cgroup_task_count() helper
  cgroup: rename freezer.c into legacy_freezer.c
  cgroup: remove extra cgroup_migrate_finish() call
parents 23c97060 f2b31bb5
...@@ -864,6 +864,8 @@ All cgroup core files are prefixed with "cgroup." ...@@ -864,6 +864,8 @@ All cgroup core files are prefixed with "cgroup."
populated populated
1 if the cgroup or its descendants contains any live 1 if the cgroup or its descendants contains any live
processes; otherwise, 0. processes; otherwise, 0.
frozen
1 if the cgroup is frozen; otherwise, 0.
cgroup.max.descendants cgroup.max.descendants
A read-write single value files. The default is "max". A read-write single value files. The default is "max".
...@@ -897,6 +899,31 @@ All cgroup core files are prefixed with "cgroup." ...@@ -897,6 +899,31 @@ All cgroup core files are prefixed with "cgroup."
A dying cgroup can consume system resources not exceeding A dying cgroup can consume system resources not exceeding
limits, which were active at the moment of cgroup deletion. limits, which were active at the moment of cgroup deletion.
cgroup.freeze
A read-write single value file which exists on non-root cgroups.
Allowed values are "0" and "1". The default is "0".
Writing "1" to the file causes freezing of the cgroup and all
descendant cgroups. This means that all belonging processes will
be stopped and will not run until the cgroup will be explicitly
unfrozen. Freezing of the cgroup may take some time; when this action
is completed, the "frozen" value in the cgroup.events control file
will be updated to "1" and the corresponding notification will be
issued.
A cgroup can be frozen either by its own settings, or by settings
of any ancestor cgroups. If any of ancestor cgroups is frozen, the
cgroup will remain frozen.
Processes in the frozen cgroup can be killed by a fatal signal.
They also can enter and leave a frozen cgroup: either by an explicit
move by a user, or if freezing of the cgroup races with fork().
If a process is moved to a frozen cgroup, it stops. If a process is
moved out of a frozen cgroup, it becomes running.
Frozen status of a cgroup doesn't affect any cgroup tree operations:
it's possible to delete a frozen (and empty) cgroup, as well as
create new sub-cgroups.
Controllers Controllers
=========== ===========
......
...@@ -65,6 +65,12 @@ enum { ...@@ -65,6 +65,12 @@ enum {
* specified at mount time and thus is implemented here. * specified at mount time and thus is implemented here.
*/ */
CGRP_CPUSET_CLONE_CHILDREN, CGRP_CPUSET_CLONE_CHILDREN,
/* Control group has to be frozen. */
CGRP_FREEZE,
/* Cgroup is frozen. */
CGRP_FROZEN,
}; };
/* cgroup_root->flags */ /* cgroup_root->flags */
...@@ -317,6 +323,25 @@ struct cgroup_rstat_cpu { ...@@ -317,6 +323,25 @@ struct cgroup_rstat_cpu {
struct cgroup *updated_next; /* NULL iff not on the list */ struct cgroup *updated_next; /* NULL iff not on the list */
}; };
struct cgroup_freezer_state {
/* Should the cgroup and its descendants be frozen. */
bool freeze;
/* Should the cgroup actually be frozen? */
int e_freeze;
/* Fields below are protected by css_set_lock */
/* Number of frozen descendant cgroups */
int nr_frozen_descendants;
/*
* Number of tasks, which are counted as frozen:
* frozen, SIGSTOPped, and PTRACEd.
*/
int nr_frozen_tasks;
};
struct cgroup { struct cgroup {
/* self css with NULL ->ss, points back to this cgroup */ /* self css with NULL ->ss, points back to this cgroup */
struct cgroup_subsys_state self; struct cgroup_subsys_state self;
...@@ -349,6 +374,11 @@ struct cgroup { ...@@ -349,6 +374,11 @@ struct cgroup {
* Dying cgroups are cgroups which were deleted by a user, * Dying cgroups are cgroups which were deleted by a user,
* but are still existing because someone else is holding a reference. * but are still existing because someone else is holding a reference.
* max_descendants is a maximum allowed number of descent cgroups. * max_descendants is a maximum allowed number of descent cgroups.
*
* nr_descendants and nr_dying_descendants are protected
* by cgroup_mutex and css_set_lock. It's fine to read them holding
* any of cgroup_mutex and css_set_lock; for writing both locks
* should be held.
*/ */
int nr_descendants; int nr_descendants;
int nr_dying_descendants; int nr_dying_descendants;
...@@ -448,6 +478,9 @@ struct cgroup { ...@@ -448,6 +478,9 @@ struct cgroup {
/* If there is block congestion on this cgroup. */ /* If there is block congestion on this cgroup. */
atomic_t congestion_count; atomic_t congestion_count;
/* Used to store internal freezer state */
struct cgroup_freezer_state freezer;
/* ids of the ancestors at each level including self */ /* ids of the ancestors at each level including self */
int ancestor_ids[]; int ancestor_ids[];
}; };
......
...@@ -881,4 +881,47 @@ static inline void put_cgroup_ns(struct cgroup_namespace *ns) ...@@ -881,4 +881,47 @@ static inline void put_cgroup_ns(struct cgroup_namespace *ns)
free_cgroup_ns(ns); free_cgroup_ns(ns);
} }
#ifdef CONFIG_CGROUPS
void cgroup_enter_frozen(void);
void cgroup_leave_frozen(bool always_leave);
void cgroup_update_frozen(struct cgroup *cgrp);
void cgroup_freeze(struct cgroup *cgrp, bool freeze);
void cgroup_freezer_migrate_task(struct task_struct *task, struct cgroup *src,
struct cgroup *dst);
static inline bool cgroup_task_freeze(struct task_struct *task)
{
bool ret;
if (task->flags & PF_KTHREAD)
return false;
rcu_read_lock();
ret = test_bit(CGRP_FREEZE, &task_dfl_cgroup(task)->flags);
rcu_read_unlock();
return ret;
}
static inline bool cgroup_task_frozen(struct task_struct *task)
{
return task->frozen;
}
#else /* !CONFIG_CGROUPS */
static inline void cgroup_enter_frozen(void) { }
static inline void cgroup_leave_frozen(bool always_leave) { }
static inline bool cgroup_task_freeze(struct task_struct *task)
{
return false;
}
static inline bool cgroup_task_frozen(struct task_struct *task)
{
return false;
}
#endif /* !CONFIG_CGROUPS */
#endif /* _LINUX_CGROUP_H */ #endif /* _LINUX_CGROUP_H */
...@@ -726,6 +726,8 @@ struct task_struct { ...@@ -726,6 +726,8 @@ struct task_struct {
#ifdef CONFIG_CGROUPS #ifdef CONFIG_CGROUPS
/* disallow userland-initiated cgroup migration */ /* disallow userland-initiated cgroup migration */
unsigned no_cgroup_migration:1; unsigned no_cgroup_migration:1;
/* task is frozen/stopped (used by the cgroup freezer) */
unsigned frozen:1;
#endif #endif
#ifdef CONFIG_BLK_CGROUP #ifdef CONFIG_BLK_CGROUP
/* to be used once the psi infrastructure lands upstream. */ /* to be used once the psi infrastructure lands upstream. */
......
...@@ -18,6 +18,7 @@ struct task_struct; ...@@ -18,6 +18,7 @@ struct task_struct;
#define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */ #define JOBCTL_TRAP_NOTIFY_BIT 20 /* trap for NOTIFY */
#define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */ #define JOBCTL_TRAPPING_BIT 21 /* switching to TRACED */
#define JOBCTL_LISTENING_BIT 22 /* ptracer is listening for events */ #define JOBCTL_LISTENING_BIT 22 /* ptracer is listening for events */
#define JOBCTL_TRAP_FREEZE_BIT 23 /* trap for cgroup freezer */
#define JOBCTL_STOP_DEQUEUED (1UL << JOBCTL_STOP_DEQUEUED_BIT) #define JOBCTL_STOP_DEQUEUED (1UL << JOBCTL_STOP_DEQUEUED_BIT)
#define JOBCTL_STOP_PENDING (1UL << JOBCTL_STOP_PENDING_BIT) #define JOBCTL_STOP_PENDING (1UL << JOBCTL_STOP_PENDING_BIT)
...@@ -26,6 +27,7 @@ struct task_struct; ...@@ -26,6 +27,7 @@ struct task_struct;
#define JOBCTL_TRAP_NOTIFY (1UL << JOBCTL_TRAP_NOTIFY_BIT) #define JOBCTL_TRAP_NOTIFY (1UL << JOBCTL_TRAP_NOTIFY_BIT)
#define JOBCTL_TRAPPING (1UL << JOBCTL_TRAPPING_BIT) #define JOBCTL_TRAPPING (1UL << JOBCTL_TRAPPING_BIT)
#define JOBCTL_LISTENING (1UL << JOBCTL_LISTENING_BIT) #define JOBCTL_LISTENING (1UL << JOBCTL_LISTENING_BIT)
#define JOBCTL_TRAP_FREEZE (1UL << JOBCTL_TRAP_FREEZE_BIT)
#define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY) #define JOBCTL_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)
#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK) #define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
......
...@@ -103,6 +103,20 @@ DEFINE_EVENT(cgroup, cgroup_rename, ...@@ -103,6 +103,20 @@ DEFINE_EVENT(cgroup, cgroup_rename,
TP_ARGS(cgrp, path) TP_ARGS(cgrp, path)
); );
DEFINE_EVENT(cgroup, cgroup_freeze,
TP_PROTO(struct cgroup *cgrp, const char *path),
TP_ARGS(cgrp, path)
);
DEFINE_EVENT(cgroup, cgroup_unfreeze,
TP_PROTO(struct cgroup *cgrp, const char *path),
TP_ARGS(cgrp, path)
);
DECLARE_EVENT_CLASS(cgroup_migrate, DECLARE_EVENT_CLASS(cgroup_migrate,
TP_PROTO(struct cgroup *dst_cgrp, const char *path, TP_PROTO(struct cgroup *dst_cgrp, const char *path,
...@@ -149,6 +163,47 @@ DEFINE_EVENT(cgroup_migrate, cgroup_transfer_tasks, ...@@ -149,6 +163,47 @@ DEFINE_EVENT(cgroup_migrate, cgroup_transfer_tasks,
TP_ARGS(dst_cgrp, path, task, threadgroup) TP_ARGS(dst_cgrp, path, task, threadgroup)
); );
DECLARE_EVENT_CLASS(cgroup_event,
TP_PROTO(struct cgroup *cgrp, const char *path, int val),
TP_ARGS(cgrp, path, val),
TP_STRUCT__entry(
__field( int, root )
__field( int, id )
__field( int, level )
__string( path, path )
__field( int, val )
),
TP_fast_assign(
__entry->root = cgrp->root->hierarchy_id;
__entry->id = cgrp->id;
__entry->level = cgrp->level;
__assign_str(path, path);
__entry->val = val;
),
TP_printk("root=%d id=%d level=%d path=%s val=%d",
__entry->root, __entry->id, __entry->level, __get_str(path),
__entry->val)
);
DEFINE_EVENT(cgroup_event, cgroup_notify_populated,
TP_PROTO(struct cgroup *cgrp, const char *path, int val),
TP_ARGS(cgrp, path, val)
);
DEFINE_EVENT(cgroup_event, cgroup_notify_frozen,
TP_PROTO(struct cgroup *cgrp, const char *path, int val),
TP_ARGS(cgrp, path, val)
);
#endif /* _TRACE_CGROUP_H */ #endif /* _TRACE_CGROUP_H */
/* This part must be outside protection */ /* This part must be outside protection */
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o freezer.o
obj-$(CONFIG_CGROUP_FREEZER) += freezer.o obj-$(CONFIG_CGROUP_FREEZER) += legacy_freezer.o
obj-$(CONFIG_CGROUP_PIDS) += pids.o obj-$(CONFIG_CGROUP_PIDS) += pids.o
obj-$(CONFIG_CGROUP_RDMA) += rdma.o obj-$(CONFIG_CGROUP_RDMA) += rdma.o
obj-$(CONFIG_CPUSETS) += cpuset.o obj-$(CONFIG_CPUSETS) += cpuset.o
......
...@@ -28,12 +28,15 @@ extern void __init enable_debug_cgroup(void); ...@@ -28,12 +28,15 @@ extern void __init enable_debug_cgroup(void);
#define TRACE_CGROUP_PATH(type, cgrp, ...) \ #define TRACE_CGROUP_PATH(type, cgrp, ...) \
do { \ do { \
if (trace_cgroup_##type##_enabled()) { \ if (trace_cgroup_##type##_enabled()) { \
spin_lock(&trace_cgroup_path_lock); \ unsigned long flags; \
spin_lock_irqsave(&trace_cgroup_path_lock, \
flags); \
cgroup_path(cgrp, trace_cgroup_path, \ cgroup_path(cgrp, trace_cgroup_path, \
TRACE_CGROUP_PATH_LEN); \ TRACE_CGROUP_PATH_LEN); \
trace_cgroup_##type(cgrp, trace_cgroup_path, \ trace_cgroup_##type(cgrp, trace_cgroup_path, \
##__VA_ARGS__); \ ##__VA_ARGS__); \
spin_unlock(&trace_cgroup_path_lock); \ spin_unlock_irqrestore(&trace_cgroup_path_lock, \
flags); \
} \ } \
} while (0) } while (0)
...@@ -240,6 +243,7 @@ int cgroup_rmdir(struct kernfs_node *kn); ...@@ -240,6 +243,7 @@ int cgroup_rmdir(struct kernfs_node *kn);
int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node, int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
struct kernfs_root *kf_root); struct kernfs_root *kf_root);
int __cgroup_task_count(const struct cgroup *cgrp);
int cgroup_task_count(const struct cgroup *cgrp); int cgroup_task_count(const struct cgroup *cgrp);
/* /*
......
...@@ -342,22 +342,6 @@ static struct cgroup_pidlist *cgroup_pidlist_find_create(struct cgroup *cgrp, ...@@ -342,22 +342,6 @@ static struct cgroup_pidlist *cgroup_pidlist_find_create(struct cgroup *cgrp,
return l; return l;
} }
/**
* cgroup_task_count - count the number of tasks in a cgroup.
* @cgrp: the cgroup in question
*/
int cgroup_task_count(const struct cgroup *cgrp)
{
int count = 0;
struct cgrp_cset_link *link;
spin_lock_irq(&css_set_lock);
list_for_each_entry(link, &cgrp->cset_links, cset_link)
count += link->cset->nr_tasks;
spin_unlock_irq(&css_set_lock);
return count;
}
/* /*
* Load a cgroup's pidarray with either procs' tgids or tasks' pids * Load a cgroup's pidarray with either procs' tgids or tasks' pids
*/ */
......
...@@ -593,6 +593,39 @@ static void cgroup_get_live(struct cgroup *cgrp) ...@@ -593,6 +593,39 @@ static void cgroup_get_live(struct cgroup *cgrp)
css_get(&cgrp->self); css_get(&cgrp->self);
} }
/**
* __cgroup_task_count - count the number of tasks in a cgroup. The caller
* is responsible for taking the css_set_lock.
* @cgrp: the cgroup in question
*/
int __cgroup_task_count(const struct cgroup *cgrp)
{
int count = 0;
struct cgrp_cset_link *link;
lockdep_assert_held(&css_set_lock);
list_for_each_entry(link, &cgrp->cset_links, cset_link)
count += link->cset->nr_tasks;
return count;
}
/**
* cgroup_task_count - count the number of tasks in a cgroup.
* @cgrp: the cgroup in question
*/
int cgroup_task_count(const struct cgroup *cgrp)
{
int count;
spin_lock_irq(&css_set_lock);
count = __cgroup_task_count(cgrp);
spin_unlock_irq(&css_set_lock);
return count;
}
struct cgroup_subsys_state *of_css(struct kernfs_open_file *of) struct cgroup_subsys_state *of_css(struct kernfs_open_file *of)
{ {
struct cgroup *cgrp = of->kn->parent->priv; struct cgroup *cgrp = of->kn->parent->priv;
...@@ -783,6 +816,8 @@ static void cgroup_update_populated(struct cgroup *cgrp, bool populated) ...@@ -783,6 +816,8 @@ static void cgroup_update_populated(struct cgroup *cgrp, bool populated)
break; break;
cgroup1_check_for_release(cgrp); cgroup1_check_for_release(cgrp);
TRACE_CGROUP_PATH(notify_populated, cgrp,
cgroup_is_populated(cgrp));
cgroup_file_notify(&cgrp->events_file); cgroup_file_notify(&cgrp->events_file);
child = cgrp; child = cgrp;
...@@ -2402,8 +2437,15 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx) ...@@ -2402,8 +2437,15 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
get_css_set(to_cset); get_css_set(to_cset);
to_cset->nr_tasks++; to_cset->nr_tasks++;
css_set_move_task(task, from_cset, to_cset, true); css_set_move_task(task, from_cset, to_cset, true);
put_css_set_locked(from_cset);
from_cset->nr_tasks--; from_cset->nr_tasks--;
/*
* If the source or destination cgroup is frozen,
* the task might require to change its state.
*/
cgroup_freezer_migrate_task(task, from_cset->dfl_cgrp,
to_cset->dfl_cgrp);
put_css_set_locked(from_cset);
} }
} }
spin_unlock_irq(&css_set_lock); spin_unlock_irq(&css_set_lock);
...@@ -2602,7 +2644,7 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx) ...@@ -2602,7 +2644,7 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx)
dst_cset = find_css_set(src_cset, src_cset->mg_dst_cgrp); dst_cset = find_css_set(src_cset, src_cset->mg_dst_cgrp);
if (!dst_cset) if (!dst_cset)
goto err; return -ENOMEM;
WARN_ON_ONCE(src_cset->mg_dst_cset || dst_cset->mg_dst_cset); WARN_ON_ONCE(src_cset->mg_dst_cset || dst_cset->mg_dst_cset);
...@@ -2634,9 +2676,6 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx) ...@@ -2634,9 +2676,6 @@ int cgroup_migrate_prepare_dst(struct cgroup_mgctx *mgctx)
} }
return 0; return 0;
err:
cgroup_migrate_finish(mgctx);
return -ENOMEM;
} }
/** /**
...@@ -3447,8 +3486,11 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of, ...@@ -3447,8 +3486,11 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of,
static int cgroup_events_show(struct seq_file *seq, void *v) static int cgroup_events_show(struct seq_file *seq, void *v)
{ {
seq_printf(seq, "populated %d\n", struct cgroup *cgrp = seq_css(seq)->cgroup;
cgroup_is_populated(seq_css(seq)->cgroup));
seq_printf(seq, "populated %d\n", cgroup_is_populated(cgrp));
seq_printf(seq, "frozen %d\n", test_bit(CGRP_FROZEN, &cgrp->flags));
return 0; return 0;
} }
...@@ -3510,6 +3552,40 @@ static int cgroup_cpu_pressure_show(struct seq_file *seq, void *v) ...@@ -3510,6 +3552,40 @@ static int cgroup_cpu_pressure_show(struct seq_file *seq, void *v)
} }
#endif #endif
static int cgroup_freeze_show(struct seq_file *seq, void *v)
{
struct cgroup *cgrp = seq_css(seq)->cgroup;
seq_printf(seq, "%d\n", cgrp->freezer.freeze);
return 0;
}
static ssize_t cgroup_freeze_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
struct cgroup *cgrp;
ssize_t ret;
int freeze;
ret = kstrtoint(strstrip(buf), 0, &freeze);
if (ret)
return ret;
if (freeze < 0 || freeze > 1)
return -ERANGE;
cgrp = cgroup_kn_lock_live(of->kn, false);
if (!cgrp)
return -ENOENT;
cgroup_freeze(cgrp, freeze);
cgroup_kn_unlock(of->kn);
return nbytes;
}
static int cgroup_file_open(struct kernfs_open_file *of) static int cgroup_file_open(struct kernfs_open_file *of)
{ {
struct cftype *cft = of->kn->priv; struct cftype *cft = of->kn->priv;
...@@ -4653,6 +4729,12 @@ static struct cftype cgroup_base_files[] = { ...@@ -4653,6 +4729,12 @@ static struct cftype cgroup_base_files[] = {
.name = "cgroup.stat", .name = "cgroup.stat",
.seq_show = cgroup_stat_show, .seq_show = cgroup_stat_show,
}, },
{
.name = "cgroup.freeze",
.flags = CFTYPE_NOT_ON_ROOT,
.seq_show = cgroup_freeze_show,
.write = cgroup_freeze_write,
},
{ {
.name = "cpu.stat", .name = "cpu.stat",
.flags = CFTYPE_NOT_ON_ROOT, .flags = CFTYPE_NOT_ON_ROOT,
...@@ -4781,9 +4863,11 @@ static void css_release_work_fn(struct work_struct *work) ...@@ -4781,9 +4863,11 @@ static void css_release_work_fn(struct work_struct *work)
if (cgroup_on_dfl(cgrp)) if (cgroup_on_dfl(cgrp))
cgroup_rstat_flush(cgrp); cgroup_rstat_flush(cgrp);
spin_lock_irq(&css_set_lock);
for (tcgrp = cgroup_parent(cgrp); tcgrp; for (tcgrp = cgroup_parent(cgrp); tcgrp;
tcgrp = cgroup_parent(tcgrp)) tcgrp = cgroup_parent(tcgrp))
tcgrp->nr_dying_descendants--; tcgrp->nr_dying_descendants--;
spin_unlock_irq(&css_set_lock);
cgroup_idr_remove(&cgrp->root->cgroup_idr, cgrp->id); cgroup_idr_remove(&cgrp->root->cgroup_idr, cgrp->id);
cgrp->id = -1; cgrp->id = -1;
...@@ -5001,12 +5085,31 @@ static struct cgroup *cgroup_create(struct cgroup *parent) ...@@ -5001,12 +5085,31 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
if (ret) if (ret)
goto out_psi_free; goto out_psi_free;
/*
* New cgroup inherits effective freeze counter, and
* if the parent has to be frozen, the child has too.
*/
cgrp->freezer.e_freeze = parent->freezer.e_freeze;
if (cgrp->freezer.e_freeze)
set_bit(CGRP_FROZEN, &cgrp->flags);
spin_lock_irq(&css_set_lock);
for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp)) { for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp)) {
cgrp->ancestor_ids[tcgrp->level] = tcgrp->id; cgrp->ancestor_ids[tcgrp->level] = tcgrp->id;
if (tcgrp != cgrp) if (tcgrp != cgrp) {
tcgrp->nr_descendants++; tcgrp->nr_descendants++;
/*
* If the new cgroup is frozen, all ancestor cgroups
* get a new frozen descendant, but their state can't
* change because of this.
*/
if (cgrp->freezer.e_freeze)
tcgrp->freezer.nr_frozen_descendants++;
}
} }
spin_unlock_irq(&css_set_lock);
if (notify_on_release(parent)) if (notify_on_release(parent))
set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
...@@ -5291,10 +5394,18 @@ static int cgroup_destroy_locked(struct cgroup *cgrp) ...@@ -5291,10 +5394,18 @@ static int cgroup_destroy_locked(struct cgroup *cgrp)
if (parent && cgroup_is_threaded(cgrp)) if (parent && cgroup_is_threaded(cgrp))
parent->nr_threaded_children--; parent->nr_threaded_children--;
spin_lock_irq(&css_set_lock);
for (tcgrp = cgroup_parent(cgrp); tcgrp; tcgrp = cgroup_parent(tcgrp)) { for (tcgrp = cgroup_parent(cgrp); tcgrp; tcgrp = cgroup_parent(tcgrp)) {
tcgrp->nr_descendants--; tcgrp->nr_descendants--;
tcgrp->nr_dying_descendants++; tcgrp->nr_dying_descendants++;
/*
* If the dying cgroup is frozen, decrease frozen descendants
* counters of ancestor cgroups.
*/
if (test_bit(CGRP_FROZEN, &cgrp->flags))
tcgrp->freezer.nr_frozen_descendants--;
} }
spin_unlock_irq(&css_set_lock);
cgroup1_check_for_release(parent); cgroup1_check_for_release(parent);
...@@ -5746,6 +5857,26 @@ void cgroup_post_fork(struct task_struct *child) ...@@ -5746,6 +5857,26 @@ void cgroup_post_fork(struct task_struct *child)
cset->nr_tasks++; cset->nr_tasks++;
css_set_move_task(child, NULL, cset, false); css_set_move_task(child, NULL, cset, false);
} }
/*
* If the cgroup has to be frozen, the new task has too.
* Let's set the JOBCTL_TRAP_FREEZE jobctl bit to get
* the task into the frozen state.
*/
if (unlikely(cgroup_task_freeze(child))) {
spin_lock(&child->sighand->siglock);
WARN_ON_ONCE(child->frozen);
child->jobctl |= JOBCTL_TRAP_FREEZE;
spin_unlock(&child->sighand->siglock);
/*
* Calling cgroup_update_frozen() isn't required here,
* because it will be called anyway a bit later
* from do_freezer_trap(). So we avoid cgroup's
* transient switch from the frozen state and back.
*/
}
spin_unlock_irq(&css_set_lock); spin_unlock_irq(&css_set_lock);
} }
...@@ -5794,6 +5925,11 @@ void cgroup_exit(struct task_struct *tsk) ...@@ -5794,6 +5925,11 @@ void cgroup_exit(struct task_struct *tsk)
spin_lock_irq(&css_set_lock); spin_lock_irq(&css_set_lock);
css_set_move_task(tsk, cset, NULL, false); css_set_move_task(tsk, cset, NULL, false);
cset->nr_tasks--; cset->nr_tasks--;
WARN_ON_ONCE(cgroup_task_frozen(tsk));
if (unlikely(cgroup_task_freeze(tsk)))
cgroup_update_frozen(task_dfl_cgroup(tsk));
spin_unlock_irq(&css_set_lock); spin_unlock_irq(&css_set_lock);
} else { } else {
get_css_set(cset); get_css_set(cset);
......
...@@ -64,8 +64,8 @@ static int current_css_set_read(struct seq_file *seq, void *v) ...@@ -64,8 +64,8 @@ static int current_css_set_read(struct seq_file *seq, void *v)
css = cset->subsys[ss->id]; css = cset->subsys[ss->id];
if (!css) if (!css)
continue; continue;
seq_printf(seq, "%2d: %-4s\t- %lx[%d]\n", ss->id, ss->name, seq_printf(seq, "%2d: %-4s\t- %p[%d]\n", ss->id, ss->name,
(unsigned long)css, css->id); css, css->id);
} }
rcu_read_unlock(); rcu_read_unlock();
spin_unlock_irq(&css_set_lock); spin_unlock_irq(&css_set_lock);
...@@ -224,8 +224,8 @@ static int cgroup_subsys_states_read(struct seq_file *seq, void *v) ...@@ -224,8 +224,8 @@ static int cgroup_subsys_states_read(struct seq_file *seq, void *v)
if (css->parent) if (css->parent)
snprintf(pbuf, sizeof(pbuf) - 1, " P=%d", snprintf(pbuf, sizeof(pbuf) - 1, " P=%d",
css->parent->id); css->parent->id);
seq_printf(seq, "%2d: %-4s\t- %lx[%d] %d%s\n", ss->id, ss->name, seq_printf(seq, "%2d: %-4s\t- %p[%d] %d%s\n", ss->id, ss->name,
(unsigned long)css, css->id, css, css->id,
atomic_read(&css->online_cnt), pbuf); atomic_read(&css->online_cnt), pbuf);
} }
......
This diff is collapsed.
This diff is collapsed.
...@@ -1225,7 +1225,9 @@ static int wait_for_vfork_done(struct task_struct *child, ...@@ -1225,7 +1225,9 @@ static int wait_for_vfork_done(struct task_struct *child,
int killed; int killed;
freezer_do_not_count(); freezer_do_not_count();
cgroup_enter_frozen();
killed = wait_for_completion_killable(vfork); killed = wait_for_completion_killable(vfork);
cgroup_leave_frozen(false);
freezer_count(); freezer_count();
if (killed) { if (killed) {
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/posix-timers.h> #include <linux/posix-timers.h>
#include <linux/livepatch.h> #include <linux/livepatch.h>
#include <linux/cgroup.h>
#define CREATE_TRACE_POINTS #define CREATE_TRACE_POINTS
#include <trace/events/signal.h> #include <trace/events/signal.h>
...@@ -146,9 +147,10 @@ static inline bool has_pending_signals(sigset_t *signal, sigset_t *blocked) ...@@ -146,9 +147,10 @@ static inline bool has_pending_signals(sigset_t *signal, sigset_t *blocked)
static bool recalc_sigpending_tsk(struct task_struct *t) static bool recalc_sigpending_tsk(struct task_struct *t)
{ {
if ((t->jobctl & JOBCTL_PENDING_MASK) || if ((t->jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE)) ||
PENDING(&t->pending, &t->blocked) || PENDING(&t->pending, &t->blocked) ||
PENDING(&t->signal->shared_pending, &t->blocked)) { PENDING(&t->signal->shared_pending, &t->blocked) ||
cgroup_task_frozen(t)) {
set_tsk_thread_flag(t, TIF_SIGPENDING); set_tsk_thread_flag(t, TIF_SIGPENDING);
return true; return true;
} }
...@@ -2108,6 +2110,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t ...@@ -2108,6 +2110,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
preempt_disable(); preempt_disable();
read_unlock(&tasklist_lock); read_unlock(&tasklist_lock);
preempt_enable_no_resched(); preempt_enable_no_resched();
cgroup_enter_frozen();
freezable_schedule(); freezable_schedule();
} else { } else {
/* /*
...@@ -2286,6 +2289,7 @@ static bool do_signal_stop(int signr) ...@@ -2286,6 +2289,7 @@ static bool do_signal_stop(int signr)
} }
/* Now we don't run again until woken by SIGCONT or SIGKILL */ /* Now we don't run again until woken by SIGCONT or SIGKILL */
cgroup_enter_frozen();
freezable_schedule(); freezable_schedule();
return true; return true;
} else { } else {
...@@ -2332,6 +2336,43 @@ static void do_jobctl_trap(void) ...@@ -2332,6 +2336,43 @@ static void do_jobctl_trap(void)
} }
} }
/**
* do_freezer_trap - handle the freezer jobctl trap
*
* Puts the task into frozen state, if only the task is not about to quit.
* In this case it drops JOBCTL_TRAP_FREEZE.
*
* CONTEXT:
* Must be called with @current->sighand->siglock held,
* which is always released before returning.
*/
static void do_freezer_trap(void)
__releases(&current->sighand->siglock)
{
/*
* If there are other trap bits pending except JOBCTL_TRAP_FREEZE,
* let's make another loop to give it a chance to be handled.
* In any case, we'll return back.
*/
if ((current->jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE)) !=
JOBCTL_TRAP_FREEZE) {
spin_unlock_irq(&current->sighand->siglock);
return;
}
/*
* Now we're sure that there is no pending fatal signal and no
* pending traps. Clear TIF_SIGPENDING to not get out of schedule()
* immediately (if there is a non-fatal signal pending), and
* put the task into sleep.
*/
__set_current_state(TASK_INTERRUPTIBLE);
clear_thread_flag(TIF_SIGPENDING);
spin_unlock_irq(&current->sighand->siglock);
cgroup_enter_frozen();
freezable_schedule();
}
static int ptrace_signal(int signr, kernel_siginfo_t *info) static int ptrace_signal(int signr, kernel_siginfo_t *info)
{ {
/* /*
...@@ -2452,9 +2493,24 @@ bool get_signal(struct ksignal *ksig) ...@@ -2452,9 +2493,24 @@ bool get_signal(struct ksignal *ksig)
do_signal_stop(0)) do_signal_stop(0))
goto relock; goto relock;
if (unlikely(current->jobctl & JOBCTL_TRAP_MASK)) { if (unlikely(current->jobctl &
do_jobctl_trap(); (JOBCTL_TRAP_MASK | JOBCTL_TRAP_FREEZE))) {
if (current->jobctl & JOBCTL_TRAP_MASK) {
do_jobctl_trap();
spin_unlock_irq(&sighand->siglock);
} else if (current->jobctl & JOBCTL_TRAP_FREEZE)
do_freezer_trap();
goto relock;
}
/*
* If the task is leaving the frozen state, let's update
* cgroup counters and reset the frozen bit.
*/
if (unlikely(cgroup_task_frozen(current))) {
spin_unlock_irq(&sighand->siglock); spin_unlock_irq(&sighand->siglock);
cgroup_leave_frozen(false);
goto relock; goto relock;
} }
...@@ -2550,6 +2606,8 @@ bool get_signal(struct ksignal *ksig) ...@@ -2550,6 +2606,8 @@ bool get_signal(struct ksignal *ksig)
fatal: fatal:
spin_unlock_irq(&sighand->siglock); spin_unlock_irq(&sighand->siglock);
if (unlikely(cgroup_task_frozen(current)))
cgroup_leave_frozen(true);
/* /*
* Anything else is fatal, maybe with a core dump. * Anything else is fatal, maybe with a core dump.
......
test_memcontrol test_memcontrol
test_core test_core
test_freezer
...@@ -5,8 +5,10 @@ all: ...@@ -5,8 +5,10 @@ all:
TEST_GEN_PROGS = test_memcontrol TEST_GEN_PROGS = test_memcontrol
TEST_GEN_PROGS += test_core TEST_GEN_PROGS += test_core
TEST_GEN_PROGS += test_freezer
include ../lib.mk include ../lib.mk
$(OUTPUT)/test_memcontrol: cgroup_util.c $(OUTPUT)/test_memcontrol: cgroup_util.c
$(OUTPUT)/test_core: cgroup_util.c $(OUTPUT)/test_core: cgroup_util.c
$(OUTPUT)/test_freezer: cgroup_util.c
...@@ -74,6 +74,16 @@ char *cg_name_indexed(const char *root, const char *name, int index) ...@@ -74,6 +74,16 @@ char *cg_name_indexed(const char *root, const char *name, int index)
return ret; return ret;
} }
char *cg_control(const char *cgroup, const char *control)
{
size_t len = strlen(cgroup) + strlen(control) + 2;
char *ret = malloc(len);
snprintf(ret, len, "%s/%s", cgroup, control);
return ret;
}
int cg_read(const char *cgroup, const char *control, char *buf, size_t len) int cg_read(const char *cgroup, const char *control, char *buf, size_t len)
{ {
char path[PATH_MAX]; char path[PATH_MAX];
...@@ -196,7 +206,32 @@ int cg_create(const char *cgroup) ...@@ -196,7 +206,32 @@ int cg_create(const char *cgroup)
return mkdir(cgroup, 0644); return mkdir(cgroup, 0644);
} }
static int cg_killall(const char *cgroup) int cg_wait_for_proc_count(const char *cgroup, int count)
{
char buf[10 * PAGE_SIZE] = {0};
int attempts;
char *ptr;
for (attempts = 10; attempts >= 0; attempts--) {
int nr = 0;
if (cg_read(cgroup, "cgroup.procs", buf, sizeof(buf)))
break;
for (ptr = buf; *ptr; ptr++)
if (*ptr == '\n')
nr++;
if (nr >= count)
return 0;
usleep(100000);
}
return -1;
}
int cg_killall(const char *cgroup)
{ {
char buf[PAGE_SIZE]; char buf[PAGE_SIZE];
char *ptr = buf; char *ptr = buf;
...@@ -227,9 +262,7 @@ int cg_destroy(const char *cgroup) ...@@ -227,9 +262,7 @@ int cg_destroy(const char *cgroup)
retry: retry:
ret = rmdir(cgroup); ret = rmdir(cgroup);
if (ret && errno == EBUSY) { if (ret && errno == EBUSY) {
ret = cg_killall(cgroup); cg_killall(cgroup);
if (ret)
return ret;
usleep(100); usleep(100);
goto retry; goto retry;
} }
...@@ -240,6 +273,14 @@ int cg_destroy(const char *cgroup) ...@@ -240,6 +273,14 @@ int cg_destroy(const char *cgroup)
return ret; return ret;
} }
int cg_enter(const char *cgroup, int pid)
{
char pidbuf[64];
snprintf(pidbuf, sizeof(pidbuf), "%d", pid);
return cg_write(cgroup, "cgroup.procs", pidbuf);
}
int cg_enter_current(const char *cgroup) int cg_enter_current(const char *cgroup)
{ {
char pidbuf[64]; char pidbuf[64];
...@@ -369,3 +410,12 @@ int set_oom_adj_score(int pid, int score) ...@@ -369,3 +410,12 @@ int set_oom_adj_score(int pid, int score)
close(fd); close(fd);
return 0; return 0;
} }
char proc_read_text(int pid, const char *item, char *buf, size_t size)
{
char path[PATH_MAX];
snprintf(path, sizeof(path), "/proc/%d/%s", pid, item);
return read_text(path, buf, size);
}
...@@ -18,6 +18,7 @@ static inline int values_close(long a, long b, int err) ...@@ -18,6 +18,7 @@ static inline int values_close(long a, long b, int err)
extern int cg_find_unified_root(char *root, size_t len); extern int cg_find_unified_root(char *root, size_t len);
extern char *cg_name(const char *root, const char *name); extern char *cg_name(const char *root, const char *name);
extern char *cg_name_indexed(const char *root, const char *name, int index); extern char *cg_name_indexed(const char *root, const char *name, int index);
extern char *cg_control(const char *cgroup, const char *control);
extern int cg_create(const char *cgroup); extern int cg_create(const char *cgroup);
extern int cg_destroy(const char *cgroup); extern int cg_destroy(const char *cgroup);
extern int cg_read(const char *cgroup, const char *control, extern int cg_read(const char *cgroup, const char *control,
...@@ -32,6 +33,7 @@ extern int cg_write(const char *cgroup, const char *control, char *buf); ...@@ -32,6 +33,7 @@ extern int cg_write(const char *cgroup, const char *control, char *buf);
extern int cg_run(const char *cgroup, extern int cg_run(const char *cgroup,
int (*fn)(const char *cgroup, void *arg), int (*fn)(const char *cgroup, void *arg),
void *arg); void *arg);
extern int cg_enter(const char *cgroup, int pid);
extern int cg_enter_current(const char *cgroup); extern int cg_enter_current(const char *cgroup);
extern int cg_run_nowait(const char *cgroup, extern int cg_run_nowait(const char *cgroup,
int (*fn)(const char *cgroup, void *arg), int (*fn)(const char *cgroup, void *arg),
...@@ -41,3 +43,6 @@ extern int alloc_pagecache(int fd, size_t size); ...@@ -41,3 +43,6 @@ extern int alloc_pagecache(int fd, size_t size);
extern int alloc_anon(const char *cgroup, void *arg); extern int alloc_anon(const char *cgroup, void *arg);
extern int is_swap_enabled(void); extern int is_swap_enabled(void);
extern int set_oom_adj_score(int pid, int score); extern int set_oom_adj_score(int pid, int score);
extern int cg_wait_for_proc_count(const char *cgroup, int count);
extern int cg_killall(const char *cgroup);
extern char proc_read_text(int pid, const char *item, char *buf, size_t size);
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment