Skip to contentSkip to conversation

freezer for cgroup v2

PATCH Superseded Roman Gushchin 3 participants 16 messages Dec 7, 2018 – Dec 20, 2018

Subsystems (5)

v5 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v5 cover freezer for cgroup v2

This patchset implements freezer for cgroup v2.

It provides similar functionality as v1 freezer, but the interface conforms to the cgroup v2 interface design principles, and it provides a better user experience: tasks can be killed, ptrace works, there is no separate controller, which has to be enabled, etc.

Patches (1), (2) and (3) are some preparational work, patch (4) contains the implementation, patch (5) is a small cgroup kselftest fix, patch (6) covers freezer adds 6 new kselftests to cover the freezer functionality. Patch (7) adds corresponding docs.

v5->v4:
- rewored cgroup state transition code (suggested by Tejun Heo)
- look at JOBCTL_TRAP_FREEZE instead of task->frozen in recalc_sigpending(), check for task->frozen and JOBCTL_TRAP_FREEZE in signal_pending_state() (suggested by Oleg Nesterov)
- some cosmetic changes in signal.c (suggested by Oleg Nesterov)
- cleaned up comments

v4->v3:
- reading nr_descendants doesn't require taking css_set_lock anymore
- fixed docs based on Mike Rapoport's feedback
- fixed double irq lock found by Dan Carpenter

v3->v2:
- dropped TASK_FROZEN for now, frozen tasks are put into TASK_INTERRUPTIBLE state; it's probably not the final version, but the API question can be discussed separately
- don't clear TIF_SIGPENDING before going to sleep, instead add task->frozen check in signal_pending_state() and recalc_sigpending()
- cgroup-level counter are now synchronized using css_set_lock, which simplified the whole code (e.g. per-cgroup works were removed)
- the amount of comments increased significantly
- many other improvements incorporating feedback from Tejun and Oleg

v2->v1:
- fixed locking aroung calling cgroup_freezer_leave()
- added docs

Roman Gushchin (7):
cgroup: rename freezer.c into legacy_freezer.c
cgroup: implement __cgroup_task_count() helper
cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
cgroup: cgroup v2 freezer
kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()
kselftests: cgroup: add freezer controller self-tests
cgroup: document cgroup v2 freezer interface

 Documentation/admin-guide/cgroup-v2.rst       |  27 +
 include/linux/cgroup-defs.h                   |  33 +
 include/linux/cgroup.h                        |  42 ++
 include/linux/sched.h                         |   2 +
 include/linux/sched/jobctl.h                  |   2 +
 include/linux/sched/signal.h                  |   3 +
 kernel/cgroup/Makefile                        |   4 +-
 kernel/cgroup/cgroup-internal.h               |   1 +
 kernel/cgroup/cgroup-v1.c                     |  16 -
 kernel/cgroup/cgroup.c                        | 145 +++-
 kernel/cgroup/freezer.c                       | 634 ++++++----------
 kernel/cgroup/legacy_freezer.c                | 481 ++++++++++++
 kernel/signal.c                               |  55 +-
 tools/testing/selftests/cgroup/.gitignore     |   1 +
 tools/testing/selftests/cgroup/Makefile       |   2 +
 tools/testing/selftests/cgroup/cgroup_util.c  |  85 ++-
 tools/testing/selftests/cgroup/cgroup_util.h  |   7 +
 tools/testing/selftests/cgroup/test_freezer.c | 685 ++++++++++++++++++
 18 files changed, 1794 insertions(+), 431 deletions(-)
 create mode 100644 kernel/cgroup/legacy_freezer.c
 create mode 100644 tools/testing/selftests/cgroup/test_freezer.c
Signature · 1 line
2.17.2
#1 lore raw

PATCH v5 1/7 cgroup: rename freezer.c into legacy_freezer.c

Freezer.c will contain an implementation of cgroup v2 freezer, so let's rename the v1 freezer to avoid naming conflicts.

---
 kernel/cgroup/Makefile                        | 2 +-
 kernel/cgroup/{freezer.c => legacy_freezer.c} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename kernel/cgroup/{freezer.c => legacy_freezer.c} (100%)
kernel/cgroup/Makefile +1 -1
index bfcdae896122..8d5689ca94b9 100644
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o
 
-obj-$(CONFIG_CGROUP_FREEZER) += 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
kernel/cgroup/legacy_freezer.c renamed from kernel/cgroup/freezer.c +0 -0
similarity index 100%
rename from kernel/cgroup/freezer.c
rename to kernel/cgroup/legacy_freezer.c
Signature · 1 line
2.17.2
#2 lore raw

PATCH v5 2/7 cgroup: implement __cgroup_task_count() helper

In reply to Roman Gushchin

The helper is identical to the existing cgroup_task_count() except it doesn't take the css_set_lock by itself, assuming that the caller does.

Also, move cgroup_task_count() implementation into kernel/cgroup/cgroup.c, as there is nothing specific to cgroup v1.

