积分234 / 贡献20

提问0答案被采纳4文章2

[经验分享] 【资源调度】Soc统一调频插件及服务:源码解析及配置说明

深开鸿_张亮亮 显示全部楼层 发表于 2023-11-2 20:00:08

Soc统一调频插件及服务:源码解析及配置说明

仓库主页及介绍: https://gitee.com/openharmony/resourceschedule\_resource\_schedule\_service 注:本文所分析源码为OpenHarmony3.2Release版本

SOC统一调频插件

简介

SOC统一调频插件(SocPerfPluing)位于资源调度子系统(ResourceScheduleService, RSS)内。其通过接收其他场景向RSS报告或通过RSS中智能分组模块转发的应用状态、焦点状态、后台任务状态等系统和应用事件,并将这些事件再分发给SOC统一调频服务(SocPerf),从而进行相关的调频仲裁,最终使用内核接口设置配置文件中设置的CPU/GPU/DDR/NPU频率策略。

配置说明

配置文件 说明
socperf_resource_config.xml 定义产品可支持的资源配置,例如CPU(大小核)/GPU/DDR/NPU等
socperf_boost_config.xml 用于性能提频的配置文件

各个xml配置文件都需要按产品定制,不同产品的配置不相同。 对于指定的某产品,所有可支持配置的资源都定义在socperf_resource_config.xml内,支持单路径/多路径配置,任何资源都有唯一的resID。socperf_boost_config.xml使用的cmdID不能重复。

默认配置如下:

  • socperf_resource_config.xml: 其中的path可以根据产品进行多路径配置,比如当产品CPU为多核时。
<Configs>
      <Resource>
          <res id="1000" name="lit_cpu_min_freq" pair="1001">
              <default>408000</default>
              <path>/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq</path>
              <node>408000 600000 816000 1104000 1416000 1608000 1800000 1992000</node>
          </res>
          <res id="1001" name="lit_cpu_max_freq" pair="1000" mode="1">
              <default>1800000</default>
              <path>/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq</path>
              <node>408000 600000 816000 1104000 1416000 1608000 1800000 1992000</node>
          </res>
          <res id="2000" name="gpu_min_freq" pair="2001">
              <default>200000000</default>
              <path>/sys/class/devfreq/fde60000.gpu/min_freq</path>
              <node>200000000 300000000 400000000 600000000 700000000 800000000</node>
          </res>
          <res id="2001" name="gpu_max_freq" pair="2000" mode="1">
              <default>700000000</default>
              <path>/sys/class/devfreq/fde60000.gpu/max_freq</path>
              <node>200000000 300000000 400000000 600000000 700000000 800000000</node>
          </res>
      </Resource>
  </Configs>
  • socperf_boost_config.xml:
<Configs>
      <cmd id="10000" name="app_start">
          <Action>
              <duration>1500</duration>
              <lit_cpu_min_freq>1992000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10001" name="warm_start">
          <Action>
              <duration>400</duration>
              <lit_cpu_min_freq>1800000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10002" name="window_switch">
          <Action>
              <duration>400</duration>
              <lit_cpu_min_freq>1608000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10006" name="click_normal">
          <Action>
              <duration>250</duration>
              <lit_cpu_min_freq>1992000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10007" name="push_page_start">
          <Action>
              <duration>300</duration>
              <lit_cpu_min_freq>1608000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10008" name="list_fling">
          <Action>
              <duration>5000</duration>
              <lit_cpu_min_freq>1992000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10009" name="slide_normal">
          <Action>
              <duration>5000</duration>
              <lit_cpu_min_freq>1992000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10010" name="touch_down_up">
          <Action>
              <duration>100</duration>
              <lit_cpu_min_freq>1608000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10011" name="push_page_complete">
          <Action>
              <duration>200</duration>
              <lit_cpu_min_freq>1416000</lit_cpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10012" name="web_gesture">
          <Action>
              <duration>800</duration>
              <lit_cpu_min_freq>1992000</lit_cpu_min_freq>
              <gpu_min_freq>800000000</gpu_min_freq>
          </Action>
      </cmd>
      <cmd id="10016" name="pop_page">
          <Action>
              <duration>400</duration>
              <lit_cpu_min_freq>1608000</lit_cpu_min_freq>
          </Action>
      </cmd>
  </Configs>
  • name:需要和ressched_report.cpp中具体事件对应的string相同。
  • duration:表示提频的持续时间,在PerfRequest接口生效。

