一、冻结流程概述
Linux休眠时的冻结过程分为两个阶段:
阶段1:冻结用户空间进程
- 调用:
freeze_processes() - 设置:
pm_freezing = true - 调用:
try_to_freeze_tasks(true)-user_only=true - 目标:只冻结用户空间进程(非内核线程)
阶段2:冻结内核线程
- 调用:
freeze_kernel_threads() - 设置:
pm_nosig_freezing = true - 调用:
try_to_freeze_tasks(false)-user_only=false - 目标:冻结可冻结的内核线程
二、代码实现分析
2.1 冻结判断逻辑
在 kernel/freezer.c 的 freezing_slow_path() 函数中:
c
bool freezing_slow_path(struct task_struct *p)
{
if (p->flags & (PF_NOFREEZE | PF_SUSPEND_TASK))
return false;
if (pm_nosig_freezing || cgroup_freezing(p))
return true; // 第二阶段:冻结所有任务(包括内核线程)
if (pm_freezing && !(p->flags & PF_KTHREAD))
return true; // 第一阶段:只冻结非内核线程
return false;
}
关键点:
- 第一阶段 (
pm_freezing=true):!(p->flags & PF_KTHREAD)- 只冻结非内核线程 - 第二阶段 (
pm_nosig_freezing=true):会冻结所有任务,包括内核线程
2.2 冻结流程代码
在 kernel/power/power.h 中:
c
static inline int suspend_freeze_processes(void)
{
// 第一阶段:冻结用户空间进程
error = freeze_processes(); // 设置 pm_freezing=true, 调用 try_to_freeze_tasks(true)
if (error)
return error;
// 第二阶段:冻结内核线程
error = freeze_kernel_threads(); // 设置 pm_nosig_freezing=true, 调用 try_to_freeze_tasks(false)
if (error) {
thaw_processes(); // 如果失败,解冻所有进程
}
return error;
}
2.3 不可冻结任务的判断
在 kernel/power/process.c 的 try_to_freeze_tasks() 函数中:
c
/* 检查是否不可冻结 */
if (p->flags & PF_NOFREEZE) {
unfreezable_tasks++;
// 标记为UNFREEZABLE,跳过,不调用freeze_task
continue;
}
/* 尝试冻结任务 */
if (!freeze_task(p)) {
failed_freeze_tasks++;
// 标记为FAILED_FREEZE
}
三、实际日志分析
从 20251219_12.log 日志中可以看到:
3.1 第一阶段:冻结用户空间进程
ini
[ 98.808286] Freezing user space processes ...
[ 98.808292] LLLLLLLL try_to_freeze_tasks: user_only=1, timeout=20000 ms
[ 98.810973] LLLLLLLL UNFREEZABLE: kthreadd (pid 2), flags=0x208040, state=1
[ 98.811793] LLLLLLLL UNFREEZABLE: rcu_gp (pid 3), flags=0x4208060, state=1026
...
观察:
user_only=1- 只冻结用户空间进程- 但是仍然会遍历所有任务,包括内核线程
- 内核线程会被标记为UNFREEZABLE(如果有PF_NOFREEZE标志)或跳过
3.2 第二阶段:冻结内核线程
重要发现 :从日志中可以看到,第一阶段就失败了,所以没有进入第二阶段:
ini
[ 119.065869] freeze_processes: failed to freeze tasks, error -16
[ 119.066756] freeze_processes: failed with error -16, thawing processes
[ 119.177013] PM: SUSPEND_DEBUG: suspend_prepare: freeze failed, error=-16
[ 119.177018] PM: SUSPEND_DEBUG: suspend_prepare: FORCE SUSPEND enabled, ignoring freeze failure
实际情况:
- 第一阶段(
freeze_processes())超时失败(error -16 = EBUSY) - 系统启用了
FORCE SUSPEND模式,即使冻结失败也继续挂起 - 因此没有进入第二阶段 (
freeze_kernel_threads()) - 这意味着内核线程没有被冻结
这解释了为什么:
- 只有3个任务被成功冻结(都是用户空间进程)
- 24个任务拒绝冻结(包括kswapd0和jbd2线程)
- 95个任务被标记为UNFREEZABLE(有PF_NOFREEZE标志的内核线程)
四、关键问题回答
Q: 在休眠唤醒时,内核的这些task也会冻结吗?
A: 是的,但分两个阶段,且有例外
详细说明:
-
第一阶段 - 只冻结用户空间进程:
- 设置
pm_freezing = true - 调用
try_to_freeze_tasks(true)-user_only=true - 在
freezing_slow_path()中:if (pm_freezing && !(p->flags & PF_KTHREAD)) - 只冻结非内核线程(用户空间进程)
- 设置
-
第二阶段 - 冻结可冻结的内核线程:
- 设置
pm_nosig_freezing = true - 调用
try_to_freeze_tasks(false)-user_only=false - 在
freezing_slow_path()中:if (pm_nosig_freezing || cgroup_freezing(p)) - 会冻结所有任务,包括内核线程
- 设置
-
例外 - 不可冻结的内核线程:
- 有
PF_NOFREEZE标志的内核线程不会被冻结 - 在
try_to_freeze_tasks()中会被跳过 - 这就是为什么我们看到95个UNFREEZABLE任务
- 有
总结:
- ✅ 用户空间进程:第一阶段被冻结
- ✅ 可冻结的内核线程(没有PF_NOFREEZE标志):第二阶段被冻结
- ❌ 不可冻结的内核线程(有PF_NOFREEZE标志):不会被冻结
五、为什么内核线程要分两个阶段冻结?
5.1 设计原因
-
用户空间进程优先:
- 用户空间进程可能正在执行关键操作
- 需要先确保用户空间进程停止,避免数据不一致
-
内核线程可能依赖用户空间:
- 某些内核线程可能正在处理来自用户空间的请求
- 先冻结用户空间,可以避免内核线程处理到一半被冻结
-
错误处理:
- 如果第一阶段失败,只需要解冻用户空间进程
- 如果第二阶段失败,需要解冻所有进程
5.2 实际好处
- 更清晰的错误处理:可以区分是用户空间还是内核线程冻结失败
- 更好的调试:可以分别查看两个阶段的日志
- 更灵活的控制:某些场景可能只需要冻结用户空间进程
六、不可冻结的内核线程
6.1 为什么有些内核线程不可冻结?
原因:
- 系统核心功能:如kthreadd、RCU相关线程,如果被冻结会导致系统死锁
- 硬件交互:如中断处理线程,必须及时响应硬件中断
- 数据一致性:某些线程正在执行关键操作,不能中途停止
6.2 如何判断内核线程是否可冻结?
判断标准:
- 有
PF_NOFREEZE标志 → 不可冻结(UNFREEZABLE) - 没有
PF_NOFREEZE标志 → 可冻结(但可能冻结失败)
从日志中可以看到:
- 95个UNFREEZABLE任务:都有
PF_NOFREEZE标志 - 6个FAILED_FREEZE任务:没有
PF_NOFREEZE标志,但冻结失败
七、总结
7.1 冻结流程
scss
suspend_freeze_processes()
↓
freeze_processes() [第一阶段]
├─ 设置 pm_freezing = true
├─ 调用 try_to_freeze_tasks(true)
└─ 只冻结用户空间进程(!(p->flags & PF_KTHREAD))
↓
freeze_kernel_threads() [第二阶段]
├─ 设置 pm_nosig_freezing = true
├─ 调用 try_to_freeze_tasks(false)
└─ 冻结可冻结的内核线程(没有PF_NOFREEZE标志的)
7.2 任务分类
| 任务类型 | 第一阶段 | 第二阶段 | 最终状态 |
|---|---|---|---|
| 用户空间进程 | ✅ 冻结 | - | 已冻结 |
| 可冻结的内核线程(无PF_NOFREEZE) | ❌ 跳过 | ✅ 冻结 | 已冻结 |
| 不可冻结的内核线程(有PF_NOFREEZE) | ❌ 跳过 | ❌ 跳过 | UNFREEZABLE |
| 冻结失败的内核线程 | ❌ 跳过 | ❌ 失败 | FAILED_FREEZE |
7.3 关键结论
理论上,内核线程也会被冻结,但是:
-
正常流程:
- 分两个阶段:先冻结用户空间进程,再冻结内核线程
- 有例外 :有
PF_NOFREEZE标志的内核线程不会被冻结 - 可能失败:某些内核线程可能无法冻结(如kswapd0、jbd2线程)
-
实际情况(从日志看):
- 第一阶段就失败了 :
freeze_processes()超时失败(error -16) - 没有进入第二阶段 :因为第一阶段失败,没有调用
freeze_kernel_threads() - 强制挂起 :系统启用了
FORCE SUSPEND,即使冻结失败也继续挂起 - 结果 :内核线程没有被冻结(除了有PF_NOFREEZE标志的,它们本来就不会被冻结)
- 第一阶段就失败了 :
-
这可能是唤醒失败的原因之一:
- 内核线程(如kswapd0、jbd2)没有被冻结
- 系统在不完整的状态下进入深度睡眠
- 唤醒时无法从不一致的状态中恢复