---
 kernel/cgroup/cgroup-internal.h |  1 +
 kernel/cgroup/cgroup-v1.c       | 16 ----------------
 kernel/cgroup/cgroup.c          | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 34 insertions(+), 16 deletions(-)
kernel/cgroup/cgroup-internal.h +1 -0
index c950864016e2..a195328431ce 100644
@@ -226,6 +226,7 @@ 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);
 
 /*
kernel/cgroup/cgroup-v1.c +0 -16
index 51063e7a93c2..6134fef07d57 100644
@@ -336,22 +336,6 @@ static struct cgroup_pidlist *cgroup_pidlist_find_create(struct cgroup *cgrp,
 	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
  */
kernel/cgroup/cgroup.c +33 -0
index e06994fd4e34..7519a4307021 100644
@@ -563,6 +563,39 @@ static void cgroup_get_live(struct cgroup *cgrp)
 	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 *cgrp = of->kn->parent->priv;
Signature · 1 line
2.17.2
#3 lore raw

PATCH v5 3/7 cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock

In reply to Roman Gushchin

The number of descendant cgroups and the number of dying descendant cgroups are currently synchronized using the cgroup_mutex.

The number of descendant cgroups will be required by the cgroup v2 freezer, which will use it to determine if a cgroup is frozen (depending on total number of descendants and number of frozen descendants). It's not always acceptable to grab the cgroup_mutex, especially from quite hot paths (e.g. exit()).

To avoid this, let's additionally synchronize these counters using the css_set_lock.

So, it's safe to read these counters with either cgroup_mutex or css_set_lock locked, and for changing both locks should be acquired.

---
 include/linux/cgroup-defs.h | 5 +++++
 kernel/cgroup/cgroup.c      | 6 ++++++
 2 files changed, 11 insertions(+)
include/linux/cgroup-defs.h +5 -0
index 8fcbae1b8db0..03355d7008ff 100644
@@ -348,6 +348,11 @@ struct cgroup {
 	 * Dying cgroups are cgroups which were deleted by a user,
 	 * but are still existing because someone else is holding a reference.
 	 * 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_dying_descendants;
kernel/cgroup/cgroup.c +6 -0
index 7519a4307021..f89dde50f693 100644
@@ -4723,9 +4723,11 @@ static void css_release_work_fn(struct work_struct *work)
 		if (cgroup_on_dfl(cgrp))
 			cgroup_rstat_flush(cgrp);
 
+		spin_lock_irq(&css_set_lock);
 		for (tcgrp = cgroup_parent(cgrp); tcgrp;
 		     tcgrp = cgroup_parent(tcgrp))
 			tcgrp->nr_dying_descendants--;
+		spin_unlock_irq(&css_set_lock);
 
 		cgroup_idr_remove(&cgrp->root->cgroup_idr, cgrp->id);
 		cgrp->id = -1;
@@ -4943,12 +4945,14 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
 	if (ret)
 		goto out_psi_free;
 
+	spin_lock_irq(&css_set_lock);
 	for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp)) {
 		cgrp->ancestor_ids[tcgrp->level] = tcgrp->id;
 
 		if (tcgrp != cgrp)
 			tcgrp->nr_descendants++;
 	}
+	spin_unlock_irq(&css_set_lock);
 
 	if (notify_on_release(parent))
 		set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
@@ -5233,10 +5237,12 @@ static int cgroup_destroy_locked(struct cgroup *cgrp)
 	if (parent && cgroup_is_threaded(cgrp))
 		parent->nr_threaded_children--;
 
+	spin_lock_irq(&css_set_lock);
 	for (tcgrp = cgroup_parent(cgrp); tcgrp; tcgrp = cgroup_parent(tcgrp)) {
 		tcgrp->nr_descendants--;
 		tcgrp->nr_dying_descendants++;
 	}
+	spin_unlock_irq(&css_set_lock);
 
 	cgroup1_check_for_release(parent);
 
Signature · 1 line
2.17.2
#4 lore raw

PATCH v5 4/7 cgroup: cgroup v2 freezer

In reply to Roman Gushchin

Cgroup v1 implements the freezer controller, which provides an ability to stop the workload in a cgroup and temporarily free up some resources (cpu, io, network bandwidth and, potentially, memory) for some other tasks. Cgroup v2 lacks this functionality.

This patch implements freezer for cgroup v2.

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach. It's also possible to migrate a frozen tasks to another cgroup.

This differs cgroup v2 freezer from cgroup v1 freezer, which mostly tried to imitate the system-wide freezer. However uninterruptible sleep is fine when all tasks are going to be frozen (hibernation case), it's not the acceptable state for some subset of the system.

Cgroup v2 freezer is not supporting freezing kthreads.
If a non-root cgroup contains kthread, the cgroup still can be frozen, but the kthread will remain running, the cgroup will be shown as non-frozen, and the notification will not be delivered.

* PTRACE_ATTACH is not working because non-fatal signal delivery is blocked in frozen state.