CPU节点配置

以RK3568为参考,其CPU/GPU/DDR/NPU的配置频率均可在./kernel/linux/rk3588-5.10/arch/arm64/boot/dts/rockchip/rk3568.dtsi中找到。

CPU节点配置以RK3588为例,GPU/DDR/NPU均与其配置过程类似。具体的配置过程如下:

  1. 明确产品的CPU型号及其节点目录:
RK3588 CPU:四核A76(大核:CPU4、CPU6)+四核A55(小核:CPU0),最高主频2.4G;

  powershell
   # CPU0:(对应4个A55:CPU0-3)
   /sys/devices/system/cpu/cpu0/cpufreq/policy0

   # CPU4: (对应2个A76:CPU4-5)
   /sys/devices/system/cpu/cpu4/cpufreq/

   # CPU6: (对应2个A76:CPU6-7)
   /sys/devices/system/cpu/cpu6/cpufreq/
  1. 根据dtsi文件查询其支持的CPU频率参数:
cd KaihongOS/kernel/linux/rk3588-5.10/arch/arm64/boot/dts/rockchip
   gedit rk3588s.dtsi # 或其他命令

在文件中搜索cluster1_opp_table,参数根据opp-xxxx进行配置。 3. 对应RK3588的三个CPU大小核,按照其上路径,根据其官方文档提供的频率范围设定频率。

<res id="1000" name="lit_cpu0_min_freq" pair="1001">
               <default>408000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 1992000</node>
           </res>
           <res id="1001" name="lit_cpu0_max_freq" pair="1000" mode="1">
               <default>1800000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 1992000</node>
           </res>
           <res id="1002" name="lit_cpu4_min_freq" pair="1003">
               <default>408000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 2016000 2208000 2304000</node>
           </res>
           <res id="1003" name="lit_cpu4_max_freq" pair="1002"  mode="1">
               <default>2304000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 2016000 2208000 2304000</node>
           </res>
           <res id="1004" name="lit_cpu6_min_freq" pair="1005">
               <default>408000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 2016000 2208000 2352000</node>
           </res>
           <res id="1005" name="lit_cpu6_max_freq" pair="1004"  mode="1">
               <default>2352000</default>
               <path>/sys/devices/system/cpu/cpufreq/policy6/scaling_max_freq</path>
               <node>408000 600000 816000 1008000 1200000 1416000 1608000 1800000 2016000 2208000 2352000</node>
           </res>

源码解析

Soc统一调频插件及服务的源码解析,分为初始化、插件分发、Soc统一调频服务三个部分来进行说明。

初始化

在初始化阶段,在文件res_sched_service_ability.cpp中调用宏RES_SCHED_SYS_ABILITY_ID注册系统能力ResSchedServiceAbility

