Linux电源管理5

Linux电源管理5(基于Linux6.6)---Generic PM Suspend介绍

 


一、概述

Linux内核提供了三种Suspend: Freeze、Standby和STR(Suspend to RAM),在用户空间向”/sys/power/state”文件分别写入”freeze”、”standby”和”mem”,即可触发它们。

在 Linux 内核中,Suspend 是一种电源管理机制,允许操作系统将系统的某些部分或整个系统置于低功耗状态,以延长电池寿命或减少能耗。Linux 提供了几种不同的挂起模式,常见的有 FreezeStandbySTR (Suspend to RAM),它们分别有不同的行为和适用场景。

以下是这三种挂起模式的概述:

1.1、Freeze (冻结模式)

Freeze 是一种较轻的挂起模式,系统中的设备和进程会被冻结,但 CPU 和内存仍然处于活动状态。

  • 工作原理:在 Freeze 模式下,操作系统会暂停所有进程的执行,尤其是用户空间的进程。内核会冻结所有与进程相关的活动,保持系统的基本状态不变。硬件设备可能会进入低功耗模式,但 CPU 会保持活动状态,内存内容保持不变。

  • 使用场景:此模式通常用于需要快速恢复的场景,因为设备和内存状态都没有被完全关闭。设备的驱动程序可能会停止处理数据或任务,但内存中的数据保持完整,允许较短时间内恢复操作。

  • 优点:恢复速度较快,因为系统状态没有被完全重置。

  • 缺点:与其他更深层次的挂起模式相比,能效较低,因为 CPU 和内存仍在消耗电力。

1.2、Standby (待机模式)

Standby 模式是一个中间级别的挂起模式,系统的部分硬件可能会关闭,但 CPU 和内存依然保持活动状态。

  • 工作原理:在 Standby 模式下,操作系统将尽量减少硬件的活动以节省电力。通常,待机模式下系统会关闭一些硬件设备,如硬盘、显示器等,但 CPU 和内存继续工作。类似于冻结模式,但是 Standby 模式下可能允许更多的硬件进入低功耗状态。

  • 使用场景:适用于不需要完全关闭设备、但仍需要降低功耗的场景。Standby 模式常用于移动设备中,以便在短时间内快速恢复工作。

  • 优点:比 Freeze 模式节省更多电力,恢复速度通常较快。

  • 缺点:虽然比 Freeze 模式更节能,但恢复速度略慢,设备的一部分仍然处于活动状态。

1.3、STR (Suspend to RAM) - 睡眠模式

STR (Suspend to RAM),也称为 Suspend to RAM睡眠模式,是一种深度挂起模式,系统会将大部分硬件和设备的电源关闭,同时将内存的内容保留在 RAM 中。

  • 工作原理:在 STR 模式下,CPU 会完全停止工作,硬件大部分会进入休眠状态,只有内存仍然保持活动(即保持电力供应)。内存中保存着系统的当前状态,包括正在运行的进程和数据。此时,内存的内容被保留在 RAM 中,电力供应给内存,以确保系统可以从挂起状态中快速恢复。

  • 使用场景:STR 模式适用于需要较长时间节省电力的情况,尤其是在笔记本电脑和移动设备上,当设备长时间不使用时,进入该模式以延长电池寿命。

  • 优点

    • 最大限度减少电力消耗,因为大部分硬件被关闭。
    • 恢复速度相对较快,因为系统的状态保存在内存中,恢复时无需重新启动。
  • 缺点

    • 内存需要持续供电,否则数据会丢失。此模式对电池的依赖较大。
    • 在某些硬件平台上,恢复过程可能不如预期顺利,存在硬件兼容性问题。

各模式的比较

模式电源消耗恢复速度使用场景特点
Freeze较低非常快快速挂起并恢复,保持进程状态冻结进程,保持内存,但不关闭硬件
Standby中等短时间内降低功耗部分硬件关闭,但 CPU 和内存继续活动
STR非常低长时间节省电力,适合闲置时CPU 停止,内存内容保持,硬件大部分关闭

 

二、 suspend&resume过程概述

下面图片对Linux suspend&resume过程做了一个概述:

三、代码分析

3.1、suspend入口

在用户空间执行如下操作:

会通过sysfs触发suspend的执行,相应的处理代码如下:

static ssize_t state_store(struct device *dev, struct device_attribute *attr,
			   const char *buf, size_t count)
{
	const int online_type = mhp_online_type_from_str(buf);
	struct memory_block *mem = to_memory_block(dev);
	int ret;

	if (online_type < 0)
		return -EINVAL;

	ret = lock_device_hotplug_sysfs();
	if (ret)
		return ret;

	switch (online_type) {
	case MMOP_ONLINE_KERNEL:
	case MMOP_ONLINE_MOVABLE:
	case MMOP_ONLINE:
		/* mem->online_type is protected by device_hotplug_lock */
		mem->online_type = online_type;
		ret = device_online(&mem->dev);
		break;
	case MMOP_OFFLINE:
		ret = device_offline(&mem->dev);
		break;
	default:
		ret = -EINVAL; /* should never happen */
	}

	unlock_device_hotplug();

	if (ret < 0)
		return ret;
	if (ret)
		return -EINVAL;

	return count;
}

