freezer在内核2.6.32和3.10的区别

Time: 五月 23, 2015
Category: cgroup

今天看了一下内核3.10的代码,对比之前看过的2.6.32的代码,cgroup里freezer子系统的行为差别还是很大的,主要有2个:

  1. freezer处于冻结状态下进程是否仍然可以attach进来,2.6是不行的,但3.10允许
  2. freeze单个cgroup变成freeze掉cgroup以及它的所有后裔

在2.6内核里,当一个进程尝试加入某个freezer控制组时,内核会检查当前freezer cgroup的状态,如果状态为FROZEN或者FREEZING等,则不允许进程加入。但是在3.10里,这个限制去掉了,不管当前freezer cgroup的状态如何,都允许进程加入。

在接口的实现上,2.6内核只提供了can_attach()函数,3.10只提供了freezer_attach() 函数,正常流程上来说,进程在attach某个cgroup的时候,内核会先调用当前cgroup上所有子系统的can_attach()检查一下,当且仅当所有的子系统can_attach()成功时才允许进程加入当前cgroup,然后内核调用freezer子系统的freezer_attach()接口完成attach工作。

而3.10内核直接跳过了can_attach()函数(不实现这个接口),只提供attach接口 freezer_attach(),内核实现如下:

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);

  /*
   * Make the new tasks conform to the current state of @new_cgrp.
   * For simplicity, when migrating any task to a FROZEN cgroup, we
   * revert it to FREEZING and let update_if_frozen() determine the
   * correct state later.
   *
   * 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.
   */
  cgroup_taskset_for_each(task, new_cgrp, tset) {
    if (!(freezer->state & CGROUP_FREEZING)) {
      __thaw_task(task);
    } else {
      freeze_task(task);
      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);
  }
}

对于第二点,3.10内核里,freezer影响的不仅仅是当前的cgroup(2.6.32的实现),而是以当前cgroup为root的整个cgroup树。例如说,当用户freeze一个cgroup时,这个cgroup和它的所有后裔,都会被freeze掉。内核通过cgroup_for_each_descendant_pre()遍历所有的cgroup并递归向下处理

/**
 * 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();
}

不过要改变一棵树的状态,操作肯定不是原子的,除非用大锁(性能会很成问题),不然就会有中间状态:

  1. cgroup:A被freezed掉之后,但是某个后裔cgroup:B还处于THAWED状态,这个时候如果有进程尝试attach进B怎么办?
  2. 一个cgroup树被FROZEN之后,如果用户尝试THAW某个后裔cgroup,怎么办?
  3. 一个cgroup树被FROZEN之后,如果用户尝试THAW当前cgroup,它的后裔会怎么样?

对于问题1,忽略不处理,因为稍后该cgroup的所有进程会被freeze掉

对于问题2,3.10内核的实现策略是,从当前cgroup节点开始到hierarchy的根节点,沿途遇到FROZEN状态的cgroup,改变为FREEZING,直到遇到第一个不是FROZEN状态的cgroup节点为止:

  /*
   * 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);
  }

对于问题3,本质操作和2是一样的。

Leave a Comment