概述
众所周知,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循环查询的操作,可以看下.