]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - kernel/cgroup_freezer.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[karo-tx-linux.git] / kernel / cgroup_freezer.c
index 975b3d8732f40aab988edf42c5c8d03dd9add86e..75dda1ea5026fdf7f8d179f31b5217348b501e0f 100644 (file)
 #include <linux/freezer.h>
 #include <linux/seq_file.h>
 
-enum freezer_state {
-       CGROUP_THAWED = 0,
-       CGROUP_FREEZING,
-       CGROUP_FROZEN,
+/*
+ * A cgroup is freezing if any FREEZING flags are set.  FREEZING_SELF is
+ * set if "FROZEN" is written to freezer.state cgroupfs file, and cleared
+ * for "THAWED".  FREEZING_PARENT is set if the parent freezer is FREEZING
+ * for whatever reason.  IOW, a cgroup has FREEZING_PARENT set if one of
+ * its ancestors has FREEZING_SELF set.
+ */
+enum freezer_state_flags {
+       CGROUP_FREEZER_ONLINE   = (1 << 0), /* freezer is fully online */
+       CGROUP_FREEZING_SELF    = (1 << 1), /* this freezer is freezing */
+       CGROUP_FREEZING_PARENT  = (1 << 2), /* the parent freezer is freezing */
+       CGROUP_FROZEN           = (1 << 3), /* this and its descendants frozen */
+
+       /* mask for all FREEZING flags */
+       CGROUP_FREEZING         = CGROUP_FREEZING_SELF | CGROUP_FREEZING_PARENT,
 };
 
 struct freezer {
        struct cgroup_subsys_state      css;
-       enum freezer_state              state;
+       unsigned int                    state;
        spinlock_t                      lock;
 };
 
@@ -46,14 +57,21 @@ static inline struct freezer *task_freezer(struct task_struct *task)
                            struct freezer, css);
 }
 
