zoukankan      html  css  js  c++  java
  • Linux动态频率调节系统CPUFreq之二:核心(core)架构与API【转】

    Linux动态频率调节系统CPUFreq之二:核心(core)架构与API

    上一节中,我们大致地讲解了一下CPUFreq在用户空间的sysfs接口和它的几个重要的数据结构,同时也提到,CPUFreq子系统把一些公共的代码逻辑组织在一起,构成了CPUFreq的核心部分,这些公共逻辑向CPUFreq和其它内核模块提供了必要的API,像cpufreq_governor、cpufreq_driver等模块通过这些API来完成一个完整的CPUFreq体系。这一节我们就来讨论一下核心架构的代码架构以及如何使用这些公共的API接口。

    核心部分的代码都在:/drivers/cpufreq/cpufreq.c中,本系列文章我使用的内核版本是3.10.0.

    1. 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);
            register_syscore_ops(&cpufreq_syscore_ops);
     
            return 0;
    }
    core_initcall(cpufreq_core_init);
    

    可见,在系统的启动阶段,经由initcall机制,cpufreq_core_init被调用,由它来完成核心部分的初始化工作,其中:

    cpufreq_policy_cpu 是一个per_cpu变量,在smp的系统下,每个cpu可以有自己独立的调频policy,也可以所有的cpu都是用一种policy,这时候就有可能出现其中一个cpu管理着某个policy,而其它cpu因为也使用同一个policy,这些cpu的policy的就交由那个管理cpu代管,这个per_cpu变量就是用来记录各个cpu的policy实际上是由那个cpu进行管理的。初始化时都被初始化为-1了,代表现在还没有开始进行policy的管理。

    接下来的kobject_create_and_add函数在/sys/devices/system/cpu这个节点下建立了一个cpufreq节点,该节点的下面以后会用来放置当前governor的一些配置参数。参数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文件节点。下图是初始化过程的序列图:

    image

    图 1.1 核心层初始化

    2. 注册cpufreq_governor

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

    int cpufreq_register_governor(struct cpufreq_governor *governor)
    {
            int err;
            ......
     
            governor->initialized = 0;
            err = -EBUSY;
            if (__find_governor(governor->name) == NULL) {
                    err = 0;
                    list_add(&governor->governor_list, &cpufreq_governor_list);
            }
     
            ......
            return err;
    }
    

    核心层定义了一个全局链表变量:cpufreq_governor_list,注册函数首先根据governor的名称,通过__find_governor()函数查找该governor是否已經被注册过,如果没有被注册过,则把代表该governor的结构体添加到cpufreq_governor_list链表中。在上一篇中我们提到,目前的内核版本提供了5种governor供我们使用,我们可以通过内核的配置項来选择需要编译的governor,同时需要指定一个默认的governor。在cpufreq.h中,将会根据配置項的选择,把CPUFREQ_DEFAULT_GOVERNOR宏指向默认governor结构体变量的地址,在注册cpufreq_driver的阶段需要使用这个宏来设定系统默认使用的governor。

    3. 注册一个cpufreq_driver驱动

    与governor不同,系统中只会存在一个cpufreq_driver驱动,根据上一篇Linux动态频率调节系统CPUFreq之一:概述的介绍,cpufreq_driver是平台相关的,负责最终实施频率的调整动作,而选择工作频率的策略是由governor完成的。所以,系统中只需要注册一个cpufreq_driver即可,它只负责知道如何控制该平台的时钟系统,从而设定由governor确定的工作频率。注册cpufreq_driver驱动会触发cpufreq核心的一系列额外的初始化动作,第一节所说的核心初始化工作非常简单,实际上,更多的初始化动作在注册cpufreq_driver阶段完成。核心提供了一个API:cpufreq_register_driver来完成注册工作。下面我们分析一下这个函数的工作过程:

    int cpufreq_register_driver(struct cpufreq_driver *driver_data)
    {
            ......
     
            if (cpufreq_disabled())
                    return -ENODEV;
     
            if (!driver_data || !driver_data->verify || !driver_data->init ||
                ((!driver_data->setpolicy) && (!driver_data->target)))
                    return -EINVAL;
    

    该API只有一个参数:一个cpufreq_driver指针,driver_data,该结构事先在驱动的代码中定义,调用该API时作为参数传入。函数先判断系统目前是否禁止了调频功能,然后检查cpufreq_driver的几个回调函数是否被实现,由代码可以看出,verify和init回调函数必须要实现,而setpolicy和target回调则至少要被实现其中的一个。这几个回调的作用请参考本系列的第一篇文章。接下来:

     write_lock_irqsave(&cpufreq_driver_lock, flags);
            if (cpufreq_driver) {
                    write_unlock_irqrestore(&cpufreq_driver_lock, flags);
                    return -EBUSY;
            }
            cpufreq_driver = driver_data;
            write_unlock_irqrestore(&cpufreq_driver_lock, flags);
    

    检查全局变量cpufreq_driver是否已经被赋值,如果没有,则传入的参数被赋值给全局变量cpufreq_driver,从而保证了系统中只会注册一个cpufreq_driver驱动。然后:

            ret = subsys_interface_register(&cpufreq_interface);
            
            ......
            ...... 
     
            register_hotcpu_notifier(&cpufreq_cpu_notifier);
    

    通过subsys_interface_register给每一个cpu建立一个cpufreq_policy,最后注册cpu hot plug通知,以便在cpu hot plug的时候,能够动态地处理各个cpu policy之间的关系(比如迁移负责管理的cpu等等)。这里要重点讨论一下subsys_interface_register的过程,回到第一节的内容,我们知道初始化阶段,cpu_subsys被建立,从而每个cpu都会在cpu总线设备下建立一个属于自己的设备:sys/devices/system/cpu/cpux。subsys_interface_register负责在cpu_subsys子系统的子设备下面注册公共的接口。我们看看参数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,它的具体工作方式我们在下一节中讨论。

    driver注册完成后,驱动被保存在全局变量cpufreq_driver中,供核心层使用,同时,每个cpu也会建立自己的policy策略,governor也开始工作,实时地监控着cpu的负载并计算合适的工作频率,然后通过driver调整真正的工作频率。下图是cpufreq_driver注册过程的序列图:

    image

    4. 为每个cpu建立频率调整策略(policy)

    为每个cpu建立频率调整策略实在注册cpufreq_driver阶段的subsys_interface_registe函数中完成的,上一节已经提到,该函数最终会调用cpufreq_add_dev回调函数,现在展开这个函数分析一下:

    因为subsys_interface_registe会枚举各个cpu设备,不管该cpu处于offline还是online状态,cpufreq_add_dev都会被调用,所以函数的一开始,判断如果cpu处于offline状态,直接返回。

    static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
    {
            ......
     
            if (cpu_is_offline(cpu))
                    return 0;
    

    如果是smp系统,本cpu的policy可能和其他cpu共同使用同一个policy,并委托另一个叫做管理cpu的cpu进行管理,下面的代码判断这种情况,如果已经委托别的cpu管理,则直接返回,核心层定义了另一个per_cpu变量:cpufreq_cpu_data,用来保存各个cpu所使用的cpufreq_policy结构的指针,cpufreq_cpu_get函数实际上就是通过这个per_cpu变量,获取该指针,如果该指针非0,代表该cpu已经建立好了它自身的policy(可能是在他之前的管理cpu建立policy期间一并建立的)。

            policy = cpufreq_cpu_get(cpu);
            if (unlikely(policy)) {
                    cpufreq_cpu_put(policy);
                    return 0;
            }
    

    因为cpu hot plug期间,cpufreq_add_dev也会被调用,下面的代码片段检测该cpu之前是否被hot-unpluged过,如果是,找到其中一个相关的cpu(这些相关的cpu都委托给同一个托管它cpu进行管理,调用cpufreq_add_policy_cpu函数,该函数只是简单地建立一个cpufreq链接,链接到管理cpu的cpufreq节点。

           for_each_online_cpu(sibling) {
                    struct cpufreq_policy *cp = per_cpu(cpufreq_cpu_data, sibling);
                    if (cp && cpumask_test_cpu(cpu, cp->related_cpus)) {
                            read_unlock_irqrestore(&cpufreq_driver_lock, flags);
                            return cpufreq_add_policy_cpu(cpu, sibling, dev);
                    }
            }
    

    当这是系统初始化阶段第一次调用cpufreq_add_dev时(subsys_interface_register枚举到的第一个cpu,通常就是cpu0),cpufreq_cpu_data应该为NULL,所以我们要为这样的cpu分配一个cpufreq_policy结构,并初始化该policy所管理的cpu,包括online的cpus字段和online+offline的cpu_related字段,并把自己设置为这个policy的管理cpu,使用默认governor初始化policy->governor字段,同时吧自己加入到online的cpus字段中:

            policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
            if (!policy)
                    goto nomem_out;
     
            if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL))
                    goto err_free_policy;
     
            if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL))
                    goto err_free_cpumask;
     
            policy->cpu = cpu;
            policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
            cpumask_copy(policy->cpus, cpumask_of(cpu));
     
            /* Initially set CPU itself as the policy_cpu */
            per_cpu(cpufreq_policy_cpu, cpu) = cpu;
    

    接下来初始化一个供kobject系统注销时使用的同步变量,初始化一个workqueue,某些时候不能马上执行对该policy的更新操作,可以使用该workqueue来延迟执行。

            init_completion(&policy->kobj_unregister);
            INIT_WORK(&policy->update, handle_update);
    

    接着,调用cpufreq_driver的init回调,进一步初始化该policy:

           ret = cpufreq_driver->init(policy);
            if (ret) {
                    pr_debug("initialization failed
    ");
                    goto err_set_policy_cpu;
            }
    

    在上述驱动的初始化内部,应该完成以下工作:

    • 设定该cpu的最大和最小工作频率
    • 设定该policy的最大和最小工作频率
    • 设定该policy可供调节的频率档位
    • 设定cpu调节频率时的延迟时间特性
    • 该policy可以管理的cpu个数(policy->cpus)

    继续:

            /* related cpus should atleast have policy->cpus */
            cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);
    

    注释已经写的很清楚了,把online的cpu加到代表online+offline的related字段中。接着,剔除offline的cpu:

            cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);
    

    然后,发出CPUFREQ_START通知:

            blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
                                         CPUFREQ_START, policy);
    

    如果是hot-plug加入的cpu,找出它上次使用的governor:

    #ifdef CONFIG_HOTPLUG_CPU
            gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu));
            if (gov) {
                    policy->governor = gov;
                    pr_debug("Restoring governor %s for cpu %d
    ",
                           policy->governor->name, cpu);
            }
    #endif
    

    最后,建立cpu设备下的sysfs文件节点:cpufreq,它的完整路径是:/sys/devices/system/cpu/cpux/cpufreq,同时,在他的下面,相应的sysfs节点也同时被建立,节点的内容请参考本系列的第一篇文章:Linux动态频率调节系统CPUFreq之一:概述:

           ret = cpufreq_add_dev_interface(cpu, policy, dev);
    

    至此,一个cpu的policy建立完成,它的频率限制条件、使用的governor策略,sysfs文件节点都已经建立完成。需要注意点是,系统中有多少个cpu,cpufreq_add_dev函数就会被调用多少次,最后,每个cpu都会建立自己的policy,当然,也有可能只有部分cpu建立了真正的policy,而其它cpu则委托这些cpu进行policy的管理,关于这一点,一开始读代码的时候可能有点困扰,为了搞清楚他们之间的关系,我们再跟入cpufreq_add_dev_interface函数看看:

    static int cpufreq_add_dev_interface(unsigned int cpu,
                                         struct cpufreq_policy *policy,
                                         struct device *dev)
    {
            ......
     
            /* prepare interface data */
            ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq,
                                       &dev->kobj, "cpufreq");
            ......
     
            /* set up files for this cpu device */
            drv_attr = cpufreq_driver->attr;
            while ((drv_attr) && (*drv_attr)) {
                    ret = sysfs_create_file(&policy->kobj, &((*drv_attr)->attr));
                    if (ret)
                            goto err_out_kobj_put;
                    drv_attr++;
            }
    

    函数的一开始,建立cpufreq文件节点,然后在它的下面再建立一系列节点,用户可以通过这些文件节点控制该policy的一些参数。这不是我们的重点,我们看下面这一段代码:

            for_each_cpu(j, policy->cpus) {
                    per_cpu(cpufreq_cpu_data, j) = policy;
                    per_cpu(cpufreq_policy_cpu, j) = policy->cpu;
            }
    

    前面的代码已经设定了该policy所管理的online cpu:policy->cpus,通过两个per_cpu变量,这里把每个online cpu的policy都设置为了本cpu(管理cpu)的policy,并且把所有online的cpu的管理cpu也指定为了本cpu。接下来,cpufreq_add_dev_symlink被调用,所有policy->cpus指定的cpu会建立一个cpufreq链接,指向本cpu(管理cpu)的真实cpufreq节点:

            ret = cpufreq_add_dev_symlink(cpu, policy);
    

    注意,假如这时的cpu是cpu0,也就是说,其它cpu的cpufreq_add_dev还没有被调用,但是在cpufreq_cpu_data中,与之对应的policy指针已经被赋值为cpu0所对应的policy,这样,回到cpufreq_add_dev函数的开头部分,当接下其它被认为使用cpu0托管他们的policy的cpu也会进入cpufreq_add_dev函数,但是,因为cpufreq_cpu_data中对应的policy已经在cpu0的建立阶段被赋值,所以这些cpu他们不会走完所有的流程,在函数的开头的判断部分,判断cpufreq_cpu_data中cpu对应的policy已经被赋值,就直接返回了。

    接着往下看cpufreq_add_dev_interface的代码:

            memcpy(&new_policy, policy, sizeof(struct cpufreq_policy));
            /* assure that the starting sequence is run in __cpufreq_set_policy */
            policy->governor = NULL;
     
            /* set default policy */
            ret = __cpufreq_set_policy(policy, &new_policy);
            policy->user_policy.policy = policy->policy;
            policy->user_policy.governor = policy->governor;
    

    通过__cpufreq_set_policy函数,最终使得该policy正式生效。到这里,每个cpu的policy已经建立完毕,并正式开始工作。关于__cpufreq_set_policy的代码这里就不展开了,我只给出它的序列图:
    image

    5. 其它API

    cpufreq的核心层除了提供上面几节讨论的注册governor,注册cpufreq_driver等API外,还提供了其他一些辅助的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更新通知

    • int cpufreq_driver_target(struct cpufreq_policy *policy,
                                       unsigned int target_freq,
                                       unsigned int relation);

    • int __cpufreq_driver_target(struct cpufreq_policy *policy,
                                         unsigned int target_freq,
                                         unsigned int relation);

    以上两个API用来设置cpu的工作频率,区别在于cpufreq_driver_target是带锁的版本,而__cpufreq_driver_target是不带锁的版本,如果确定是在governor的上下文中,使用不带锁的版本,否则需要使用带锁的版本。

    • void cpufreq_verify_within_limits(struct cpufreq_policy *policy, unsigned int min, unsigned int max);

    这个API用来检查并重新设定policy的最大和最小频率。

    • int cpufreq_update_policy(unsigned int cpu);

    这个API用来触发cpufreq核心进行policy的更新操作。

  • 相关阅读:
    [ZZ]asp.net页面生命周期
    [ZZ]关于内存中栈和堆的区别
    我的第一个ASP.NET网页
    服务应用之WEB与WCF使用之见
    WEB服务于WCF服务的技术资料
    常用的Web服务和WCF服务
    笔试面试,几个字解决(原创)
    beij~~~
    Symbian 逐步深入(三)
    More 平台
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/12793780.html
Copyright © 2011-2022 走看看