power_attr定义了一个名称为state的attribute文件,该文件的store接口为state_store,该接口在lock住autosleep功能后,解析用户传入的buffer(freeze、standby or mem),转换成state参数。

state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:

typedef int __bitwise suspend_state_t;
 
#define PM_SUSPEND_ON           ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE       ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY      ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM          ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN          PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX          ((__force suspend_state_t) 4)

根据state的值,如果不是(PM_SUSPEND_MAX,对应hibernate功能),则调用pm_suspend接口,进行后续的处理。 

pm_suspend在kernel/power/suspend.c定义,处理所有的suspend过程。  

3.2、pm_suspend & enter_state

pm_suspend的实现非常简单,简单的做一下参数合法性判断,直接调用enter_state接口,如下:

/**
 * pm_suspend - Externally visible function for suspending the system.
 * @state: System sleep state to enter.
 *
 * Check if the value of @state represents one of the supported states,
 * execute enter_state() and update system suspend statistics.
 */
int pm_suspend(suspend_state_t state)
{
	int error;

	if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
		return -EINVAL;

	pr_info("suspend entry (%s)\n", mem_sleep_labels[state]);
	error = enter_state(state);
	if (error) {
		suspend_stats.fail++;
		dpm_save_failed_errno(error);
	} else {
		suspend_stats.success++;
	}
	pr_info("suspend exit\n");
	return error;
}
EXPORT_SYMBOL(pm_suspend);

enter_state代码为:

/**
 * enter_state - Do common work needed to enter system sleep state.
 * @state: System sleep state to enter.
 *
 * Make sure that no one else is trying to put the system into a sleep state.
 * Fail if that's not the case.  Otherwise, prepare for system suspend, make the
 * system enter the given sleep state and clean up after wakeup.
 */
static int enter_state(suspend_state_t state)
{
	int error;

	trace_suspend_resume(TPS("suspend_enter"), state, true);
	if (state == PM_SUSPEND_TO_IDLE) {
#ifdef CONFIG_PM_DEBUG
		if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
			pr_warn("Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.\n");
			return -EAGAIN;
		}
#endif
	} else if (!valid_state(state)) {
		return -EINVAL;
	}
	if (!mutex_trylock(&system_transition_mutex))
		return -EBUSY;

	if (state == PM_SUSPEND_TO_IDLE)
		s2idle_begin();

	if (sync_on_suspend_enabled) {
		trace_suspend_resume(TPS("sync_filesystems"), 0, true);
		ksys_sync_helper();
		trace_suspend_resume(TPS("sync_filesystems"), 0, false);
	}

	pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]);
	pm_suspend_clear_flags();
	error = suspend_prepare(state);
	if (error)
		goto Unlock;

	if (suspend_test(TEST_FREEZER))
		goto Finish;

	trace_suspend_resume(TPS("suspend_enter"), state, false);
	pm_pr_dbg("Suspending system (%s)\n", mem_sleep_labels[state]);
	pm_restrict_gfp_mask();
	error = suspend_devices_and_enter(state);
	pm_restore_gfp_mask();

 Finish:
	events_check_enabled = false;
	pm_pr_dbg("Finishing wakeup.\n");
	suspend_finish();
 Unlock:
	mutex_unlock(&system_transition_mutex);
	return error;
}

主要工作包括:

a)调用valid_state,判断该平台是否支持该电源状态。

suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。

最后看一下valid_state的实现:

static bool valid_state(suspend_state_t state)
{
	/*
	 * The PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states require low-level
	 * support and need to be valid to the low-level implementation.
	 *
	 * No ->valid() or ->enter() callback implies that none are valid.
	 */
	return suspend_ops && suspend_ops->valid && suspend_ops->valid(state) &&
		suspend_ops->enter;
}

如果是freeze,无需平台代码参与即可支持,直接返回true。对于standby和mem,则需要调用suspend_ops的valid回掉,由底层平台代码判断是否支持。 

b)加互斥锁,只允许一个实例处理suspend。

c)如果state是freeze,调用freeze_begin,进行suspend to freeze相关的特殊动作。我会在后面统一分析freeze的特殊动作,这里暂不描述。

d)打印提示信息,同步文件系统。

e)调用suspend_prepare,进行suspend前的准备,主要包括switch console和process&thread freezing。如果失败,则终止suspend过程。

f)然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。

g)最后,调用suspend_finish,恢复(或等待恢复)process&thread,还原console。  

3.3、suspend_prepare

suspend_prepare的代码如下:

