Linux进程冻结技术

作者:itrocker 发布于:2015-11-24 15:01 分类:电源管理子系统

1 什么是进程冻结

进程冻结技术(freezing of tasks)是指在系统hibernate或者suspend的时候,将用户进程和部分内核线程置于“可控”的暂停状态。

2 为什么需要冻结技术

假设没有冻结技术,进程可以在任意可调度的点暂停,而且直到cpu_down才会暂停并迁移。这会给系统带来很多问题:

(1)有可能破坏文件系统。在系统创建hibernate imagecpu down之间,如果有进程还在修改文件系统的内容,这将会导致系统恢复之后无法完全恢复文件系统;

(2)有可能导致创建hibernation image失败。创建hibernation image需要足够的内存空间,但是在这期间如果还有进程在申请内存,就可能导致创建失败;

(3)有可能干扰设备的suspendresume。在cpu down之前,device suspend期间,如果进程还在访问设备,尤其是访问竞争资源,就有可能引起设备suspend异常;

(4)有可能导致进程感知系统休眠。系统休眠的理想状态是所有任务对休眠过程无感知,睡醒之后全部自动恢复工作,但是有些进程,比如某个进程需要所有cpu online才能正常工作,如果进程不冻结,那么在休眠过程中将会工作异常。

3 代码实现框架

冻结的对象是内核中可以被调度执行的实体,包括用户进程、内核线程和work_queue。用户进程默认是可以被冻结的,借用信号处理机制实现;内核线程和work_queue默认是不能被冻结的,少数内核线程和work_queue在创建时指定了freezable标志,这些任务需要对freeze状态进行判断,当系统进入freezing时,主动暂停运行。

kernel threads可以通过调用kthread_freezable_should_stop来判断freezing状态,并主动调用__refrigerator进入冻结;work_queue通过判断max_active属性,如果max_active=0,则不能入队新的work,所有work延后执行。


标记系统freeze状态的有三个重要的全局变量:pm_freezingsystem_freezing_cntpm_nosig_freezing,如果全为0,表示系统未进入冻结;system_freezing_cnt>0表示系统进入冻结,pm_freezing=true表示冻结用户进程,pm_nosig_freezing=true表示冻结内核线程和workqueue。它们会在freeze_processesfreeze_kernel_threads中置位,在thaw_processesthaw_kernel_threads中清零。

fake_signal_wake_up函数巧妙的利用了信号处理机制,只设置任务的TIF_SIGPENDING位,但不传递任何信号,然后唤醒任务;这样任务在返回用户态时会进入信号处理流程,检查系统的freeze状态,并做相应处理。

任务主动调用try_to_freeze的代码如下:

static inline bool try_to_freeze_unsafe(void)
{
	if (likely(!freezing(current))) //检查系统是否处于freezing状态
		return false;
	return __refrigerator(false); //主动进入冻结
}

static inline bool freezing(struct task_struct *p)
{
	if (likely(!atomic_read(&system_freezing_cnt))) //系统总体进入freezing
		return false;
	return freezing_slow_path(p);
}

bool freezing_slow_path(struct task_struct *p)
{
	if (p->flags & PF_NOFREEZE)  //当前进程是否允许冻结
		return false;

	if (pm_nosig_freezing || cgroup_freezing(p))  //系统冻结kernel threads
		return true;

	if (pm_freezing && !(p->flags & PF_KTHREAD)) //系统冻结用户进程
		return true;

	return false;
}

进入冻结状态直到恢复的主要函数:

bool __refrigerator(bool check_kthr_stop)

{
...
	for (;;) {
		set_current_state(TASK_UNINTERRUPTIBLE);  //设置进程为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();
	}
......
}

4 参考文献

(1) http://www.wowotech.net/linux_kenrel/suspend_and_resume.html

(2) http://www.wowotech.net/linux_kenrel/std_str_func.html

(3) kenrel document: freezing-of-tasks.txt


标签: Linux freeze

评论:

bsp
2021-07-20 17:18
提个问题:
为什么UNINTERRUPTIBLE的task设计成 不能freeze,即freeze_task()为什么调用wake_up_state(p, TASK_INTERRUPTIBLE)而不是 wake_up_state(p, TASK_UNINTERRUPTIBLE);

