Linux电源管理(四)CPUFreq

CPUFreq简介

CPUFreq是一种实时的电压和频率调节技术,也叫DVFS(Dynamic Voltage and Frequency Scaling)动态电压频率调节。

为何需要CPUFreq

随着技术的发展,CPU的频率越来越高,性能越来越好,芯片制造工艺也越来越先进。但高性能的同时也带来高发热。其实移动嵌入式设备并不需要时刻保持高性能。因此,需要一种机制,实现动态地调节频率和电压,以实现性能和功耗的平衡。

CPUFreq软件框架

和一般的linux子系统类似,CPUFreq采用了机制与策略分离的设计架构。分为三个模块:

  • cpufreq core: 对cpufreq governors和cpufreq drivers进行了封装和抽象并定义了清晰的接口,从而在设计上完成了对机制和策略的分离。

  • cpufreq drivers:位于cpucore的底层,用于设置具体cpu硬件的频率。通过cpufreq driver可以使cpu频率得到调整。cpufreq driver借助Linux Cpufreq标准子系统中的cpufreq_driver结构体,完成cpu调频驱动的注册及实现。

  • cpufreq governor:位于cpucore的上层,用于CPU升降频检测,根据系统和负载,决定cpu频率要调节到多少。cpufreq governor借助于linux cpufreq子系统中cpufreq_governor结构体,完成了cpu调频策略的注册和实现。

这里写图片描述

CPUFreq实现原理

linux cpufreq通过向系统注册实现cpufreq driver和cpufreq governor。cpu governor实现调频的策略,cpu driver实现调频的实际操作,从而完成动态调节频率和电压。一般情况下,优先调节频率,频率无法满足,再调节电压以实现调频。

CPUFreq sys用户态接口

cpufreq相关的节点位于/sys/devices/system/cpu/cpu0/cpufreq目录下:

$ cd /sys/devices/system/cpu/cpu0/cpufreq

可以看到以下节点:

shell@tiny4412:/sys/devices/system/cpu/cpu0/cpufreq # ls
affected_cpus
cpuinfo_cur_freq
cpuinfo_max_freq
cpuinfo_min_freq
cpuinfo_transition_latency
related_cpus
scaling_available_governors
scaling_cur_freq
scaling_driver
scaling_governor
scaling_max_freq
scaling_min_freq
scaling_setspeed
stats

具体含义如下表:
这里写图片描述

CPUFreq实现分析 

CPUFreq Core层

CPUFreq子系统将一些共同的逻辑代码组织在一起,构成了CPUFreq核心模块。这些公共逻辑模块向CPUFreq和其它内核模块提供了必要的API完成一个完整的CPUFreq子系统。这一节我们分析CPUFreq核心层的一些重要API的实现及使用。

代码位置:

/drivers/cpufreq/cpufreq.c

CPUFreq子系统初始化

static int __init cpufreq_core_init(void)
{
    int cpu;

    if (cpufreq_disabled())
        return -ENODEV;

    for_each_possible_cpu(cpu) {
        per_cpu(cpufreq_policy_cpu, cpu) = -1;
        init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
    }

    cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
    BUG_ON(!cpufreq_global_kobject);

#if defined(CONFIG_ARCH_SUNXI) && defined(CONFIG_HOTPLUG_CPU)
    /* register reboot notifier for process cpus when reboot */
    register_reboot_notifier(&reboot_notifier);
#endif

    return 0;
}
core_initcall(cpufreq_core_init);

可见,CPUFreq子系统在系统启动阶段由Initcall机制调用完成核心部分的初始化工作。cpufreq_policy_cpu是一个per_cpu变量,在smp系统下,每个cpu可以有自己独立的policy,也可以与其它cpu共用一个policy。通过kobject_create_and_add函数建立cpufreq节点,这与我们之前看到的sys下的cpufreq节点相吻合。该节点以后会用来放其它一些参数。
参数cpu_subsys是内核的一个全局变量,是由更早期的初始化时初始化的,代码在drivers/base/cpu.c中:

struct bus_type cpu_subsys = {
    .name = "cpu",
    .dev_name = "cpu",
};
EXPORT_SYMBOL_GPL(cpu_subsys);

void __init cpu_dev_init(void)
{
    if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
        panic("Failed to register CPU subsystem");

    cpu_dev_register_generic();
}

这将会建立一根cpu总线,总线下挂着系统中所有的cpu,cpu总线设备的根目录就位于:/sys/devices/system/cpu,同时,/sys/bus下也会出现一个cpu的总线节点。cpu总线设备的根目录下会依次出现cpu0,cpu1,…… cpux节点,每个cpu对应其中的一个设备节点。CPUFreq子系统利用这个cpu_subsys来获取系统中的cpu设备,并在这些cpu设备下面建立相应的cpufreq对象,这个我们在后面再讨论。
这样看来,cpufreq子系统的初始化其实没有做什么重要的事情,只是初始化了几个per_cpu变量和建立了一个cpufreq文件节点。下图是初始化过程的序列图:
这里写图片描述

注册cpufreq_governor