/**
 * suspend_prepare - Prepare for entering system sleep state.
 * @state: Target system sleep state.
 *
 * Common code run for every system sleep state that can be entered (except for
 * hibernation).  Run suspend notifiers, allocate the "suspend" console and
 * freeze processes.
 */
static int suspend_prepare(suspend_state_t state)
{
	int error;

	if (!sleep_state_supported(state))
		return -EPERM;

	pm_prepare_console();

	error = pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND);
	if (error)
		goto Restore;

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
	pm_notifier_call_chain(PM_POST_SUSPEND);
 Restore:
	pm_restore_console();
	return error;
}

主要工作为:

a)检查suspend_ops是否提供了.enter回调,没有的话,返回错误。

b)调用pm_prepare_console,将当前console切换到一个虚拟console并重定向内核的kmsg(需要的话)。

c)调用pm_notifier_call_chain,发送suspend开始的消息(PM_SUSPEND_PREPARE)。

d)调用suspend_freeze_processes,freeze用户空间进程和一些内核线程。

e)如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,并返回错误,以便能终止suspend。  

3.4、suspend_devices_and_enter

suspend_devices_and_enter的过程较为复杂,代码实现如下:

/**
 * suspend_devices_and_enter - Suspend devices and enter system sleep state.
 * @state: System sleep state to enter.
 */
int suspend_devices_and_enter(suspend_state_t state)
{
	int error;
	bool wakeup = false;

	if (!sleep_state_supported(state))
		return -ENOSYS;

	pm_suspend_target_state = state;

	if (state == PM_SUSPEND_TO_IDLE)
		pm_set_suspend_no_platform();

	error = platform_suspend_begin(state);
	if (error)
		goto Close;

#ifdef CONFIG_SCM_CM
#ifdef CONFIG_SCM_CORE_CLK
	pr_info("current ddr_level:%d\n", ddr_get_freq_lv());
#endif
#endif
	suspend_console();
	suspend_test_start();
	error = dpm_suspend_start(PMSG_SUSPEND);
	if (error) {
		pr_err("Some devices failed to suspend, or early wake event detected\n");
		goto Recover_platform;
	}
	suspend_test_finish("suspend devices");
	if (suspend_test(TEST_DEVICES))
		goto Recover_platform;

	do {
		error = suspend_enter(state, &wakeup);
	} while (!error && !wakeup && platform_suspend_again(state));

 Resume_devices:
	suspend_test_start();
	dpm_resume_end(PMSG_RESUME);
	suspend_test_finish("resume devices");
	trace_suspend_resume(TPS("resume_console"), state, true);
	resume_console();
	trace_suspend_resume(TPS("resume_console"), state, false);

 Close:
	platform_resume_end(state);
	pm_suspend_target_state = PM_SUSPEND_ON;
	return error;

 Recover_platform:
	platform_recover(state);
	goto Resume_devices;
}

a)再次检查平台代码是否需要提供以及是否提供了suspend_ops。

b)调用suspend_ops的begin回调,通知平台代码,以便让其作相应的处理。可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。

c)调用suspend_console,挂起console。

d)调用ftrace_stop,停止ftrace功能。ftrace是一个很有意思的功能,后面再介绍。

e)调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数,suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。

f)以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下:

/**
 * suspend_enter - Make the system enter the given sleep state.
 * @state: System sleep state to enter.
 * @wakeup: Returns information that the sleep state should not be re-entered.
 *
 * This function should be called after devices have been suspended.
 */
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	int error;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		pr_err("late suspend of devices failed\n");
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		pr_err("noirq suspend of devices failed\n");
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	if (state == PM_SUSPEND_TO_IDLE) {
		s2idle_loop();
		goto Platform_wake;
	}

	error = pm_sleep_disable_secondary_cpus();
	if (error || suspend_test(TEST_CPUS))
		goto Enable_cpus;

	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());

	system_state = SYSTEM_SUSPEND;

	error = syscore_suspend();
	if (!error) {
		*wakeup = pm_wakeup_pending();
		if (!(suspend_test(TEST_CORE) || *wakeup)) {
			trace_suspend_resume(TPS("machine_suspend"),
				state, true);
			error = suspend_ops->enter(state);
			trace_suspend_resume(TPS("machine_suspend"),
				state, false);
		} else if (*wakeup) {
			error = -EBUSY;
		}
		syscore_resume();
	}

	system_state = SYSTEM_RUNNING;

	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	pm_sleep_enable_secondary_cpus();

 Platform_wake:
	platform_resume_noirq(state);
	dpm_resume_noirq(PMSG_RESUME);

 Platform_early_resume:
	platform_resume_early(state);

 Devices_early_resume:
	dpm_resume_early(PMSG_RESUME);

 Platform_finish:
	platform_resume_finish(state);
	return error;
}

1)该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。

2)调用suspend_ops的prepare回调,通知平台代码,以便让其在即将进行状态切换之时,再做一些处理。该回调可能失败,失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。

