1. 概述
1.1 什么是进程冻结?
进程冻结(Process Freezing)是 Linux 内核在系统挂起(Suspend)时使用的一种机制,用于暂停所有用户空间进程的执行,使系统能够安全地进入低功耗状态。
1.2 核心特点
- 状态保存在 RAM 中:不需要写入磁盘
- 快速恢复:恢复时间通常在毫秒级
- 自动保存:调度器自动处理寄存器保存
- 透明性:用户空间进程无需修改代码(但需要正确使用系统调用)
1.3 与休眠(Hibernation)的对比
| 特性 | 进程冻结(Freeze) | 休眠(Hibernate) |
|---|---|---|
| 状态保存位置 | RAM(内存) | 磁盘(swap分区) |
| 保存方式 | 自动(调度器) | 手动(写入磁盘) |
| 恢复时间 | 毫秒级(<100ms) | 秒级(1-10秒) |
| 功耗 | 低(但仍在运行) | 极低(几乎为零) |
| 断电支持 | 不支持(需要供电) | 支持(可断电) |
| 使用场景 | 挂起(Suspend to RAM) | 休眠(Suspend to Disk) |
| 状态大小 | 整个RAM | 压缩后的内存镜像 |
2. 进程状态的数据结构
2.1 task_struct - 进程的核心数据结构
每个进程在内核中都有一个 task_struct 结构,包含了进程的所有信息:
c
struct task_struct {
// ========== 进程标识 ==========
pid_t pid; // 进程ID
pid_t tgid; // 线程组ID
char comm[TASK_COMM_LEN]; // 进程名称
// ========== 进程状态 ==========
volatile long state; // 进程状态:
// TASK_RUNNING (0) - 可运行
// TASK_INTERRUPTIBLE (1) - 可中断睡眠
// TASK_UNINTERRUPTIBLE (2) - 不可中断睡眠
// TASK_STOPPED (4) - 停止
// TASK_TRACED (8) - 被跟踪
// EXIT_DEAD (16) - 死亡
// EXIT_ZOMBIE (32) - 僵尸
// ========== 进程标志 ==========
unsigned int flags; // 进程标志位:
// PF_FROZEN (0x00000040) - 已冻结
// PF_FREEZER_SKIP (0x400000) - 跳过冻结
// PF_NOFREEZE (0x800000) - 不可冻结
// PF_KTHREAD (0x00200000) - 内核线程
// ========== 内存管理 ==========
struct mm_struct *mm; // 内存描述符(用户空间)
struct mm_struct *active_mm; // 活动内存描述符
// ========== CPU 寄存器状态 ==========
struct thread_struct thread; // CPU 寄存器状态
// 这是状态保存的关键!
// ========== 文件系统 ==========
struct files_struct *files; // 打开的文件描述符
// ========== 信号处理 ==========
struct signal_struct *signal; // 信号处理结构
struct sigpending pending; // 待处理的信号
// ========== 定时器 ==========
struct list_head cpu_timers[3]; // CPU 定时器列表
// ========== 调度相关 ==========
int prio; // 优先级
int static_prio; // 静态优先级
struct sched_entity se; // 调度实体
// ========== 其他 ==========
struct list_head tasks; // 进程链表
// ... 更多字段
};
2.2 thread_struct - CPU 寄存器状态
这是状态保存的核心!所有 CPU 寄存器都保存在这里:
c
struct thread_struct {
// ARM64 架构示例
unsigned long sp; // 栈指针(Stack Pointer)
unsigned long pc; // 程序计数器(Program Counter)
unsigned long lr; // 链接寄存器(Link Register)
unsigned long sp_el0; // EL0 栈指针
unsigned long sp_el1; // EL1 栈指针
unsigned long elr_el1; // EL1 异常链接寄存器
unsigned long spsr_el1;// EL1 保存的程序状态寄存器
// 通用寄存器
unsigned long regs[31]; // x0-x30 通用寄存器
// 浮点寄存器
struct fpsimd_state fpsimd_state; // 浮点和 SIMD 寄存器
// 其他架构特定寄存器
// ...
};
关键点:
- 当进程被调度出去时,调度器自动将所有寄存器保存到这里
- 当进程被调度回来时,调度器自动从这里恢复所有寄存器
- 不需要手动保存或恢复,这是调度器的标准功能
2.3 mm_struct - 内存映射
c
struct mm_struct {
struct vm_area_struct *mmap; // 虚拟内存区域链表
pgd_t *pgd; // 页全局目录(Page Global Directory)
atomic_t mm_users; // 使用计数
atomic_t mm_count; // 引用计数
// 内存区域
unsigned long start_code; // 代码段起始地址
unsigned long end_code; // 代码段结束地址
unsigned long start_data; // 数据段起始地址
unsigned long end_data; // 数据段结束地址
unsigned long start_brk; // 堆起始地址
unsigned long brk; // 堆当前地址
unsigned long start_stack; // 栈起始地址
// 页表
struct rw_semaphore mmap_sem; // 内存映射信号量
// ...
};
关键点:
- 所有内存映射信息都在这个结构中
- 内存内容本身在 RAM 中,不需要特殊保存
- 挂起时 RAM 保持供电(或进入低功耗模式),内存内容不丢失
3. 冻结信号的发送机制
3.1 冻结流程的入口
c
// kernel/power/process.c
int freeze_processes(void)
{
int error;
int oom_kills_saved;
// 1. 记录当前的 OOM kill 计数
oom_kills_saved = oom_kills_count();
// 2. 设置全局冻结标志
error = __usermodehelper_disable(UMH_FREEZING);
if (error)
return error;
// 3. 开始冻结用户空间进程
if (!pm_freezing)
atomic_inc(&system_freezing_cnt);
pm_freezing = true;
// 4. 尝试冻结所有任务
error = try_to_freeze_tasks(true);
// 5. 检查是否有进程无法冻结
if (!error && !oom_kills_saved && oom_kills_count() != oom_kills_saved)
error = -EBUSY;
if (error) {
// 冻结失败,恢复状态
thaw_processes();
} else {
// 冻结成功
pr_info("Freezing user space processes ... (elapsed %d.%03d seconds) done.\n",
elapsed_msecs / 1000, elapsed_msecs % 1000);
}
return error;
}
3.2 try_to_freeze_tasks - 冻结所有任务
c
// kernel/power/process.c
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;
struct timeval start, end;
u64 elapsed_msecs64;
unsigned int elapsed_msecs;
bool wakeup = false;
int sleep_usecs = USEC_PER_MSEC;
// 1. 记录开始时间
do_gettimeofday(&start);
end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);
// 2. 循环尝试冻结所有任务
while (true) {
todo = 0;
read_lock(&tasklist_lock);
// 3. 遍历所有进程
for_each_process_thread(g, p) {
// 跳过内核线程(如果只冻结用户空间)
if (user_only && p->flags & PF_KTHREAD)
continue;
// 跳过不可冻结的进程
if (freezer_should_skip(p))
continue;
// 4. 尝试冻结这个进程
if (!freeze_task(p))
todo++;
}
read_unlock(&tasklist_lock);
// 5. 检查工作队列
if (!wq_busy) {
wq_busy = freeze_workqueues_busy();
todo += wq_busy;
}
// 6. 检查是否所有进程都已冻结
if (!todo || time_after(jiffies, end_time))
break;
// 7. 等待一段时间后重试
usleep_range(sleep_usecs, sleep_usecs + sleep_usecs / 2);
sleep_usecs = min_t(unsigned int, sleep_usecs * 2, 100 * USEC_PER_MSEC);
}
// 8. 检查结果
do_gettimeofday(&end);
elapsed_msecs64 = timeval_to_ns(&end) - timeval_to_ns(&start);
elapsed_msecs = elapsed_msecs64 / NSEC_PER_MSEC;
if (todo) {
// 有进程无法冻结
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);
// 打印无法冻结的进程信息
// ...
return -EBUSY;
}
return 0;
}
3.3 freeze_task - 向单个进程发送冻结信号
c
// kernel/freezer.c
bool freeze_task(struct task_struct *p)
{
unsigned long flags;
// 1. 检查是否应该跳过这个进程
if (freezer_should_skip(p))
return false;
// 2. 获取 freezer_lock
spin_lock_irqsave(&freezer_lock, flags);
// 3. 检查进程是否已经在冻结或已冻结
if (!freezing(p) || frozen(p)) {
spin_unlock_irqrestore(&freezer_lock, flags);
return false;
}
// 4. 发送冻结信号
if (!(p->flags & PF_KTHREAD)) {
// 用户空间进程:发送"假信号"
fake_signal_wake_up(p);
} else {
// 内核线程:直接唤醒
wake_up_state(p, TASK_INTERRUPTIBLE);
}
spin_unlock_irqrestore(&freezer_lock, flags);
return true;
}
3.4 fake_signal_wake_up - 发送"假信号"
c
// kernel/freezer.c
static void fake_signal_wake_up(struct task_struct *p)
{
unsigned long flags;
// 1. 获取信号处理锁
if (lock_task_sighand(p, &flags)) {
// 2. 唤醒进程(如果进程在睡眠)
signal_wake_up(p, 0);
// 注意:这里没有真正发送信号,只是:
// - 设置 TIF_SIGPENDING 标志
// - 唤醒进程(如果进程在 TASK_INTERRUPTIBLE 状态)
unlock_task_sighand(p, &flags);
}
}
关键点:
fake_signal_wake_up()不发送真正的信号- 它只是:
- 设置
TIF_SIGPENDING标志(表示有信号待处理) - 唤醒进程(如果进程在睡眠)
- 设置
- 进程被唤醒后,会在系统调用返回时检查这个标志
- 如果发现需要冻结,进程会调用
try_to_freeze()
4. 进程如何响应冻结信号
4.1 检查时机
进程在以下时机检查是否需要冻结:
- 系统调用返回时
- 从睡眠状态唤醒时
- 显式调用
try_to_freeze()时
4.2 系统调用返回时的检查
当用户空间进程执行系统调用时:
c
// 用户空间代码
int nfds = epoll_wait(epfd, events, maxevents, timeout);
// ↑ 系统调用
// 内核中的系统调用实现(简化版)
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
// ... 等待事件或超时 ...
// 系统调用返回前
if (freezing(current)) { // ← 检查是否需要冻结
__refrigerator(false); // ← 进入冻结状态
}
return nfds;
}
关键点:
- 进程只有在系统调用返回时才能检查冻结条件
- 如果系统调用长时间阻塞(如
epoll_wait使用 30 秒超时),进程就无法及时响应冻结请求
4.3 try_to_freeze - 检查并冻结
c
// include/linux/freezer.h
static inline bool try_to_freeze(void)
{
// 1. 检查是否有 PF_NOFREEZE 标志
if (!(current->flags & PF_NOFREEZE))
debug_check_no_locks_held();
// 2. 调用实际的冻结函数
return try_to_freeze_unsafe();
}
static inline bool try_to_freeze_unsafe(void)
{
might_sleep();
// 3. 检查是否需要冻结
if (likely(!freezing(current)))
return false;
// 4. 进入冻结状态
return __refrigerator(false);
}
4.4 freezing - 检查是否需要冻结
c
// include/linux/freezer.h
static inline bool freezing(struct task_struct *p)
{
// 1. 快速路径:如果没有冻结条件,直接返回 false
if (likely(!atomic_read(&system_freezing_cnt)))
return false;
// 2. 慢速路径:详细检查
return freezing_slow_path(p);
}
// kernel/freezer.c
bool freezing_slow_path(struct task_struct *p)
{
// 1. 检查是否有 PF_NOFREEZE 或 PF_SUSPEND_TASK 标志
if (p->flags & (PF_NOFREEZE | PF_SUSPEND_TASK))
return false;
// 2. 检查是否有 TIF_MEMDIE 标志(内存不足时杀死)
if (test_tsk_thread_flag(p, TIF_MEMDIE))
return false;
// 3. 检查 cgroup 冻结
if (pm_nosig_freezing || cgroup_freezing(p))
return true;
// 4. 检查 PM 冻结(用户空间进程)
if (pm_freezing && !(p->flags & PF_KTHREAD))
return true;
return false;
}
5. __refrigerator 的详细工作流程
5.1 完整代码分析
c
// kernel/freezer.c
bool __refrigerator(bool check_kthr_stop)
{
bool was_frozen = false;
unsigned int save = get_current_state(); // 保存当前状态
pr_debug("%s entered refrigerator\n", current->comm);
// ========== 进入无限循环 ==========
for (;;) {
// ========== 步骤 1:设置为不可中断状态 ==========
set_current_state(TASK_UNINTERRUPTIBLE);
// 这确保进程不会被信号唤醒(除了解冻信号)
// ========== 步骤 2:设置冻结标志 ==========
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);
// ========== 步骤 3:检查是否需要退出 ==========
if (!(current->flags & PF_FROZEN)) {
// 不需要冻结了,退出循环
break;
}
// ========== 步骤 4:标记为已冻结 ==========
was_frozen = true;
// ========== 步骤 5:进入睡眠(关键!)==========
schedule(); // ← 进程在这里被调度出去
// ========== 步骤 6:被唤醒后继续循环 ==========
// 当进程被解冻时,会从这里继续执行
// 继续循环检查是否还需要冻结
}
pr_debug("%s left refrigerator\n", current->comm);
// ========== 步骤 7:恢复之前的状态 ==========
set_current_state(save);
return was_frozen;
}
5.2 详细步骤说明
步骤 1:设置进程状态为 TASK_UNINTERRUPTIBLE
c
set_current_state(TASK_UNINTERRUPTIBLE);
作用:
- 将进程状态设置为不可中断睡眠
- 这确保进程不会被普通信号唤醒
- 只有解冻信号才能唤醒进程
状态转换:
scss
TASK_RUNNING (0)
↓
TASK_INTERRUPTIBLE (1) // 如果进程之前在等待
↓
TASK_UNINTERRUPTIBLE (2) // 现在设置为不可中断
步骤 2:设置 PF_FROZEN 标志
c
spin_lock_irq(&freezer_lock);
current->flags |= PF_FROZEN;
作用:
- 标记进程为已冻结状态
- 其他代码可以通过
frozen(p)检查进程是否已冻结
标志位:
c
#define PF_FROZEN 0x00000040 // 第 6 位
步骤 3:调用 schedule() - 关键步骤
c
schedule(); // 进程在这里被调度出去
这是状态保存的关键时刻!
当 schedule() 被调用时:
- 调度器选择下一个进程运行
- 自动保存当前进程的所有寄存器到
thread_struct - 进程被移出运行队列
- 进程进入 TASK_UNINTERRUPTIBLE 状态
- 进程的所有状态都保留在 RAM 中
详细过程:
c
// 调度器代码(简化版)
void schedule(void)
{
struct task_struct *prev = current;
struct task_struct *next;
// 1. 选择下一个要运行的进程
next = pick_next_task();
// 2. 如果切换进程
if (prev != next) {
// 3. 保存当前进程的寄存器
context_switch(prev, next);
}
}
void context_switch(struct task_struct *prev, struct task_struct *next)
{
// 1. 保存当前进程的寄存器到 thread_struct
switch_to(prev, next, prev);
// ↑ 这里会调用架构特定的代码保存所有寄存器
}
// ARM64 架构的寄存器保存(简化版)
.macro switch_to prev, next, last
// 保存所有通用寄存器
stp x19, x20, [\prev, #THREAD_CPU_CONTEXT + 16 * 0]
stp x21, x22, [\prev, #THREAD_CPU_CONTEXT + 16 * 1]
// ... 保存所有寄存器
// 保存栈指针
mov x10, sp
str x10, [\prev, #THREAD_CPU_CONTEXT + 16 * 13]
// 恢复下一个进程的寄存器
ldp x19, x20, [\next, #THREAD_CPU_CONTEXT + 16 * 0]
ldp x21, x22, [\next, #THREAD_CPU_CONTEXT + 16 * 1]
// ... 恢复所有寄存器
// 恢复栈指针
ldr x10, [\next, #THREAD_CPU_CONTEXT + 16 * 13]
mov sp, x10
.endm
关键点:
- 所有寄存器(包括 SP、PC、LR、通用寄存器、浮点寄存器)都被保存到
thread_struct - 这是自动的,不需要手动保存
- 保存的位置是
task_struct.thread,在 RAM 中
步骤 4:进程被唤醒
当系统解冻时,进程会被唤醒:
c
void __thaw_task(struct task_struct *p)
{
if (frozen(p)) {
wake_up_process(p); // 唤醒进程
}
}
唤醒过程:
- 清除 PF_FROZEN 标志
- 将进程状态改为 TASK_RUNNING
- 将进程加入运行队列
- 调度器选择进程运行
- 从
thread_struct恢复所有寄存器 - 从
schedule()返回,继续执行
6. 调度器如何保存和恢复状态
6.1 寄存器保存的详细过程
ARM64 架构示例
c
// arch/arm64/include/asm/processor.h
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp; // x29 - 帧指针
unsigned long sp; // x30 - 栈指针(实际上是 x31)
unsigned long pc; // 程序计数器
};
struct thread_struct {
struct cpu_context cpu_context; // CPU 寄存器上下文
unsigned long tp_value; // 线程指针
struct fpsimd_state fpsimd_state; // 浮点和 SIMD 寄存器
// ...
};
保存过程(汇编代码)
assembly
// arch/arm64/kernel/entry.S
.macro save_all_regs
// 保存通用寄存器 x19-x28
stp x19, x20, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x23, x24, [sp, #-16]!
stp x25, x26, [sp, #-16]!
stp x27, x28, [sp, #-16]!
// 保存帧指针和链接寄存器
stp x29, x30, [sp, #-16]!
// 保存程序计数器(PC)
mrs x0, elr_el1
str x0, [sp, #-8]
// 保存栈指针
mov x0, sp
str x0, [sp, #-8]
.endm
恢复过程(汇编代码)
assembly
// arch/arm64/kernel/entry.S
.macro restore_all_regs
// 恢复栈指针
ldr x0, [sp, #-8]
mov sp, x0
// 恢复程序计数器
ldr x0, [sp, #-8]
msr elr_el1, x0
// 恢复帧指针和链接寄存器
ldp x29, x30, [sp], #16
// 恢复通用寄存器 x19-x28
ldp x27, x28, [sp], #16
ldp x25, x26, [sp], #16
ldp x23, x24, [sp], #16
ldp x21, x22, [sp], #16
ldp x19, x20, [sp], #16
.endm
6.2 浮点寄存器保存
c
// arch/arm64/include/asm/fpsimd.h
struct fpsimd_state {
__uint128_t vregs[32]; // 128位 SIMD 寄存器
u32 fpsr; // 浮点状态寄存器
u32 fpcr; // 浮点控制寄存器
};
保存时机:
- 当进程被调度出去时,如果使用了浮点/SIMD 指令
- 保存到
thread_struct.fpsimd_state
7. 内存状态的保存
7.1 内存布局
进程的内存空间包括:
scss
高地址
┌─────────────────┐
│ 栈 (Stack) │ ← 向下增长
│ │
├─────────────────┤
│ │
│ 堆 (Heap) │ ← 向上增长
│ │
├─────────────────┤
│ 数据段 (BSS) │
├─────────────────┤
│ 数据段 (Data) │
├─────────────────┤
│ 代码段 (Text) │
└─────────────────┘
低地址
7.2 内存映射信息
所有内存映射信息都在 mm_struct 中:
c
struct mm_struct {
// 虚拟内存区域链表
struct vm_area_struct *mmap;
// 页表
pgd_t *pgd; // 页全局目录
// 内存区域
unsigned long start_code; // 代码段起始
unsigned long end_code; // 代码段结束
unsigned long start_data; // 数据段起始
unsigned long end_data; // 数据段结束
unsigned long start_brk; // 堆起始
unsigned long brk; // 堆当前
unsigned long start_stack; // 栈起始
// ...
};
7.3 内存内容的位置
关键点:内存内容本身在 RAM 中!
css
┌─────────────────────────────────────┐
│ RAM (物理内存) │
├─────────────────────────────────────┤
│ 进程 A 的代码段 │ ← 在 RAM 中
│ 进程 A 的数据段 │ ← 在 RAM 中
│ 进程 A 的堆 │ ← 在 RAM 中
│ 进程 A 的栈 │ ← 在 RAM 中
├─────────────────────────────────────┤
│ 进程 B 的代码段 │ ← 在 RAM 中
│ 进程 B 的数据段 │ ← 在 RAM 中
│ ... │
└─────────────────────────────────────┘
挂起时的行为:
- RAM 保持供电(或进入低功耗模式)
- 内存内容不会丢失
- 不需要写入磁盘
- 恢复时直接从 RAM 读取
7.4 页表
页表定义了虚拟地址到物理地址的映射:
虚拟地址空间 页表 物理地址空间
┌──────────┐ ┌──────┐ ┌──────────┐
│ 0x400000 │ ────→ │ PTE │ ────→ │ 0x100000 │
│ │ └──────┘ │ │
│ 0x500000 │ ────→ │ PTE │ ────→ │ 0x200000 │
│ │ └──────┘ │ │
└──────────┘ └──────────┘
关键点:
- 页表本身也在 RAM 中
- 挂起时页表保持不变
- 恢复时页表仍然有效
8. 文件描述符和打开文件的保存
8.1 files_struct
c
struct files_struct {
atomic_t count; // 引用计数
struct fdtable *fdt; // 文件描述符表
struct file *fd_array[NR_OPEN_DEFAULT]; // 打开的文件数组
// ...
};
struct fdtable {
unsigned int max_fds; // 最大文件描述符数
struct file **fd; // 文件指针数组
// ...
};
8.2 文件状态
每个打开的文件都有:
c
struct file {
struct path f_path; // 文件路径
struct inode *f_inode; // inode
struct file_operations *f_op; // 文件操作
loff_t f_pos; // 文件位置(当前读写位置)
unsigned int f_flags; // 文件标志
// ...
};
关键点:
- 所有打开的文件信息都在
task_struct.files中 - 文件位置(
f_pos)等信息都保存在内存中 - 挂起时这些信息保持不变
- 恢复时文件描述符仍然有效
8.3 文件内容
文件内容本身不在进程的内存空间中,而是在:
- 磁盘上的文件系统
- 或者内核的页缓存中
关键点:
- 文件内容不需要特殊保存
- 挂起时文件系统保持不变
- 恢复时可以直接访问文件
9. 信号和定时器的保存
9.1 信号结构
c
struct signal_struct {
atomic_t count; // 引用计数
struct sigpending pending; // 待处理的信号
// ...
};
struct sigpending {
struct list_head list; // 信号链表
sigset_t signal; // 信号集合
};
9.2 待处理的信号
c
struct sigqueue {
struct list_head list; // 链表节点
siginfo_t info; // 信号信息
// ...
};
关键点:
- 所有待处理的信号都在
task_struct.signal.pending中 - 挂起时信号队列保持不变
- 恢复时信号仍然有效
9.3 定时器
c
struct task_struct {
struct list_head cpu_timers[3]; // CPU 定时器列表
// ...
};
struct cpu_timer {
struct list_head entry; // 链表节点
u64 expires; // 到期时间
void (*function)(unsigned long); // 回调函数
unsigned long data; // 回调数据
// ...
};
关键点:
- 所有定时器都在
task_struct.cpu_timers中 - 挂起时定时器暂停(或调整到期时间)
- 恢复时定时器继续工作
10. 解冻过程详解
10.1 thaw_processes - 解冻所有进程
c
// kernel/power/process.c
void thaw_processes(void)
{
struct task_struct *g, *p;
pr_info("Restarting tasks ... ");
// 1. 清除全局冻结标志
__usermodehelper_set_disable_depth(UMH_FREEZING);
atomic_dec(&system_freezing_cnt);
pm_freezing = false;
pm_nosig_freezing = false;
// 2. 唤醒所有冻结的进程
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (frozen(p)) {
__thaw_task(p);
}
}
read_unlock(&tasklist_lock);
// 3. 恢复用户空间辅助程序
__usermodehelper_set_disable_depth(UMH_DISABLED);
schedule();
pr_cont("done.\n");
}
10.2 __thaw_task - 解冻单个进程
c
// kernel/freezer.c
void __thaw_task(struct task_struct *p)
{
unsigned long flags;
spin_lock_irqsave(&freezer_lock, flags);
if (frozen(p)) {
// 清除冻结标志
p->flags &= ~PF_FROZEN;
// 唤醒进程
wake_up_process(p);
}
spin_unlock_irqrestore(&freezer_lock, flags);
}
10.3 进程恢复执行
当进程被唤醒后:
- 调度器选择进程运行
- 从
thread_struct恢复所有寄存器 - 从
schedule()返回 - 继续执行
__refrigerator()中的循环 - 检查
PF_FROZEN标志,发现已清除 - 退出
__refrigerator() - 恢复之前的状态
- 继续正常执行
11. 完整示例:一个进程的冻结过程
11.1 场景设置
假设有一个用户空间进程,正在执行:
c
// 用户空间代码
int main() {
int epfd = epoll_create1(0);
struct epoll_event events[10];
while (1) {
// 使用 30 秒超时(问题代码)
int nfds = epoll_wait(epfd, events, 10, 30000);
if (nfds > 0) {
// 处理事件
handle_events(events, nfds);
}
}
}
11.2 冻结过程的时间线
ini
T=0s: 进程调用 epoll_wait(epfd, events, 10, 30000)
↓
T=0s: 进入内核,开始等待事件或超时
↓
T=0s: 进程进入 TASK_INTERRUPTIBLE 状态
↓
T=5s: 系统尝试挂起,调用 freeze_processes()
↓
T=5s: 设置 pm_freezing = true
↓
T=5s: 调用 freeze_task(进程)
↓
T=5s: 调用 fake_signal_wake_up(进程)
↓
T=5s: 设置 TIF_SIGPENDING 标志
↓
T=5s: 唤醒进程(从 TASK_INTERRUPTIBLE 唤醒)
↓
T=5s: 进程被唤醒,但 epoll_wait 还没超时
↓
T=5s: 进程检查:有事件吗?没有
↓
T=5s: 进程检查:超时了吗?没有(还有25秒)
↓
T=5s: 进程继续等待 ← 问题:没有返回!
↓
T=30s: epoll_wait 超时返回
↓
T=30s: 系统调用返回,检查冻结条件
↓
T=30s: 发现 freezing(current) == true
↓
T=30s: 调用 try_to_freeze()
↓
T=30s: 调用 __refrigerator(false)
↓
T=30s: 设置 PF_FROZEN 标志
↓
T=30s: 调用 schedule() ← 进程被调度出去
↓
T=30s: 【调度器自动保存所有寄存器到 thread_struct】
↓
T=30s: 进程进入 TASK_UNINTERRUPTIBLE 状态
↓
T=30s: 进程的所有状态都保留在 RAM 中:
- 寄存器 → thread_struct(在 RAM 中)
- 内存(代码/数据/栈/堆)→ RAM
- 文件描述符 → files_struct(在 RAM 中)
- 信号/定时器 → signal_struct(在 RAM 中)
↓
T=30s: 进程成功冻结!
11.3 修复后的代码
c
// 修复后的代码
int main() {
int epfd = epoll_create1(0);
struct epoll_event events[10];
while (1) {
// 使用 1 秒超时(修复后)
int nfds = epoll_wait(epfd, events, 10, 1000);
if (nfds > 0) {
// 处理事件
handle_events(events, nfds);
} else if (nfds == 0) {
// 超时,检查是否需要退出
if (should_exit()) {
break;
}
} else {
// 错误处理
if (errno == EINTR) {
// 被信号中断,继续循环
continue;
}
// 其他错误处理
}
}
}
11.4 修复后的冻结过程
ini
T=0s: 进程调用 epoll_wait(epfd, events, 10, 1000)
↓
T=0s: 进入内核,开始等待事件或超时
↓
T=1s: epoll_wait 超时返回(正常情况)
↓
T=1s: 系统调用返回,检查冻结条件
↓
T=1s: 发现 freezing(current) == false(还没有冻结请求)
↓
T=1s: 继续循环
↓
T=5s: 系统尝试挂起,调用 freeze_processes()
↓
T=5s: 设置 pm_freezing = true
↓
T=5s: 调用 freeze_task(进程)
↓
T=5s: 调用 fake_signal_wake_up(进程)
↓
T=5s: 设置 TIF_SIGPENDING 标志
↓
T=5s: 进程正在执行循环,准备调用 epoll_wait
↓
T=5s: 进程调用 epoll_wait(epfd, events, 10, 1000)
↓
T=5s: 进入内核,检查 TIF_SIGPENDING 标志
↓
T=5s: 发现需要冻结,epoll_wait 提前返回(或快速超时)
↓
T=5s: 系统调用返回,检查冻结条件
↓
T=5s: 发现 freezing(current) == true
↓
T=5s: 调用 try_to_freeze()
↓
T=5s: 调用 __refrigerator(false)
↓
T=5s: 设置 PF_FROZEN 标志
↓
T=5s: 调用 schedule() ← 进程被调度出去
↓
T=5s: 【调度器自动保存所有寄存器到 thread_struct】
↓
T=5s: 进程成功冻结!← 只用了 5 秒,而不是 30 秒!
12. 总结
12.1 状态保存的位置
| 状态类型 | 保存位置 | 保存方式 | 是否需要特殊处理 |
|---|---|---|---|
| CPU 寄存器 | task_struct.thread |
调度器自动保存 | ❌ 不需要 |
| 内存(代码/数据) | RAM | 已在内存中 | ❌ 不需要 |
| 内存映射信息 | task_struct.mm |
已在内存中 | ❌ 不需要 |
| 文件描述符 | task_struct.files |
已在内存中 | ❌ 不需要 |
| 信号队列 | task_struct.signal |
已在内存中 | ❌ 不需要 |
| 定时器 | task_struct.cpu_timers |
已在内存中 | ❌ 不需要 |
| 进程状态 | task_struct.state |
调度器自动设置 | ❌ 不需要 |
12.2 关键点
- 所有状态都在 RAM 中:不需要写入磁盘
- 自动保存:调度器在进程切换时自动保存寄存器
- 内存保留:挂起时 RAM 保持供电,内存内容不丢失
- 快速恢复:恢复时只需恢复寄存器,从 RAM 继续执行
- 透明性:用户空间进程无需修改代码(但需要正确使用系统调用)
12.3 为什么需要短超时?
因为进程只有在系统调用返回时才能检查冻结条件。如果系统调用长时间阻塞,进程就无法及时响应冻结请求。
12.4 与休眠的区别
- 冻结(Freeze):状态在 RAM 中,快速恢复,用于挂起
- 休眠(Hibernate):状态写入磁盘,慢速恢复,用于长时间断电
附录:相关数据结构完整定义
A.1 task_struct(部分)
c
struct task_struct {
// 进程标识
pid_t pid;
pid_t tgid;
char comm[TASK_COMM_LEN];
// 进程状态
volatile long state;
unsigned int flags;
// 内存管理
struct mm_struct *mm;
struct mm_struct *active_mm;
// CPU 寄存器
struct thread_struct thread;
// 文件系统
struct files_struct *files;
// 信号处理
struct signal_struct *signal;
struct sigpending pending;
// 定时器
struct list_head cpu_timers[3];
// 调度
int prio;
struct sched_entity se;
// 链表
struct list_head tasks;
// ...
};
A.2 thread_struct(ARM64)
c
struct thread_struct {
struct cpu_context cpu_context;
unsigned long tp_value;
struct fpsimd_state fpsimd_state;
unsigned long fault_address;
unsigned long fault_code;
// ...
};
A.3 mm_struct(部分)
c
struct mm_struct {
struct vm_area_struct *mmap;
struct rb_root mm_rb;
pgd_t *pgd;
atomic_t mm_users;
atomic_t mm_count;
unsigned long start_code;
unsigned long end_code;
unsigned long start_data;
unsigned long end_data;
unsigned long start_brk;
unsigned long brk;
unsigned long start_stack;
// ...
};
文档结束