There are some interface differences between cgroup v1 and cgroup v2 freezer too, which are required to conform the cgroup v2 interface design principles:
1) There is no separate controller, which has to be turned on:
the functionality is always available and is represented by cgroup.freeze and cgroup.events cgroup control files.
2) The desired state is defined by the cgroup.freeze control file. Any hierarchical configuration is allowed.
3) The interface is asynchronous. The actual state is available using cgroup.events control file ("frozen" field). There are no dedicated transitional states.
4) It's allowed to make any changes with the cgroup hierarchy (create new cgroups, remove old cgroups, move tasks between cgroups) no matter if some cgroups are frozen.

---
 include/linux/cgroup-defs.h  |  28 ++++
 include/linux/cgroup.h       |  42 +++++
 include/linux/sched.h        |   2 +
 include/linux/sched/jobctl.h |   2 +
 include/linux/sched/signal.h |   3 +
 kernel/cgroup/Makefile       |   2 +-
 kernel/cgroup/cgroup.c       | 106 +++++++++++-
 kernel/cgroup/freezer.c      | 313 +++++++++++++++++++++++++++++++++++
 kernel/signal.c              |  55 +++++-
 9 files changed, 544 insertions(+), 9 deletions(-)
 create mode 100644 kernel/cgroup/freezer.c
include/linux/cgroup-defs.h +28 -0
index 03355d7008ff..e2e3e2f4c692 100644
@@ -64,6 +64,12 @@ enum {
 	 * specified at mount time and thus is implemented here.
 	 */
 	CGRP_CPUSET_CLONE_CHILDREN,
+
+	/* Control group has to be frozen. */
+	CGRP_FREEZE,
+
+	/* Cgroup is frozen. */
+	CGRP_FROZEN,
 };
 
 /* cgroup_root->flags */