3)调用dpm_suspend_end,调用所有设备的->suspend_late和->suspend_noirq回调函数,suspend late suspend设备和需要在关中断下suspend的设备。需要说明的是,这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。同样,该操作可能会失败,失败的话,跳至Platform_finish处,执行恢复动作。

4)调用suspend_ops的prepare_late回调,通知平台代码,以便让其在最后关头,再做一些处理。该回调可能失败,失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。

5)如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。

6)调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。

7)调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。

8)调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。有关syscore,我会在另一篇文章中详细描述。

9)如果很幸运,以上操作都成功了,那么,切换吧。不过,别高兴太早,还得调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。

10)如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……

11)suspend过程中,唤醒事件发生,系统唤醒,该函数接着执行resume动作,并最终返回。resume动作基本上是suspend的反动作,就不再继续分析了。

12)或者,由于意外,suspend终止,该函数也会返回。

以上1-12是对f的解析。

g)suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。

h)继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。

i)该函数返回后,表示系统已经resume。 

3.5、suspend_finish

比较简单:

/**
 * suspend_finish - Clean up before finishing the suspend sequence.
 *
 * Call platform code to clean up, restart processes, and free the console that
 * we've allocated. This routine is not called for hibernation.
 */
static void suspend_finish(void)
{
#ifdef CONFIG_SCM_CM
#ifdef CONFIG_SCM_CORE_CLK
	pr_info("current ddr_level:%d\n", ddr_get_freq_lv());
#endif
#endif
	suspend_thaw_processes();
	pm_notifier_call_chain(PM_POST_SUSPEND);
	pm_restore_console();
}

a)恢复所有的用户空间进程和内核线程。

b)发送suspend结束的通知。

c)将console切换回原来的。   

四、回顾知识

4.1、VT switch

通常情况下,系统控制台模块(drivers\tty\vt\)会在suspend的过程中,重新分配一个console,并将控制台切换到该console上。然后在resume时,切换回旧的console。这就是VT switch功能。VT switch是很耗时的,因此内核提供了一些机制,控制是否使用这个功能:

1)提供一个接口函数pm_set_vt_switch(drivers\tty\vt\vt_ioctl.c),方便其它内核模块从整体上关闭或者开启VT switch功能。

2)VT switch全局开关处于开启状态时,满足如下的一种条件(可参考kernel\power\console.c相关的描述),即会使能VT switch。

        a)有console driver调用pm_vt_switch_required接口,显式的要求使能VT switch。PM core的console模块会把这些信息记录在一个名称为pm_vt_switch_list的链表中。

        b)系统禁止在suspend的过程中suspend console(由kernel/printk.c中的console_suspend_enabled变量控制)。很有可能需要使用console查看suspend过程,此时为了使console不混乱,有必要进行VT switch。

        c)没有任何console driver关心是否需要VT switch,换句话说没有任何driver调用pm_vt_switch_required接口要求使能或禁止VT switch功能。此时会按照旧的习惯,进行VT switch。 

因此,suspend过程对console的处理分为4步:

第一步:prepare console:负责在需要VT swich时,将当前console切换到SUSPEND console。

kernel/power/console.c 

void pm_prepare_console(void)
{
	if (!pm_vt_switch())
		return;

	orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE, 1);
	if (orig_fgconsole < 0)
		return;

	orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
	return;
}

第二步:suspend console:挂起console,由kernel/printk.c实现,主要是hold住console用的互斥锁,使他人无法使用console。  

第三步:resume console:对console解锁。

第四步:restore console:将console恢复为初始的console。

kernel/power/console.c 

void pm_restore_console(void)
{
	if (!pm_vt_switch())
		return;

	if (orig_fgconsole >= 0) {
		vt_move_to_console(orig_fgconsole, 0);
		vt_kmsg_redirect(orig_kmsg);
	}
}

4.2、freezing of task

进程的freezing功能,是suspend、hibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(freeze)。该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间进程和部分内核线程处于一个稳定的状态。

1. 电源管理中的任务冻结(系统休眠)

在系统进入休眠操作(例如进入睡眠或休眠状态)时,Linux 内核会冻结任务,以确保在休眠过程中不会有任务继续执行或干扰系统的电源管理操作。

  • 冻结进程freeze 操作停止一个进程的执行,但不会终止它。它只是暂停该进程,直到它被显式地恢复。这在系统进入睡眠状态(如休眠到 RAM 或休眠到磁盘)时非常常见。

  • 冻结的应用场景

    • 系统休眠:当系统进入低功耗状态(如休眠到 RAM)时,内核会冻结所有用户空间的进程,确保在系统进入睡眠模式时不会有进程执行。
    • 休眠:在休眠过程中,冻结任务是为了确保系统的状态(包括进程)可以被安全地保存到磁盘,而不会有进程活动干扰。
  • 任务冻结的工作原理

    • 内核冻结:内核会将任务标记为“冻结”状态,这意味着任务不能继续执行,但任务仍然保留在内存中。
    • freeze_processes():内核提供了 freeze_processes() API,可以用来冻结所有进程或特定进程。这对于电源管理和休眠操作非常有用。