+static struct freezer *parent_freezer(struct freezer *freezer)
+{
+       struct cgroup *pcg = freezer->css.cgroup->parent;
+
+       if (pcg)
+               return cgroup_freezer(pcg);
+       return NULL;
+}
+
 bool cgroup_freezing(struct task_struct *task)
 {
-       enum freezer_state state;
        bool ret;
 
        rcu_read_lock();
-       state = task_freezer(task)->state;
-       ret = state == CGROUP_FREEZING || state == CGROUP_FROZEN;
+       ret = task_freezer(task)->state & CGROUP_FREEZING;
        rcu_read_unlock();
 
        return ret;
@@ -63,26 +81,18 @@ bool cgroup_freezing(struct task_struct *task)
  * cgroups_write_string() limits the size of freezer state strings to
  * CGROUP_LOCAL_BUFFER_SIZE
  */
-static const char *freezer_state_strs[] = {
-       "THAWED",
-       "FREEZING",
-       "FROZEN",
+static const char *freezer_state_strs(unsigned int state)
+{
+       if (state & CGROUP_FROZEN)
+               return "FROZEN";
+       if (state & CGROUP_FREEZING)
+               return "FREEZING";
+       return "THAWED";
 };
 
-/*
- * State diagram
- * Transitions are caused by userspace writes to the freezer.state file.
- * The values in parenthesis are state labels. The rest are edge labels.
- *
- * (THAWED) --FROZEN--> (FREEZING) --FROZEN--> (FROZEN)
- *    ^ ^                    |                     |
- *    | \_______THAWED_______/                     |
- *    \__________________________THAWED____________/
- */
-
 struct cgroup_subsys freezer_subsys;
 
-static struct cgroup_subsys_state *freezer_create(struct cgroup *cgroup)
+static struct cgroup_subsys_state *freezer_css_alloc(struct cgroup *cgroup)
 {
        struct freezer *freezer;
 
@@ -91,17 +101,69 @@ static struct cgroup_subsys_state *freezer_create(struct cgroup *cgroup)
                return ERR_PTR(-ENOMEM);
 
        spin_lock_init(&freezer->lock);
-       freezer->state = CGROUP_THAWED;
        return &freezer->css;
 }
 
-static void freezer_destroy(struct cgroup *cgroup)
+/**
+ * freezer_css_online - commit creation of a freezer cgroup
+ * @cgroup: cgroup being created
+ *
+ * We're committing to creation of @cgroup.  Mark it online and inherit
+ * parent's freezing state while holding both parent's and our
+ * freezer->lock.
+ */
+static int freezer_css_online(struct cgroup *cgroup)
 {
        struct freezer *freezer = cgroup_freezer(cgroup);
+       struct freezer *parent = parent_freezer(freezer);
 
-       if (freezer->state != CGROUP_THAWED)
+       /*
+        * The following double locking and freezing state inheritance
+        * guarantee that @cgroup can never escape ancestors' freezing
+        * states.  See cgroup_for_each_descendant_pre() for details.
+        */
+       if (parent)
+               spin_lock_irq(&parent->lock);
+       spin_lock_nested(&freezer->lock, SINGLE_DEPTH_NESTING);
+
+       freezer->state |= CGROUP_FREEZER_ONLINE;
+
+       if (parent && (parent->state & CGROUP_FREEZING)) {
+               freezer->state |= CGROUP_FREEZING_PARENT | CGROUP_FROZEN;
+               atomic_inc(&system_freezing_cnt);
+       }
+
+       spin_unlock(&freezer->lock);
+       if (parent)
+               spin_unlock_irq(&parent->lock);
+
+       return 0;
+}
+
+/**
+ * freezer_css_offline - initiate destruction of @cgroup
+ * @cgroup: cgroup being destroyed
+ *
+ * @cgroup is going away.  Mark it dead and decrement system_freezing_count
+ * if it was holding one.
+ */
+static void freezer_css_offline(struct cgroup *cgroup)
+{
+       struct freezer *freezer = cgroup_freezer(cgroup);
+
+       spin_lock_irq(&freezer->lock);
+
+       if (freezer->state & CGROUP_FREEZING)
                atomic_dec(&system_freezing_cnt);
-       kfree(freezer);
+
+       freezer->state = 0;
+
+       spin_unlock_irq(&freezer->lock);
+}
+
+static void freezer_css_free(struct cgroup *cgroup)
+{
+       kfree(cgroup_freezer(cgroup));
 }
 
 /*
@@ -117,6 +179,7 @@ static void freezer_attach(struct cgroup *new_cgrp, struct cgroup_taskset *tset)
 {
        struct freezer *freezer = cgroup_freezer(new_cgrp);
        struct task_struct *task;
+       bool clear_frozen = false;
 
        spin_lock_irq(&freezer->lock);
 
@@ -129,19 +192,32 @@ static void freezer_attach(struct cgroup *new_cgrp, struct cgroup_taskset *tset)
         * Tasks in @tset are on @new_cgrp but may not conform to its
         * current state before executing the following - !frozen tasks may
         * be visible in a FROZEN cgroup and frozen tasks in a THAWED one.
-        * This means that, to determine whether to freeze, one should test
-        * whether the state equals THAWED.
         */
        cgroup_taskset_for_each(task, new_cgrp, tset) {
-               if (freezer->state == CGROUP_THAWED) {
+               if (!(freezer->state & CGROUP_FREEZING)) {
                        __thaw_task(task);
                } else {
                        freeze_task(task);
-                       freezer->state = CGROUP_FREEZING;
+                       freezer->state &= ~CGROUP_FROZEN;
+                       clear_frozen = true;
                }
        }
 
        spin_unlock_irq(&freezer->lock);
+
+       /*
+        * Propagate FROZEN clearing upwards.  We may race with
+        * update_if_frozen(), but as long as both work bottom-up, either
+        * update_if_frozen() sees child's FROZEN cleared or we clear the
+        * parent's FROZEN later.  No parent w/ !FROZEN children can be
+        * left FROZEN.
+        */
+       while (clear_frozen && (freezer = parent_freezer(freezer))) {
+               spin_lock_irq(&freezer->lock);
+               freezer->state &= ~CGROUP_FROZEN;
+               clear_frozen = freezer->state & CGROUP_FREEZING;
+               spin_unlock_irq(&freezer->lock);
+       }
 }
 
 static void freezer_fork(struct task_struct *task)
@@ -159,34 +235,54 @@ static void freezer_fork(struct task_struct *task)
                goto out;
 
        spin_lock_irq(&freezer->lock);
-       /*
-        * @task might have been just migrated into a FROZEN cgroup.  Test
-        * equality with THAWED.  Read the comment in freezer_attach().
-        */
-       if (freezer->state != CGROUP_THAWED)
+       if (freezer->state & CGROUP_FREEZING)
                freeze_task(task);
        spin_unlock_irq(&freezer->lock);
 out:
        rcu_read_unlock();
 }
 
-/*
- * We change from FREEZING to FROZEN lazily if the cgroup was only
- * partially frozen when we exitted write.  Caller must hold freezer->lock.
+/**
+ * update_if_frozen - update whether a cgroup finished freezing
+ * @cgroup: cgroup of interest
+ *
+ * Once FREEZING is initiated, transition to FROZEN is lazily updated by
+ * calling this function.  If the current state is FREEZING but not FROZEN,
+ * this function checks whether all tasks of this cgroup and the descendant
+ * cgroups finished freezing and, if so, sets FROZEN.
+ *
+ * The caller is responsible for grabbing RCU read lock and calling
+ * update_if_frozen() on all descendants prior to invoking this function.
  *
  * Task states and freezer state might disagree while tasks are being
  * migrated into or out of @cgroup, so we can't verify task states against
  * @freezer state here.  See freezer_attach() for details.
  */
-static void update_if_frozen(struct freezer *freezer)
+static void update_if_frozen(struct cgroup *cgroup)
 {
-       struct cgroup *cgroup = freezer->css.cgroup;
+       struct freezer *freezer = cgroup_freezer(cgroup);
+       struct cgroup *pos;
        struct cgroup_iter it;
        struct task_struct *task;
 
-       if (freezer->state != CGROUP_FREEZING)
-               return;
+       WARN_ON_ONCE(!rcu_read_lock_held());
+
+       spin_lock_irq(&freezer->lock);
+
+       if (!(freezer->state & CGROUP_FREEZING) ||
+           (freezer->state & CGROUP_FROZEN))
+               goto out_unlock;
 
+       /* are all (live) children frozen? */
+       cgroup_for_each_child(pos, cgroup) {
+               struct freezer *child = cgroup_freezer(pos);
+
+               if ((child->state & CGROUP_FREEZER_ONLINE) &&
+                   !(child->state & CGROUP_FROZEN))
+                       goto out_unlock;
+       }
+
+       /* are all tasks frozen? */
        cgroup_iter_start(cgroup, &it);
 
        while ((task = cgroup_iter_next(cgroup, &it))) {
@@ -198,27 +294,32 @@ static void update_if_frozen(struct freezer *freezer)
                         * the usual frozen condition.
                         */
                        if (!frozen(task) && !freezer_should_skip(task))
-                               goto notyet;
+                               goto out_iter_end;
                }
        }
 
-       freezer->state = CGROUP_FROZEN;
-notyet:
+       freezer->state |= CGROUP_FROZEN;
+out_iter_end:
        cgroup_iter_end(cgroup, &it);
+out_unlock:
+       spin_unlock_irq(&freezer->lock);
 }
 
 static int freezer_read(struct cgroup *cgroup, struct cftype *cft,
                        struct seq_file *m)
 {
-       struct freezer *freezer = cgroup_freezer(cgroup);
-       enum freezer_state state;
+       struct cgroup *pos;
 
-       spin_lock_irq(&freezer->lock);
-       update_if_frozen(freezer);
-       state = freezer->state;
-       spin_unlock_irq(&freezer->lock);
+       rcu_read_lock();
+
+       /* update states bottom-up */
+       cgroup_for_each_descendant_post(pos, cgroup)
+               update_if_frozen(pos);
+       update_if_frozen(cgroup);
+
+       rcu_read_unlock();
 
-       seq_puts(m, freezer_state_strs[state]);
+       seq_puts(m, freezer_state_strs(cgroup_freezer(cgroup)->state));
        seq_putc(m, '\n');
        return 0;
 }