namespace ResourceSchedule {
const bool REGISTER_RESULT =
    SystemAbility::MakeAndRegisterAbility(DelayedSingleton<ResSchedServiceAbility>::GetInstance().get());

ResSchedServiceAbility::ResSchedServiceAbility() : SystemAbility(RES_SCHED_SYS_ABILITY_ID, true)
{
}

ResSchedServiceAbility被拉起来后,就会解析两个xml配置文件并初始化所有插件。

void PluginMgr::Init()
{
    ...

    // 读取res_sched_plugin_switch.xml:从etc/ressched/res_sched_plugin_switch.xml中加载插件
    if (!pluginSwitch_) {
        pluginSwitch_ = make_unique<PluginSwitch>();
        std::string realPath = GetRealConfigPath(PLUGIN_SWITCH_FILE_NAME);
        if (realPath.empty() || !pluginSwitch_->LoadFromConfigFile(realPath)) {
            RESSCHED_LOGW("%{public}s, PluginMgr load switch config file failed!", __func__);
        }
    }

    // 读取res_sched_config.xml:从etc/ressched/res_sched_config.xml中加载插件配置
    if (!configReader_) {
        configReader_ = make_unique<ConfigReader>();
        std::string realPath = GetRealConfigPath(CONFIG_FILE_NAME);
        if (realPath.empty() || !configReader_->LoadFromCustConfigFile(realPath)) {
            RESSCHED_LOGW("%{public}s, PluginMgr load config file failed!", __func__);
        }
    }

    // 通过dlopen打开so动态库,调用onPluginInitFunc初始化插件
    LoadPlugin();
    RESSCHED_LOGI("PluginMgr::Init success!");
}

插件分发

RSS的所有操作都是通过业务场景访问对外暴露的外部接口ReportData来进行的。

其他业务场景事件对SOC统一调频插件的调用:

业务场景 Event Status
ace RES_TYPE_SLIDE_RECOGNIZE // ace滑动事件 SLIDE_OFF_EVENT = 0SLIDE_ON_EVENT = 1
RES_TYPE_WEB_GESTURE // web手势识别事件
RES_TYPE_CLICK_RECOGNIZE // ace手势点击识别事件 INVALID_EVENT = 0TOUCH_EVENT = 1CLICK_EVENT = 2
RES_TYPE_PUSH_PAGE // 页面加载 PUSH_PAGE_START_EVENT = 0PUSH_PAGE_COMPLETE_EVENT = 1
RES_TYPE_POP_PAGE // 页面退出 POP_PAGE_EVENT = 0
RemoteAnimation RES_TYPE_SHOW_REMOTE_ANIMATION // 应用过渡动画事件 REMOTE_ANIMATION_BEGIN = 0REMOTE_ANIMATION_END = 1
WindowImpl RES_TYPE_RESIZE_WINDOW // 调整窗口大小事件 WINDOW_RESIZING = 0WINDOW_RESIZE_STOP = 1
RES_TYPE_MOVE_WINDOW // 移动窗口事件 WINDOW_MOVING = 0WINDOW_MOVE_STOP = 1
AbilityManagerService RES_TYPE_APP_ABILITY_START // startAblity事件 APP_COLD_START = 0APP_WARM_START = 1
Cgroup转发 RES_TYPE_WINDOW_FOCUS // 窗口聚焦事件 WINDOW_FOCUS = 0WINDOW_UNFOCUS = 1

RSS中的所有事件见:

foundation/resourceschedule/resource_schedule_service/ressched/interfaces/innerkits/ressched_client/include/res_type.h 进入资源调度管理ResSchedMgr之后,从ReportData兵分两路,一路去Cgroup分组,一路去分发插件。

void ResSchedMgr::ReportData(uint32_t resType, int64_t value, const nlohmann::json& payload)
{
    ...
    // dispatch resource async
    ...
    mainHandler_->PostTask([this, resType, value, payload] {
        // Cgroup分组
        DispatchResourceInner(resType, value, payload);
       // 分发插件
        PluginMgr::GetInstance().DispatchResource(std::make_shared<ResData>(resType, value, payload));
    });
    ...
}

ResSchedMgrDispatchResource到PluginMgr后,会在其中调用deliverResourceToPlugin去分发插件。

void PluginMgr::DispatchResource(const std::shared_ptr<ResData>& resData)
{
    RemoveDisablePluginHandler();

    ...

    for (const auto& libPath : iter->second) {
        if (!dispatcherHandlerMap_[libPath]) {
            RESSCHED_LOGE("%{public}s, failed, %{public}s dispatcher handler is stopped.", __func__,
                libPath.c_str());
            continue;
        }
        dispatcherHandlerMap_[libPath]->PostTask(
            [libPath, resData, this] { deliverResourceToPlugin(libPath, resData); });
    }
}

deliverResourceToPlugin中通过pluginDispatchFunc(resData)来打开SOC统一调频插件的so库:libsocperf_plugin.z.so调用插件。

void PluginMgr::deliverResourceToPlugin(const std::string& pluginLib, const std::shared_ptr<ResData>& resData)
{
    ...

    OnDispatchResourceFunc pluginDispatchFunc = libInfo.onDispatchResourceFunc_;
    if (!pluginDispatchFunc) {
        RESSCHED_LOGE("%{public}s, no DispatchResourceFun !", __func__);
        return;
    }

    auto beginTime = Clock::now();
    // 打开动态链接库
    pluginDispatchFunc(resData);
    auto endTime = Clock::now();
    int32_t costTime = (endTime - beginTime) / std::chrono::milliseconds(1);
    if (costTime > DISPATCH_TIME_OUT) {
        // dispatch resource use too long time, unload it
        ...
        RepairPlugin(endTime, pluginLib, libInfo);
    } else if (costTime > DISPATCH_WARNING_TIME) {
       // log
    }
}

在这步,代码对插件进行了一个约束:

插件的事件处理,必须快速完成。超过10ms(DISPATCH_WARNING_TIME),框架会打印日志报告;超过50ms(DISPATCH_TIME_OUT),框架会认为插件用时太久异常,就会调用RepairPlugin(),在其中会disable该插件并重新enable;如果在一分钟内有三次调用该插件时的处理时间超过50ms,就会永远disable此插件。

之后,SOC统一调频插件将事件分发到SOC统一调频服务中,由其进行调频事件的处理和仲裁。

extern "C" void OnDispatchResource(const std::shared_ptr<ResData>& data)
{
    SocPerfPlugin::GetInstance().DispatchResource(data);
}

SOC统一调频服务

SOC统一调频服务(SOCPERF)主要负责cpu/gpu频率的修改,其中有两个关键类:

