进程冻结是当系统hibernate或者suspend时,对进程进行暂停挂起的一种机制,后面主要以hibernate为例进行介绍。那么为什么要在hibernate或者suspend时需要把进程冻结呢?主要是出于如下的原因:
有3个 per-task 的 flag 用于描述进程冻结状态:
PF_NOFREEZE:如果置位表示该进程不会被冻结,为0表示进程需要在suspend或者hibernate时被冻结
PF_FROZEN:表示进程已经处于冻结状态
PF_FREEZER_SKIP:附加备用状态
3个重要的全局变量:
system_freezing_cnt:大于 0 表示系统进入了冻结状态
pm_freezing: true 表示用户进程被冻结
pm_nosig_freezing: true 表示内核进程和 workqueue 被冻结
重要的函数API:
freeze_processes():
-冻结用户态进程,内部会调用try_to_freeze_tasks(true)。freeze_kernel_threads():
-冻结内核线程,内核会调用try_to_freeze_tasks(false)(实际上是冻结所有进程,因为也会扫描用户态进程)thaw_kernel_threads():-解冻内核线程thaw_processes():-解冻所有进程(包括内核线程和用户态进程)
freeze_processes 和 freeze_kernel_threads 最终都会调用到一个关键函数 try_to_freeze_tasks
static int try_to_freeze_tasks(bool user_only)
{struct task_struct *g, *p;unsigned long end_time;unsigned int todo;bool wq_busy = false;ktime_t start, end, elapsed;unsigned int elapsed_msecs;bool wakeup = false;int sleep_usecs = USEC_PER_MSEC;
#ifdef CONFIG_PM_SLEEPchar suspend_abort[MAX_SUSPEND_ABORT_LEN];
#endifstart = ktime_get_boottime();end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);if (!user_only) //根据传入的参数判断是否只冻结用户态进程freeze_workqueues_begin(); //如果为false,则冻结WQ_FREEZABLE类型的workqueuewhile (true) {todo = 0;read_lock(&tasklist_lock);for_each_process_thread(g, p) { //遍历系统中所有进程if (p == current || !freeze_task(p)) //对非本进程的进程尝试冻结,冻结成功执行continue下一个continue;if (!freezer_should_skip(p)) //运行到这里说明尝试冻结失败了,如果该进程不能跳过冻结,todo需要+1todo++;}read_unlock(&tasklist_lock);if (!user_only) { //尝试对内核workqueue进行冻结wq_busy = freeze_workqueues_busy();todo += wq_busy;}if (!todo || time_after(jiffies, end_time)) //判断冻结操作是否完成,完成了就break退出循环,如果超时也会break,当做冻结失败break;if (pm_wakeup_pending()) {
#ifdef CONFIG_PM_SLEEPpm_get_active_wakeup_sources(suspend_abort,MAX_SUSPEND_ABORT_LEN);log_suspend_abort_reason(suspend_abort);
#endifwakeup = true;break;}/** We need to retry, but first give the freezing tasks some* time to enter the refrigerator. Start with an initial* 1 ms sleep followed by exponential backoff until 8 ms.*/usleep_range(sleep_usecs / 2, sleep_usecs); //等待一段时间后重新尝试冻结操作if (sleep_usecs < 8 * USEC_PER_MSEC)sleep_usecs *= 2;} //end of whileend = ktime_get_boottime();elapsed = ktime_sub(end, start);elapsed_msecs = ktime_to_ms(elapsed);if (wakeup) { //是否是被打断pr_cont("\n");pr_err("Freezing of tasks aborted after %d.%03d seconds",elapsed_msecs / 1000, elapsed_msecs % 1000);} else if (todo) { //超时退出时会到这里,此时todo大于0,还有进程未冻结,冻结失败pr_cont("\n");pr_err("Freezing of tasks failed after %d.%03d seconds"" (%d tasks refusing to freeze, wq_busy=%d):\n",elapsed_msecs / 1000, elapsed_msecs % 1000,todo - wq_busy, wq_busy);if (wq_busy)show_workqueue_state();read_lock(&tasklist_lock);for_each_process_thread(g, p) {if (p != current && !freezer_should_skip(p)&& freezing(p) && !frozen(p))sched_show_task(p);}read_unlock(&tasklist_lock);} else { //冻结成功pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,elapsed_msecs % 1000);}return todo ? -EBUSY : 0;
}
【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
freeze_task 是用来对一个进程进行freeze操作的函数,冻结进程的行为实际上只能由被冻结进程本身进行处理,而此函数只是向被冻结进程发送一个信号。
bool freeze_task(struct task_struct *p)
{unsigned long flags;/** This check can race with freezer_do_not_count, but worst case that* will result in an extra wakeup being sent to the task. It does not* race with freezer_count(), the barriers in freezer_count() and* freezer_should_skip() ensure that either freezer_count() sees* freezing == true in try_to_freeze() and freezes, or* freezer_should_skip() sees !PF_FREEZE_SKIP and freezes the task* normally.*/if (freezer_should_skip(p)) // ----------- step 1return false;spin_lock_irqsave(&freezer_lock, flags);if (!freezing(p) || frozen(p)) { // --------step 2spin_unlock_irqrestore(&freezer_lock, flags);return false;}if (!(p->flags & PF_KTHREAD))fake_signal_wake_up(p); // --------- step 3elsewake_up_state(p, TASK_INTERRUPTIBLE); // -------- step 4spin_unlock_irqrestore(&freezer_lock, flags);return true;
}
前面介绍到当对一个用户态的进程进行冻结请求时,会发送一个虚假的信号 fake_signal_wake_up 来唤醒用户态进程处理信号。那么这个流程是怎样的呢?
static void fake_signal_wake_up(struct task_struct *p)
{unsigned long flags;if (lock_task_sighand(p, &flags)) {signal_wake_up(p, 0);unlock_task_sighand(p, &flags);}
}
static inline void signal_wake_up(struct task_struct *t, bool resume)
{signal_wake_up_state(t, resume ? TASK_WAKEKILL : 0);
}void signal_wake_up_state(struct task_struct *t, unsigned int state)
{set_tsk_thread_flag(t, TIF_SIGPENDING); //设置SIGPENDING标记,这样当进程返回用户空间时会先处理信号/** TASK_WAKEKILL also means wake it up in the stopped/traced/killable* case. We don't check t->state here because there is a race with it* executing another processor and just now entering stopped state.* By using wake_up_state, we ensure the process will wake up and* handle its death signal.*/if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) //设置进程为TASK_INTERRUPTIBLE状态,并唤醒进程kick_process(t); //让进程陷入内核态处理信号
}
signal_wake_up_state 里面主要做了两件事情:
/**** kick_process - kick a running thread to enter/exit the kernel* @p: the to-be-kicked thread* Cause a process which is running on another CPU to enter* kernel-mode, without any delay. (to get signals handled.)
*/
void kick_process(struct task_struct *p)
{int cpu;preempt_disable();cpu = task_cpu(p); //找到该进程要被运行的cpuif ((cpu != smp_processor_id()) && task_curr(p))smp_send_reschedule(cpu);// 发送CPU间的IPI中断请求preempt_enable();
}void smp_send_reschedule(int cpu)
{BUG_ON(cpu_is_offline(cpu));smp_cross_call_common(cpumask_of(cpu), IPI_RESCHEDULE);
}
这里需要注意为什么最后要发送一个IPI中断给到需要运行该进程的CPU,因为需要冻结用户态进程,因为该中断会使得用户进程陷入到内核态,处理中断,中断处理完成后都会调用ret_to_user返回用户态继续运行,而在ret_to_user中,也就会返回用户态前会判断是否有pending signal要被处理,这时就可以处理进程冻结了。
->ret_to_user
-->do_notify_resume
--->do_signal
---->get_signal
------>try_to_freeze //执行线程具体眠动作
对于一个需要睡眠的内核线程,一般的处理流程如下:
set_freezable();
do {hub_events();wait_event_freezable(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
et_freezable 会清除本进程的 PF_NOFREEZE 标志,也就意味着该内核进程可以被冻结。或者:
set_freezable();
while (!kthread_should_stop()) {try_to_freeze();
......
}
while循环在每次运行到 try_to_freeze 时都会检测调用 freezing 函数检测一下本进程是否可以被冻结,如果可以就直接进行冻结操作。
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();if (likely(!freezing(current)))return false;return __refrigerator(false);//冻结存储
}
如果我们想要创建一个需要冻结的内核线程,就需要遵守上面的要求来实现它,否则会导致系统 freeze 失败从而无法休眠。