系统中可以同时存在多个governor策略,一个policy通过cpufreq_policy结构中的governor指针和某个governor相关联。要想一个governor被policy使用,首先要把该governor注册到cpufreq的核心中,我们可以通过核心层提供的API来完成注册:

int cpufreq_register_governor(struct cpufreq_governor *governor)
{
    int err;

    if (!governor)
        return -EINVAL;

    if (cpufreq_disabled())
        return -ENODEV;

    mutex_lock(&cpufreq_governor_mutex);

    governor->initialized = 0;
    err = -EBUSY;
    if (__find_governor(governor->name) == NULL) {
        err = 0;
        list_add(&governor->governor_list, &cpufreq_governor_list);
    }

    mutex_unlock(&cpufreq_governor_mutex);
    return err;
}

核心层定义了一个全局链表变量:cpufreq_governor_list,注册函数首先根据governor的名称,通过__find_governor()函数查找该governor是否已經被注册过,如果没有被注册过,则把代表该governor的结构体添加到cpufreq_governor_list链表中。

注册cpufreq_driver驱动

与governor不同,系统中只会存在一个cpufreq_driver驱动,cpufreq_driver是平台相关的,负责最终实施频率的调整动作,而选择工作频率的策略是由governor完成的。所以,系统中只需要注册一个cpufreq_driver即可,它只负责如何控制该平台的时钟系统,从而设定由governor确定的工作频率。核心提供了一个API:cpufreq_register_driver来完成注册工作。
下面我们分析一下这个函数的工作过程:

int cpufreq_register_driver(struct cpufreq_driver *driver_data)
{
    unsigned long flags;
    int ret;

    if (cpufreq_disabled())
        return -ENODEV;
    // 从代码可以看到,verify和init回调函数必须要实现,而setpolicy和target回调则至少要被实现其中的一个。
    if (!driver_data || !driver_data->verify || !driver_data->init ||
        ((!driver_data->setpolicy) && (!driver_data->target)))
        return -EINVAL;

    pr_debug("trying to register driver %s\n", driver_data->name);

    if (driver_data->setpolicy)
        driver_data->flags |= CPUFREQ_CONST_LOOPS;

    write_lock_irqsave(&cpufreq_driver_lock, flags);
    //检查全局变量cpufreq_driver是否已经被赋值,如果没有,则传入的参数被赋值给全局变量cpufreq_driver,从而保证了系统中只会注册一个cpufreq_driver驱动
    if (cpufreq_driver) {
        write_unlock_irqrestore(&cpufreq_driver_lock, flags);
        return -EBUSY;
    }
    cpufreq_driver = driver_data;
    write_unlock_irqrestore(&cpufreq_driver_lock, flags);
    //通过subsys_interface_register给每一个cpu建立一个cpufreq_policy
    ret = subsys_interface_register(&cpufreq_interface);
    if (ret)
        goto err_null_driver;

    if (!(cpufreq_driver->flags & CPUFREQ_STICKY)) {
        int i;
        ret = -ENODEV;

        /* check for at least one working CPU */
        for (i = 0; i < nr_cpu_ids; i++)
            if (cpu_possible(i) && per_cpu(cpufreq_cpu_data, i)) {
                ret = 0;
                break;
            }

        /* if all ->init() calls failed, unregister */
        if (ret) {
            pr_debug("no CPU initialized for driver %s\n",
                            driver_data->name);
            goto err_if_unreg;
        }
    }
    //注册cpu hot plug通知,以便在cpu hot plug的时候,能够动态地处理各个cpu policy之间的关系(比如迁移负责管理的cpu等等)
    register_hotcpu_notifier(&cpufreq_cpu_notifier);
    pr_debug("driver %s up and running\n", driver_data->name);

    return 0;
err_if_unreg:
    subsys_interface_unregister(&cpufreq_interface);
err_null_driver:
    write_lock_irqsave(&cpufreq_driver_lock, flags);
    cpufreq_driver = NULL;
    write_unlock_irqrestore(&cpufreq_driver_lock, flags);
    return ret;
}

cpufreq_interface结构体如下:

 static struct subsys_interface cpufreq_interface = {
    .name       = "cpufreq",
    .subsys     = &cpu_subsys,
    .add_dev    = cpufreq_add_dev,
    .remove_dev = cpufreq_remove_dev,
};

subsys_interface_register遍历子系统下面的每一个子设备,然后用这个子设备作为参数,调用cpufrq_interface结构的add_dev回调函数,这里的回调函数被指向了cpufreq_add_dev。

下图是cpufreq_driver注册过程的序列图:
这里写图片描述

通过__cpufreq_set_policy函数,最终使得该policy正式生效。到这里,每个cpu的policy已经建立完毕,并正式开始工作。

__cpufreq_set_policy函数时序图如下:

这里写图片描述

其它API

int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);

以上两个API用于注册和注销cpufreq系统的通知消息,第二个参数可以选择通知的类型,可以有以下两种类型:

  • CPUFREQ_TRANSITION_NOTIFIER 收到频率变更通知
  • CPUFREQ_POLICY_NOTIFIER 收到policy更新通知

cpufreq_driver_target:用来设置目标频率,实际回调cpufreq的target函数。

int __cpufreq_drive
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值