一、Linux cpuidle framework(1)_概述和软件架构

1. 在Linux kernel中,所有进程都不再运行时,即CPU的无所事事,这种状态被称作idle状态,而cpuidle framework,就是为了管理这种状态。

2. 当idle进程被调度到时,则说明系统的其它进程不再运行了,也即CPU idle了。最终,由idle进程调用idle指令(这里为WFI),让CPU进入idle状态。
WFI Wakeup events会把CPU从WFI状态唤醒,通常情况下,这些events是一些中断事件,因此CPU唤醒后会执行中断handler,在handler中会wakeup某些进程,在handler返回的时候进行调度,当没有其他进程需要调度执行的时候,调度器会恢复idle进程的执行,当然,idle进程不做什么,继续进入idle状态,等待下一次的wakeup。

3. 一般情况下,ARM CPU idle时,可以使用WFI指令,把CPU置为Wait for interrupt状态。该状态下,至少(和具体ARM core的实现有关)会把ARM core的clock关闭,以节省功耗。

4. 很多CPU会从“退出时的延迟”和“idle状态下的功耗”两个方面考虑,设计多种idle级别。对延迟较敏感的场合,可以使用低延迟、高功耗的idle;对延迟不敏感的场合,可以使用高延迟、低功耗的idle。在恰当的时候,选择一个合适的idle状态,这就是cpuidle framework的存在意义。

5. 软件架构
Linux kernel中,cpuidle framework位于“drivers/cpuidle”文件夹中,包含cpuidle core、cpuidle governors和cpuidle drivers三个模块,再结合位于kernel sched中的cpuidle entry,共同完成cpu的idle管理。软件架构如下图:

(1) kernel schedule模块
位于kernel\sched\idle.c中,负责实现idle线程的通用入口(cpuidle entry)逻辑,包括idle模式的选择、idle的进入等等。这个文件其实就是初始化了一个
struct seched_class idle_seched_class 调度类。

(2) cpuidle core
cpuidle core负责实现cpuidle framework的整体框架,主要功能包括:

根据cpuidle的应用场景,抽象出cpuidle device、cpuidle driver、cpuidle governor三个实体;
以函数调用的形式,向上层sched模块提供接口;
以sysfs的形式,向用户空间提供接口;
向下层的cpuidle drivers模块,提供统一的driver注册和管理接口;
向下层的governors模块,提供统一的governor注册和管理接口。

cpuidle core的代码主要包括drivers/cpuidle下的:cpuidle.c、driver.c、governor.c、sysfs.c。

(3) cpuidle drivers
负责idle机制的实现,即:如何进入idle状态,什么条件下会退出,等等。不同的architecture、不同的CPU core,会有不同的cpuidle driver,平台驱动的开发者,可以在cpuidle core提供的框架之下,开发自己的cpuidle driver。代码主要包括:cpuidle-xxx.c。

(4) cpuidle governors
Linux kernel的framework有两种比较固定的抽象模式:
模式1:provider/consumer模式,interrupt、clock、timer、regulator等大多数的framework是这种模式。它的特点是,这个硬件模块是为其它一个或多个模块服务的,因而framework需要从对上(consumer)和对下(provider)两个角度进行软件抽象;
模式2:driver/governor模式,本文所描述的cpuidle framework即是这种模式。它的特点是:硬件(或者该硬件所对应的驱动软件)可以提供多种可选“方案”(这里即idle level),“方案”的实现(即机制),由driver负责,但是到底选择哪一种“方案”(即策略),则由另一个模块负责(即这里所说的governor)。

模式2的cpuidle的场景里面:很多CPU提供了多种idle级别(即上面所说的“方案”),这些idle 级别的主要区别是“idle时的功耗”和“退出时延迟”。cpuidle driver(机制)负责定义这些idle状态(每一个状态的功耗和延迟分别是多少),并实现进入和退出相关的操作。最终,cpuidle driver会把这些信息告诉governor,由governor根据具体的应用场景,决定要选用哪种idle状态(策略)。