自问自答下:
系统suspend的流程肯定是先freeze所有的task,然后suspend driver,类似人睡眠时 先停止跳舞/吃饭的活动,再躺下闭上眼睛 停止身体;
但是一个 UNINTERRUPTIBLE的task,比如一个file write的操作,给disk发送了指令 并等待IO完成;如果他被freeze了,在从free_task到suspend_disk中间,disk的IO完成了,此时 之前的task已经freeze无法响应此complete或者需要重新唤醒来响应此IO的complete。
所有,最好还是设计成UNINTERRUPTIBLE的task在freeze时失败,并循环检测几次(freeze_timeout_msecs = 20s)等待IO完成,如果IO一直没有完成,退出suspend,一个task处于suspend时间过长也是有问题的。
rikeyone
2019-11-20 17:40
hibernate的流程是不是有点问题,我看到的代码是先执行freeze,然后在生成snapshot

error = freeze_processes();
if (error)
    goto Exit;

lock_device_hotplug();
/* Allocate memory management structures */
error = create_basic_memory_bitmaps();
if (error)
    goto Thaw;

error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM);


否则,snapshot和 freeze之间是可能导致进程修改文件系统数据的
nowar
2019-08-27 14:52
有两个问题请帮忙解答,先行谢过
1)因为只有指明了freezable的内核线程才会进入freeze, 意思是在suspend后,其实系统中默认的内核线程原来处于running状态的依然在running状态吗?
2)那些本来就已经在uninterruptable 状态的用户线程是否也要响应freeze请求进入freeze状态呢?如果是的话,这些状态的线程没有机会来响应这个请求吧?
---------------------------------
static int try_to_freeze_tasks(bool user_only)
48                 do_each_thread(g, p) {   //这里应该遍历所有状态的线程,不管是否已经sleep了
49                         if (p == current || !freeze_task(p))
50                                 continue;
--------------------------
finics
2021-03-10 10:51
@nowar:可惜发布者可能太忙了,好像很久没有更新了。
因为最近也在看这个部分,就借用这个平台一起讨论下吧。
---------------------------------------------------------
(1)我理解是的,内核线程如果没有刻意配置为freezable by set_freezable(),这个内核thread就不会被freeze

(2) 其实对于这一点,我也觉得很奇怪,我就基于目前4.19 kernel的实现谈一下我的理解,希望高人来确认吧。
基于如下的调用路径,在wake_up_state(task A)中,如果task A目前具有mask flag TASK_INTERRUPTIBLE的话,这个task A就可以被加入到running queue等待下一次的调度;如果task A 的mask flag 没有 TASK_INTERRUPTIBLE的话,不管A目前是TASK_UNINTERRUPTABLE或者TASK_NORMAL,都会调用kick_process,通过一个ipi软中断触发了当前另外一个CPU上正在运行的user mode task A 进行freezing。这里就有两个疑问,如果task A是TASK_UNINTERRUPTABLE flag,理论上不应该kick process,因为当前运行的task并不是A;另外一个疑问是是不是只有正在另外一个CPU上运行的user mode application才可能被freeze,另外一些user mode task,虽然也在running queue上,但是正好还没有被调度到,是不是就没有机会被freeze了。
freeze_processes
try_to_freeze_tasks
  freeze_task
   fake_signal_wake_up
    signal_wake_up
     signal_wake_up_state
       wake_up_state
finics
2021-03-10 11:14
@finics:对于疑问“是不是只有正在另外一个CPU上运行的user mode application才可能被freeze,另外一些user mode task,虽然也在running queue上,但是正好还没有被调度到,是不是就没有机会被freeze了。”
自己又看了下如下代码。我觉得应该这样理解:所有的user mode task都会被添加sigpending标志,所以只要这个user mode task被调度,当系统调用ret_to_user时候,这个task就会被freeze。如果这个user mode task正在执行的话,就应该立刻kick_process,让这个task进行freezing. 感觉这里面是不是有个bug,如果这个task没有正在执行,并且时uninterrutable flag时,不应该直接调用kick_process吧。
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
    set_tsk_thread_flag(t, TIF_SIGPENDING);---》这个task添加sigpending标志,

    if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
        kick_process(t);
}
finics
2021-03-10 11:28
@finics:突然灵光一闪,再来自问自答一下,抱歉。
对于问题“如果这个task没有正在执行,并且是uninterrutable flag时,不应该直接调用kick_process吧”

如果这个task处于TASK_UNINTERRUPTIBLE状态,如下代码并不会kick_process。见kick_process()代码,只有当前task正在运行时候才去kick。所以可以这样理解,如果task处于TASK_UNINTERRUPTIBLE状态,除了加个sig_pending啥也不做,等这个user mode task自己去完成自己的事情;如果task处于TASK_INTERRUPTIBLE状态,把这个task直接唤醒,然后最终通过ret_to_user再把这个task freeze;如果这个task正在运行的话,kick_process,通过ipi中断让这个task别干活了,快点去freeze.
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
    set_tsk_thread_flag(t, TIF_SIGPENDING);---》这个task添加sigpending标志,

    if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
        kick_process(t);
}

