freezer for cgroup v2
Subsystems (5)
v4 cover freezer for cgroup v2
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.
v4->v3:
- fixed cgroup state transitions on task migration (by Tejun Heo)
- 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 | 31 + 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 | 151 +++- kernel/cgroup/freezer.c | 653 +++++++---------- kernel/cgroup/legacy_freezer.c | 481 ++++++++++++ kernel/ptrace.c | 7 + kernel/signal.c | 58 +- 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 ++++++++++++++++++ 19 files changed, 1811 insertions(+), 447 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
PATCH v4 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
@@ -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
Signature · 1 line
2.17.2
PATCH v4 2/7 cgroup: implement __cgroup_task_count() helper
In reply to Roman GushchinThe 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
@@ -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
@@ -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
@@ -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
PATCH v4 3/7 cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
In reply to Roman GushchinThe 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 | 3 +++ kernel/cgroup/cgroup.c | 6 ++++++ 2 files changed, 9 insertions(+)
include/linux/cgroup-defs.h +3 -0
@@ -348,6 +348,9 @@ 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. */ int nr_descendants;int nr_dying_descendants;
kernel/cgroup/cgroup.c +6 -0
@@ -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
Tejun Heo
maintainerRe: [PATCH v4 3/7] cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
include/linux/cgroup-defs.h struct cgroup { · line 353
Quoted 2 lines · Roman Gushchin
include/linux/cgroup-defs.h +2
-0+* nr_descendants and nr_dying_descendants are protected +* by cgroup_mutex and css_set_lock.
Can you be a bit more specific - hold both for writes, either for reads.
Thanks.
Signature · 1 line
tejun
Re: [PATCH v4 3/7] cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
Quoted 6 lines · Tejun Heo
include/linux/cgroup-defs.h struct cgroup { · line 353
Quoted 2 lines · Roman Gushchin
include/linux/cgroup-defs.h +2
-0+* nr_descendants and nr_dying_descendants are protected +* by cgroup_mutex and css_set_lock. Can you be a bit more specific - hold both for writes, either for reads.
Sure. Thanks!
PATCH v4 4/7 cgroup: cgroup v2 freezer
In reply to Roman GushchinCgroup 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 | 112 +++++++++++- kernel/cgroup/freezer.c | 318 +++++++++++++++++++++++++++++++++++ kernel/ptrace.c | 7 + kernel/signal.c | 58 +++++-- 10 files changed, 556 insertions(+), 18 deletions(-) create mode 100644 kernel/cgroup/freezer.c
include/linux/cgroup-defs.h +28 -0
@@ -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;
@@ -450,6 +475,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
@@ -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
@@ -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
@@ -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))) +return __fatal_signal_pending (p);return (state &TASK_INTERRUPTIBLE ) ||__fatal_signal_pending (p); }
kernel/cgroup/Makefile +1 -1
@@ -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 +109 -3
165 lines not on this page · raw message
kernel/cgroup/freezer.c new file +318 -0
318 lines not on this page · raw message
kernel/ptrace.c +7 -0
@@ -410,6 +410,13 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* If the process is frozen, let's wake it up to give it a chance +* to enter the ptrace trap. +*/ +if (cgroup_task_frozen (task)) +wake_up_process (task); +/* * If the task is already STOPPED, set JOBCTL_TRAP_STOP and * TRAPPING, and kick it so that it transits to TRACED. TRAPPING
kernel/signal.c +44 -14
102 lines not on this page · raw message
Signature · 1 line
2.17.2
Oleg Nesterov
maintainerRe: [PATCH v4 4/7] cgroup: cgroup v2 freezer
To be honest, I fail to understand this patch. At least after a quick glance, I will try to read it again tomorrow but so far I do not even understand the desired semantics wrt signals/ptrace.
include/linux/sched/signal.h static inline int signal_pending_state(long state, struct task_struct *p) · line 373
Quoted 7 lines · Roman Gushchin
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))) +return __fatal_signal_pending (p);
Oh, this is not nice. And doesn't look right.
Quoted 12 lines
kernel/cgroup/freezer.c new file +12
-0+/* +* Entry path into frozen state. +* If the task was not frozen before, counters are updated and the cgroup state +* is revisited. Otherwise, the task is put into the TASK_KILLABLE sleep. +*/ +void cgroup_enter_frozen (void ) +{ +if (!current->frozen) { +struct cgroup *cgrp; + +spin_lock_irq (&css_set_lock); + current->frozen = true;
kernel/cgroup/freezer.c line 101
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ cgrp =task_dfl_cgroup (current); + cgrp->freezer.nr_frozen_tasks++; +WARN_ON_ONCE (cgrp->freezer.nr_frozen_tasks > + cgrp->freezer.nr_tasks_to_freeze); +cgroup_update_frozen (cgrp, true); +spin_unlock_irq (&css_set_lock); + } + +__set_current_state (TASK_INTERRUPTIBLE ); +schedule ();
The comment above says TASK_KILLABLE, very confusing.
Probably this pairs with the change in signal_pending_state() above. So this schedule() should actually "work" in that it won't return if signal_pending().
But this can't protect from another signal_wake_up(). Yes, iiuc in this case cgroup_enter_frozen() will be called again "soon" but this all looks strange.
kernel/ptrace.c static int ptrace_attach(struct task_struct *task, long request, · line 418
Quoted 12 lines
kernel/ptrace.c +6
-0@@ -410,6 +410,13 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* If the process is frozen, let's wake it up to give it a chance +* to enter the ptrace trap. +*/ +if (cgroup_task_frozen (task)) +wake_up_process (task);
And why this can't race with cgroup_enter_frozen() ?
Or think of PTRACE_INTERRUPT. It can race with cgroup_enter_frozen() too, the tracee can miss this request because of that change in signal_pending_state().
Quoted 46 lines
kernel/ptrace.c +1
-0static void do_jobctl_trap (void ) { +struct sighand_struct *sighand = current->sighand;struct signal_struct *signal = current->signal;kernel/ptrace.c +25
-11int signr = current->jobctl &JOBCTL_STOP_SIGMASK ; -if (current->ptrace &PT_SEIZED ) { -if (!signal->group_stop_count && - !(signal->flags &SIGNAL_STOP_STOPPED )) - signr =SIGTRAP ; -WARN_ON_ONCE (!signr); -ptrace_do_notify (signr, signr | (PTRACE_EVENT_STOP <<8 ), -CLD_STOPPED ); - }else { -WARN_ON_ONCE (!signr); -ptrace_stop (signr,CLD_STOPPED ,0 ,NULL ); - current->exit_code =0 ; +if (current->jobctl & (JOBCTL_TRAP_STOP |JOBCTL_TRAP_NOTIFY )) { +if (current->ptrace &PT_SEIZED ) { +if (!signal->group_stop_count && + !(signal->flags &SIGNAL_STOP_STOPPED )) + signr =SIGTRAP ; +WARN_ON_ONCE (!signr); +ptrace_do_notify (signr, + signr | (PTRACE_EVENT_STOP <<8 ), +CLD_STOPPED ); + }else { +WARN_ON_ONCE (!signr); +ptrace_stop (signr,CLD_STOPPED ,0 ,NULL ); + current->exit_code =0 ; + } + }else if (current->jobctl &JOBCTL_TRAP_FREEZE ) { +/* +* Enter the frozen state, unless the task is about to exit. +*/ +if (fatal_signal_pending (current)) { + current->jobctl &= ~JOBCTL_TRAP_FREEZE ; + }else { +spin_unlock_irq (&sighand->siglock); +cgroup_enter_frozen (); +spin_lock_irq (&sighand->siglock); + } } }
kernel/ptrace.c bool get_signal(struct ksignal *ksig) · line 2425
Quoted 7 lines
kernel/ptrace.c +2
-1@@ -2401,12 +2420,23 @@ bool get_signal(struct ksignal *ksig) do_signal_stop(0)) goto relock; -if (unlikely (current->jobctl &JOBCTL_TRAP_MASK )) { +if (unlikely (current->jobctl & + (JOBCTL_TRAP_MASK |JOBCTL_TRAP_FREEZE ))) {do_jobctl_trap ();
Cosmetic nit, but can't you add another helper? To me something like
if (JOBCTL_TRAP_MASK) do_jobctl_trap(); else if (JOBCTL_TRAP_FREEZE) do_jobctl_freeze();
will look more clean, but I won't insist.
kernel/ptrace.c bool get_signal(struct ksignal *ksig) · line 2433
Quoted 8 lines
kernel/ptrace.c +8
-0+/* +* 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); +cgroup_leave_frozen (); +spin_lock_irq (&sighand->siglock);
looks like this needs another "goto relock", no?
And perhaps this another reason for the new do_jobctl_freeze() helper which could absorb this leave_frozen() ?
Oleg.
Re: [PATCH v4 4/7] cgroup: cgroup v2 freezer
Quoted 31 lines · Oleg Nesterov
To be honest, I fail to understand this patch. At least after a quick glance, I will try to read it again tomorrow but so far I do not even understand the desired semantics wrt signals/ptrace.
include/linux/sched/signal.h static inline int signal_pending_state(long state, struct task_struct *p) · line 373
Quoted 7 lines · Roman Gushchin
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))) +return __fatal_signal_pending (p);Oh, this is not nice. And doesn't look right.
Quoted 16 lines
kernel/cgroup/freezer.c new file +16
-0+/* +* Entry path into frozen state. +* If the task was not frozen before, counters are updated and the cgroup state +* is revisited. Otherwise, the task is put into the TASK_KILLABLE sleep. +*/ +void cgroup_enter_frozen (void ) +{ +if (!current->frozen) { +struct cgroup *cgrp; + +spin_lock_irq (&css_set_lock); + current->frozen = true; + cgrp =task_dfl_cgroup (current); + cgrp->freezer.nr_frozen_tasks++; +WARN_ON_ONCE (cgrp->freezer.nr_frozen_tasks > + cgrp->freezer.nr_tasks_to_freeze);
Quoted 8 lines
kernel/cgroup/freezer.c line 101
Quoted 6 lines
kernel/cgroup/freezer.c new file +6
-0+cgroup_update_frozen (cgrp, true); +spin_unlock_irq (&css_set_lock); + } + +__set_current_state (TASK_INTERRUPTIBLE ); +schedule ();The comment above says
TASK_KILLABLE, very confusing.
Sorry, it's a leftover from one of the previous versions. Fixed.
Quoted 6 lines
Probably this pairs with the change in
signal_pending_state()above. So thisschedule()should actually "work" in that it won't return ifsignal_pending().But this can't protect from another
signal_wake_up(). Yes, iiuc in this casecgroup_enter_frozen()will be called again "soon" but this all looks strange.
So, the idea here is to make ptrace traps and fatal signals working, but non-fatal shouldn't be delivered.
As soon as the frozen task is looping in the signal delivery loop, it's fine, it's not going anywhere.
Without the change above the task is getting out of schedule() immediately, if any signal is pending (including non-fatals). So it becomes a busy-loop.
Quoted 16 lines
kernel/ptrace.c static int ptrace_attach(struct task_struct *task, long request, · line 418
Quoted 13 lines
kernel/ptrace.c +6
-0@@ -410,6 +410,13 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* If the process is frozen, let's wake it up to give it a chance +* to enter the ptrace trap. +*/ +if (cgroup_task_frozen (task)) +wake_up_process (task);And why this can't race with
cgroup_enter_frozen()?
Quoted 2 lines
Or think of
PTRACE_INTERRUPT. It can race withcgroup_enter_frozen()too, the tracee can miss this request because of that change insignal_pending_state().
It's a good point. So I need an additional synchronization around checking/setting the JOBCTL_TRAP_FREEZE?
Quoted 58 lines
Quoted 48 lines
kernel/ptrace.c +1
-0static void do_jobctl_trap (void ) { +struct sighand_struct *sighand = current->sighand;struct signal_struct *signal = current->signal;kernel/ptrace.c +25
-11int signr = current->jobctl &JOBCTL_STOP_SIGMASK ; -if (current->ptrace &PT_SEIZED ) { -if (!signal->group_stop_count && - !(signal->flags &SIGNAL_STOP_STOPPED )) - signr =SIGTRAP ; -WARN_ON_ONCE (!signr); -ptrace_do_notify (signr, signr | (PTRACE_EVENT_STOP <<8 ), -CLD_STOPPED ); - }else { -WARN_ON_ONCE (!signr); -ptrace_stop (signr,CLD_STOPPED ,0 ,NULL ); - current->exit_code =0 ; +if (current->jobctl & (JOBCTL_TRAP_STOP |JOBCTL_TRAP_NOTIFY )) { +if (current->ptrace &PT_SEIZED ) { +if (!signal->group_stop_count && + !(signal->flags &SIGNAL_STOP_STOPPED )) + signr =SIGTRAP ; +WARN_ON_ONCE (!signr); +ptrace_do_notify (signr, + signr | (PTRACE_EVENT_STOP <<8 ), +CLD_STOPPED ); + }else { +WARN_ON_ONCE (!signr); +ptrace_stop (signr,CLD_STOPPED ,0 ,NULL ); + current->exit_code =0 ; + } + }else if (current->jobctl &JOBCTL_TRAP_FREEZE ) { +/* +* Enter the frozen state, unless the task is about to exit. +*/ +if (fatal_signal_pending (current)) { + current->jobctl &= ~JOBCTL_TRAP_FREEZE ; + }else { +spin_unlock_irq (&sighand->siglock); +cgroup_enter_frozen (); +spin_lock_irq (&sighand->siglock); + } } }kernel/ptrace.c bool get_signal(struct ksignal *ksig) · line 2425
Quoted 7 lines
kernel/ptrace.c +2
-1@@ -2401,12 +2420,23 @@ bool get_signal(struct ksignal *ksig) do_signal_stop(0)) goto relock; -if (unlikely (current->jobctl &JOBCTL_TRAP_MASK )) { +if (unlikely (current->jobctl & + (JOBCTL_TRAP_MASK |JOBCTL_TRAP_FREEZE ))) {do_jobctl_trap ();Cosmetic nit, but can't you add another helper? To me something like
Quoted 6 lines
if (JOBCTL_TRAP_MASK) do_jobctl_trap(); else if (JOBCTL_TRAP_FREEZE) do_jobctl_freeze();will look more clean, but I won't insist.
Sure.
Quoted 13 lines
kernel/ptrace.c bool get_signal(struct ksignal *ksig) · line 2433
Quoted 10 lines
kernel/ptrace.c +8
-0+/* +* 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); +cgroup_leave_frozen (); +spin_lock_irq (&sighand->siglock);looks like this needs another "goto relock", no?
Quoted 2 lines
And perhaps this another reason for the new
do_jobctl_freeze()helper which could absorb thisleave_frozen()?
Makes sense. Will follow this path in v5.
Thank you!
Oleg Nesterov
maintainerRe: [PATCH v4 4/7] cgroup: cgroup v2 freezer
In reply to Roman GushchinQuoted 9 lines · Roman Gushchin
kernel/cgroup/freezer.c new file +8
-0+void cgroup_enter_frozen (void ) +{ +if (!current->frozen) { +struct cgroup *cgrp; + +spin_lock_irq (&css_set_lock); + current->frozen = true; + cgrp =task_dfl_cgroup (current);
kernel/cgroup/freezer.c line 102
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ cgrp->freezer.nr_frozen_tasks++; +WARN_ON_ONCE (cgrp->freezer.nr_frozen_tasks > + cgrp->freezer.nr_tasks_to_freeze); +cgroup_update_frozen (cgrp, true); +spin_unlock_irq (&css_set_lock); + } + +__set_current_state (TASK_INTERRUPTIBLE ); +schedule (); +__set_current_state (TASK_RUNNING );
forgot to mention, schedule() always returns in TASK_RUNNING, so the 2nd __set_current_state() is pointless.
Oleg.
Oleg Nesterov
maintainerRe: [PATCH v4 4/7] cgroup: cgroup v2 freezer
In reply to Roman Gushchinkernel/signal.c void recalc_sigpending_and_wake(struct task_struct *t) · line 176
Quoted 6 lines · Roman Gushchin
void
recalc_sigpending(void)
{kernel/signal.c +1
-1if (!recalc_sigpending_tsk (current) && !freezing (current) && - !klp_patch_pending (current)) + !klp_patch_pending (current) && !cgroup_task_frozen (current))
I must have missed something... but shouldn't it check JOBCTL_TRAP_FREEZE rather than ->frozen?
Oleg.
PATCH v4 5/7 kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()
In reply to Roman GushchinIf 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
@@ -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
PATCH v4 6/7 kselftests: cgroup: add freezer controller self-tests
In reply to Roman GushchinThis 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
@@ -1,2 +1,3 @@ test_memcontrol test_core +test_freezer
tools/testing/selftests/cgroup/Makefile +2 -0
@@ -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
@@ -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
PATCH v4 7/7 cgroup: document cgroup v2 freezer interface
In reply to Roman GushchinDescribe 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
@@ -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
Re: [PATCH v4 7/7] cgroup: document cgroup v2 freezer interface
Quoted 6 lines · Roman Gushchin
Describe cgroup v2 freezer interface in the cgroup v2 admin guide.
Signed-off-by Roman Gushchin <[email protected]>Cc Tejun Heo <[email protected]>
Quoted 52 lines
--- Documentation/admin-guide/cgroup-v2.rst | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+)Documentation/admin-guide/cgroup-v2.rst +27
-0@@ -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
Signature · 2 lines
Sincerely yours, Mike.