2. 内核操作中的任务冻结

任务冻结也可用于内核操作,例如在内核需要执行某些关键操作时,冻结任务可以防止这些任务对内核操作产生干扰。

  • 内核线程:一些内核线程可能在执行特定操作时被冻结,例如在更新系统的关键数据结构时。这样可以确保这些任务不会在操作进行时更改状态。

  • 任务状态:冻结任务通常通过将任务的状态转换为特殊的“冻结”状态来实现,例如通过 TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE 等状态标记。内核也可以使用 freeze_task() 来显式冻结任务。

3. 任务调度中的任务冻结

在 Linux 中,任务调度是精确管理的,以确保每个任务(进程)都能公平地获得 CPU 时间。在某些条件下,可能需要冻结任务。例如:

  • 在关键操作期间:当内核执行关键操作时,任务可能会被暂时冻结,以防止它们并行执行或打断内核操作。

  • 任务冻结的 API:内核提供了冻结和解冻任务的函数。例如:

    • freeze_task():该函数用于冻结特定的任务,冻结后任务将不会继续执行,直到被解冻。
    • thaw_task():该函数用于恢复冻结的任务,使其可以继续执行。

4. 冻结任务的代码示例

任务冻结通常在内核模块中使用,特别是在电源管理的上下文中。以下是一个简单的代码示例,展示了如何冻结和解冻任务:

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/suspend.h>

static int __init freeze_task_example_init(void) {
    struct task_struct *task;
    
    // 示例:遍历系统中的所有任务(进程)
    for_each_process(task) {
        printk(KERN_INFO "Freezing task: %s\n", task->comm);
        
        // 冻结任务(这是一个简化的示例)
        freeze_task(task);
        
        // 可以做其他操作(例如等待、处理数据等)
        // 后续可以恢复(解冻)任务
        thaw_task(task);
    }

    return 0;
}

static void __exit freeze_task_example_exit(void) {
    printk(KERN_INFO "Exiting freeze task example module\n");
}

module_init(freeze_task_example_init);
module_exit(freeze_task_example_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("示例作者");
MODULE_DESCRIPTION("一个简单的模块来演示任务冻结");

5. 控制组(cgroup)和任务冻结

除了内核的核心机制,任务冻结在 控制组(cgroup) 的上下文中也非常常见。cgroup 是 Linux 内核的一项功能,允许按组管理和限制进程的资源使用。

  • 冻结 cgroup:cgroup(进程组)可以通过 cgroup.freeze 控制文件来冻结。这在某些场景下非常有用,比如应用某些资源限制,或者在迁移进程时冻结其状态。

  • echo 1 > /sys/fs/cgroup/freezer/my_cgroup/freezer.state  # 冻结 cgroup
    echo 0 > /sys/fs/cgroup/freezer/my_cgroup/freezer.state  # 解冻 cgroup
    
  • 容器中的进程冻结:在容器或虚拟化环境中,冻结任务也是常见的做法。例如,在进行容器检查点或迁移时,冻结容器中的进程。

6. 系统电源状态中的任务冻结

在电源状态转换期间,冻结任务是进入和退出低功耗状态的重要组成部分,尤其在笔记本和嵌入式系统中。

  • 休眠到 RAM(STR):在支持休眠到 RAM(即睡眠模式)的系统中,通常会冻结所有任务,以确保在进入休眠状态时没有进程继续执行。
  • 休眠到磁盘:在休眠到磁盘(即休眠模式)时,内核冻结所有进程,保存系统状态到磁盘,然后关闭电源。唤醒后,系统恢复状态并解冻任务。

4.3、PM notifier

PM notifier是基于内核blocking notifier功能实现的。blocking notifier提供了一种kernel内部的消息通知机制,消息接受者通过notifier注册的方式,注册一个回调函数,关注消息发送者发出的notifier。当消息产生时,消息产生者通过调用回调函数的形式,通知消息接受者。这种调用,是可以被阻塞的,因此称作blocking notifier。

那suspend功能为什么使用notifier呢?原因可能有多种:

由之前的描述可知,suspend过程中,suspend device发生在进程被freeze之后,resume device发生在进程被恢复之前。那么:

1)如果有些设备就需要在freeze进程之前suspend怎么办?

2)如果有些设备的resume动作需要较多延时,或者要等待什么事情发生,那么如果它的resume动作发生在进程恢复之前,岂不是要阻止所有进程的恢复?更甚者,如果该设备要等待某个进程的数据才能resume,怎么办?

再来看suspend_prepare和suspend_finish中的处理:

static int suspend_prepare(suspend_state_t state)
{
	int error;

	if (!sleep_state_supported(state))
		return -EPERM;

	pm_prepare_console();

	error = pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND);
	if (error)
		goto Restore;

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
	pm_notifier_call_chain(PM_POST_SUSPEND);
 Restore:
	pm_restore_console();
	return error;
}
 