void kick_process(struct task_struct *p)
{
    int cpu;

    preempt_disable();
    cpu = task_cpu(p);
    if ((cpu != smp_processor_id()) && task_curr(p))
        smp_send_reschedule(cpu);
    preempt_enable();
}
bsp
2021-07-20 17:03
@finics:是的,TASK_UNINTERRUPTIBLE的task 不会去wakeup;
kernel-4.14中:
static int try_to_freeze_tasks(bool user_only)
        while (true) {
                todo = 0;
                for_each_process_thread(g, p) {
                        if (p == current || !freeze_task(p))
                                continue;

                        if (!freezer_should_skip(p))
                                todo++;
                }
}
这里freeze_task会通过wake_up_state(p, TASK_INTERRUPTIBLE),发送信号给interruptable的task,p->flags |= PF_FREEZER_SKIP,并返回true;
后面freezer_should_skip检测p->flags,UNINTERRUPTIBLE的task->flags没有置1,返回false,todo++,表示这个task freeze失败。
后面就会打印error并退出suspend。
        } else if (todo) {
                pr_cont("\n");
                pr_err("Freezing of tasks failed after %d.%03d seconds"
                       " (%d tasks refusing to freeze,
test
2018-06-01 15:52
从3.10的内核来看,内核线程的冻结这一块描述的不太准确,调查发现,对于内核线程的冻结,
一部分是忽略了绝大多数的内核线程,一部分是主动调用try_to_freeze()进行的冻结操作,
但是并没有调用kthread_freeable_should_stop.

请参考一下
https://www.kernel.org/doc/Documentation/power/freezing-of-tasks.txt
中的"III. Which kernel threads are freezable?"

相关的代码分析参考如下,

static int try_to_freeze_tasks(bool user_only)
48                 do_each_thread(g, p) {
49                         if (p == current || !freeze_task(p))
50                                 continue;
51
52                         if (!freezer_should_skip(p))
53                                 todo++;
54                 } while_each_thread(g, p);

在冻结用户空间的进程时,内核线程是直接忽略过去的
也就是freeze_task(p)返回的是false,

bool freeze_task(struct task_struct *p)
125         spin_lock_irqsave(&freezer_lock, flags);
126         if (!freezing(p) || frozen(p)) {
127                 spin_unlock_irqrestore(&freezer_lock, flags);
128                 return false;  
129         }      
因为查看freezing(),
34 static inline bool freezing(struct task_struct *p)
35 {
36         if (likely(!atomic_read(&system_freezing_cnt)))
37                 return false;
38         return freezing_slow_path(p);
39 }  
而freezing_slow_path的实现如下,
34 bool freezing_slow_path(struct task_struct *p)
35 {
36         if (p->flags & PF_NOFREEZE)
37                 return false;
38
39         if (pm_nosig_freezing || cgroup_freezing(p))
40                 return true;
41
42         if (pm_freezing && !(p->flags & PF_KTHREAD))
43                 return true;
44
45         return false;
46 }
对于绝大多数内核线程都是PF_NOFREEZE的.
而对于冻结用户空间进程的过程中,pm_freezing=true,所以对于内核线程,!(p->flags & PF_KTHREAD)=0,
所以freezing()返回false,进一步讲,freeze_task()返回false,所以在try_to_freeze_tasks()的循环中直接就continue了,没有让todo++,

而另一方面,对于冻结内核空间的进程而言,除去绝大多数的PF_NOFREEZE的内核线程以外,由于pm_nosig_freezing=true, freezing_slow_path()返回true,
freezing()返回true,
在freeze_task()中,
bool freeze_task(struct task_struct *p)
125         spin_lock_irqsave(&freezer_lock, flags);
126         if (!freezing(p) || frozen(p)) {
127                 spin_unlock_irqrestore(&freezer_lock, flags);
128                 return false;  
129         }      
需要等待 frozen(p)返回1,而进程的冻结就是通过进程自己主动调用try_to_freeze()来完成的.
典型的例子就是kswapd%d进程和file-storage进程.
Rafe
2018-04-09 18:28
@wowo : applicaton被freeze,然后系统resume回来,对于app来说,像是什么都没发生一样。
请问,application有方法知道,系统是resume回来的吗?
nemo
2020-04-16 19:06
@Rafe:app 的 watchdog?
hzm
2018-01-11 22:17
@wowo:
你在下面回答的问题,我有个疑问
a.关键点在于:freeze task的过程是“请求freeze”,也就是说给要被freeze的thread发一个信号,接下来的freeze过程是thread自行处理的。
=>看了代码的流程,没看到有发信号的地方呀,能指出一下吗?
b.因此kthread_freezable_should_stop是被freeze thread自己调用的。
=>同样,没进程处理signal的时候有调用到这个函数,能一起把代码指出一下吗?

非常感谢~
hzm
2018-01-15 11:43
@hzm:看到了,应该是这里
2138int get_signal(struct ksignal *ksig)
2139{
2140    struct sighand_struct *sighand = current->sighand;
2141    struct signal_struct *signal = current->signal;
2142    int signr;
2143
2144    if (unlikely(current->task_works))
2145        task_work_run();
2146
2147    if (unlikely(uprobe_deny_signal()))
2148        return 0;
2149
2150    /*
2151     * Do this once, we can't return to user-mode if freezing() == T.
2152     * do_signal_stop() and ptrace_stop() do freezable_schedule() and
2153     * thus do not need another check after return.
2154     */
2155    try_to_freeze();
2156
wowo
2018-01-16 09:38
@hzm:找到就行了,最近太忙,没来得及回复,抱歉哈~~
wmzjzwlzs
2017-11-04 09:27
1.没有设置为可冻结状态的内核线程有可能在device suspend之后还在运行?
2.如果一个内核线程设置成可冻结的,是不是系统会等待这个线程主动冻结自己后才不调度它,如果这个线程就是不主动冻结自己,是不是系统就睡不下去了
wowo
2017-11-06 09:10
@wmzjzwlzs:是的。这个问题其实很好验证,了解了kernel的机制之后,你可以写一个不可冻结的应用程序,实际测试一下。
wmzjzwlzs
2017-11-06 15:23
@wowo:谢谢wowo大神,测试了下
1.没有设置为可冻结状态的内核线程确实在device suspend之后还在运行
2.我把一个内核线程设置成可冻结的,然后就是不主动冻结自己,系统确实就睡不下去了
wowo
2017-11-06 16:55
@wmzjzwlzs:实践是检验真理的唯一标准啊,赞!!!~~~
yinjian
2017-10-23 15:23
你好,对于内核线程freeze的过程,还有点疑问:执行完wake_up_state函数后,是如何跑到kthread_freezable_should_stop函数的? :)
wowo
2017-10-24 09:27
@yinjian:关键点在于:freeze task的过程是“请求freeze”,也就是说给要被freeze的thread发一个信号,接下来的freeze过程是thread自行处理的。
因此kthread_freezable_should_stop是被freeze thread自己调用的。
kernel中有对应的例子,你可以看看。
废柴
2017-04-01 15:15
@wo神,

有个疑惑,android6.0以后搞出来一个Doze模式,请问Doze和PM suspend有关系么,还是说Doze仅仅只是android层的一种低功耗模式,和PM suspend没毛线关系?

谢谢

废柴
wowo
2017-04-01 16:43
@废柴:Doze是应用层的节电行为,和kernel中的PM没有关系。
comlee
2017-02-18 14:41
@wowo
你好,请问一个弱弱的问题。
如果应用释放的wake_lock锁是系统中的最后一把wake_lock锁的话,那么系统会马上进入suspend吗?还是要系统中的所有应用不再跑或进入阻塞等状态才进suspend?
wowo
2017-02-18 16:11
@comlee:一般情况下,负责电源管理的进度或者线程,优先级应该是最低的。
另外,休眠的过程中,执行freeze的过程,也会给应用机会,所以休眠的时候,应该应该不跑了。
comlee
2017-02-19 12:33
@wowo:@wowo:我看到/proc/power/wake_unlock这个节点的内核实现会在unlock时check并进入suspend流程。另外,我自己写了两个应用进行测试:
1.    main
     {
         while(1)
           ;
     }
发现这个系统是可以进入suspend的。

但是
    main()
    {
         system("echo 'mylock'>/proc/power/wake_lock");
         while(1)
           ;
     }
跑这个的话,系统进不了suspend(两个测试程序分别都是在init rc里面启动)。

所以看上去系统不会等应用层的程序阻塞或休眠才suspend? 只要应用释放的是系统最后一把wake_lock就走suspend流程?
wowo
2017-02-19 21:18
@comlee:是的,wakeup lock的思路就是,假如你不想让系统睡,你就要持一个锁。既然你没有保持锁,系统就假设可以睡了。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog