linux Cgroup Freezer 子系统原理

概述

众所周知,Linux的Cgroup中的Freezer子系统可以批量的挂起线程,冻结的对象是内核中可以被调度执行的实体,包括了用户进程,内核线程和work_queue,其中用户进程默认是可以被冻结的,通过信号处理机制来实现,而内核线程和work_queue默认是不能被冻结的,少数内核线程和work_queue可以在创建的时候指定freezable标识,这些任务需要对freeze状态进行判断,主动暂停运行。

本篇我们只讨论用户进程的冻结原理。

设置冻结

我们知道 Cgroup的子系统都在/sys/fs/cgroup/freezer目录下,state标识了当前用户组的状态,而tasks则是其中包含的tid,查看一下可以看到目前是THAWED状态,也就是解冻状态。

grus:/sys/fs/cgroup/freezer/perf # ls
cgroup.clone_children cgroup.procs freezer.parent_freezing freezer.self_freezing freezer.state frozen notify_on_release tasks thawed 
grus:/sys/fs/cgroup/freezer/perf # cat freezer.state                                                                                                      
THAWED

那么如果我们将FROZEN写入,也就是改变了当前group的状态

// freezer.c
static struct cftype files[] = {
	{
		.name = "state",
		.flags = CFTYPE_NOT_ON_ROOT,
		.seq_show = freezer_read,
		.write = freezer_write,
	},
...
};
static ssize_t freezer_write(struct kernfs_open_file *of,
			     char *buf, size_t nbytes, loff_t off)
{
	...
	freezer_change_state(css_freezer(of_css(of)), freeze);
	return nbytes;
}
static void freezer_change_state(struct freezer *freezer, bool freeze)
{
			freezer_apply_state(pos_f, freeze,
					    CGROUP_FREEZING_SELF);
		else
			freezer_apply_state(pos_f,
					    parent->state & CGROUP_FREEZING,
					    CGROUP_FREEZING_PARENT)
}
static void freezer_apply_state(struct freezer *freezer, bool freeze,
				unsigned int state)
{
	if (freeze) {
		if (!(freezer->state & CGROUP_FREEZING))
			atomic_inc(&system_freezing_cnt);
		freezer->state |= state;
		freeze_cgroup(freezer);
	} 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);
		}
	}
}
static void freeze_cgroup(struct freezer *freezer)
{
	struct css_task_iter it;
	struct task_struct *task;

	css_task_iter_start(&freezer->css, 0, &it);
	while ((task = css_task_iter_next(&it)))
		freeze_task(task);
	css_task_iter_end(&it);
}
bool freeze_task(struct task_struct *p)
{
	if (!(p->flags & PF_KTHREAD)) //关键点
		fake_signal_wake_up(p);
	else
		wake_up_state(p, TASK_INTERRUPTIBLE);
}

经过一系列的调用,判断如果当前是用户进程,那么就通过伪造singal的方式,来中断当前用户进程的运行。

用户态的进程利用了signal机制,只设置了任务的TIF_SIGPENGDING位,但是不传递任何信号,然后唤醒任务,这样任务在返回用户态的时候就会进入信号处理流程,检查状态。具体可以查看Linux信号处理,流程是ret_to_user()->do_notify_resume()->do_signal()->get_signal()->try_to_freeze()

冻结过程

// freezer.c
static inline bool try_to_freeze(void)
{
	if (!(current->flags & PF_NOFREEZE))
		debug_check_no_locks_held();
	return try_to_freeze_unsafe();
}
static inline bool try_to_freeze_unsafe(void)
{
	might_sleep();
    // 检查系统是否在freezing状态
	if (likely(!freezing(current)))
		return false;
	return __refrigerator(false);
}

bool __refrigerator(bool check_kthr_stop)
{
	for (;;) {
        // 设置当前为D状态
		set_current_state(TASK_UNINTERRUPTIBLE);

		spin_lock_irq(&freezer_lock);
		current->flags |= PF_FROZEN;
        // 如果当前系统退出了冻结,取消冻结状态,跳出循环
		if (!freezing(current) ||
		    (check_kthr_stop && kthread_should_stop()))
			current->flags &= ~PF_FROZEN;
		spin_unlock_irq(&freezer_lock);
        
		if (!(current->flags & PF_FROZEN))
			break;
		was_frozen = true;
		schedule();
	}

	pr_debug("%s left refrigerator\n", current->comm);
	set_current_state(save);

	return was_frozen;
}

冰箱也就是进入了无限循环状态,循环查看系统状态,如果系统退出了FROZEN状态,那么就可以退出冻结了。

总结

用户进程的冻结,巧妙的利用了信号处理机制,设置任务的TIF_SIGPENDING位,这样在进程中断后返回用户态的时候,就会进行信号的处理,此时就可以进行freeze操作了,而freeze操作的本质其实就是一个不停for循环查询的操作,可以看下时序图.