static void suspend_finish(void)
{
#ifdef CONFIG_SCM_CM
#ifdef CONFIG_SCM_CORE_CLK
	pr_info("current ddr_level:%d\n", ddr_get_freq_lv());
#endif
#endif
	suspend_thaw_processes();
	pm_notifier_call_chain(PM_POST_SUSPEND);
	pm_restore_console();
}

原来PM notifier是在设备模型的框架外,开了一个后门,那些比较特殊的driver,可以绕过设备模型,直接接收PM发送的suspend信息,以便执行自身的suspend动作。特别是resume时,可以在其它进程都正好工作的时候,只让suspend进程等待driver的resume。

举例说明:

以下是一个简单的示例,展示了如何注册和处理 PM Notifier 回调函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/notifier.h>
#include <linux/pm.h>

// PM Notifier 回调函数
static int pm_notifier_callback(struct notifier_block *nb, unsigned long event, void *data) {
    switch (event) {
        case PM_SUSPEND:
            pr_info("System is about to suspend.\n");
            break;
        case PM_RESUME:
            pr_info("System has resumed.\n");
            break;
        case PM_HIBERNATE:
            pr_info("System is about to hibernate.\n");
            break;
        case PM_RESTORE:
            pr_info("System has restored from hibernation.\n");
            break;
        default:
            pr_info("Received unhandled PM event: %lu\n", event);
            break;
    }
    return NOTIFY_OK;
}

// Notifier 结构体
static struct notifier_block pm_notifier = {
    .notifier_call = pm_notifier_callback,
};

// 模块初始化函数
static int __init pm_notifier_init(void) {
    // 注册 PM Notifier
    int ret = register_pm_notifier(&pm_notifier);
    if (ret) {
        pr_err("Failed to register PM notifier: %d\n", ret);
        return ret;
    }
    pr_info("PM notifier registered successfully.\n");
    return 0;
}

// 模块退出函数
static void __exit pm_notifier_exit(void) {
    // 注销 PM Notifier
    unregister_pm_notifier(&pm_notifier);
    pr_info("PM notifier unregistered.\n");
}