  1. SocPerf:解析两个xml配置文件,接收触发提频的RES_TYPE事件,再根据事件类型向SocPerfHandler发送对应的INNER_EVENT事件。

  2. SocPerfHandler:接收SocPerf发送的INNER_EVENT,根据事件类型调用对应接口,进行调频事件处理和调频仲裁。

    首先介绍其中的重要接口。

接口 说明
PerfRequest(int32_t cmdId, const std::string& msg) 用于性能提频使用
PerfRequestEx(int32_t cmdId, bool onOffTag, const std::string& msg) 用于性能提频使用且支持ON/OFF事件
PowerLimitBoost(bool onOffTag, const std::string& msg) 用于限制boost无法突破功耗限频
ThermalLimitBoost(bool onOffTag, const std::string& msg) 用于限制boost无法突破热限频
LimitRequest(int32_t clientId, const std::vector& tags, const std::vector& configs, const std::string& msg) 用于热或功耗模块的限频且支持多项值一同设置

如表所示,提频接口都以cmdID为核心,将调频场景和调频参数互相关联,实现提频或者限频的功能。 带onOffTag参数的接口表示该接口支持ON/OFF的开关调频模式,一般用于生效时间不固定的长期调频事件,需要调用者手动开启或者关闭(测试),或者根据事件的状态进行触发。 msg参数为拓展字符串信息,可承载例如调用客户端pid/tid等信息。

目前由于后三个接口无实际使用场景,因此只介绍前两个提频接口。

SOCPERF被拉起后,插件会根据触发的事件调用对应的事件处理函数。

void SocPerfPlugin::DispatchResource(const std::shared_ptr<ResData>& data)
{
    auto funcIter = functionMap.find(data->resType);
    if (funcIter != functionMap.end()) {
        auto function = funcIter->second;
        if (function) {
            // 调用函数进行对应事件处理
            function(data);
        }
    }
}
void SocPerfPlugin::Init()
{
    functionMap = {
        { RES_TYPE_WINDOW_FOCUS,
            [this](const std::shared_ptr<ResData>& data) { HandleWindowFocus(data); } },
        { RES_TYPE_CLICK_RECOGNIZE,
            [this](const std::shared_ptr<ResData>& data) { HandleEventClick(data); } },
        { RES_TYPE_PUSH_PAGE,
            [this](const std::shared_ptr<ResData>& data) { HandlePushPage(data); } },
        { RES_TYPE_SLIDE_RECOGNIZE,
            [this](const std::shared_ptr<ResData>& data) { HandleEventSlide(data); } },
        { RES_TYPE_WEB_GESTURE,
            [this](const std::shared_ptr<ResData>& data) { HandleEventWebGesture(data); } },
        { RES_TYPE_POP_PAGE,
            [this](const std::shared_ptr<ResData>& data) { HandlePopPage(data); } },
        { RES_TYPE_APP_ABILITY_START,
            [this](const std::shared_ptr<ResData>& data) { HandleAppAbilityStart(data); } },
        { RES_TYPE_RESIZE_WINDOW,
            [this](const std::shared_ptr<ResData>& data) { HandleResizeWindow(data); } },
        { RES_TYPE_MOVE_WINDOW,
            [this](const std::shared_ptr<ResData>& data) { HandleMoveWindow(data); } },
        { RES_TYPE_SLIDE_NORMAL,
            [this](const std::shared_ptr<ResData>& data) { HandleSlideNormal(data); } },
    };
    resTypes = {
        RES_TYPE_WINDOW_FOCUS,
        RES_TYPE_CLICK_RECOGNIZE,
        RES_TYPE_PUSH_PAGE,
        RES_TYPE_SLIDE_RECOGNIZE,
        RES_TYPE_WEB_GESTURE,
        RES_TYPE_POP_PAGE,
        RES_TYPE_APP_ABILITY_START,
        RES_TYPE_RESIZE_WINDOW,
        RES_TYPE_MOVE_WINDOW,
        RES_TYPE_SLIDE_NORMAL,
    };
    for (auto resType : resTypes) {
        PluginMgr::GetInstance().SubscribeResource(LIB_NAME, resType);
    }
    RESSCHED_LOGI("SocPerfPlugin::Init success");
}

以事件RES_TYPE_WINDOW_FOCUS为例。

首先需要在上面业务场景中提到的WindowImpl类中,在WINDOW_FOCUS对应的回调下调用RSS提供的接口ReportData向SOC统一调频插件分发事件,从而实现提频请求。

即,要实现窗口聚焦事件的提频,分为窗口聚焦事件发生、事件上报给RSS、框架给SOC统一调频插件分发事件、生效调频服务四个步骤。

过程如下:

调用处理窗口聚焦事件的函数后,会发送提频请求:

void SocPerfPlugin::HandleWindowFocus(const std::shared_ptr<ResData>& data)
{
    if (data->value == WindowFocusStatus::WINDOW_FOCUS) {
        RESSCHED_LOGI("SocPerfPlugin: socperf->WINDOW_SWITCH");
        OHOS::SOCPERF::SocPerfClient::GetInstance().PerfRequest(PERF_REQUEST_CMD_ID_WINDOW_SWITCH, "");
    }
}

之后调用SocPerf::PerfRequest进行提频操作:

void SocPerf::PerfRequest(int32_t cmdId, const std::string& msg)
{
    ...
    DoFreqActions(perfActionsInfo[cmdId], EVENT_INVALID, ACTION_TYPE_PERF);
    ...
}

调用SocPerf::DoFreqActions,向SocPerfHandler发送事件,进行调频事件处理和调频仲裁。

void SocPerf::DoFreqActions(std::shared_ptr<Actions> actions, int32_t onOff, int32_t actionType)
{
    for (auto iter = actions->actionList.begin(); iter != actions->actionList.end(); iter++) {
        std::shared_ptr<Action> action = *iter;
        for (int32_t i = 0; i < (int32_t)action->variable.size() - 1; i += RES_ID_AND_VALUE_PAIR) {
            auto handler = GetHandlerByResId(action->variable[i]);
            if (!handler) {
                continue;
            }
            auto resAction = std::make_shared<ResAction>(action->variable[i + 1], action->duration, actionType, onOff);
            // 向SocPerfHandler发送对应的内部时间,进行调频处理
            auto event = AppExecFwk::InnerEvent::Get(INNER_EVENT_ID_DO_FREQ_ACTION, resAction, action->variable[i]);
            handler->SendEvent(event);
        }
    }
}

SocPerfHandler::ProcessEvent根据传入的事件IDINNER_EVENT_ID_DO_FREQ_ACTION进行调频事件的处理。

void SocPerfHandler::ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)
{
    if (event == nullptr) {
        return;
    }
    switch (event->GetInnerEventId()) {
        ...
        case INNER_EVENT_ID_DO_FREQ_ACTION: {
            int32_t resId = event->GetParam();
            if (!IsValidResId(resId)) {
                return;
            }
            std::shared_ptr<ResAction> resAction = event->GetSharedObject<ResAction>();
            if (resAction != nullptr) {
                UpdateResActionList(resId, resAction, false);
            }
            break;
        }
        ...
        default: {
            break;
        }
    }
}

最终调用WriteNode(),向设备中写入解析的xml文件中的resNode信息。(如RK3568事件触发提频时向:/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq等等路径写入频率信息)

[/i][/i]

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部