kernel中,cpuidle governor都位于drivers/cpuidle/governors/目录下(Qcom的位于dirvers/cpuidle/lpm_levels.c中,cpuidle driver也是实现在这个文件中了)。

6. 软件流程
kernel会在系统启动完成后,在init进程中,处理cpuidle相关的事情。大致的过程是这样的:
(1) 先启动主CPU,启动过程和传统的单核系统类似:stext --> start_kernel --> rest_init --> cpu_startup_entry
(2) 启动其它CPU,可以有多种方式,例如CPU hotplug等,启动过程为:secondary_startup --> __secondary_switched --> secondary_start_kernel --> cpu_startup_entry

cpu_startup_entry接口位于kernel/sched/idle.c中,负责处理CPU idle的事情,流程如下:

cpu_startup_entry 
        arch_cpu_idle_prepare //进行idle前的准备工作,ARM64中没有实现 
        cpu_idle_loop //进入cpuidle的主循环while(1) do_idle();
                如果系统当前不需要调度(!need_resched()),执行后续的动作 
                local_irq_disable //关闭irq中断 
                arch_cpu_idle_enter //arch相关的cpuidle enter,ARM64中没有实现 
                cpuidle_idle_call //main idle function 
                        cpuidle_select //通过cpuidle governor->select(),选择一个cpuidle state 
                        cpuidle_enter //通过cpuidle state,进入该idle状态 
                        … 
                        中断产生,idle返回(注意,此时irq是被禁止的,因此CPU不能响应产生中断的事件) 
                        cpuidle_reflect //通知cpuidle governor,更新状态 
                        local_irq_enable //使能中断,响应中断事件,跳转到对应的中断处理函数 
                        …                         
                arch_cpu_idle_exit //和enter类似,ARM64没有实现 

7. 使用cpuidle framework进入idle状态时,本地irq是处于关闭的状态,因此从idle返回时,只能接着往下执行,直到irq被打开,才能执行相应的中断handler。这和之前传统的cpuidle不同。同时也间接证实了“Linux cpuidle framework(4)_menu governor”中所提及的,为什么menu governor在reflect接口中只是简单的置一个标志。因为reflect是在关中断时被调用的,需要尽快返回,以便处理中断事件。

8. 若内核模块参数sleed_disabled为Y时,CPU将无法进入低功耗的idle模式,原理如下:

Qcom的module_parameter(sleep_disabled)会创建sysfs文件节点

write /sys/module/lpm_levels/parameters/sleep_disabled N  //若是Y的话,CPU不能进入低功耗模式,原理如下:

# cat /sys/devices/system/cpu/cpuidle/current_driver
msm_idle
# cat /sys/devices/system/cpu/cpuidle/current_governor_ro
qcom

在系统进入idle时调用governor->select(),即下面这个governor

static struct cpuidle_governor lpm_governor = {
 .name =  "qcom",
 .rating = 30,
 .select = lpm_cpuidle_select,
};

cpu_startup_entry 
    while (1) do_idle(); //init线程处理完后就在这死循环一直尝试进入idle状态
        cpuidle_idle_call //kernel\sched\idle.c
            next_state = cpuidle_select(drv, dev, &stop_tick); //调用governor->select()
                lpm_cpuidle_select
                    cpu_power_select(dev, cpu); //drivers\cpuidle\lpm-levels.c
                        if (lpm_disallowed(sleep_us, dev->cpu, cpu)) //这个函数中:if (sleep_disabled) return true;
                            goto done_select; //此时返回的best_level为0,也即是next_state为0
        call_cpuidle(drv, dev, next_state); //kernel\sched\idle.c
            cpuidle_enter(drv, dev, next_state); //drivers\cpuidle\cpuidle.c
            struct cpuidle_state *target_state = &drv->states[index]; //选择的index就一直是0,应该就是state0,即wfi.

# cat /sys/devices/system/cpu/cpu0/cpuidle/state0/desc
wfi

 

 

 

 

参考:http://www.wowotech.net/pm_subsystem/cpuidle_overview.html

 

posted on 2020-12-14 00:22  Hello-World3  阅读(1578)  评论(0编辑  收藏  举报

导航