module_init(pm_notifier_init);
module_exit(pm_notifier_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple PM notifier example.");

4.4、device PM ops 和platform PM ops的调用时机

device PM ops和platform PM ops就是电源管理(suspend)的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。

struct dev_pm_ops {
        int (*prepare)(struct device *dev);
        void (*complete)(struct device *dev);
        int (*suspend)(struct device *dev);
        int (*resume)(struct device *dev);
        int (*freeze)(struct device *dev);
        int (*thaw)(struct device *dev);
        int (*poweroff)(struct device *dev);
        int (*restore)(struct device *dev);
        int (*suspend_late)(struct device *dev);
        int (*resume_early)(struct device *dev);
        int (*freeze_late)(struct device *dev);
        int (*thaw_early)(struct device *dev);
        int (*poweroff_late)(struct device *dev);
        int (*restore_early)(struct device *dev);
        int (*suspend_noirq)(struct device *dev);
        int (*resume_noirq)(struct device *dev);
        int (*freeze_noirq)(struct device *dev);
        int (*thaw_noirq)(struct device *dev);
        int (*poweroff_noirq)(struct device *dev);
        int (*restore_noirq)(struct device *dev);
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
};
 
struct platform_suspend_ops {
        int (*valid)(suspend_state_t state);
        int (*begin)(suspend_state_t state);
        int (*prepare)(void);
        int (*prepare_late)(void);
        int (*enter)(suspend_state_t state);
        void (*wake)(void);
        void (*finish)(void);
        bool (*suspend_again)(void);
        void (*end)(void);
        void (*recover)(void);
};

1. Device PM Ops

Device PM Ops 主要用于设备的电源管理。每个设备在内核中都有对应的 struct device 结构体,并且在这个结构体中,设备的电源管理操作(例如挂起、恢复等)是由 struct dev_pm_ops 定义的。

设备电源管理操作 (dev_pm_ops):

设备的 dev_pm_ops 包含了一组函数指针,这些指针指向设备的电源管理操作。例如:

  • suspend: 使设备进入低功耗状态(如待机或挂起)。
  • resume: 恢复设备的正常工作状态。
  • runtime_suspend: 使设备进入运行时待机状态。
  • runtime_resume: 恢复设备的运行状态。
  • freeze: 冻结设备状态(通常用于系统挂起时)。
  • thaw: 解冻设备状态。

调用时机:

设备的 PM 操作函数通常是在以下时机调用的:

  • suspend / resume

    • suspend:当系统准备进入挂起(如休眠)或关闭时,内核会调用设备的 suspend 操作,来挂起设备并关闭它的电源。
    • resume:当系统从挂起或休眠状态恢复时,内核会调用设备的 resume 操作,以恢复设备的正常工作。
  • runtime_suspend / runtime_resume

    • runtime_suspend:在系统空闲时(即设备不再被频繁使用),内核可能会调用设备的 runtime_suspend 操作,使设备进入低功耗状态。
    • runtime_resume:当设备重新需要工作时,内核会调用设备的 runtime_resume 操作,以恢复设备的工作状态。
  • freeze / thaw

    • freeze:系统进入挂起时,所有设备会冻结,以确保在系统挂起时不发生进一步的设备活动。
    • thaw:当系统恢复时,所有设备会解冻,恢复正常工作。

典型代码示例:

struct dev_pm_ops my_device_pm_ops = {
    .suspend = my_device_suspend,
    .resume = my_device_resume,
    .runtime_suspend = my_device_runtime_suspend,
    .runtime_resume = my_device_runtime_resume,
};

static int my_device_suspend(struct device *dev)
{
    // Perform suspend operations
    return 0;
}

static int my_device_resume(struct device *dev)
{
    // Perform resume operations
    return 0;
}

2. Platform PM Ops

Platform PM Ops 主要与平台相关的电源管理操作有关,通常是在平台设备的上下文中(例如一个 SoC)执行的。平台设备通过 struct platform_device 来表示,platform_device 结构体中的电源管理操作通常是通过 platform_pm_ops 来定义的。

平台电源管理操作 (platform_pm_ops):

平台的电源管理操作包括:

  • suspend: 使平台(如 SoC)进入挂起状态。
  • resume: 恢复平台的正常工作状态。
  • shutdown: 系统关闭时调用的平台电源管理操作。

调用时机:

平台的电源管理操作通常是在以下时机调用的:

  • suspend / resume:当系统进行全局挂起或恢复时,平台的电源管理操作会被调用。

    • 在系统挂起时,内核会调用平台设备的 suspend 操作来执行平台级别的电源管理。
    • 在系统恢复时,平台设备的 resume 操作会被调用。
  • shutdown:当系统即将关闭时(例如关机时),平台设备的 shutdown 操作会被调用。

典型代码示例:

struct platform_driver my_platform_driver = {
    .driver = {
        .name = "my_platform_driver",
        .pm = &my_platform_pm_ops,  // PM ops for platform driver
    },
    .probe = my_platform_probe,
    .remove = my_platform_remove,
};

static const struct dev_pm_ops my_platform_pm_ops = {
    .suspend = my_platform_suspend,
    .resume = my_platform_resume,
    .shutdown = my_platform_shutdown,
};

static int my_platform_suspend(struct device *dev)
{
    // Perform platform suspend operations
    return 0;
}

static int my_platform_resume(struct device *dev)
{
    // Perform platform resume operations
    return 0;
}

static int my_platform_shutdown(struct platform_device *pdev)
{
    // Perform platform shutdown operations
    return 0;
}

3. Device PM Ops 和 Platform PM Ops 的关系

  • 设备电源管理 是针对设备本身的电源状态管理(如设备的挂起、恢复、运行时待机等),每个设备都可以有自己的 dev_pm_ops
  • 平台电源管理 是针对整个平台的电源管理,通常涉及多个设备的电源管理操作,特别是在系统的挂起、恢复或关机时,平台设备的操作会影响到整个系统的电源状态。

4. 调用时机总结

时机Device PM OpsPlatform PM Ops
挂起调用设备的 suspend 方法调用平台的 suspend 方法
恢复调用设备的 resume 方法调用平台的 resume 方法
运行时待机调用设备的 runtime_suspend 方法主要用于设备级的电源管理,一般不涉及平台级操作
恢复运行调用设备的 runtime_resume 方法主要用于设备级的电源管理,一般不涉及平台级操作
关机不涉及平台操作,设备可以自行处理关机调用平台的 shutdown 方法

4.5、suspend过程的同步和PM wakeup

最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程,应该终止suspend,返回并处理事件。但由于suspend过程的特殊性,进程被freeze、关中断等等,导致事情并没有那么简单,以至于在很久的一段时间内,kernel都不能很好的处理。这也称作suspend过程的同步问题。

1. 挂起过程的同步

在挂起过程中,系统需要确保所有设备、驱动和系统组件都进入低功耗状态,而挂起操作的同步性则意味着所有相关操作必须完成并且按照特定顺序执行。

设备电源管理操作(PM Ops)

设备挂起操作(dev_pm_ops.suspend)和恢复操作(dev_pm_ops.resume)在挂起过程中的同步执行对于设备的正确工作至关重要。挂起过程中,系统通常会逐个设备地调用其电源管理操作,具体步骤如下:

  1. 系统准备挂起

    • 系统会进入挂起前的准备阶段。所有的设备电源管理函数(如 suspendruntime_suspend)会被调用。
    • 挂起过程可能是 完全挂起(suspend-to-disk)运行时挂起(suspend-to-RAM)。对于完全挂起,系统会把当前系统状态(内存内容)保存到磁盘。对于运行时挂起,系统将内存状态保留在 RAM 中,其他设备和 CPU 进入低功耗状态。
  2. 同步机制

    • 设备挂起:挂起时,所有设备必须首先停止操作(通过 suspend 操作),并进入低功耗状态。每个设备的挂起操作是同步的,也就是说,系统必须等待当前设备的挂起操作完成后才能进行下一个设备的挂起操作。
    • 挂起阶段同步:挂起操作是按顺序执行的,设备驱动程序中的 suspend 函数会在进入挂起模式之前做一些同步工作。例如,可能需要停止所有 I/O 操作,禁止中断,保存设备状态等。这些操作需要确保在挂起之前完成。
  3. 系统挂起的同步

    • 在系统整体挂起时,系统需要将所有设备的状态保存到内存或磁盘,这也是一个同步操作。
    • 内核通过 pm_suspendsystem_suspend 来协调整个系统的挂起过程。所有设备的挂起操作会通过该函数被串行化(逐个调用),并且在每个操作完成之前,不能跳过或并行执行。
  4. 内存页的冻结

    • 在完全挂起(suspend-to-disk)过程中,内存中的所有页面需要被冻结,以便安全地保存系统状态。这也需要确保没有其他进程或线程在内存冻结期间进行操作。

挂起的时序

挂起的时序通常是按以下顺序进行:

  1. 关闭中断,确保在挂起过程中没有其他干扰。
  2. 挂起 CPU,进入低功耗状态。
  3. 挂起所有设备。
  4. 保存系统状态(内存或磁盘)。
  5. 进入系统挂起状态(待机或休眠)。
  6. 恢复系统状态并恢复设备。

2. PM Wakeup(电源管理唤醒)

唤醒是指系统从低功耗状态恢复到工作状态。唤醒的过程也需要同步,以确保在恢复过程中所有设备和系统状态能够正确恢复。

唤醒过程

唤醒操作与挂起操作相反,目的是让系统从低功耗状态恢复到正常工作状态。恢复过程中可能会遇到以下几个关键点:

  1. 恢复设备状态

    • 在唤醒时,系统需要依次调用设备的 resume 操作,以恢复设备的正常工作状态。和挂起过程一样,设备恢复是同步执行的。
    • 设备唤醒操作:每个设备的恢复操作(resume)需要等待前一个设备的恢复操作完成,确保设备按照正确的顺序重新激活。
  2. 恢复内存状态

    • 如果是完全挂起(suspend-to-disk),系统会恢复之前保存的内存内容。在恢复过程中,内存中的页面会被重新加载,确保系统从最后的挂起状态恢复。
  3. CPU 唤醒

    • 唤醒时,CPU 会从休眠状态恢复,并且恢复中断。系统唤醒的时序通常要求恢复 CPU 之前保存的状态,重新启用中断,确保 CPU 进入可以处理正常任务的工作状态。

PM Wakeup 的触发

PM 唤醒通常由硬件中断、定时器、按键、外部事件等触发。唤醒事件会通过硬件(如 RTC 定时器、GPIO 等)或者软件事件(如用户请求)来触发唤醒操作。

唤醒的常见触发方式包括:

  • 硬件触发:硬件设备(如外部定时器、按键、外部传感器等)可以通过中断或信号来唤醒系统。
  • 定时器唤醒:内核可以设置定时器,在指定时间唤醒系统。
  • 软件唤醒:用户或进程通过特定接口(如 rtcwake)来请求唤醒。

Wakeup Source(唤醒源)

唤醒源是指触发系统唤醒的设备或事件。常见的唤醒源包括:

  • 定时器(Timer):内核或硬件定时器可以设置在系统进入低功耗状态后,触发唤醒。
  • 外部中断(IRQ):例如,通过硬件中断信号来触发唤醒。
  • GPIO 按键:按下特定按钮可以触发系统唤醒。

电源管理唤醒同步

唤醒过程涉及确保唤醒事件正确处理,通常包括:

  1. 设备驱动程序检查唤醒事件并恢复设备状态。
  2. 恢复设备的中断和 DMA。
  3. 恢复内存中的系统状态,确保进程和系统能正确恢复。

在唤醒过程中,所有相关的唤醒事件必须被同步处理。如果唤醒事件未正确同步,可能会导致系统错误或设备无法正常工作。

3. 总结

  • 挂起过程的同步:挂起操作通常是设备驱动层和系统层的同步过程,确保设备和系统能够按顺序进入低功耗状态。设备的 suspend 操作、CPU 的挂起、内存的冻结等都需要同步进行。
  • PM Wakeup:唤醒操作是系统从低功耗状态恢复到正常工作状态的过程,涉及设备状态的恢复、内存恢复和 CPU 恢复等步骤。唤醒事件(如硬件中断、定时器等)会触发唤醒过程,并且整个唤醒过程需要按照特定的顺序同步进行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值