@@ -247,48 +348,113 @@ static void unfreeze_cgroup(struct freezer *freezer)
        cgroup_iter_end(cgroup, &it);
 }
 
-static void freezer_change_state(struct freezer *freezer,
-                                enum freezer_state goal_state)
+/**
+ * freezer_apply_state - apply state change to a single cgroup_freezer
+ * @freezer: freezer to apply state change to
+ * @freeze: whether to freeze or unfreeze
+ * @state: CGROUP_FREEZING_* flag to set or clear
+ *
+ * Set or clear @state on @cgroup according to @freeze, and perform
+ * freezing or thawing as necessary.
+ */
+static void freezer_apply_state(struct freezer *freezer, bool freeze,
+                               unsigned int state)
 {
        /* also synchronizes against task migration, see freezer_attach() */
-       spin_lock_irq(&freezer->lock);
+       lockdep_assert_held(&freezer->lock);
+
+       if (!(freezer->state & CGROUP_FREEZER_ONLINE))
+               return;
 
-       switch (goal_state) {
-       case CGROUP_THAWED:
-               if (freezer->state != CGROUP_THAWED)
-                       atomic_dec(&system_freezing_cnt);
-               freezer->state = CGROUP_THAWED;
-               unfreeze_cgroup(freezer);
-               break;
-       case CGROUP_FROZEN:
-               if (freezer->state == CGROUP_THAWED)
+       if (freeze) {
+               if (!(freezer->state & CGROUP_FREEZING))
                        atomic_inc(&system_freezing_cnt);
-               freezer->state = CGROUP_FREEZING;
+               freezer->state |= state;
                freeze_cgroup(freezer);
-               break;
-       default:
-               BUG();
+       } else {
+               bool was_freezing = freezer->state & CGROUP_FREEZING;
+
+               freezer->state &= ~state;
+
+               if (!(freezer->state & CGROUP_FREEZING)) {
+                       if (was_freezing)
+                               atomic_dec(&system_freezing_cnt);
+                       freezer->state &= ~CGROUP_FROZEN;
+                       unfreeze_cgroup(freezer);
+               }
        }
+}
+
+/**
+ * freezer_change_state - change the freezing state of a cgroup_freezer
+ * @freezer: freezer of interest
+ * @freeze: whether to freeze or thaw
+ *
+ * Freeze or thaw @freezer according to @freeze.  The operations are
+ * recursive - all descendants of @freezer will be affected.
+ */
+static void freezer_change_state(struct freezer *freezer, bool freeze)
+{
+       struct cgroup *pos;
 
+       /* update @freezer */
+       spin_lock_irq(&freezer->lock);
+       freezer_apply_state(freezer, freeze, CGROUP_FREEZING_SELF);
        spin_unlock_irq(&freezer->lock);
+
+       /*
+        * Update all its descendants in pre-order traversal.  Each
+        * descendant will try to inherit its parent's FREEZING state as
+        * CGROUP_FREEZING_PARENT.
+        */
+       rcu_read_lock();
+       cgroup_for_each_descendant_pre(pos, freezer->css.cgroup) {
+               struct freezer *pos_f = cgroup_freezer(pos);
+               struct freezer *parent = parent_freezer(pos_f);
+
+               /*
+                * Our update to @parent->state is already visible which is
+                * all we need.  No need to lock @parent.  For more info on
+                * synchronization, see freezer_post_create().
+                */
+               spin_lock_irq(&pos_f->lock);
+               freezer_apply_state(pos_f, parent->state & CGROUP_FREEZING,
+                                   CGROUP_FREEZING_PARENT);
+               spin_unlock_irq(&pos_f->lock);
+       }
+       rcu_read_unlock();
 }
 
 static int freezer_write(struct cgroup *cgroup, struct cftype *cft,
                         const char *buffer)
 {
-       enum freezer_state goal_state;
+       bool freeze;
 
-       if (strcmp(buffer, freezer_state_strs[CGROUP_THAWED]) == 0)
-               goal_state = CGROUP_THAWED;
-       else if (strcmp(buffer, freezer_state_strs[CGROUP_FROZEN]) == 0)
-               goal_state = CGROUP_FROZEN;
+       if (strcmp(buffer, freezer_state_strs(0)) == 0)
+               freeze = false;
+       else if (strcmp(buffer, freezer_state_strs(CGROUP_FROZEN)) == 0)
+               freeze = true;
        else
                return -EINVAL;
 
-       freezer_change_state(cgroup_freezer(cgroup), goal_state);
+       freezer_change_state(cgroup_freezer(cgroup), freeze);
        return 0;
 }
 
+static u64 freezer_self_freezing_read(struct cgroup *cgroup, struct cftype *cft)
+{
+       struct freezer *freezer = cgroup_freezer(cgroup);
+
+       return (bool)(freezer->state & CGROUP_FREEZING_SELF);
+}
+
+static u64 freezer_parent_freezing_read(struct cgroup *cgroup, struct cftype *cft)
+{
+       struct freezer *freezer = cgroup_freezer(cgroup);
+
+       return (bool)(freezer->state & CGROUP_FREEZING_PARENT);
+}
+
 static struct cftype files[] = {
        {
                .name = "state",
@@ -296,23 +462,27 @@ static struct cftype files[] = {
                .read_seq_string = freezer_read,
                .write_string = freezer_write,
        },
+       {
+               .name = "self_freezing",
+               .flags = CFTYPE_NOT_ON_ROOT,
+               .read_u64 = freezer_self_freezing_read,
+       },
+       {
+               .name = "parent_freezing",
+               .flags = CFTYPE_NOT_ON_ROOT,
+               .read_u64 = freezer_parent_freezing_read,
+       },
        { }     /* terminate */
 };
 
 struct cgroup_subsys freezer_subsys = {
        .name           = "freezer",
-       .create         = freezer_create,
-       .destroy        = freezer_destroy,
+       .css_alloc      = freezer_css_alloc,
+       .css_online     = freezer_css_online,
+       .css_offline    = freezer_css_offline,
+       .css_free       = freezer_css_free,
        .subsys_id      = freezer_subsys_id,
        .attach         = freezer_attach,
        .fork           = freezer_fork,
        .base_cftypes   = files,
-
-       /*
-        * freezer subsys doesn't handle hierarchy at all.  Frozen state
-        * should be inherited through the hierarchy - if a parent is
-        * frozen, all its children should be frozen.  Fix it and remove
-        * the following.
-        */
-       .broken_hierarchy = true,
 };