Linux电源管理7

Linux电源管理7(基于Linux6.6)---Wakeup count介绍

 

一、概述

在 Linux 内核中,wakeup count(唤醒计数)是与进程调度、睡眠管理和 CPU 资源调度相关的一个概念。它用于跟踪和管理进程进入睡眠状态和唤醒状态之间的转换,确保在正确的时机唤醒进程并让系统资源得到合理的分配。具体来说,wakeup count 主要涉及以下几个方面:

1. 唤醒计数的作用

  • 跟踪进程的睡眠状态:当进程因等待某些事件(如 I/O 操作完成、信号接收、定时器过期等)而进入睡眠状态时,内核需要跟踪该进程等待的唤醒事件。wakeup count 就是用来记录这些唤醒事件的数量。

  • 唤醒机制的管理:每当一个唤醒事件发生时,系统会递减对应进程的 wakeup count,直到所有相关的事件都触发,从而唤醒该进程。wakeup count 为 0 时,表示该进程可以被唤醒并继续执行。

2. 内核中的应用场景

wakeup count 在以下场景中起到了重要作用:

  • 睡眠与唤醒的同步:Linux 内核中有很多机制,如 wait queues(等待队列),用来管理进程的睡眠与唤醒。当进程在某些条件下进入睡眠状态时,系统会记录与该进程相关的唤醒事件。这些事件触发时会依次减少唤醒计数,最终唤醒进程。

  • 高效管理唤醒事件:在多核系统中,唤醒事件的处理必须高效。如果一个进程等待多个事件(比如 I/O 操作、定时器),那么它的 wakeup count 就是这些事件的总和。内核必须确保所有事件完成后,进程才能真正唤醒并恢复执行。

  • 节能与调度优化:wakeup count 也有助于内核进行节能优化。进程在等待事件时可能处于低功耗模式,内核会根据唤醒事件的数量来决定什么时候唤醒进程,避免不必要的频繁唤醒和过度消耗系统资源。

3. 内核实现细节

在 Linux 内核中,wakeup count 主要通过 wait queues 和相关的唤醒机制来实现:

  • Wait Queue:当一个进程因等待某个事件而进入睡眠时,它会被放入一个等待队列中。每个等待队列的事件都会增加该队列中的进程的唤醒计数。当某个事件完成时,内核会从等待队列中唤醒相应的进程并减少唤醒计数。

  • wakeup_count 变量:每个进程可能会有一个关联的唤醒计数,表示它需要多少个唤醒事件才能恢复执行。在内核内部,wakeup count 的更新通常与事件发生时触发的回调函数相关。

4. saved_count 的关系

在一些内核实现中,wakeup count 与 saved_count(保存计数)共同作用:

  • saved_count:当进程进入睡眠状态时,它的 saved_count 用于记录它当前在等待的事件数量。此值可能是 wakeup_count 的一个副本。当事件发生并且进程被唤醒时,saved_count 会被检查,确认进程是否需要等待更多的唤醒事件。

  • 相互配合:当进程因为多个事件而进入睡眠时,内核需要确保所有事件完成后才唤醒进程。wakeup_countsaved_count 用于确保这一点,避免唤醒不完整或不必要的进程。

Wakeup count是Wakeup events framework的组成部分,用于解决“system suspend和system wakeup events之间的同步问题”。

二、wakeup count在电源管理中的位置

wakeup count的实现位于wakeup events framework中(drivers/base/power/wakeup.c),主要为两个模块提供接口:通过PM core向用户空间提供sysfs接口;直接向autosleep提供接口。

三、 wakeup count的功能

wakeup count的功能是suspend同步,实现思路是这样的:

下面是关于如何通过 wakeup count 实现 suspend 同步 的思路和实现细节:

3.1、问题背景:

在 Linux 内核中,当一个进程需要挂起(比如进入低功耗状态或者等待特定事件时),内核需要确保它没有任何未完成的任务或事件依赖。否则,如果在进程挂起后这些任务或事件仍然发生,可能会导致资源不一致或者在进程恢复时需要重新同步状态。

为了实现同步,内核利用 wakeup count 来追踪等待的事件。只有在所有相关的事件都已完成并触发了唤醒,内核才会允许进程挂起或进入低功耗状态。

