freezer for cgroup v2
Subsystems (5)
v2 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) and (2) are some preparational work, patch (3) contains the implementation, patch (4) is a small cgroup kselftest fix, patch (5) covers freezer adds 6 new kselftests to cover the freezer functionality. Patch (6) adds corresponding docs.
v2->v1:
- fixed locking aroung calling cgroup_freezer_leave()
- added docs
Roman Gushchin
cgroup: rename freezer.c into legacy_freezer.c
cgroup: implement __cgroup_task_count() helper
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 | 26 + include/linux/cgroup-defs.h | 33 + include/linux/cgroup.h | 42 ++ include/linux/sched.h | 5 +- include/linux/sched/jobctl.h | 5 +- kernel/cgroup/Makefile | 4 +- kernel/cgroup/cgroup-internal.h | 1 + kernel/cgroup/cgroup-v1.c | 16 - kernel/cgroup/cgroup.c | 132 +++- kernel/cgroup/freezer.c | 590 +++++---------- kernel/cgroup/legacy_freezer.c | 481 ++++++++++++ kernel/ptrace.c | 6 + kernel/signal.c | 54 +- 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, 1724 insertions(+), 451 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 1/1 cgroup: document cgroup v2 freezer interface
Describe cgroup v2 freezer interface in the cgroup v2 admin guide.
--- Documentation/admin-guide/cgroup-v2.rst | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
Documentation/admin-guide/cgroup-v2.rst +26 -0
@@ -862,6 +862,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".
@@ -895,6 +897,30 @@ 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 the process + is complete, the "frozen" value in the cgroup.events control file + will be updated and the corresponding notification will be issued. + + Cgroup can be frozen either by its own settings, either 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, either if freezing of the cgroup races with fork(). + If a cgroup is moved to a frozen cgroup, it stops. If a process is + moving 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
PATCH v2 1/6 cgroup: rename freezer.c into legacy_freezer.c
In reply to Roman GushchinFreezer.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 v2 2/6 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
@@ -224,6 +224,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
@@ -561,6 +561,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 v2 3/6 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. However the functionality is similar, the interface is different to cgroup v1: it follows cgroup v2 interface principles.
Key differences are:
1) There is no separate controller: 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.
5) Tasks in a frozen cgroup can be killed.
--- include/linux/cgroup-defs.h | 33 +++++ include/linux/cgroup.h | 42 ++++++ include/linux/sched.h | 5 +- include/linux/sched/jobctl.h | 5 +- kernel/cgroup/Makefile | 2 +- kernel/cgroup/cgroup.c | 99 +++++++++++++- kernel/cgroup/freezer.c | 247 +++++++++++++++++++++++++++++++++++ kernel/ptrace.c | 6 + kernel/signal.c | 54 ++++++-- 9 files changed, 475 insertions(+), 18 deletions(-) create mode 100644 kernel/cgroup/freezer.c
include/linux/cgroup-defs.h +33 -0
@@ -63,6 +63,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 */
@@ -314,6 +320,27 @@ struct cgroup_rstat_cpu {struct cgroup *updated_next;/* NULL iff not on the list */ }; +struct cgroup_freezer_state { +/* Should the cgroup actually be frozen? +* (protected by cgroup_mutex) +*/ +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; + +/* Used for delayed notifications */ +struct work_struct notify_work; +}; +struct cgroup {/* self css with NULL ->ss, points back to this cgroup */ struct cgroup_subsys_state self;
@@ -442,6 +469,12 @@ struct cgroup {/* If there is block congestion on this cgroup. */ atomic_t congestion_count; +/* Should the cgroup and its descendants be frozen. */ +bool freeze; + +/* 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 +4 -1
@@ -83,7 +83,8 @@ struct task_group;#define TASK_WAKING 0x0200#define TASK_NOLOAD 0x0400#define TASK_NEW 0x0800 -#define TASK_STATE_MAX 0x1000 +#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000/* Convenience macros for the sake of set_current_state: */ #define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
@@ -733,6 +734,8 @@ struct task_struct {#ifdef CONFIG_CGROUPS /* disallow userland-initiated cgroup migration */ unsigned no_cgroup_migration:1; +/* task is in the cgroup freezer loop */ + unsigned frozen:1;#endif #ifdef CONFIG_BLK_CGROUP /* to be used once the psi infrastructure lands upstream. */
include/linux/sched/jobctl.h +4 -1
@@ -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,8 +27,10 @@ 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_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY | \ + JOBCTL_TRAP_FREEZE)#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)extern bool task_set_jobctl_pending (struct task_struct *task,unsigned long mask);
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 +96 -3
151 lines not on this page · raw message
kernel/cgroup/freezer.c new file +247 -0
247 lines not on this page · raw message
kernel/ptrace.c +6 -0
@@ -400,6 +400,12 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* Kick the process to get it out of the refrigerator. +*/ +if (cgroup_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 +42 -12
81 lines not on this page · raw message
Signature · 1 line
2.17.2
Tejun Heo
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
(cc'ing Linus, Andrew, Ingo for the addition of a new task state)
Hello,
Quoted 9 lines · 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. However the functionality is similar, the interface is different to cgroup v1: it follows cgroup v2 interface principles.
Quoted 13 lines
Key differences are:
1) There is no separate controller: 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.
5) Tasks in a frozen cgroup can be killed.
I think it'd be worthwhile to explain that tasks are now frozen in a state which is essentially equivalent to the job control stop state but which can only be controlled by the freezer and its interactions with ptrace.
Oleg, I'd really appreciate if you can review the signal delivery / ptrace parts of the patch.
Quoted 46 lines
include/linux/cgroup-defs.h +27
-0@@ -63,6 +63,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 */ @@ -314,6 +320,27 @@ struct cgroup_rstat_cpu {struct cgroup *updated_next;/* NULL iff not on the list */ }; +struct cgroup_freezer_state { +/* Should the cgroup actually be frozen? +* (protected by cgroup_mutex) +*/ +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; + +/* Used for delayed notifications */ +struct work_struct notify_work; +}; +struct cgroup {/* self css with NULL ->ss, points back to this cgroup */ struct cgroup_subsys_state self;
include/linux/cgroup-defs.h struct cgroup { · line 473
Quoted 5 lines
include/linux/cgroup-defs.h +2
-0@@ -442,6 +469,12 @@ struct cgroup {/* If there is block congestion on this cgroup. */ atomic_t congestion_count; +/* Should the cgroup and its descendants be frozen. */ +bool freeze;
Why not have this in freezer too?
Quoted 21 lines
include/linux/cgroup-defs.h +3
-0+/* 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 +7
-0@@ -871,4 +871,46 @@ static inline void put_cgroup_ns(struct cgroup_namespace *ns)free_cgroup_ns (ns); } +#ifdef CONFIG_CGROUPS + +static inline bool cgroup_frozen (struct task_struct *task) +{ +bool ret; + +rcu_read_lock ();
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 890
Quoted 10 lines
include/linux/cgroup.h +10
-0+ ret =test_bit (CGRP_FREEZE , &task_dfl_cgroup (task)->flags); +rcu_read_unlock (); + +return ret; +} + +static inline bool cgroup_task_in_freezer (struct task_struct *task) +{ +return task->frozen; +}
I think the above are pretty confusing. CGRP_FREEZE is frozen and task->frozen is cgroup_task_in_freezer()? Can't they be e.g. cgroup_task_freeze() (or freezing, which probably is clearer) and cgroup_task_frozen()?
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 893
Quoted 2 lines
include/linux/cgroup.h +2
-0+void cgroup_freezer_enter (void ); +void cgroup_freezer_leave (void );
So, if we use freeze, freezing, frozen instead, the aboves can be cgroup_frozen_enter() and cgroup_frozen_leave() (or begin/end).
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 895
Quoted 2 lines
include/linux/cgroup.h +2
-0+void cgroup_freeze (struct cgroup *cgrp,bool freeze); +void cgroup_notify_frozen_fn (struct work_struct *work);
This doesn't need to be exported outside of cgroup proper, right?
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 899
Quoted 4 lines
include/linux/cgroup.h +4
-0+void cgroup_notify_frozen (struct cgroup *cgrp,bool frozen); +void cgroup_queue_notify_frozen (struct cgroup *cgrp); +void cgroup_freezer_migrate_task (struct task_struct *task,struct cgroup *src, +struct cgroup *dst);
Ditto. Do these need to be exposed outside freezer or cgroup proper?
Quoted 22 lines
include/linux/cgroup.h +16
-0+ +#else /* !CONFIG_CGROUPS */ + +static inline bool cgroup_task_in_freezer (struct task_struct *task) +{ +return false; +} +static inline void cgroup_freezer_enter (void ) { } +static inline void cgroup_freezer_leave (void ) { } +static inline bool cgroup_frozen (struct task_struct *task) +{ +return false; +} + +#endif /* !CONFIG_CGROUPS */ +#endif /* _LINUX_CGROUP_H */ include/linux/sched.h +0
-0
include/linux/sched.h struct task_group; · line 87
Quoted 6 lines
include/linux/sched.h +2
-1@@ -83,7 +83,8 @@ struct task_group;#define TASK_WAKING 0x0200#define TASK_NOLOAD 0x0400#define TASK_NEW 0x0800 -#define TASK_STATE_MAX 0x1000 +#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000
We should also cc [email protected] as this is visible from userland, I think.
include/linux/sched.h struct task_struct { · line 737
Quoted 7 lines
include/linux/sched.h +1
-0/* Convenience macros for the sake of set_current_state: */ #define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)@@ -733,6 +734,8 @@ struct task_struct {#ifdef CONFIG_CGROUPS /* disallow userland-initiated cgroup migration */ unsigned no_cgroup_migration:1; +/* task is in the cgroup freezer loop */
The above comment isn't strictly true, right?
Quoted 154 lines
include/linux/sched.h +1
-0+ unsigned frozen:1;#endif #ifdef CONFIG_BLK_CGROUP /* to be used once the psi infrastructure lands upstream. */ include/linux/sched/jobctl.h +4
-1@@ -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,8 +27,10 @@ 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_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY | \ + JOBCTL_TRAP_FREEZE)#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)extern bool task_set_jobctl_pending (struct task_struct *task,unsigned long mask);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.okernel/cgroup/cgroup.c +61
-3@@ -2358,6 +2358,10 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)css_set_move_task (task, from_cset, to_cset, true);put_css_set_locked (from_cset); from_cset->nr_tasks--; + +cgroup_freezer_migrate_task (task, + from_cset->dfl_cgrp, + to_cset->dfl_cgrp); } }spin_unlock_irq (&css_set_lock);@@ -3401,8 +3405,11 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of,static int cgroup_events_show (struct seq_file *seq,void *v) { -seq_printf (seq,"populated %d\n" , -cgroup_is_populated (seq_css (seq)->cgroup)); +struct cgroup *cgrp =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 ; }@@ -3449,6 +3456,40 @@ static int cpu_stat_show(struct seq_file *seq, void *v)return ret; } +static int cgroup_freeze_show (struct seq_file *seq,void *v) +{ +struct cgroup *cgrp =seq_css (seq)->cgroup; + +seq_printf (seq,"%d\n" , cgrp->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) {struct cftype *cft = of->kn->priv;@@ -4574,6 +4615,12 @@ static struct cftype cgroup_base_files[] = { .name = "cgroup.stat", .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", .flags = CFTYPE_NOT_ON_ROOT,@@ -4899,11 +4946,20 @@ static struct cgroup *cgroup_create(struct cgroup *parent)if (ret) goto out_idr_free; +INIT_WORK (&cgrp->freezer.notify_work, cgroup_notify_frozen_fn); + cgrp->freezer.e_freeze = parent->freezer.e_freeze; +if (cgrp->freezer.e_freeze) +set_bit (CGRP_FROZEN , &cgrp->flags); +for (tcgrp = cgrp; tcgrp; tcgrp =cgroup_parent (tcgrp)) { cgrp->ancestor_ids[tcgrp->level] = tcgrp->id; -if (tcgrp != cgrp) +if (tcgrp != cgrp) { tcgrp->nr_descendants++; + +if (cgrp->freezer.e_freeze) + tcgrp->freezer.nr_frozen_descendants++; + } }if (notify_on_release (parent))@@ -5190,6 +5246,8 @@ static int cgroup_destroy_locked(struct cgroup *cgrp)for (tcgrp =cgroup_parent (cgrp); tcgrp; tcgrp =cgroup_parent (tcgrp)) { tcgrp->nr_descendants--; tcgrp->nr_dying_descendants++; +if (cgrp->freezer.e_freeze) + tcgrp->freezer.nr_frozen_descendants--; }cgroup1_check_for_release (parent);
kernel/cgroup/cgroup.c void cgroup_post_fork(struct task_struct *child) · line 5705
Quoted 6 lines
kernel/cgroup/cgroup.c +3
-0@@ -5642,6 +5700,23 @@ void cgroup_post_fork(struct task_struct *child) cset->nr_tasks++;css_set_move_task (child,NULL , cset, false); } + +if (unlikely (cgroup_frozen (child) && + (child->flags & ~PF_KTHREAD ))) {
I don't think we need explicit PF_KTHREAD test here. We don't allow kthreads in non-root cgroups anyway and if we wanna change that there are a bunch of other things which need updating anyway.
Quoted 18 lines
kernel/cgroup/cgroup.c +14
-0+struct cgroup *cgrp; +unsigned long flags; + +if (lock_task_sighand (child, &flags)) { + cgrp = cset->dfl_cgrp; + cgrp->freezer.nr_tasks_to_freeze++; +WARN_ON_ONCE (cgrp->freezer.nr_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks); + child->jobctl |=JOBCTL_TRAP_FREEZE ; +signal_wake_up (child, false); +unlock_task_sighand (child, &flags); + } + } +spin_unlock_irq (&css_set_lock); }
kernel/cgroup/cgroup.c void cgroup_exit(struct task_struct *tsk) · line 5770
Quoted 6 lines
kernel/cgroup/cgroup.c +3
-0@@ -5690,6 +5765,24 @@ void cgroup_exit(struct task_struct *tsk)spin_lock_irq (&css_set_lock);css_set_move_task (tsk, cset,NULL , false); cset->nr_tasks--; + +if (unlikely (cgroup_frozen (tsk) && + (tsk->flags & ~PF_KTHREAD ))) {
Ditto.
kernel/cgroup/cgroup.c void cgroup_exit(struct task_struct *tsk) · line 5775
Quoted 5 lines
kernel/cgroup/cgroup.c +5
-0+struct cgroup *frozen_cgrp = cset->dfl_cgrp; + + frozen_cgrp->freezer.nr_tasks_to_freeze--; + +WARN_ON (tsk->frozen);
Why not WARN_ON_ONCE here too?
Quoted 33 lines
kernel/cgroup/cgroup.c +10
-0+WARN_ON_ONCE (frozen_cgrp->freezer.nr_tasks_to_freeze < +0 ); +WARN_ON_ONCE (frozen_cgrp->freezer.nr_tasks_to_freeze < + frozen_cgrp->freezer.nr_frozen_tasks); + +if (frozen_cgrp->freezer.nr_frozen_tasks == + frozen_cgrp->freezer.nr_tasks_to_freeze) +cgroup_queue_notify_frozen (frozen_cgrp); + } +spin_unlock_irq (&css_set_lock); }else {get_css_set (cset);kernel/cgroup/freezer.c new file +14
-0@@ -0,0 +1,247 @@ +//SPDX-License-Identifier: GPL-2.0 +#include <linux/cgroup.h> +#include <linux/sched.h> +#include <linux/sched/task.h> +#include <linux/sched/signal.h> + +#include "cgroup-internal.h" + +void cgroup_notify_frozen (struct cgroup *cgrp,bool frozen) +{ +int delta =1 ; + +lockdep_assert_held (&cgroup_mutex); +
kernel/cgroup/freezer.c line 24
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+/* +* Did we race with fork() or exit()? Np, everything is still frozen. +*/ +if (frozen ==test_bit (CGRP_FROZEN , &cgrp->flags)) +return ; + +if (frozen) +set_bit (CGRP_FROZEN , &cgrp->flags); +else +clear_bit (CGRP_FROZEN , &cgrp->flags);
I'm not sure this is wrong but it feels a bit weird to tie the actual state transition to notification. Wouldn't it be more straight-forward if CGRP_FROZEN bit is purely determined by whether the tasks are frozen or not and the notification just checks that against the last notified state and generate a notification if they're different?
Quoted 13 lines
kernel/cgroup/freezer.c new file +13
-0+cgroup_file_notify (&cgrp->events_file); + +while ((cgrp =cgroup_parent (cgrp))) { +if (frozen) { + cgrp->freezer.nr_frozen_descendants += delta; +if (!test_bit (CGRP_FROZEN , &cgrp->flags) && +test_bit (CGRP_FREEZE , &cgrp->flags) && + cgrp->freezer.nr_frozen_descendants == + cgrp->nr_descendants) { +set_bit (CGRP_FROZEN , &cgrp->flags); +cgroup_file_notify (&cgrp->events_file); + delta++; + }
kernel/cgroup/freezer.c line 47
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ }else { + cgrp->freezer.nr_frozen_descendants -= delta; +if (test_bit (CGRP_FROZEN , &cgrp->flags)) { +clear_bit (CGRP_FROZEN , &cgrp->flags); +cgroup_file_notify (&cgrp->events_file); + delta++; + } + } + } +}
So that all these state transitions are synchronous with the actual freezing events and we can just queue per-cgroup work items all the way to the top if the new state is different from the last one cgroup-by-cgroup?
Quoted 17 lines
kernel/cgroup/freezer.c new file +17
-0+void cgroup_notify_frozen_fn (struct work_struct *work) +{ + struct cgroup *cgrp = container_of(work, struct cgroup, + freezer.notify_work); + +mutex_lock (&cgroup_mutex); +cgroup_notify_frozen (cgrp, true); +mutex_unlock (&cgroup_mutex); + +css_put (&cgrp->self); +} + +void cgroup_queue_notify_frozen (struct cgroup *cgrp) +{ +if (work_pending (&cgrp->freezer.notify_work)) +return ; +
kernel/cgroup/freezer.c line 75
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+css_get (&cgrp->self); +schedule_work (&cgrp->freezer.notify_work); +} + +void cgroup_freezer_enter (void ) +{ +long state = current->state; +struct cgroup *cgrp; + +if (!current->frozen) {
I think this needs a lot more comments explaining what's going on. It's really subtle that this is where the frozen state is becoming sticky until the task breaks out of the signal delivery path.
Quoted 7 lines
kernel/cgroup/freezer.c new file +7
-0+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_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks);
kernel/cgroup/freezer.c line 92
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks) +cgroup_queue_notify_frozen (cgrp); +spin_unlock_irq (&css_set_lock); + } + +/* refrigerator */ +set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN ); +clear_thread_flag (TIF_SIGPENDING );
The toggling of TIF_SIGPENDING needs explanation too.
Quoted 30 lines
kernel/cgroup/freezer.c new file +30
-0+schedule (); +recalc_sigpending (); + +set_current_state (state); +} + +void cgroup_freezer_leave (void ) +{ +struct cgroup *cgrp; + +spin_lock_irq (&css_set_lock); + cgrp =task_dfl_cgroup (current); + cgrp->freezer.nr_frozen_tasks--; +WARN_ON_ONCE (cgrp->freezer.nr_frozen_tasks <0 ); + current->frozen = false; +spin_unlock_irq (&css_set_lock); +} + +static void cgroup_do_freeze (struct cgroup *cgrp,bool freeze) +{ +struct css_task_iter it; +struct task_struct *task; +unsigned long flags; + +lockdep_assert_held (&cgroup_mutex); + +spin_lock_irq (&css_set_lock); +if (freeze) { + cgrp->freezer.nr_tasks_to_freeze =__cgroup_task_count (cgrp); +set_bit (CGRP_FREEZE , &cgrp->flags);
kernel/cgroup/freezer.c line 132
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ }else { +clear_bit (CGRP_FREEZE , &cgrp->flags); +cgroup_notify_frozen (cgrp, false); + } +spin_unlock_irq (&css_set_lock); + +css_task_iter_start (&cgrp->self,0 , &it); +while ((task =css_task_iter_next (&it))) { +if (task->flags &PF_KTHREAD ) +continue ;
WARN_ON_ONCE?
kernel/cgroup/freezer.c line 135
Quoted 2 lines
kernel/cgroup/freezer.c new file +2
-0+if (!lock_task_sighand (task, &flags)) +continue ;
So, this gotta be a dying task. A comment wouldn't hurt.
kernel/cgroup/freezer.c line 142
Quoted 6 lines
kernel/cgroup/freezer.c new file +6
-0+if (freeze) { + task->jobctl |=JOBCTL_TRAP_FREEZE ; +signal_wake_up (task, false); + }else { + task->jobctl &= ~JOBCTL_TRAP_FREEZE ; +wake_up_process (task);
Again, a comment explaining why one's signal_wake_up() and the other's wake_up_process() would be great.
Quoted 5 lines
kernel/cgroup/freezer.c new file +5
-0+ } + +unlock_task_sighand (task, &flags); + } +css_task_iter_end (&it);
kernel/cgroup/freezer.c line 157
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ +if (freeze && cgrp->nr_descendants == + cgrp->freezer.nr_frozen_descendants) { +spin_lock_irq (&css_set_lock); +WARN_ON_ONCE (cgrp->freezer.nr_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks); +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks) +cgroup_notify_frozen (cgrp, true); +spin_unlock_irq (&css_set_lock);
I think notification handling would be easier if we separate out state transitions and notifications.
Quoted 22 lines
kernel/cgroup/freezer.c new file +22
-0+ } +} + +void cgroup_freezer_migrate_task (struct task_struct *task, +struct cgroup *src,struct cgroup *dst) +{ +unsigned long flags; + +lockdep_assert_held (&css_set_lock); + +if (task->flags &PF_KTHREAD ) +return ; + +if (test_bit (CGRP_FREEZE , &src->flags) || task->frozen) + src->freezer.nr_tasks_to_freeze--; +if (test_bit (CGRP_FREEZE , &dst->flags) || task->frozen) + dst->freezer.nr_tasks_to_freeze++; + +if (task->frozen) { + src->freezer.nr_frozen_tasks--; + dst->freezer.nr_frozen_tasks++; + }
kernel/cgroup/freezer.c line 189
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ +if (test_bit (CGRP_FREEZE , &src->flags) == +test_bit (CGRP_FREEZE , &dst->flags)) +return ; + +if (lock_task_sighand (task, &flags)) { +if (test_bit (CGRP_FREEZE , &dst->flags)) + task->jobctl |=JOBCTL_TRAP_FREEZE ; +else + task->jobctl &= ~JOBCTL_TRAP_FREEZE ;
How are these flags synchronized?
kernel/cgroup/freezer.c line 187
Quoted 1 line
kernel/cgroup/freezer.c new file +1
-0+signal_wake_up (task, false);
Here, we're using signal_wake_up() for both transitions unlike the earlier spot.
Quoted 79 lines
kernel/cgroup/freezer.c new file +57
-0+unlock_task_sighand (task, &flags); + } +} + +void cgroup_freeze (struct cgroup *cgrp,bool freeze) +{ +struct cgroup_subsys_state *css; +struct cgroup *dsct; +bool applied = false; + +lockdep_assert_held (&cgroup_mutex); + +/* +* Nothing changed? Just exit. +*/ +if (cgrp->freeze == freeze) +return ; + + cgrp->freeze = freeze; + +/* +* Propagate changes downwards the cgroup tree. +*/ +css_for_each_descendant_pre (css, &cgrp->self) { + dsct = css->cgroup; + +if (cgroup_is_dead (dsct)) +continue ; + +if (freeze) { + dsct->freezer.e_freeze++; +/* +* Already frozen because of ancestor's settings? +*/ +if (dsct->freezer.e_freeze >1 ) +continue ; + }else { + dsct->freezer.e_freeze--; +/* +* Still frozen because of ancestor's settings? +*/ +if (dsct->freezer.e_freeze >0 ) +continue ; + +WARN_ON_ONCE (dsct->freezer.e_freeze <0 ); + } + +/* +* Do change actual state: freeze or unfreeze. +*/ +cgroup_do_freeze (dsct, freeze); + applied = true; + } + +if (!applied) +cgroup_file_notify (&cgrp->events_file); +}kernel/ptrace.c +6
-0@@ -400,6 +400,12 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* Kick the process to get it out of the refrigerator. +*/ +if (cgroup_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 +0
-0
kernel/signal.c void task_clear_jobctl_pending(struct task_struct *task, unsigned long mask) · line 333
Quoted 8 lines
kernel/signal.c +5
-0@@ -326,6 +326,11 @@ void task_clear_jobctl_pending(struct task_struct *task, unsigned long mask)if (mask &JOBCTL_STOP_PENDING ) mask |=JOBCTL_STOP_CONSUME |JOBCTL_STOP_DEQUEUED ; +/* +* JOBCTL_TRAP_FREEZE is set and cleared from cgroup side, +* don't touch it here. +*/ + mask &= ~JOBCTL_TRAP_FREEZE ;
It's a bit weird to toggle the bit from here. Can't we do this from the callers?
Quoted 46 lines
kernel/signal.c +17
-12task->jobctl &= ~mask;if (!(task->jobctl &JOBCTL_PENDING_MASK ))@@ -2252,7 +2257,7 @@ static bool do_signal_stop(int signr) }/** -* do_jobctl_trap - take care of ptrace jobctl traps +* do_jobctl_trap - take care of ptrace and cgroup freezer jobctl traps * * When PT_SEIZED, it's used for both group stop and explicit * SEIZE/INTERRUPT traps. Both generate PTRACE_EVENT_STOP trap with @@ -2268,20 +2273,35 @@ static bool do_signal_stop(int signr)*/ static void do_jobctl_trap (void ) { +struct sighand_struct *sighand = current->sighand;struct signal_struct *signal = current->signal;int 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 ) {
kernel/signal.c static bool do_signal_stop(int signr) · line 2304
Quoted 10 lines
kernel/signal.c +10
-0+/* +* Enter the freezer, unless the task is about to exit. +*/ +if (fatal_signal_pending (current)) { + current->jobctl &= ~JOBCTL_TRAP_FREEZE ; + }else { +spin_unlock_irq (&sighand->siglock); +cgroup_freezer_enter (); +spin_lock_irq (&sighand->siglock); + }
We'll need a healthy amount of explanation in terms of how freezer interacts with jobctl stop and ptrace.
Quoted 10 lines
kernel/signal.c +3
-0} }@@ -2403,6 +2423,16 @@ bool get_signal(struct ksignal *ksig) goto relock; } +/* +* If the task is leaving the freezer look, let's notify +* the cgroup and reset the frozen mark.
kernel/signal.c bool get_signal(struct ksignal *ksig) · line 2438
Quoted 10 lines
kernel/signal.c +7
-0+*/ +if (unlikely (cgroup_task_in_freezer (current))) { +spin_unlock_irq (&sighand->siglock); +cgroup_freezer_leave (); +spin_lock_irq (&sighand->siglock); + } + signr =dequeue_signal (current, ¤t->blocked, &ksig->info);if (!signr)
Thanks!
Signature · 1 line
tejun
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 28 lines · Tejun Heo
(cc'ing Linus, Andrew, Ingo for the addition of a new task state)
Hello,
Quoted 9 lines · 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. However the functionality is similar, the interface is different to cgroup v1: it follows cgroup v2 interface principles.
Quoted 13 lines
Key differences are:
1) There is no separate controller: 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.
5) Tasks in a frozen cgroup can be killed.
Quoted 4 lines
I think it'd be worthwhile to explain that tasks are now frozen in a state which is essentially equivalent to the job control stop state but which can only be controlled by the freezer and its interactions with ptrace.
Agreed.
Quoted 53 lines
Oleg, I'd really appreciate if you can review the signal delivery / ptrace parts of the patch.
Quoted 49 lines
include/linux/cgroup-defs.h +27
-0@@ -63,6 +63,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 */ @@ -314,6 +320,27 @@ struct cgroup_rstat_cpu {struct cgroup *updated_next;/* NULL iff not on the list */ }; +struct cgroup_freezer_state { +/* Should the cgroup actually be frozen? +* (protected by cgroup_mutex) +*/ +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; + +/* Used for delayed notifications */ +struct work_struct notify_work; +}; +struct cgroup {/* self css with NULL ->ss, points back to this cgroup */ struct cgroup_subsys_state self;@@ -442,6 +469,12 @@ struct cgroup {/* If there is block congestion on this cgroup. */ atomic_t congestion_count;
Quoted 4 lines
include/linux/cgroup-defs.h struct cgroup { · line 473
Quoted 2 lines
include/linux/cgroup-defs.h +2
-0+/* Should the cgroup and its descendants be frozen. */ +bool freeze;Why not have this in freezer too?
I thought that this variable is just the state of the cgroup.freeze knob, where the freezer field contains the internal state of the freezer, and can in theory be allocated dynamically.
Not a strong preference, I can move it there too, if you prefer to.
Quoted 29 lines
Quoted 29 lines
include/linux/cgroup-defs.h +3
-0+/* 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 +14
-0@@ -871,4 +871,46 @@ static inline void put_cgroup_ns(struct cgroup_namespace *ns)free_cgroup_ns (ns); } +#ifdef CONFIG_CGROUPS + +static inline bool cgroup_frozen (struct task_struct *task) +{ +bool ret; + +rcu_read_lock (); + ret =test_bit (CGRP_FREEZE , &task_dfl_cgroup (task)->flags); +rcu_read_unlock (); + +return ret; +} + +static inline bool cgroup_task_in_freezer (struct task_struct *task)
Quoted 8 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 890
Quoted 3 lines
include/linux/cgroup.h +3
-0+{ +return task->frozen; +}I think the above are pretty confusing.
CGRP_FREEZEis frozen andtask->frozeniscgroup_task_in_freezer()? Can't they be e.g.cgroup_task_freeze()(or freezing, which probably is clearer) andcgroup_task_frozen()?
Yeah, looks good to me, will rename.
Quoted 6 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 893
Quoted 3 lines
include/linux/cgroup.h +2
-0+void cgroup_freezer_enter (void ); +void cgroup_freezer_leave (void );So, if we use freeze, freezing, frozen instead, the aboves can be
cgroup_frozen_enter()andcgroup_frozen_leave()(or begin/end).
Idk, maybe cgroup_enter_frozen()/cgroup_leave_frozen() ?
Quoted 6 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 895
Quoted 3 lines
include/linux/cgroup.h +2
-0+void cgroup_freeze (struct cgroup *cgrp,bool freeze); +void cgroup_notify_frozen_fn (struct work_struct *work);This doesn't need to be exported outside of cgroup proper, right?
Quoted 6 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 899
Quoted 4 lines
include/linux/cgroup.h +4
-0+void cgroup_notify_frozen (struct cgroup *cgrp,bool frozen); +void cgroup_queue_notify_frozen (struct cgroup *cgrp); +void cgroup_freezer_migrate_task (struct task_struct *task,struct cgroup *src, +struct cgroup *dst);Ditto. Do these need to be exposed outside freezer or cgroup proper?
Yeah, makes sense, will remove.
Quoted 24 lines
Quoted 24 lines
include/linux/cgroup.h +16
-0+ +#else /* !CONFIG_CGROUPS */ + +static inline bool cgroup_task_in_freezer (struct task_struct *task) +{ +return false; +} +static inline void cgroup_freezer_enter (void ) { } +static inline void cgroup_freezer_leave (void ) { } +static inline bool cgroup_frozen (struct task_struct *task) +{ +return false; +} + +#endif /* !CONFIG_CGROUPS */ +#endif /* _LINUX_CGROUP_H */ include/linux/sched.h +0
-0@@ -83,7 +83,8 @@ struct task_group;#define TASK_WAKING 0x0200
Quoted 8 lines
include/linux/sched.h struct task_group; · line 87
Quoted 5 lines
include/linux/sched.h +2
-1#define TASK_NOLOAD 0x0400#define TASK_NEW 0x0800 -#define TASK_STATE_MAX 0x1000 +#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000We should also cc [email protected] as this is visible from userland, I think.
Ok.
Quoted 10 lines
include/linux/sched.h struct task_struct { · line 737
Quoted 8 lines
include/linux/sched.h +1
-0/* Convenience macros for the sake of set_current_state: */ #define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)@@ -733,6 +734,8 @@ struct task_struct {#ifdef CONFIG_CGROUPS /* disallow userland-initiated cgroup migration */ unsigned no_cgroup_migration:1; +/* task is in the cgroup freezer loop */ The above comment isn't strictly true, right?
Why so?
It actually means that the task is looping somewhere in the signal delivery loop after entering cgroup_freezer_enter() and before cgroup_freezer_leave().
Maybe simple "task is frozen by the cgroup freezer"?
Quoted 153 lines
Quoted 153 lines
include/linux/sched.h +1
-0+ unsigned frozen:1;#endif #ifdef CONFIG_BLK_CGROUP /* to be used once the psi infrastructure lands upstream. */ include/linux/sched/jobctl.h +4
-1@@ -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,8 +27,10 @@ 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_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY | \ + JOBCTL_TRAP_FREEZE)#define JOBCTL_PENDING_MASK (JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)extern bool task_set_jobctl_pending (struct task_struct *task,unsigned long mask);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.okernel/cgroup/cgroup.c +61
-3@@ -2358,6 +2358,10 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)css_set_move_task (task, from_cset, to_cset, true);put_css_set_locked (from_cset); from_cset->nr_tasks--; + +cgroup_freezer_migrate_task (task, + from_cset->dfl_cgrp, + to_cset->dfl_cgrp); } }spin_unlock_irq (&css_set_lock);@@ -3401,8 +3405,11 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of,static int cgroup_events_show (struct seq_file *seq,void *v) { -seq_printf (seq,"populated %d\n" , -cgroup_is_populated (seq_css (seq)->cgroup)); +struct cgroup *cgrp =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 ; }@@ -3449,6 +3456,40 @@ static int cpu_stat_show(struct seq_file *seq, void *v)return ret; } +static int cgroup_freeze_show (struct seq_file *seq,void *v) +{ +struct cgroup *cgrp =seq_css (seq)->cgroup; + +seq_printf (seq,"%d\n" , cgrp->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) {struct cftype *cft = of->kn->priv;@@ -4574,6 +4615,12 @@ static struct cftype cgroup_base_files[] = { .name = "cgroup.stat", .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", .flags = CFTYPE_NOT_ON_ROOT,@@ -4899,11 +4946,20 @@ static struct cgroup *cgroup_create(struct cgroup *parent)if (ret) goto out_idr_free; +INIT_WORK (&cgrp->freezer.notify_work, cgroup_notify_frozen_fn); + cgrp->freezer.e_freeze = parent->freezer.e_freeze; +if (cgrp->freezer.e_freeze) +set_bit (CGRP_FROZEN , &cgrp->flags); +for (tcgrp = cgrp; tcgrp; tcgrp =cgroup_parent (tcgrp)) { cgrp->ancestor_ids[tcgrp->level] = tcgrp->id; -if (tcgrp != cgrp) +if (tcgrp != cgrp) { tcgrp->nr_descendants++; + +if (cgrp->freezer.e_freeze) + tcgrp->freezer.nr_frozen_descendants++; + } }if (notify_on_release (parent))@@ -5190,6 +5246,8 @@ static int cgroup_destroy_locked(struct cgroup *cgrp)for (tcgrp =cgroup_parent (cgrp); tcgrp; tcgrp =cgroup_parent (tcgrp)) { tcgrp->nr_descendants--; tcgrp->nr_dying_descendants++; +if (cgrp->freezer.e_freeze) + tcgrp->freezer.nr_frozen_descendants--; }
Quoted 12 lines
kernel/cgroup/cgroup.c void cgroup_post_fork(struct task_struct *child) · line 5705
Quoted 8 lines
kernel/cgroup/cgroup.c +3
-0cgroup1_check_for_release (parent);@@ -5642,6 +5700,23 @@ void cgroup_post_fork(struct task_struct *child) cset->nr_tasks++;css_set_move_task (child,NULL , cset, false); } + +if (unlikely (cgroup_frozen (child) && + (child->flags & ~PF_KTHREAD ))) {I don't think we need explicit
PF_KTHREADtest here. We don't allow kthreads in non-root cgroups anyway and if we wanna change that there are a bunch of other things which need updating anyway.
Don't we? I think we do. I've proposed a patch to fix this some time ago
(https://lkml.org/lkml/2017/10/12/556), but was NAKed by Peter.
Quoted 28 lines
Quoted 19 lines
kernel/cgroup/cgroup.c +14
-0+struct cgroup *cgrp; +unsigned long flags; + +if (lock_task_sighand (child, &flags)) { + cgrp = cset->dfl_cgrp; + cgrp->freezer.nr_tasks_to_freeze++; +WARN_ON_ONCE (cgrp->freezer.nr_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks); + child->jobctl |=JOBCTL_TRAP_FREEZE ; +signal_wake_up (child, false); +unlock_task_sighand (child, &flags); + } + } +spin_unlock_irq (&css_set_lock); }kernel/cgroup/cgroup.c void cgroup_exit(struct task_struct *tsk) · line 5770
Quoted 6 lines
kernel/cgroup/cgroup.c +3
-0@@ -5690,6 +5765,24 @@ void cgroup_exit(struct task_struct *tsk)spin_lock_irq (&css_set_lock);css_set_move_task (tsk, cset,NULL , false); cset->nr_tasks--; + +if (unlikely (cgroup_frozen (tsk) && + (tsk->flags & ~PF_KTHREAD ))) {Ditto.
Quoted 7 lines
kernel/cgroup/cgroup.c void cgroup_exit(struct task_struct *tsk) · line 5775
Quoted 5 lines
kernel/cgroup/cgroup.c +5
-0+struct cgroup *frozen_cgrp = cset->dfl_cgrp; + + frozen_cgrp->freezer.nr_tasks_to_freeze--; + +WARN_ON (tsk->frozen);Why not
WARN_ON_ONCEhere too?
My bad, will fix.
Quoted 43 lines
Quoted 43 lines
kernel/cgroup/cgroup.c +10
-0+WARN_ON_ONCE (frozen_cgrp->freezer.nr_tasks_to_freeze < +0 ); +WARN_ON_ONCE (frozen_cgrp->freezer.nr_tasks_to_freeze < + frozen_cgrp->freezer.nr_frozen_tasks); + +if (frozen_cgrp->freezer.nr_frozen_tasks == + frozen_cgrp->freezer.nr_tasks_to_freeze) +cgroup_queue_notify_frozen (frozen_cgrp); + } +spin_unlock_irq (&css_set_lock); }else {get_css_set (cset);kernel/cgroup/freezer.c new file +23
-0@@ -0,0 +1,247 @@ +//SPDX-License-Identifier: GPL-2.0 +#include <linux/cgroup.h> +#include <linux/sched.h> +#include <linux/sched/task.h> +#include <linux/sched/signal.h> + +#include "cgroup-internal.h" + +void cgroup_notify_frozen (struct cgroup *cgrp,bool frozen) +{ +int delta =1 ; + +lockdep_assert_held (&cgroup_mutex); + +/* +* Did we race with fork() or exit()? Np, everything is still frozen. +*/ +if (frozen ==test_bit (CGRP_FROZEN , &cgrp->flags)) +return ; + +if (frozen) +set_bit (CGRP_FROZEN , &cgrp->flags); +else
Quoted 8 lines
kernel/cgroup/freezer.c line 24
Quoted 1 line
kernel/cgroup/freezer.c new file +1
-0+clear_bit (CGRP_FROZEN , &cgrp->flags);I'm not sure this is wrong but it feels a bit weird to tie the actual state transition to notification. Wouldn't it be more straight-forward if
CGRP_FROZENbit is purely determined by whether the tasks are frozen or not and the notification just checks that against the last notified state and generate a notification if they're different?
So, maybe cgroup_notify_frozen() is not the best name, maybe cgroup_propagate_frozen() better reflects what's happening here. We need to recalc the state of ancestor cgroups, and we have to do it with cgroup_mutex held, this is why we do it from the delayed work context (on hot paths).
The first pat of the function can be probably separated and called immediately. Is this what you're suggesting?
Quoted 21 lines
Quoted 21 lines
kernel/cgroup/freezer.c new file +20
-0+cgroup_file_notify (&cgrp->events_file); + +while ((cgrp =cgroup_parent (cgrp))) { +if (frozen) { + cgrp->freezer.nr_frozen_descendants += delta; +if (!test_bit (CGRP_FROZEN , &cgrp->flags) && +test_bit (CGRP_FREEZE , &cgrp->flags) && + cgrp->freezer.nr_frozen_descendants == + cgrp->nr_descendants) { +set_bit (CGRP_FROZEN , &cgrp->flags); +cgroup_file_notify (&cgrp->events_file); + delta++; + } + }else { + cgrp->freezer.nr_frozen_descendants -= delta; +if (test_bit (CGRP_FROZEN , &cgrp->flags)) { +clear_bit (CGRP_FROZEN , &cgrp->flags); +cgroup_file_notify (&cgrp->events_file); + delta++; + }
Quoted 8 lines
kernel/cgroup/freezer.c line 47
Quoted 3 lines
kernel/cgroup/freezer.c new file +3
-0+ } + } +}So that all these state transitions are synchronous with the actual freezing events and we can just queue per-cgroup work items all the way to the top if the new state is different from the last one cgroup-by-cgroup?
Hm, Idk. Why it's better?
Quoted 24 lines
Quoted 24 lines
kernel/cgroup/freezer.c new file +23
-0+void cgroup_notify_frozen_fn (struct work_struct *work) +{ + struct cgroup *cgrp = container_of(work, struct cgroup, + freezer.notify_work); + +mutex_lock (&cgroup_mutex); +cgroup_notify_frozen (cgrp, true); +mutex_unlock (&cgroup_mutex); + +css_put (&cgrp->self); +} + +void cgroup_queue_notify_frozen (struct cgroup *cgrp) +{ +if (work_pending (&cgrp->freezer.notify_work)) +return ; + +css_get (&cgrp->self); +schedule_work (&cgrp->freezer.notify_work); +} + +void cgroup_freezer_enter (void ) +{
Quoted 8 lines
kernel/cgroup/freezer.c line 75
Quoted 4 lines
kernel/cgroup/freezer.c new file +4
-0+long state = current->state; +struct cgroup *cgrp; + +if (!current->frozen) {I think this needs a lot more comments explaining what's going on. It's really subtle that this is where the frozen state is becoming sticky until the task breaks out of the signal delivery path.
Agreed.
Quoted 55 lines
Quoted 8 lines
kernel/cgroup/freezer.c new file +7
-0+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_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks);kernel/cgroup/freezer.c line 92
Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks) +cgroup_queue_notify_frozen (cgrp); +spin_unlock_irq (&css_set_lock); + } + +/* refrigerator */ +set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN ); +clear_thread_flag (TIF_SIGPENDING );The toggling of
TIF_SIGPENDINGneeds explanation too.Quoted 34 lines
kernel/cgroup/freezer.c new file +34
-0+schedule (); +recalc_sigpending (); + +set_current_state (state); +} + +void cgroup_freezer_leave (void ) +{ +struct cgroup *cgrp; + +spin_lock_irq (&css_set_lock); + cgrp =task_dfl_cgroup (current); + cgrp->freezer.nr_frozen_tasks--; +WARN_ON_ONCE (cgrp->freezer.nr_frozen_tasks <0 ); + current->frozen = false; +spin_unlock_irq (&css_set_lock); +} + +static void cgroup_do_freeze (struct cgroup *cgrp,bool freeze) +{ +struct css_task_iter it; +struct task_struct *task; +unsigned long flags; + +lockdep_assert_held (&cgroup_mutex); + +spin_lock_irq (&css_set_lock); +if (freeze) { + cgrp->freezer.nr_tasks_to_freeze =__cgroup_task_count (cgrp); +set_bit (CGRP_FREEZE , &cgrp->flags); + }else { +clear_bit (CGRP_FREEZE , &cgrp->flags); +cgroup_notify_frozen (cgrp, false); + }
Quoted 8 lines
kernel/cgroup/freezer.c line 132
Quoted 6 lines
kernel/cgroup/freezer.c new file +6
-0+spin_unlock_irq (&css_set_lock); + +css_task_iter_start (&cgrp->self,0 , &it); +while ((task =css_task_iter_next (&it))) { +if (task->flags &PF_KTHREAD ) +continue ;
WARN_ON_ONCE?
Unfortunately, no. See comments above.
Quoted 5 lines
kernel/cgroup/freezer.c line 135
Quoted 3 lines
kernel/cgroup/freezer.c new file +2
-0+if (!lock_task_sighand (task, &flags)) +continue ;So, this gotta be a dying task. A comment wouldn't hurt.
Agree.
Quoted 21 lines
kernel/cgroup/freezer.c line 142
Quoted 7 lines
kernel/cgroup/freezer.c new file +6
-0+if (freeze) { + task->jobctl |=JOBCTL_TRAP_FREEZE ; +signal_wake_up (task, false); + }else { + task->jobctl &= ~JOBCTL_TRAP_FREEZE ; +wake_up_process (task);Again, a comment explaining why one's
signal_wake_up()and the other'swake_up_process()would be great.Quoted 10 lines
kernel/cgroup/freezer.c new file +10
-0+ } + +unlock_task_sighand (task, &flags); + } +css_task_iter_end (&it); + +if (freeze && cgrp->nr_descendants == + cgrp->freezer.nr_frozen_descendants) { +spin_lock_irq (&css_set_lock); + WARN_ON_ONCE(cgrp->freezer.nr_tasks_to_freeze <
Quoted 8 lines
kernel/cgroup/freezer.c line 157
Quoted 5 lines
kernel/cgroup/freezer.c new file +5
-0+ cgrp->freezer.nr_frozen_tasks); +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks) +cgroup_notify_frozen (cgrp, true); +spin_unlock_irq (&css_set_lock);I think notification handling would be easier if we separate out state transitions and notifications.
Hm, really? I mean the notification part is really simple: every time we do the state transition (set or clear CGRP_FROZEN flag), we call cgroup_file_notify(). That's it.
I can wrap it to a helper function though, might be looking better. E.g. cgroup_set/clear_frozen(), which will toggle the bit and call cgroup_file_notify() if necessary.
Quoted 27 lines
Quoted 27 lines
kernel/cgroup/freezer.c new file +26
-0+ } +} + +void cgroup_freezer_migrate_task (struct task_struct *task, +struct cgroup *src,struct cgroup *dst) +{ +unsigned long flags; + +lockdep_assert_held (&css_set_lock); + +if (task->flags &PF_KTHREAD ) +return ; + +if (test_bit (CGRP_FREEZE , &src->flags) || task->frozen) + src->freezer.nr_tasks_to_freeze--; +if (test_bit (CGRP_FREEZE , &dst->flags) || task->frozen) + dst->freezer.nr_tasks_to_freeze++; + +if (task->frozen) { + src->freezer.nr_frozen_tasks--; + dst->freezer.nr_frozen_tasks++; + } + +if (test_bit (CGRP_FREEZE , &src->flags) == +test_bit (CGRP_FREEZE , &dst->flags)) +return ;
Quoted 8 lines
kernel/cgroup/freezer.c line 189
Quoted 6 lines
kernel/cgroup/freezer.c new file +6
-0+ +if (lock_task_sighand (task, &flags)) { +if (test_bit (CGRP_FREEZE , &dst->flags)) + task->jobctl |=JOBCTL_TRAP_FREEZE ; +else + task->jobctl &= ~JOBCTL_TRAP_FREEZE ;How are these flags synchronized?
Using the css_set_lock.
Quoted 5 lines
kernel/cgroup/freezer.c line 187
Quoted 2 lines
kernel/cgroup/freezer.c new file +1
-0+signal_wake_up (task, false);Here, we're using
signal_wake_up()for both transitions unlike the earlier spot.
Yeah, let me check it out.
Quoted 83 lines
Quoted 83 lines
kernel/cgroup/freezer.c new file +57
-0+unlock_task_sighand (task, &flags); + } +} + +void cgroup_freeze (struct cgroup *cgrp,bool freeze) +{ +struct cgroup_subsys_state *css; +struct cgroup *dsct; +bool applied = false; + +lockdep_assert_held (&cgroup_mutex); + +/* +* Nothing changed? Just exit. +*/ +if (cgrp->freeze == freeze) +return ; + + cgrp->freeze = freeze; + +/* +* Propagate changes downwards the cgroup tree. +*/ +css_for_each_descendant_pre (css, &cgrp->self) { + dsct = css->cgroup; + +if (cgroup_is_dead (dsct)) +continue ; + +if (freeze) { + dsct->freezer.e_freeze++; +/* +* Already frozen because of ancestor's settings? +*/ +if (dsct->freezer.e_freeze >1 ) +continue ; + }else { + dsct->freezer.e_freeze--; +/* +* Still frozen because of ancestor's settings? +*/ +if (dsct->freezer.e_freeze >0 ) +continue ; + +WARN_ON_ONCE (dsct->freezer.e_freeze <0 ); + } + +/* +* Do change actual state: freeze or unfreeze. +*/ +cgroup_do_freeze (dsct, freeze); + applied = true; + } + +if (!applied) +cgroup_file_notify (&cgrp->events_file); +}kernel/ptrace.c +6
-0@@ -400,6 +400,12 @@ static int ptrace_attach(struct task_struct *task, long request,spin_lock (&task->sighand->siglock); +/* +* Kick the process to get it out of the refrigerator. +*/ +if (cgroup_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 +0
-0@@ -326,6 +326,11 @@ void task_clear_jobctl_pending(struct task_struct *task, unsigned long mask)if (mask &JOBCTL_STOP_PENDING ) mask |=JOBCTL_STOP_CONSUME |JOBCTL_STOP_DEQUEUED ;
Quoted 8 lines
kernel/signal.c void task_clear_jobctl_pending(struct task_struct *task, unsigned long mask) · line 333
Quoted 5 lines
kernel/signal.c +5
-0+/* +* JOBCTL_TRAP_FREEZE is set and cleared from cgroup side, +* don't touch it here. +*/ + mask &= ~JOBCTL_TRAP_FREEZE ;It's a bit weird to toggle the bit from here. Can't we do this from the callers?
Probably, not. But we might exclude JOBCTL_TRAP_FREEZE from the JOBCTL_PENDING_MASK, and it will be enough.
Quoted 52 lines
Quoted 52 lines
kernel/signal.c +22
-12task->jobctl &= ~mask;if (!(task->jobctl &JOBCTL_PENDING_MASK ))@@ -2252,7 +2257,7 @@ static bool do_signal_stop(int signr) }/** -* do_jobctl_trap - take care of ptrace jobctl traps +* do_jobctl_trap - take care of ptrace and cgroup freezer jobctl traps * * When PT_SEIZED, it's used for both group stop and explicit * SEIZE/INTERRUPT traps. Both generate PTRACE_EVENT_STOP trap with @@ -2268,20 +2273,35 @@ static bool do_signal_stop(int signr)*/ static void do_jobctl_trap (void ) { +struct sighand_struct *sighand = current->sighand;struct signal_struct *signal = current->signal;int 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 freezer, unless the task is about to exit. +*/ +if (fatal_signal_pending (current)) { + current->jobctl &= ~JOBCTL_TRAP_FREEZE ;
Quoted 8 lines
kernel/signal.c static bool do_signal_stop(int signr) · line 2304
Quoted 5 lines
kernel/signal.c +5
-0+ }else { +spin_unlock_irq (&sighand->siglock); +cgroup_freezer_enter (); +spin_lock_irq (&sighand->siglock); + }We'll need a healthy amount of explanation in terms of how freezer interacts with jobctl stop and ptrace.
Sure, will add lot more comments, once we'll agree on the actual implementation in the thread with Oleg.
Thank you for the code review!
Tejun Heo
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Hello, Roman.
Quoted 10 lines · Roman Gushchin
Quoted 4 lines
include/linux/cgroup-defs.h struct cgroup { · line 473
Quoted 2 lines
include/linux/cgroup-defs.h +2
-0+/* Should the cgroup and its descendants be frozen. */ +bool freeze;Why not have this in freezer too?
I thought that this variable is just the state of the cgroup.freeze knob, where the freezer field contains the internal state of the freezer, and can in theory be allocated dynamically.
Not a strong preference, I can move it there too, if you prefer to.
Yeah, let's just put it together.
Quoted 7 lines
Quoted 5 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 893
Quoted 2 lines
include/linux/cgroup.h +2
-0+void cgroup_freezer_enter (void ); +void cgroup_freezer_leave (void );So, if we use freeze, freezing, frozen instead, the aboves can be
cgroup_frozen_enter()andcgroup_frozen_leave()(or begin/end).Idk, maybe cgroup_enter_frozen()/cgroup_leave_frozen() ?
Sure.
Quoted 10 lines
Quoted 3 lines
include/linux/sched.h struct task_struct { · line 737
Quoted 1 line
include/linux/sched.h +1
-0+/* task is in the cgroup freezer loop */ The above comment isn't strictly true, right?
Why so?
It actually means that the task is looping somewhere in the signal delivery loop after entering
cgroup_freezer_enter()and beforecgroup_freezer_leave().Maybe simple "task is frozen by the cgroup freezer"?
Yeah, sounds good.
Quoted 12 lines
Quoted 11 lines
kernel/cgroup/cgroup.c void cgroup_post_fork(struct task_struct *child) · line 5705
Quoted 7 lines
kernel/cgroup/cgroup.c +3
-0@@ -5642,6 +5700,23 @@ void cgroup_post_fork(struct task_struct *child) cset->nr_tasks++;css_set_move_task (child,NULL , cset, false); } + +if (unlikely (cgroup_frozen (child) && + (child->flags & ~PF_KTHREAD ))) {I don't think we need explicit
PF_KTHREADtest here. We don't allow kthreads in non-root cgroups anyway and if we wanna change that there are a bunch of other things which need updating anyway.
Quoted 2 lines
Don't we? I think we do. I've proposed a patch to fix this some time ago
(https://lkml.org/lkml/2017/10/12/556), but was NAKed by Peter.
Ah, right, I thought that went in. Oh well, let's keep the test then.
Quoted 18 lines
Quoted 9 lines
Quoted 9 lines
kernel/cgroup/freezer.c new file +9
-0+/* +* Did we race with fork() or exit()? Np, everything is still frozen. +*/ +if (frozen ==test_bit (CGRP_FROZEN , &cgrp->flags)) +return ; + +if (frozen) +set_bit (CGRP_FROZEN , &cgrp->flags); +else Quoted 8 lines
Review void cgroup_post_fork(struct task_struct *child) · line 5715
Quoted 1 line
(quoted diff) +1
-0+ clear_bit(CGRP_FROZEN, &cgrp->flags);I'm not sure this is wrong but it feels a bit weird to tie the actual state transition to notification. Wouldn't it be more straight-forward if
CGRP_FROZENbit is purely determined by whether the tasks are frozen or not and the notification just checks that against the last notified state and generate a notification if they're different?
Quoted 5 lines
So, maybe
cgroup_notify_frozen()is not the best name, maybecgroup_propagate_frozen()better reflects what's happening here. We need to recalc the state of ancestor cgroups, and we have to do it withcgroup_mutexheld, this is why we do it from the delayed work context (on hot paths).
Can't we protect that state with css_set_lock too? That's what task states are protected by and the cgroup state is a mere aggregation of task states.
Quoted 2 lines
The first pat of the function can be probably separated and called immediately. Is this what you're suggesting?
Pretty much. I think separating out state transitions and notifications would make it more straightforward.
Quoted 6 lines
Quoted 4 lines
So that all these state transitions are synchronous with the actual freezing events and we can just queue per-cgroup work items all the way to the top if the new state is different from the last one cgroup-by-cgroup?
Hm, Idk. Why it's better?
So, the pieces are - 1. task states, 2. cgroup states and
3. notifications. The current code ties together #2 and #3 together which is weird because #2 is a mere aggregation of #1. Also, that way, notifications become a lot more robust because whether to generate a notification or not can be solely determined from #2 flipping. ie. sth like the following
change_task_frozen_state()
{
update counters
if (cgroup state needs to change) {
change cgroup state;
queue notification work;
repeat for the parent;
}
}where notification work always notifies should work and trivially satisfies the requirement (there should be at least one notification since the last state transition) without any further work. Wouldn't this be easier and more robust? The current code depends on annotating each possible transition event, which is kinda fragile.
Quoted 9 lines
Quoted 7 lines
kernel/cgroup/freezer.c line 189
Quoted 5 lines
kernel/cgroup/freezer.c new file +5
-0+if (lock_task_sighand (task, &flags)) { +if (test_bit (CGRP_FREEZE , &dst->flags)) + task->jobctl |=JOBCTL_TRAP_FREEZE ; +else + task->jobctl &= ~JOBCTL_TRAP_FREEZE ;How are these flags synchronized?
Using the
css_set_lock.
But other JOBCTL_TRAP bits aren't synchronized by css_set_lock, right?
Thanks.
Signature · 1 line
tejun
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 80 lines · Tejun Heo
Hello, Roman.
Quoted 10 lines · Roman Gushchin
Quoted 4 lines
include/linux/cgroup-defs.h struct cgroup { · line 473
Quoted 2 lines
include/linux/cgroup-defs.h +2
-0+/* Should the cgroup and its descendants be frozen. */ +bool freeze;Why not have this in freezer too?
I thought that this variable is just the state of the cgroup.freeze knob, where the freezer field contains the internal state of the freezer, and can in theory be allocated dynamically.
Not a strong preference, I can move it there too, if you prefer to.
Yeah, let's just put it together.
Quoted 7 lines
Quoted 5 lines
include/linux/cgroup.h static inline void put_cgroup_ns(struct cgroup_namespace *ns) · line 893
Quoted 2 lines
include/linux/cgroup.h +2
-0+void cgroup_freezer_enter (void ); +void cgroup_freezer_leave (void );So, if we use freeze, freezing, frozen instead, the aboves can be
cgroup_frozen_enter()andcgroup_frozen_leave()(or begin/end).Idk, maybe cgroup_enter_frozen()/cgroup_leave_frozen() ?
Sure.
Quoted 10 lines
Quoted 3 lines
include/linux/sched.h struct task_struct { · line 737
Quoted 1 line
include/linux/sched.h +1
-0+/* task is in the cgroup freezer loop */ The above comment isn't strictly true, right?
Why so?
It actually means that the task is looping somewhere in the signal delivery loop after entering
cgroup_freezer_enter()and beforecgroup_freezer_leave().Maybe simple "task is frozen by the cgroup freezer"?
Yeah, sounds good.
Quoted 12 lines
Quoted 11 lines
kernel/cgroup/cgroup.c void cgroup_post_fork(struct task_struct *child) · line 5705
Quoted 7 lines
kernel/cgroup/cgroup.c +3
-0@@ -5642,6 +5700,23 @@ void cgroup_post_fork(struct task_struct *child) cset->nr_tasks++;css_set_move_task (child,NULL , cset, false); } + +if (unlikely (cgroup_frozen (child) && + (child->flags & ~PF_KTHREAD ))) {I don't think we need explicit
PF_KTHREADtest here. We don't allow kthreads in non-root cgroups anyway and if we wanna change that there are a bunch of other things which need updating anyway.Quoted 2 lines
Don't we? I think we do. I've proposed a patch to fix this some time ago
(https://lkml.org/lkml/2017/10/12/556), but was NAKed by Peter.Ah, right, I thought that went in. Oh well, let's keep the test then.
Quoted 18 lines
Quoted 9 lines
Quoted 9 lines
kernel/cgroup/freezer.c new file +9
-0+/* +* Did we race with fork() or exit()? Np, everything is still frozen. +*/ +if (frozen ==test_bit (CGRP_FROZEN , &cgrp->flags)) +return ; + +if (frozen) +set_bit (CGRP_FROZEN , &cgrp->flags); +else Quoted 8 lines
Review void cgroup_post_fork(struct task_struct *child) · line 5715
Quoted 1 line
(quoted diff) +1
-0+ clear_bit(CGRP_FROZEN, &cgrp->flags);I'm not sure this is wrong but it feels a bit weird to tie the actual state transition to notification. Wouldn't it be more straight-forward if
CGRP_FROZENbit is purely determined by whether the tasks are frozen or not and the notification just checks that against the last notified state and generate a notification if they're different?Quoted 5 lines
So, maybe
cgroup_notify_frozen()is not the best name, maybecgroup_propagate_frozen()better reflects what's happening here. We need to recalc the state of ancestor cgroups, and we have to do it withcgroup_mutexheld, this is why we do it from the delayed work context (on hot paths).
Quoted 3 lines
Can't we protect that state with
css_set_locktoo? That's what task states are protected by and the cgroup state is a mere aggregation of task states.
See below.
Quoted 31 lines
Quoted 3 lines
The first pat of the function can be probably separated and called immediately. Is this what you're suggesting?
Pretty much. I think separating out state transitions and notifications would make it more straightforward.
Quoted 6 lines
Quoted 4 lines
So that all these state transitions are synchronous with the actual freezing events and we can just queue per-cgroup work items all the way to the top if the new state is different from the last one cgroup-by-cgroup?
Hm, Idk. Why it's better?
So, the pieces are - 1. task states, 2. cgroup states and
3. notifications. The current code ties together #2 and #3 together which is weird because #2 is a mere aggregation of #1. Also, that way, notifications become a lot more robust because whether to generate a notification or not can be solely determined from #2 flipping. ie. sth like the followingchange_task_frozen_state() { update counters if (cgroup state needs to change) { change cgroup state; queue notification work; repeat for the parent; } }
Quoted 5 lines
where notification work always notifies should work and trivially satisfies the requirement (there should be at least one notification since the last state transition) without any further work. Wouldn't this be easier and more robust? The current code depends on annotating each possible transition event, which is kinda fragile.
Ok, it looks like I can additionally synchronize descendant counters using css_set_lock, and then eliminate the whole thing with delayed transitions/notifications.
Will do in v3.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
kernel/cgroup/freezer.c line 189
Quoted 6 lines
kernel/cgroup/freezer.c new file +5
-0+if (lock_task_sighand (task, &flags)) { +if (test_bit (CGRP_FREEZE , &dst->flags)) + task->jobctl |=JOBCTL_TRAP_FREEZE ; +else + task->jobctl &= ~JOBCTL_TRAP_FREEZE ;How are these flags synchronized?
Quoted 3 lines
Quoted 1 line
Using the
css_set_lock.But other
JOBCTL_TRAPbits aren't synchronized bycss_set_lock, right?
But if we don't touch this bit anywhere else, should be fine, right?
Thanks!
Tejun Heo
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 8 lines · Roman Gushchin
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
kernel/cgroup/freezer.c line 189
Quoted 5 lines
kernel/cgroup/freezer.c new file +5
-0+if (lock_task_sighand (task, &flags)) { +if (test_bit (CGRP_FREEZE , &dst->flags)) + task->jobctl |=JOBCTL_TRAP_FREEZE ; +else + task->jobctl &= ~JOBCTL_TRAP_FREEZE ;How are these flags synchronized?
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Using the
css_set_lock.But other
JOBCTL_TRAPbits aren't synchronized bycss_set_lock, right?But if we don't touch this bit anywhere else, should be fine, right?
But other JOBCTL_TRAP_ bits aren't synchronized with css_set_lock, so they can be updated (read-modify-write) concurrently and clobber each other, no?
Thanks.
Signature · 1 line
tejun
Oleg Nesterov
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
In reply to Roman GushchinQuoted 4 lines · Roman Gushchin
This patch implements freezer for cgroup v2. However the functionality is similar, the interface is different to cgroup v1: it follows cgroup v2 interface principles.
Oh, it seems that I actually need to apply this patch to (try to) understand the details ;) Will try tomorrow.
Quoted 11 lines
include/linux/sched/jobctl.h +1
-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)
include/linux/sched/jobctl.h struct task_struct; · line 33
Quoted 8 lines
include/linux/sched/jobctl.h +3
-1@@ -26,8 +27,10 @@ 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_TRAP_MASK (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY | \ + JOBCTL_TRAP_FREEZE)
Again, I didn't actually read the patch yet, but my gut feeling tells me we shouldn't change JOBCTL_TRAP_MASK... and the fact you had to change task_clear_jobctl_pending() to filter out JOBCTL_TRAP_FREEZE bit may be proves this.
This
if (current->jobctl & (JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)) ... else if (current->jobctl & JOBCTL_TRAP_FREEZE)
code in do_jobctl_trap() doesn't look nice too.
OK, please forget for now, but perhaps it would be more clean to add JOBCTL_TRAP_FREEZE to the JOBCTL_PENDING_MASK check in recalc_sigpending() and change get_signal to check JOBCTL_TRAP_MASK | JOBCTL_TRAP_FREEZE; and I am not even sure cgroup_freezer_enter() should live in do_jobctl_trap().
include/linux/sched/jobctl.h void cgroup_post_fork(struct task_struct *child) · line 5709
Quoted 11 lines
include/linux/sched/jobctl.h +7
-0@@ -5642,6 +5700,23 @@ void cgroup_post_fork(struct task_struct *child) cset->nr_tasks++;css_set_move_task (child,NULL , cset, false); } + +if (unlikely (cgroup_frozen (child) && + (child->flags & ~PF_KTHREAD ))) { +struct cgroup *cgrp; +unsigned long flags; + +if (lock_task_sighand (child, &flags)) {
You can just do spin_lock_irq(siglock). The new child can't go away until wake_up_new_task(), otherwise any usage of "child" including lock_task_sighand() was not safe.
include/linux/sched/jobctl.h void cgroup_post_fork(struct task_struct *child) · line 5715
Quoted 6 lines
include/linux/sched/jobctl.h +6
-0+ cgrp = cset->dfl_cgrp; + cgrp->freezer.nr_tasks_to_freeze++; +WARN_ON_ONCE (cgrp->freezer.nr_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks); + child->jobctl |=JOBCTL_TRAP_FREEZE ; +signal_wake_up (child, false);
signal_wake_up() is pointless.
wake_up_process() has no effect, set_tsk_thread_flag(TIF_SIGPENDING) is not needed because schedule_tail() does calculate_sigpending() which should notice JOBCTL_TRAP_FREEZE.
include/linux/sched/jobctl.h void cgroup_post_fork(struct task_struct *child) · line 5721
Quoted 6 lines
include/linux/sched/jobctl.h +6
-0+ }else if (current->jobctl &JOBCTL_TRAP_FREEZE ) { +/* +* Enter the freezer, unless the task is about to exit. +*/ +if (fatal_signal_pending (current)) { + current->jobctl &= ~JOBCTL_TRAP_FREEZE ;
And again, please note that we need this because task_clear_jobctl_pending() drops JOBCTL_TRAP_FREEZE. It shouldn't, I think...
Oleg.
Tejun Heo
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Hello, Oleg.
Quoted 8 lines · Oleg Nesterov
Quoted 4 lines · Roman Gushchin
This patch implements freezer for cgroup v2. However the functionality is similar, the interface is different to cgroup v1: it follows cgroup v2 interface principles.
Oh, it seems that I actually need to apply this patch to (try to) understand the details ;) Will try tomorrow.
Yeah, it's a bit of a head spin like everything in signal delivery / ptrace paths.
...
Quoted 4 lines
OK, please forget for now, but perhaps it would be more clean to add
JOBCTL_TRAP_FREEZEto theJOBCTL_PENDING_MASKcheck inrecalc_sigpending()and changeget_signalto checkJOBCTL_TRAP_MASK|JOBCTL_TRAP_FREEZE; and I am not even surecgroup_freezer_enter()should live indo_jobctl_trap().
I'm sure you're aware of the context but just to refresh - one thing which was really broken about cgroup1 freezer was that it piggybacked on hibernation freezer and put frozen tasks in a state which is undefined when seen from userspace - they're just stuck in D sleep somewhere in the kernel. That's fine when the whole system is not gonna be running, but not when only a subportion is being frozen.
So, the primary goal of cgroup2 freezer is putting the tasks in an equivalent state as jobctl stop. It's a jobctl stop but controlled by cgroup frozen state, meaning that they can be killed, PTRACE_SEIZE'd and INTERRUPT'ed (PTRACE_ATTACH doesn't work as signal delivery should be blocked but that's fine) and so on.
Thanks.
Signature · 1 line
tejun
Oleg Nesterov
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Hi Tejun,
Quoted 6 lines · Tejun Heo
Quoted 5 lines
OK, please forget for now, but perhaps it would be more clean to add
JOBCTL_TRAP_FREEZEto theJOBCTL_PENDING_MASKcheck inrecalc_sigpending()and changeget_signalto checkJOBCTL_TRAP_MASK|JOBCTL_TRAP_FREEZE; and I am not even surecgroup_freezer_enter()should live indo_jobctl_trap().
Quoted 6 lines
I'm sure you're aware of the context but just to refresh - one thing which was really broken about cgroup1 freezer was that it piggybacked on hibernation freezer and put frozen tasks in a state which is undefined when seen from userspace - they're just stuck in D sleep somewhere in the kernel. That's fine when the whole system is not gonna be running, but not when only a subportion is being frozen.
Thanks, I see.
Quoted 5 lines
So, the primary goal of cgroup2 freezer is putting the tasks in an equivalent state as jobctl stop. It's a jobctl stop but controlled by cgroup frozen state, meaning that they can be killed, PTRACE_SEIZE'd and INTERRUPT'ed (
PTRACE_ATTACHdoesn't work as signal delivery should be blocked but that's fine) and so on.
And I agree, JOBCTL_TRAP_FREEZE looks fine.
Just somehow I _feel_ that we can improve this logic a bit, but let me repeat that of course I can be easily wrong and I didn't even read the patch yet.
Oleg.
Oleg Nesterov
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
In reply to Roman Gushchininclude/linux/sched.h struct task_group; · line 87
Quoted 10 lines · Roman Gushchin
include/linux/sched.h +2
-1@@ -83,7 +83,8 @@ struct task_group;#define TASK_WAKING 0x0200#define TASK_NOLOAD 0x0400#define TASK_NEW 0x0800 -#define TASK_STATE_MAX 0x1000 +#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000
Just noticed the new task state... Why? Can't we avoid it?
...
include/linux/sched.h struct task_group; · line 90
Quoted 3 lines
include/linux/sched.h +3
-0+void cgroup_freezer_enter (void ) +{ +long state = current->state;
Why? it must be TASK_RUNNING?
If not set_current_state() at the end is simply wrong... Yes, __refrigerator() does this, but at least it has a reason although it is wrong too.
Quoted 9 lines
include/linux/sched.h +9
-0+struct cgroup *cgrp; + +if (!current->frozen) { +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_tasks_to_freeze <
include/linux/sched.h struct task_group;
Quoted 10 lines
include/linux/sched.h +10
-0+ cgrp->freezer.nr_frozen_tasks); + +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks) +cgroup_queue_notify_frozen (cgrp); +spin_unlock_irq (&css_set_lock); + } + +/* refrigerator */ +set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN );
Why not __set_current_state() ?
If ->state include TASK_INTERRUPTIBLE, why do we need TASK_WAKEKILL?
And again, why TASK_FROZEN?
include/linux/sched.h
Quoted 3 lines
include/linux/sched.h +3
-0+clear_thread_flag (TIF_SIGPENDING ); +schedule (); +recalc_sigpending ();
I simply can't understand these 3 lines above but I bet this is not correct ;)
if nothing else recalc_sigpending() without ->siglock is wrong, it can race with signal_wakeup/etc.
include/linux/sched.h
Quoted 1 line
include/linux/sched.h +1
-0+set_current_state (state);
see above...
Oleg.
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Hi Oleg!
Quoted 13 lines · Oleg Nesterov
include/linux/sched.h struct task_group; · line 87
Quoted 10 lines · Roman Gushchin
include/linux/sched.h +2
-1@@ -83,7 +83,8 @@ struct task_group;#define TASK_WAKING 0x0200#define TASK_NOLOAD 0x0400#define TASK_NEW 0x0800 -#define TASK_STATE_MAX 0x1000 +#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000Just noticed the new task state... Why? Can't we avoid it?
We can, but it's nice to show to userspace that tasks are frozen, rather than just stuck somewhere in the kernel...
Quoted 25 lines
...
include/linux/sched.h
Quoted 3 lines
include/linux/sched.h +3
-0+void cgroup_freezer_enter (void ) +{ +long state = current->state;Why? it must be
TASK_RUNNING?If not
set_current_state()at the end is simply wrong... Yes,__refrigerator()does this, but at least it has a reason although it is wrong too.Quoted 13 lines
include/linux/sched.h +13
-0+struct cgroup *cgrp; + +if (!current->frozen) { +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_tasks_to_freeze < + cgrp->freezer.nr_frozen_tasks); + +if (cgrp->freezer.nr_tasks_to_freeze == + cgrp->freezer.nr_frozen_tasks)
Quoted 8 lines
include/linux/sched.h
Quoted 6 lines
include/linux/sched.h +6
-0+cgroup_queue_notify_frozen (cgrp); +spin_unlock_irq (&css_set_lock); + } + +/* refrigerator */ +set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN );Why not
__set_current_state()?
Hm, it's not a hot path at all, so set_current_state() is good enough. Not a strong preference, of course.
Quoted 4 lines
If ->state include
TASK_INTERRUPTIBLE, why do we needTASK_WAKEKILL?And again, why
TASK_FROZEN?
So, should it be just TASK_INTERRUPTIBLE | TASK_FROZEN ?
Quoted 6 lines
include/linux/sched.h
Quoted 4 lines
include/linux/sched.h +3
-0+clear_thread_flag (TIF_SIGPENDING ); +schedule (); +recalc_sigpending ();I simply can't understand these 3 lines above but I bet this is not correct ;)
So, yeah, the problem is that if there is TIF_SIGPENDING bit set, schedule() will return immediately, so we're getting pretty much a busy loop here. This is a nasty workaround.
I believe we can clear and not call recalc_sigpending() at all. Does this seem to be correct?
Quoted 7 lines
if nothing else
recalc_sigpending()without ->siglock is wrong, it can race with signal_wakeup/etc.include/linux/sched.h
Quoted 1 line
include/linux/sched.h +1
-0+set_current_state (state);see above...
Thank you for the review!
And looking forward for more comments from you!
Oleg Nesterov
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Hi Roman,
Quoted 8 lines · Roman Gushchin
Quoted 5 lines
include/linux/sched.h struct task_group; · line 87
Quoted 3 lines
include/linux/sched.h +2
-0+#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000Just noticed the new task state... Why? Can't we avoid it?
We can, but it's nice to show to userspace that tasks are frozen, rather than just stuck somewhere in the kernel...
But then you need to change get_task_state() too. Which iiuc could probably check ->frozen along with ->state.
I do not think the new task state is a good idea, at least I would like to ask you to make a separate patch which we can discuss separately.
Quoted 6 lines
Quoted 3 lines
kernel/cgroup/freezer.c line 91
Quoted 1 line
kernel/cgroup/freezer.c new file +1
-0+set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN );Why not
__set_current_state()?Hm, it's not a hot path at all, so
set_current_state()is good enough. Not a strong preference, of course.
It is not about performance, to me set_current_state() looks as if we need a memory barrier for some obscure/undocumented reason and this doesn't help to understand the code.
Quoted 5 lines
Quoted 3 lines
If ->state include
TASK_INTERRUPTIBLE, why do we needTASK_WAKEKILL?And again, why
TASK_FROZEN?So, should it be just
TASK_INTERRUPTIBLE|TASK_FROZEN?
Again, TASK_FROZEN is pointless at least until you change fs/proc or until you have wake_up_state(TASK_FROZEN). May be cgroup_do_freeze() and/or ptrace_attach() could use it, but see above, I'd suggest to make another patch.
Looks like you need TASK_KILLABLE, see below.
Quoted 8 lines
Quoted 5 lines
kernel/cgroup/freezer.c line 94
Quoted 3 lines
kernel/cgroup/freezer.c new file +3
-0+clear_thread_flag (TIF_SIGPENDING ); +schedule (); +recalc_sigpending ();I simply can't understand these 3 lines above but I bet this is not correct ;)
So, yeah, the problem is that if there is
TIF_SIGPENDINGbit set,schedule()will return immediately, so we're getting pretty much a busy loop here.
I suspected this answer ;)
Quoted 1 line
This is a nasty workaround.
No, this is very wrong. Just suppose the caller is killed right before clear_thread_flag(TIF_SIGPENDING).
Quoted 2 lines
I believe we can clear and not call
recalc_sigpending()at all. Does this seem to be correct?
I think you need to simply remove both clear_thread_flag() and recalc_sigpending(). If schedule() is called in TASK_KILLABLE state it will return only if fatal_signal_pending() is true, and this is what we want, right?
OK, it seems you are going to make the new version anyway, so I can wait for it and not read this series ;)
Oleg.
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 9 lines · Oleg Nesterov
Hi Roman,
Quoted 6 lines · Roman Gushchin
Quoted 5 lines
include/linux/sched.h struct task_group; · line 87
Quoted 3 lines
include/linux/sched.h +2
-0+#define TASK_FROZEN 0x1000 +#define TASK_STATE_MAX 0x2000Just noticed the new task state... Why? Can't we avoid it?
Quoted 8 lines
Quoted 2 lines
We can, but it's nice to show to userspace that tasks are frozen, rather than just stuck somewhere in the kernel...
But then you need to change
get_task_state()too. Which iiuc could probably check ->frozen along with ->state.I do not think the new task state is a good idea, at least I would like to ask you to make a separate patch which we can discuss separately.
Yeah, I have separated it in v3.
Quoted 35 lines
Quoted 8 lines
Quoted 5 lines
kernel/cgroup/freezer.c line 91
Quoted 3 lines
kernel/cgroup/freezer.c new file +1
-0+set_current_state (TASK_WAKEKILL |TASK_INTERRUPTIBLE |TASK_FROZEN );Why not
__set_current_state()?Hm, it's not a hot path at all, so
set_current_state()is good enough. Not a strong preference, of course.It is not about performance, to me
set_current_state()looks as if we need a memory barrier for some obscure/undocumented reason and this doesn't help to understand the code.Quoted 5 lines
Quoted 3 lines
If ->state include
TASK_INTERRUPTIBLE, why do we needTASK_WAKEKILL?And again, why
TASK_FROZEN?So, should it be just
TASK_INTERRUPTIBLE|TASK_FROZEN?Again,
TASK_FROZENis pointless at least until you change fs/proc or until you havewake_up_state(TASK_FROZEN). May becgroup_do_freeze()and/orptrace_attach()could use it, but see above, I'd suggest to make another patch.Looks like you need
TASK_KILLABLE, see below.Quoted 8 lines
Quoted 5 lines
kernel/cgroup/freezer.c line 94
Quoted 3 lines
kernel/cgroup/freezer.c new file +3
-0+clear_thread_flag (TIF_SIGPENDING ); +schedule (); +recalc_sigpending ();I simply can't understand these 3 lines above but I bet this is not correct ;)
So, yeah, the problem is that if there is
TIF_SIGPENDINGbit set,schedule()will return immediately, so we're getting pretty much a busy loop here.
Quoted 6 lines
I suspected this answer ;)
Quoted 1 line
This is a nasty workaround.
No, this is very wrong. Just suppose the caller is killed right before
clear_thread_flag(TIF_SIGPENDING).
So, I had TASK_KILLABLE before, but had some issues with ptrace/gdb. I'll revisit this option.
Quoted 10 lines
Quoted 3 lines
I believe we can clear and not call
recalc_sigpending()at all. Does this seem to be correct?I think you need to simply remove both
clear_thread_flag()andrecalc_sigpending(). Ifschedule()is called inTASK_KILLABLEstate it will return only iffatal_signal_pending()is true, and this is what we want, right?OK, it seems you are going to make the new version anyway, so I can wait for it and not read this series ;)
Sure! I'm about to post it.
Thanks!
Oleg Nesterov
maintainerRe: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 6 lines · Roman Gushchin
Quoted 3 lines
No, this is very wrong. Just suppose the caller is killed right before
clear_thread_flag(TIF_SIGPENDING).So, I had
TASK_KILLABLEbefore, but had some issues with ptrace/gdb. I'll revisit this option.
Yes, ptrace_signal_wake_up() won't wake a TASK_KILLABLE tracee up...
BTW. Could you explain in the changelog how should a frozen task interact with ptrace? 0/6 says "ptrace works" but that is all. It is not clear to me what do we actually want wrt ptrace...
Oleg.
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
Quoted 5 lines · Oleg Nesterov
Quoted 4 lines · Roman Gushchin
Quoted 3 lines
No, this is very wrong. Just suppose the caller is killed right before
clear_thread_flag(TIF_SIGPENDING).
Quoted 8 lines
Quoted 2 lines
So, I had
TASK_KILLABLEbefore, but had some issues with ptrace/gdb. I'll revisit this option.Yes,
ptrace_signal_wake_up()won't wake aTASK_KILLABLEtracee up...BTW. Could you explain in the changelog how should a frozen task interact with ptrace? 0/6 says "ptrace works" but that is all. It is not clear to me what do we actually want wrt ptrace...
Sure, will add.
Basically, I want some minimal functionality to work properly. E.g. attach to a frozen task, get some info, detach.
Re: [PATCH v2 3/6] cgroup: cgroup v2 freezer
In reply to Oleg NesterovQuoted 4 lines · Oleg Nesterov
Hi Roman,
OK, it seems you are going to make the new version anyway, so I can wait for it and not read this series ;)
Hi Oleg!
Can you, please, take a look at v3, which I posted recently?
https://lkml.org/lkml/2018/11/17/193
Thank you!
PATCH v2 4/6 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 v2 5/6 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 v2 6/6 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 | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
Documentation/admin-guide/cgroup-v2.rst +26 -0
@@ -862,6 +862,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".
@@ -895,6 +897,30 @@ 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 the process + is complete, the "frozen" value in the cgroup.events control file + will be updated and the corresponding notification will be issued. + + Cgroup can be frozen either by its own settings, either 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, either if freezing of the cgroup races with fork(). + If a cgroup is moved to a frozen cgroup, it stops. If a process is + moving 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