@@ -316,6 +322,25 @@ struct cgroup_rstat_cpu {
 	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 to freeze */
+	int nr_tasks_to_freeze;
+
+	/* Number of frozen tasks */
+	int nr_frozen_tasks;
+};
+
 struct cgroup {
 	/* self css with NULL ->ss, points back to this cgroup */
 	struct cgroup_subsys_state self;
@@ -452,6 +477,9 @@ struct cgroup {
 	/* If there is block congestion on this cgroup. */
 	atomic_t congestion_count;
 
+	/* Used to store internal freezer state */
+	struct cgroup_freezer_state freezer;
+
 	/* ids of the ancestors at each level including self */
 	int ancestor_ids[];
 };
include/linux/cgroup.h +42 -0

46 lines not on this page · raw message

include/linux/sched.h +2 -0
index a51c13c2b1a0..6e82c88998f5 100644
@@ -731,6 +731,8 @@ struct task_struct {
 #ifdef CONFIG_CGROUPS
 	/* disallow userland-initiated cgroup migration */
 	unsigned			no_cgroup_migration:1;
+	/* task is frozen by the cgroup freezer */
+	unsigned			frozen:1;
 #endif
 #ifdef CONFIG_BLK_CGROUP
 	/* to be used once the psi infrastructure lands upstream. */
include/linux/sched/jobctl.h +2 -0
index 98228bd48aee..fa067de9f1a9 100644
@@ -18,6 +18,7 @@ struct task_struct;
 #define JOBCTL_TRAP_NOTIFY_BIT	20	/* trap for NOTIFY */
 #define JOBCTL_TRAPPING_BIT	21	/* switching to TRACED */
 #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_PENDING	(1UL << JOBCTL_STOP_PENDING_BIT)
@@ -26,6 +27,7 @@ struct task_struct;
 #define JOBCTL_TRAP_NOTIFY	(1UL << JOBCTL_TRAP_NOTIFY_BIT)
 #define JOBCTL_TRAPPING		(1UL << JOBCTL_TRAPPING_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_PENDING_MASK	(JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
include/linux/sched/signal.h +3 -0
index 13789d10a50e..7b7dfe3c6a6a 100644
@@ -8,6 +8,7 @@
 #include <linux/sched/jobctl.h>
 #include <linux/sched/task.h>
 #include <linux/cred.h>
+#include <linux/cgroup.h>
 
 /*
  * Types defining task->signal and task->sighand and APIs using them:
@@ -368,6 +369,8 @@ static inline int signal_pending_state(long state, struct task_struct *p)
 		return 0;
 	if (!signal_pending(p))
 		return 0;
+	if (unlikely(cgroup_task_frozen(p) && p->jobctl == JOBCTL_TRAP_FREEZE))
+		return __fatal_signal_pending(p);
 
 	return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
 }
kernel/cgroup/Makefile +1 -1
index 8d5689ca94b9..5d7a76bfbbb7 100644
@@ -1,5 +1,5 @@
 # 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) += legacy_freezer.o
 obj-$(CONFIG_CGROUP_PIDS) += pids.o
kernel/cgroup/cgroup.c +103 -3

159 lines not on this page · raw message

kernel/cgroup/freezer.c new file +313 -0

313 lines not on this page · raw message

kernel/signal.c +50 -5

94 lines not on this page · raw message

Signature · 1 line
2.17.2
#5 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 5 lines · Roman Gushchin

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach.

I fail to understand how this all supposed to work.

include/linux/sched/signal.h static inline int signal_pending_state(long state, struct task_struct *p) · line 373

Quoted 6 lines
include/linux/sched/signal.h +2 -0
@@ -368,6 +369,8 @@ static inline int signal_pending_state(long state, struct task_struct *p)
 		return 0;
 	if (!signal_pending(p))
 		return 0;
+	if (unlikely(cgroup_task_frozen(p) && p->jobctl == JOBCTL_TRAP_FREEZE))
+		return __fatal_signal_pending(p);

I think I will never agree with this change ;) and I don't think it actually helps.

kernel/cgroup/freezer.c line 146

Quoted 11 lines
kernel/cgroup/freezer.c new file +11 -0
+void cgroup_enter_frozen(void)
+{
+	if (!current->frozen) {
+		spin_lock_irq(&css_set_lock);
+		current->frozen = true;
+		cgroup_inc_frozen_cnt(task_dfl_cgroup(current), false, true);
+		spin_unlock_irq(&css_set_lock);
+	}
+
+	__set_current_state(TASK_INTERRUPTIBLE);
+	schedule();

So once again, suppose it races with PTRACE_INTERRUPT, or SIGSTOP, or something else which should be handled by get_signal() before do_freezer_trap().

If (say) PTRACE_INTERRUPT comes before schedule it will be lost. Otherwise the frozen task will react. This can't be right. Or I am totally confused.

Perhaps you can split this patch? start with cgroup_enter_frozen() using TASK_KILLABLE, then teach it to handle ptrace/stop/etc? I think this way it would be simpler to discuss the necessary changes and document what exactly are you trying to do.

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

Oleg.

#6 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 9 lines · Oleg Nesterov
Quoted 5 lines · Roman Gushchin

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach.

I fail to understand how this all supposed to work.

Quoted 8 lines

Review static inline int signal_pending_state(long state, struct task_struct *p) · line 373

Quoted 6 lines
(quoted diff) +2 -0
@@ -368,6 +369,8 @@ static inline int signal_pending_state(long state, struct task_struct *p)
 		return 0;
 	if (!signal_pending(p))
 		return 0;
+	if (unlikely(cgroup_task_frozen(p) && p->jobctl == JOBCTL_TRAP_FREEZE))
+		return __fatal_signal_pending(p);

I think I will never agree with this change ;) and I don't think it actually helps.

See below.

Quoted 16 lines

Review static inline int signal_pending_state(long state, struct task_struct *p)

Quoted 12 lines
(quoted diff) +11 -0
+void cgroup_enter_frozen(void)
+{
+	if (!current->frozen) {
+		spin_lock_irq(&css_set_lock);
+		current->frozen = true;
+		cgroup_inc_frozen_cnt(task_dfl_cgroup(current), false, true);
+		spin_unlock_irq(&css_set_lock);
+	}
+
+	__set_current_state(TASK_INTERRUPTIBLE);
+	schedule();

So once again, suppose it races with PTRACE_INTERRUPT, or SIGSTOP, or something else which should be handled by get_signal() before do_freezer_trap().

Quoted 2 lines

If (say) PTRACE_INTERRUPT comes before schedule it will be lost. Otherwise the frozen task will react. This can't be right. Or I am totally confused.

Why?
PTRACE_INTERRUPT will set JOBCTL_TRAP_STOP, so signal_pending_state() will return true, schedule() will return immediately, and we'll handle the trap.

Quoted 8 lines

Perhaps you can split this patch? start with cgroup_enter_frozen() using TASK_KILLABLE, then teach it to handle ptrace/stop/etc? I think this way it would be simpler to discuss the necessary changes and document what exactly are you trying to do.

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

I have to think a bit more here, but something like this will probably work:

kernel/freezer.c +1 -1
index b162b74611e4..590ac4d10b02 100644
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }

--

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

Thanks!

#7 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 38 lines · Roman Gushchin
Quoted 9 lines · Oleg Nesterov
Quoted 5 lines · Roman Gushchin

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach.

I fail to understand how this all supposed to work.

Quoted 8 lines

Review static inline int signal_pending_state(long state, struct task_struct *p) · line 373

Quoted 6 lines
(quoted diff) +2 -0
@@ -368,6 +369,8 @@ static inline int signal_pending_state(long state, struct task_struct *p)
 		return 0;
 	if (!signal_pending(p))
 		return 0;
+	if (unlikely(cgroup_task_frozen(p) && p->jobctl == JOBCTL_TRAP_FREEZE))
+		return __fatal_signal_pending(p);

I think I will never agree with this change ;) and I don't think it actually helps.

See below.

Quoted 16 lines

Review static inline int signal_pending_state(long state, struct task_struct *p)

Quoted 12 lines
(quoted diff) +11 -0
+void cgroup_enter_frozen(void)
+{
+	if (!current->frozen) {
+		spin_lock_irq(&css_set_lock);
+		current->frozen = true;
+		cgroup_inc_frozen_cnt(task_dfl_cgroup(current), false, true);
+		spin_unlock_irq(&css_set_lock);
+	}
+
+	__set_current_state(TASK_INTERRUPTIBLE);
+	schedule();

So once again, suppose it races with PTRACE_INTERRUPT, or SIGSTOP, or something else which should be handled by get_signal() before do_freezer_trap().

Quoted 6 lines
Quoted 2 lines

If (say) PTRACE_INTERRUPT comes before schedule it will be lost. Otherwise the frozen task will react. This can't be right. Or I am totally confused.

Why?
PTRACE_INTERRUPT will set JOBCTL_TRAP_STOP, so signal_pending_state() will return true, schedule() will return immediately, and we'll handle the trap.

OK, I misread the JOBCTL_TRAP_FREEZE check as "jobctl & JOBCTL_TRAP_FREEZE".

But p->jobctl == JOBCTL_TRAP_FREEZE doesn't look right too. For example, JOBCTL_STOP_DEQUEUED can be set. You probably need something like

	jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE) == JOBCTL_TRAP_FREEZE

And you need a barrier in between, iow you need set_current_state(TASK_INTERRUPTIBLE).

But this doesn't really matter. I don't think you need to modify signal_pending_state() and penalize schedule(). You can do something like

	spin_lock_irq(sigllock);
	if (jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE) == JOBCTL_TRAP_FREEZE &&
	    !__fatal_signal_pending())
	{
		__set_current_state(TASK_INTERRUPTIBLE);
		clear_thread_flag(TIF_SIGPENDING);
	}
	spin_unlock_irq(siglock);

	schedule();
	// recalc_sigpending() is not needed

in cgroup_enter_frozen() with the same effect. Which looks equally ugly and suboptimal, but at least this doesn't touch the sched code.

Quoted 21 lines
Quoted 2 lines

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

I have to think a bit more here, but something like this will probably work:

kernel/freezer.c +1 -2
index b162b74611e4..590ac4d10b02 100644
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

And what about TASK_STOPPED/TASK_TRACED tasks? They can not be frozen or thawed, right? This doesn't look good, and this differs from the current freezer controller...

Oleg.

#8 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 70 lines · Oleg Nesterov
Quoted 38 lines · Roman Gushchin
Quoted 9 lines · Oleg Nesterov
Quoted 5 lines · Roman Gushchin

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach.

I fail to understand how this all supposed to work.

Quoted 8 lines

Review static inline int signal_pending_state(long state, struct task_struct *p) · line 373

Quoted 6 lines
(quoted diff) +2 -0
@@ -368,6 +369,8 @@ static inline int signal_pending_state(long state, struct task_struct *p)
 		return 0;
 	if (!signal_pending(p))
 		return 0;
+	if (unlikely(cgroup_task_frozen(p) && p->jobctl == JOBCTL_TRAP_FREEZE))
+		return __fatal_signal_pending(p);

I think I will never agree with this change ;) and I don't think it actually helps.

See below.

Quoted 16 lines

Review static inline int signal_pending_state(long state, struct task_struct *p)

Quoted 12 lines
(quoted diff) +11 -0
+void cgroup_enter_frozen(void)
+{
+	if (!current->frozen) {
+		spin_lock_irq(&css_set_lock);
+		current->frozen = true;
+		cgroup_inc_frozen_cnt(task_dfl_cgroup(current), false, true);
+		spin_unlock_irq(&css_set_lock);
+	}
+
+	__set_current_state(TASK_INTERRUPTIBLE);
+	schedule();

So once again, suppose it races with PTRACE_INTERRUPT, or SIGSTOP, or something else which should be handled by get_signal() before do_freezer_trap().

Quoted 6 lines
Quoted 2 lines

If (say) PTRACE_INTERRUPT comes before schedule it will be lost. Otherwise the frozen task will react. This can't be right. Or I am totally confused.

Why?
PTRACE_INTERRUPT will set JOBCTL_TRAP_STOP, so signal_pending_state() will return true, schedule() will return immediately, and we'll handle the trap.

OK, I misread the JOBCTL_TRAP_FREEZE check as "jobctl & JOBCTL_TRAP_FREEZE".

But p->jobctl == JOBCTL_TRAP_FREEZE doesn't look right too. For example, JOBCTL_STOP_DEQUEUED can be set. You probably need something like

	jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE) == JOBCTL_TRAP_FREEZE

And you need a barrier in between, iow you need set_current_state(TASK_INTERRUPTIBLE).

But this doesn't really matter. I don't think you need to modify signal_pending_state() and penalize schedule(). You can do something like

	spin_lock_irq(sigllock);
	if (jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE) == JOBCTL_TRAP_FREEZE &&
	    !__fatal_signal_pending())
	{
		__set_current_state(TASK_INTERRUPTIBLE);
		clear_thread_flag(TIF_SIGPENDING);
	}
	spin_unlock_irq(siglock);

	schedule();
	// recalc_sigpending() is not needed
Quoted 2 lines

in cgroup_enter_frozen() with the same effect. Which looks equally ugly and suboptimal, but at least this doesn't touch the sched code.

Gotcha. Will follow this approach in v6.

Quoted 29 lines
Quoted 22 lines
Quoted 3 lines

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

I have to think a bit more here, but something like this will probably work:

kernel/freezer.c +1 -2
index b162b74611e4..590ac4d10b02 100644
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

Quoted 2 lines

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

But this is what we do with stopped and ptraced tasks, isn't it? We do use freezable_schedule() and the system freezer just ignores such tasks. I believe that cgroup v2 freezer should follow the same path.

Quoted 4 lines

And what about TASK_STOPPED/TASK_TRACED tasks? They can not be frozen or thawed, right? This doesn't look good, and this differs from the current freezer controller...

Good question!

It looks like cgroup v1 freezer just ignores them treating as already frozen, which doesn't look nice.

I'd say s/signal_wake_up(task, 0)/signal_wake_up(task, 1) in cgroup_freeze_task() will do the job of moving them into the frozen state. The question is how to get them back into the stopped state, if cgroup is unfrozen. At this point there are no more signs, that the task has been previously frozen. I've no better idea, than to introduce another per-task bit/flag. If you have any better ideas, please, share.

Thank you for the review!

#9 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 30 lines · Roman Gushchin
Quoted 28 lines · Oleg Nesterov
Quoted 21 lines
Quoted 2 lines

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

I have to think a bit more here, but something like this will probably work:

kernel/freezer.c +1 -2
index b162b74611e4..590ac4d10b02 100644
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

Quoted 4 lines
Quoted 2 lines

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

But this is what we do with stopped and ptraced tasks, isn't it?

No,

Quoted 1 line

We do use freezable_schedule() and the system freezer just ignores such tasks.

	static inline void freezable_schedule(void)
	{
		freezer_do_not_count();
		schedule();
		freezer_count();
	}

and note that freezer_count() calls try_to_freeze().

IOW, the task sleeping in freezable_schedule() doesn't really differ from the task sleeping in __refrigerator(). It is not that "the system freezer just ignores such tasks", it ignores them because it can safely count them as frozen.

Quoted 8 lines
Quoted 3 lines

And what about TASK_STOPPED/TASK_TRACED tasks? They can not be frozen or thawed, right? This doesn't look good, and this differs from the current freezer controller...

Good question!

It looks like cgroup v1 freezer just ignores them treating as already frozen, which doesn't look nice.

Not sure I understand you, but see above... cgroup v1 freezer looks fine wrt stopped/traced tasks.

Oleg.

#10 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 47 lines · Oleg Nesterov
Quoted 30 lines · Roman Gushchin
Quoted 28 lines · Oleg Nesterov
Quoted 21 lines
Quoted 2 lines

and btw.... what about suspend? try_to_freeze_tasks() will obviously fail if there is a ->frozen thread?

I have to think a bit more here, but something like this will probably work:

kernel/freezer.c +1 -2
index b162b74611e4..590ac4d10b02 100644
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

Quoted 4 lines
Quoted 2 lines

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

But this is what we do with stopped and ptraced tasks, isn't it?

No,

Quoted 1 line

We do use freezable_schedule() and the system freezer just ignores such tasks.

	static inline void freezable_schedule(void)
	{
		freezer_do_not_count();
		schedule();
		freezer_count();
	}
Quoted 5 lines

and note that freezer_count() calls try_to_freeze().

IOW, the task sleeping in freezable_schedule() doesn't really differ from the task sleeping in __refrigerator(). It is not that "the system freezer just ignores such tasks", it ignores them because it can safely count them as frozen.

Right, so the task is sleeping peacefully, and we know, that it won't get anywhere, because we'll catch it in freezer_count(). We allow it to sleep there, we don't force it to __refrigerator(), and we treat it as frozen.

How's that different to cgroup v2 freezer? If the task is frozen by cgroup v2 freezer, let it sleep there, and catch if it tries to escape. Exactly as it works for SIGSTOP.

Am I missing something?

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

And what about TASK_STOPPED/TASK_TRACED tasks? They can not be frozen or thawed, right? This doesn't look good, and this differs from the current freezer controller...

Quoted 7 lines
Quoted 4 lines

Good question!

It looks like cgroup v1 freezer just ignores them treating as already frozen, which doesn't look nice.

Not sure I understand you, but see above... cgroup v1 freezer looks fine wrt stopped/traced tasks.

So, you think that v2 freezer should follow the same approach, and allow tasks sleeping on SIGSTOP, for instance, to be treated as frozen?
Hm, maybe. I have to think more here.

Thank you!

#11 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 48 lines · Roman Gushchin
Quoted 38 lines
Quoted 22 lines
Quoted 22 lines
Quoted 15 lines
kernel/freezer.c +1 -2
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

Quoted 4 lines
Quoted 2 lines

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

But this is what we do with stopped and ptraced tasks, isn't it?

No,

Quoted 1 line

We do use freezable_schedule() and the system freezer just ignores such tasks.

	static inline void freezable_schedule(void)
	{
		freezer_do_not_count();
		schedule();
		freezer_count();
	}
Quoted 5 lines

and note that freezer_count() calls try_to_freeze().

IOW, the task sleeping in freezable_schedule() doesn't really differ from the task sleeping in __refrigerator(). It is not that "the system freezer just ignores such tasks", it ignores them because it can safely count them as frozen.

Right, so the task is sleeping peacefully, and we know, that it won't get anywhere, because we'll catch it in freezer_count(). We allow it to sleep there, we don't force it to __refrigerator(), and we treat it as frozen.

Quoted 5 lines

How's that different to cgroup v2 freezer? If the task is frozen by cgroup v2 freezer, let it sleep there, and catch if it tries to escape. Exactly as it works for SIGSTOP.

Am I missing something?

Roman, perhaps we misunderstood each other...

I still think that the cgroup_task_frozen() check in freeze_task() you proposed
a) is not right, and b) it is not what we do with the STOPPED/TRACED tasks which call freezable_schedule(). This is what I tried to say.

If you meant that freezer v2 can too use freezable_schedule() - I agree.

Quoted 3 lines

So, you think that v2 freezer should follow the same approach, and allow tasks sleeping on SIGSTOP, for instance, to be treated as frozen?
Hm, maybe. I have to think more here.

I think this would be nice. Otherwise, say, CGRP_FREEZE can be never reported if I read this code correctly. And this looks "symmetrical" with the fact that a ->frozen task reacts to SIGSTOP and it is still treated as frozen after that.

Oleg.

#12 lore raw

Re: [PATCH v5 4/7] cgroup: cgroup v2 freezer

Quoted 55 lines · Oleg Nesterov
Quoted 48 lines · Roman Gushchin
Quoted 38 lines
Quoted 22 lines
Quoted 22 lines
Quoted 15 lines
kernel/freezer.c +1 -2
@@ -134,7 +134,7 @@ bool freeze_task(struct task_struct *p)
                return false;
 
        spin_lock_irqsave(&freezer_lock, flags);
-       if (!freezing(p) || frozen(p)) {
+       if (!freezing(p) || frozen(p) || cgroup_task_frozen()) {
                spin_unlock_irqrestore(&freezer_lock, flags);
                return false;
        }
 
--
Quoted 2 lines

If the task is already frozen by the cgroup freezer, we don't have to do anything additionally.

I don't think so. A cgroup_task_frozen() task can be killed after try_to_freeze_tasks() succeeds, and the exiting task can close files, do IO, etc. Or it can be thawed by cgroup_freeze_task(false).

Quoted 4 lines
Quoted 2 lines

In short, if try_to_freeze_tasks() succeeds, the caller has all rights to assume that nobody can escape from __refrigerator().

But this is what we do with stopped and ptraced tasks, isn't it?

No,

Quoted 1 line

We do use freezable_schedule() and the system freezer just ignores such tasks.

	static inline void freezable_schedule(void)
	{
		freezer_do_not_count();
		schedule();
		freezer_count();
	}
Quoted 5 lines

and note that freezer_count() calls try_to_freeze().

IOW, the task sleeping in freezable_schedule() doesn't really differ from the task sleeping in __refrigerator(). It is not that "the system freezer just ignores such tasks", it ignores them because it can safely count them as frozen.

Right, so the task is sleeping peacefully, and we know, that it won't get anywhere, because we'll catch it in freezer_count(). We allow it to sleep there, we don't force it to __refrigerator(), and we treat it as frozen.

Quoted 5 lines

How's that different to cgroup v2 freezer? If the task is frozen by cgroup v2 freezer, let it sleep there, and catch if it tries to escape. Exactly as it works for SIGSTOP.

Am I missing something?

Quoted 7 lines

Roman, perhaps we misunderstood each other...

I still think that the cgroup_task_frozen() check in freeze_task() you proposed
a) is not right, and b) it is not what we do with the STOPPED/TRACED tasks which call freezable_schedule(). This is what I tried to say.

If you meant that freezer v2 can too use freezable_schedule() - I agree.

Sorry for the confusion. Yeah, what I'm saying, is that freezable_schedule() will work for v2 as well.

Quoted 8 lines
Quoted 4 lines

So, you think that v2 freezer should follow the same approach, and allow tasks sleeping on SIGSTOP, for instance, to be treated as frozen?
Hm, maybe. I have to think more here.

I think this would be nice. Otherwise, say, CGRP_FREEZE can be never reported if I read this code correctly. And this looks "symmetrical" with the fact that a ->frozen task reacts to SIGSTOP and it is still treated as frozen after that.

Yeah, looks so. I'll try to implement this in v6.

Thanks!

#13 lore raw

PATCH v5 5/7 kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()

In reply to Roman Gushchin

If the cgroup destruction races with an exit() of a belonging process(es), cg_kill_all() may fail. It's not a good reason to make cg_destroy() fail and leave the cgroup in place, potentially causing next test runs to fail.

---
 tools/testing/selftests/cgroup/cgroup_util.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
tools/testing/selftests/cgroup/cgroup_util.c +1 -3
index 14c9fe284806..eba06f94433b 100644
@@ -227,9 +227,7 @@ int cg_destroy(const char *cgroup)
 retry:
 	ret = rmdir(cgroup);
 	if (ret && errno == EBUSY) {
-		ret = cg_killall(cgroup);
-		if (ret)
-			return ret;
+		cg_killall(cgroup);
 		usleep(100);
 		goto retry;
 	}
Signature · 1 line
2.17.2
#14 lore raw

PATCH v5 6/7 kselftests: cgroup: add freezer controller self-tests

In reply to Roman Gushchin

This patch implements six tests for the freezer controller for cgroup v2:
1) a simple test, which aims to freeze and unfreeze a cgroup with 100 processes
2) a more complicated tree test, which creates a hierarchy of cgroups, puts some processes in some cgroups, and tries to freeze and unfreeze different parts of the subtree
3) a forkbomb test: the test aims to freeze a forkbomb running in a cgroup, kill all tasks in the cgroup and remove the cgroup without the unfreezing.
4) rmdir test: the test creates two nested cgroups, freezes the parent one, checks that the child can be successfully removed, and a new child can be created
5) migration tests: the test checks migration of a task between frozen cgroups: from a frozen to a running, from a running to a frozen, and from a frozen to a frozen.
6) ptrace test: the test checks that it's possible to attach to a process in a frozen cgroup, get some information and detach, and the cgroup will remain frozen.

Expected output:

  $ ./test_freezer
  ok 1 test_cgfreezer_simple
  ok 2 test_cgfreezer_tree
  ok 3 test_cgfreezer_forkbomb
  ok 4 test_cgrreezer_rmdir
  ok 5 test_cgfreezer_migrate
  ok 6 test_cgfreezer_ptrace
---
 tools/testing/selftests/cgroup/.gitignore     |   1 +
 tools/testing/selftests/cgroup/Makefile       |   2 +
 tools/testing/selftests/cgroup/cgroup_util.c  |  81 ++-
 tools/testing/selftests/cgroup/cgroup_util.h  |   7 +
 tools/testing/selftests/cgroup/test_freezer.c | 685 ++++++++++++++++++
 5 files changed, 775 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/cgroup/test_freezer.c
tools/testing/selftests/cgroup/.gitignore +1 -0
index adacda50a4b2..7f9835624793 100644
@@ -1,2 +1,3 @@
 test_memcontrol
 test_core
+test_freezer
tools/testing/selftests/cgroup/Makefile +2 -0
index 23fbaa4a9630..8d369b6a2069 100644
@@ -5,8 +5,10 @@ all:
 
 TEST_GEN_PROGS = test_memcontrol
 TEST_GEN_PROGS += test_core
+TEST_GEN_PROGS += test_freezer
 
 include ../lib.mk
 
 $(OUTPUT)/test_memcontrol: cgroup_util.c
 $(OUTPUT)/test_core: cgroup_util.c
+$(OUTPUT)/test_freezer: cgroup_util.c
tools/testing/selftests/cgroup/cgroup_util.c +80 -1

102 lines not on this page · raw message

tools/testing/selftests/cgroup/cgroup_util.h +7 -0
index 9ac8b7958f83..8ee63c00a668 100644
@@ -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 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_control(const char *cgroup, const char *control);
 extern int cg_create(const char *cgroup);
 extern int cg_destroy(const char *cgroup);
 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);
 extern int cg_run(const char *cgroup,
 		  int (*fn)(const char *cgroup, void *arg),
 		  void *arg);
+extern int cg_enter(const char *cgroup, int pid);
 extern int cg_enter_current(const char *cgroup);
 extern int cg_run_nowait(const char *cgroup,
 			 int (*fn)(const char *cgroup, void *arg),
@@ -41,3 +43,8 @@ extern int alloc_pagecache(int fd, size_t size);
 extern int alloc_anon(const char *cgroup, void *arg);
 extern int is_swap_enabled(void);
 extern int set_oom_adj_score(int pid, int score);
+extern int cg_for_all_procs(const char *cgroup, int (*fn)(int pid, void *arg),
+			    void *arg);
+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);
tools/testing/selftests/cgroup/test_freezer.c new file +685 -0

685 lines not on this page · raw message

Signature · 1 line
2.17.2
#15 lore raw

PATCH v5 7/7 cgroup: document cgroup v2 freezer interface

In reply to Roman Gushchin

Describe cgroup v2 freezer interface in the cgroup v2 admin guide.

---
 Documentation/admin-guide/cgroup-v2.rst | 27 +++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
Documentation/admin-guide/cgroup-v2.rst +27 -0
index 07e06136a550..f8335e26b362 100644
@@ -864,6 +864,8 @@ All cgroup core files are prefixed with "cgroup."
 	  populated
 		1 if the cgroup or its descendants contains any live
 		processes; otherwise, 0.
+	  frozen
+		1 if the cgroup is frozen; otherwise, 0.
 
   cgroup.max.descendants
 	A read-write single value files.  The default is "max".
@@ -897,6 +899,31 @@ All cgroup core files are prefixed with "cgroup."
 		A dying cgroup can consume system resources not exceeding
 		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
 ===========
Signature · 1 line
2.17.2