3.2、实现思路:

a. 进程进入挂起状态时,设置 wakeup count:

当一个进程需要挂起时(比如进入睡眠状态或者等待某个事件),内核会设置 wakeup count,表示该进程依赖于多少个外部事件才能真正进入挂起状态。每个进程会记录其待处理的事件数量,这些事件可能包括:

  • I/O 操作完成
  • 超时事件(如定时器过期)
  • 外部信号或条件(如某个标志被设定)

b. 事件发生时更新 wakeup count:

当某个事件发生(如 I/O 操作完成、定时器到期等),内核会减少进程的 wakeup count。每触发一次事件,wakeup count 减少一次。

c. 挂起条件的判断:

当进程处于等待挂起的状态时,内核会检查进程的 wakeup count 是否已经为 0。只有当所有相关的事件都发生,并且所有的 wakeup count 都被减少为 0 时,内核才会允许进程进入挂起状态或进行其他类似操作。

d. 进程恢复(唤醒)时,恢复 wakeup count:

如果进程从挂起状态恢复过来,内核会根据需要重新设置进程的 wakeup count,以便它可以继续等待新的事件或任务。

3.3、同步实现的细节:

Wait Queue 和 Wakeup Count 结合:

内核通常通过 wait queues 来管理进程的睡眠和唤醒。当进程因某个条件而需要进入挂起或等待状态时,它会被放入一个 wait queue,并且其 wakeup count 会被设置为该进程需要等待的事件数量。例如:

  • 如果进程等待一个 I/O 操作的完成,它的 wakeup count 就是 1,表示等待一个事件。
  • 如果进程等待多个事件(例如多个 I/O 操作完成),则 wakeup count 可能是多个。

触发事件时的操作:

当外部事件发生(如 I/O 操作完成、信号到达等),内核会通知等待的进程,并且减少该进程的 wakeup count。如果 wakeup count 减少到 0,进程就会被从等待队列中移除并唤醒。

在事件触发时,内核会按照以下步骤操作:

  1. 检查进程的 wakeup count,如果事件满足条件,减少相应的计数。
  2. 如果 wakeup count 已经为 0,唤醒该进程并恢复其执行。

高效的同步控制:

为了确保多事件的同步和高效的挂起控制,内核可能会使用以下技术:

  • 原子操作:对 wakeup count 的更新通常使用原子操作(例如 atomic_addatomic_sub),以确保在多核系统上同步操作的安全性。
  • 延迟唤醒:如果某个事件尚未触发,内核可能会让进程在某个合理的时机恢复检查,以避免过度的 CPU 占用。
  • 事件批量处理:当多个事件同时发生时,内核可以批量更新多个进程的 wakeup count,提高效率。

3.4、具体应用:

  • 挂起/恢复机制:对于一些长时间处于等待状态的进程,如 I/O 等待进程,内核通过 wakeup count 来确保在所有依赖事件完成前,进程不会进入挂起状态。只有当进程的所有依赖事件都完成时,才能挂起或者进行电源管理。

  • 节能和调度优化:利用 wakeup count,系统可以精确地调度进程的挂起和唤醒,以确保系统资源得到合理利用,尤其是在大规模并发环境和多核系统中。

通过 wakeup count,内核能够精确跟踪和管理进程等待的事件数量。只有当所有相关的事件都已完成时,内核才允许进程挂起或恢复。这种机制能够确保:

  • 进程在所有依赖事件都处理完毕时才进入挂起状态。
  • 系统在多任务和高并发环境下有效地进行进程同步和资源管理。

四、 wakeup count的实现逻辑

4.1、举例

在进行代码分析之前,先用伪代码的形式,写出一个利用wakeup count进行suspend操作的例子,然后基于该例子,分析相关的实现。

do {
    ret = read(&cnt, "/sys/power/wakeup_count");
    if (ret) {
        ret = write(cnt, "/sys/power/wakeup_count");
    } else {
        countine;
    }
} while (!ret);
 
write("mem", "/sys/power/state");
 
/* goto here after wakeup */

说明:

a)读取wakeup count值,如果成功,将读取的值回写。否则说明有正在处理的wakeup events,continue。

