蜗窝科技

Linux进程冻结技术

itrocker · · 电源管理子系统 · 阅读 45517 · 2015年11月存档

1 什么是进程冻结

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

2 为什么需要冻结技术

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

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

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

(3)有可能干扰设备的suspend和resume。在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_freezing、system_freezing_cnt和pm_nosig_freezing,如果全为0,表示系统未进入冻结;system_freezing_cnt>0表示系统进入冻结,pm_freezing=true表示冻结用户进程,pm_nosig_freezing=true表示冻结内核线程和workqueue。它们会在freeze_processes和freeze_kernel_threads中置位,在thaw_processes和thaw_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) /pm_subsystem/suspend_and_resume.html

(2) /pm_subsystem/std_str_func.html

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

标签:Linuxfreeze

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(48 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • Daniel Shieh

    文章短小精悍^_^.
    • itrocker

      @Daniel Shieh:^_^ freeze确实简单而巧妙,我思考它最多的地方是为什么会产生这个技术
  • 云

    很棒的分析, suspend很重要的一个环节就是freezing task :) 感谢分享
    • itrocker

      @云:O(∩_∩)O
  • wowo

    非常感谢itrocker帮我把这篇文章补齐啊!~~~ 现在freeze也被独立成一个电源状态,和standby、STR并列,而且设备的电源管理接口(struct dev_pm_ops)中也有很多freeze的回调函数(.freeze、freeze_late、.freeze_noirq),不知道itrocker 对此有什么看法?或者在哪里看到过这样的用法没?
    • itrocker

      @wowo:哈哈,主要是参考了wowo对suspend的精彩介绍。 freeze作为电源状态应该是一种只冻结进程,而不改变cpu状态的低阶休眠。 在suspend_enter中,如果是freeze状态,系统就保持awake if (state == PM_SUSPEND_FREEZE) { freeze_enter(); goto Platform_wake; } dev_pm_ops中的freeze回调应该是考虑到系统暂停过程的各个阶段,一些复杂外设可能会有有不同的处理。.freeze、.freeze_late、.freeze_noirq分别会在dpm_suspend_start和dpm_suspend_end中调用 不过这些回调依赖于编译选项CONFIG_HIBERNATE_CALLBACKS,目前我还没有用到过。
      • 云

        @itrocker:我实现过suspend to RAM. 我想了解下目前在ARM上有实现HIBERNATE么?比如suspend to disk这种方案,suspend时候需要把memory snapshot保存在flash(nand或者emmc)上。
        • itrocker

          @云:hibernate好像一般用在PC或服务器,arm一般用于终端设备,所以很少使用CONFIG_HIBERNATION选项
  • 云

    另外一个想与大伙交流下。 根据这篇文章,大伙都已经知道了在suspend过程中,application层是被freezed了,resume后该application会继续运行。(仿佛都不知道自己“休眠”了一段时间) 那么我们现在假设这么一个场景: 1.该APP是控制(或者与其通信)CPU外围一个运行着firmware的模块(比如DSP,FPGA) 2.在系统进入STR以后除了DDR里面内容是保持的,其他模块的logic都loss了(包括CPU) 3.系统resume以后,由于运行在CPU上的Linux有机制保证还原运行时的context, 但外围设备却不行,firmware是需要re-load 4.这样一来,如何能保证这个application能正常运行?
    • itrocker

      @云:有些firmware会有自己的suspend逻辑。 个人感觉如果firmware上下文与AP强相关,会有相互同步协议保证一致性。
    • wowo

      @云:我觉得里面的关键点是:你这个APP,应该能保证不依赖DSP/FPGA的状态而独立运行,这样它才有机会re-load。因此只要在resume后,APP能判断是否需要re-load即可。
  • C

    今年CLK南京的会议上的topic就是suspend-to-idle。 freeze task的一个重要作用就是支持s2i。 因为freeze task之后一些有趣的优化就成为了可能,比如cpu可能进入更深的cstate,可以停掉timer,timekeeping module甚至tsc,达到进一步节能。主要是,suspend to idle可以替代suspend to mem,毕竟后者需要firmware支持。
    • wowo

      @C:suspend-to-idle是很诱人的,我们在很久之前一个项目中也想这样做,但是迫于框架的不完善,最终没敢行动。我觉得它的好处主要是在于便利性,随时睡,随时醒。如果省电效果不错的话,还是很好用的。
      • passerby

        @wowo:@wowo,suspend-to-idle是指所有CPU都不会掉电,只是进入CPU的低功耗状态吗?这样就不需要将task irq等从nonboot cpu中转移到boot cpu吗?
        • wowo

          @passerby:是的,对软件来说,这些CPU都还在,只是小睡了一下。
    • archer

      @C:请问一下,为什么要freeze task呢?就是为了避免进程运行干扰suspend的过程吧?但是suspend to idle也可以达到同样的效果吧?
  • heziq

    我比较关心那么没有freeze的kernel thread,在cpu down的时候,怎么处理的?resume的时候 ,它们又是从哪里开始的。
    • itrocker

      @heziq:这个点在文中第2节只是一笔带过。其实不只是kernel thread,如果没有freeze,所有的task都是在cpu down时从dying cpu的runqueue迁移到剩余active cpu的runqueue resume时如果是STR,就直接从内存中的runqueue继续调度
      • C

        @itrocker:freeze跟cpu down没关系吧。有些涉及到磁盘操作的kernel thread是freezable的,因为freeze task最早提出是因为做磁盘的hibernation,比如kjournald,而workqueue则不再接收新的work item的queue请求。
        • C

          @C:sorry我没看到最初的问题,请无视上面的回答。
      • wmzjzwlzs

        @itrocker:1.没有设置为可冻结状态的内核线程有可能在device suspend之后还在运行? 2.如果一个内核线程设置成可冻结的,是不是系统会等待这个线程主动冻结自己后才不调度它,如果这个线程就是不主动冻结自己,是不是系统就睡不下去了
  • comlee

    @wowo 你好,请问一个弱弱的问题。 如果应用释放的wake_lock锁是系统中的最后一把wake_lock锁的话,那么系统会马上进入suspend吗?还是要系统中的所有应用不再跑或进入阻塞等状态才进suspend?
    • wowo

      @comlee:一般情况下,负责电源管理的进度或者线程,优先级应该是最低的。 另外,休眠的过程中,执行freeze的过程,也会给应用机会,所以休眠的时候,应该应该不跑了。
      • comlee

        @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

          @comlee:是的,wakeup lock的思路就是,假如你不想让系统睡,你就要持一个锁。既然你没有保持锁,系统就假设可以睡了。
  • 废柴

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

      @废柴:Doze是应用层的节电行为,和kernel中的PM没有关系。
  • yinjian

    你好,对于内核线程freeze的过程,还有点疑问:执行完wake_up_state函数后,是如何跑到kthread_freezable_should_stop函数的? :)
    • wowo

      @yinjian:关键点在于:freeze task的过程是“请求freeze”,也就是说给要被freeze的thread发一个信号,接下来的freeze过程是thread自行处理的。 因此kthread_freezable_should_stop是被freeze thread自己调用的。 kernel中有对应的例子,你可以看看。
      • 杰少

        @wowo:你这回答看不懂呀?感觉跟没回答一样
  • wmzjzwlzs

    1.没有设置为可冻结状态的内核线程有可能在device suspend之后还在运行? 2.如果一个内核线程设置成可冻结的,是不是系统会等待这个线程主动冻结自己后才不调度它,如果这个线程就是不主动冻结自己,是不是系统就睡不下去了
    • wowo

      @wmzjzwlzs:是的。这个问题其实很好验证,了解了kernel的机制之后,你可以写一个不可冻结的应用程序,实际测试一下。
      • wmzjzwlzs

        @wowo:谢谢wowo大神,测试了下 1.没有设置为可冻结状态的内核线程确实在device suspend之后还在运行 2.我把一个内核线程设置成可冻结的,然后就是不主动冻结自己,系统确实就睡不下去了
        • wowo

          @wmzjzwlzs:实践是检验真理的唯一标准啊,赞!!!~~~
  • hzm

    @wowo: 你在下面回答的问题,我有个疑问 a.关键点在于:freeze task的过程是“请求freeze”,也就是说给要被freeze的thread发一个信号,接下来的freeze过程是thread自行处理的。 =>看了代码的流程,没看到有发信号的地方呀,能指出一下吗? b.因此kthread_freezable_should_stop是被freeze thread自己调用的。 =>同样,没进程处理signal的时候有调用到这个函数,能一起把代码指出一下吗? 非常感谢~
    • hzm

      @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

        @hzm:找到就行了,最近太忙,没来得及回复,抱歉哈~~
  • Rafe

    @wowo : applicaton被freeze,然后系统resume回来,对于app来说,像是什么都没发生一样。 请问,application有方法知道,系统是resume回来的吗?
    • nemo

      @Rafe:app 的 watchdog?
  • test

    从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进程.
  • nowar

    有两个问题请帮忙解答,先行谢过 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

      @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

        @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

          @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

            @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,
            • 禅机子

              @bsp:想问一下,最近我遇到一个问题,就是user_task,存在扫描文件的操作,如果刚好在suspend流程中,会出现freezing of tasks。 有什么解决思路嘛,麻烦啦
  • rikeyone

    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之间是可能导致进程修改文件系统数据的
  • bsp

    提个问题: 为什么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时间过长也是有问题的。