b)回写后,判断返回值是否成功,如果不成功(说明读、写的过程中产生了wakeup events),继续读、写,直到成功。成功后,可以触发电源状态切换。

4.2、 /sys/power/wakeup_count

wakeup_count文件是在kernel/power/main.c中,利用power_attr注册的,如下:

static ssize_t wakeup_count_show(struct kobject *kobj,
				struct kobj_attribute *attr,
				char *buf)
{
	unsigned int val;

	return pm_get_wakeup_count(&val, true) ?
		sprintf(buf, "%u\n", val) : -EINTR;
}

static ssize_t wakeup_count_store(struct kobject *kobj,
				struct kobj_attribute *attr,
				const char *buf, size_t n)
{
	unsigned int val;
	int error;

	error = pm_autosleep_lock();
	if (error)
		return error;

	if (pm_autosleep_state() > PM_SUSPEND_ON) {
		error = -EBUSY;
		goto out;
	}

	error = -EINVAL;
	if (sscanf(buf, "%u", &val) == 1) {
		if (pm_save_wakeup_count(val))
			error = n;
		else
			pm_print_active_wakeup_sources();
	}

 out:
	pm_autosleep_unlock();
	return error;
}

power_attr(wakeup_count);

实现很简单:read时,直接调用pm_get_wakeup_count(注意第2个参数);write时,直接调用pm_save_wakeup_count(注意用户空间的wakeup count功能和auto sleep互斥。这两个接口均是wakeup events framework提供的接口,跟着代码往下看吧。

4.3、pm_get_wakeup_count

pm_get_wakeup_count的实现如下:

drivers/base/power/wakeup.c 

bool pm_get_wakeup_count(unsigned int *count, bool block)
{
	unsigned int cnt, inpr;

	if (block) {
		DEFINE_WAIT(wait);

		for (;;) {
			prepare_to_wait(&wakeup_count_wait_queue, &wait,
					TASK_INTERRUPTIBLE);
			split_counters(&cnt, &inpr);
			if (inpr == 0 || signal_pending(current))
				break;
			pm_print_active_wakeup_sources();
			schedule();
		}
		finish_wait(&wakeup_count_wait_queue, &wait);
	}

	split_counters(&cnt, &inpr);
	*count = cnt;
	return !inpr;
}

该接口有两个参数,一个是保存返回的count值得指针,另一个指示是否block,具体请参考代码逻辑:

a)如果block为false,直接读取registered wakeup events和wakeup events in progress两个counter值,将registered wakeup events交给第一个参数,并返回wakeup events in progress的状态(若返回false,说明当前有wakeup events正在处理,不适合suspend)。

b)如果block为true,定义一个等待队列,等待wakeup events in progress为0,再返回counter。

4.4、pm_save_wakeup_count

pm_save_wakeup_count的实现如下:

drivers/base/power/wakeup.c

bool pm_save_wakeup_count(unsigned int count)
{
	unsigned int cnt, inpr;
	unsigned long flags;

	events_check_enabled = false;
	raw_spin_lock_irqsave(&events_lock, flags);
	split_counters(&cnt, &inpr);
	if (cnt == count && inpr == 0) {
		saved_count = count;
		events_check_enabled = true;
	}
	raw_spin_unlock_irqrestore(&events_lock, flags);
	return events_check_enabled;
}

1)注意这个变量,events_check_enabled,如果它不为真,pm_wakeup_pending接口直接返回false,意味着如果不利用wakeup count功能,suspend过程中不会做任何wakeup events检查,也就不会进行任何的同步。

2)解除当前的registered wakeup events、wakeup events in progress,保存在变量cnt和inpr中。

3)如果写入的值和cnt不同(说明读、写的过程中产生events),或者inpr不为零(说明有events正在被处理),返回false(说明此时不宜suspend)。

4)否则,events_check_enabled置位(后续的pm_wakeup_pending才会干活),返回true(可以suspend),并将当前的wakeup count保存在saved count变量中。

4.5、/sys/power/state

再回忆一下中suspend的流程,在suspend_enter接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,代码如下:

kernel/power/suspend.c

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;
}

在write wakeup_count到调用pm_wakeup_pending这一段时间内,wakeup events framework会照常产生wakeup events,因此如果pending返回true,则不能“enter”,终止suspend吧!等待wakeup。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值