OpenHarmony开发者论坛

标题: 【资源调度】Soc统一调频插件及服务:源码解析及配置说明 [打印本页]

作者: 深开鸿_张亮亮    时间: 2023-11-2 20:00
标题: 【资源调度】Soc统一调频插件及服务:源码解析及配置说明
[md]# 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/
```

2. 根据dtsi文件查询其支持的CPU频率参数:

```powershell
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<luginSwitch>();
        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("luginMgr::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_->ostTask([this, resType, value, payload] {
        // Cgroup分组
        DispatchResourceInner(resType, value, payload);
       // 分发插件
        PluginMgr::GetInstance().DispatchResource(std::make_shared<ResData>(resType, value, payload));
    });
    ...
}
```

从`ResSchedMgr`DispatchResource到`PluginMgr`后,会在其中调用`deliverResourceToPlugin`去分发插件。

```
void PluginMgr:ispatchResource(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]->ostTask(
            [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:ispatchResource(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:erfRequest`进行提频操作:

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

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

```
void SocPerf:oFreqActions(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);
            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);
            handler->SendEvent(event);
        }
    }
}
```

`SocPerfHandler:rocessEvent`根据传入的事件ID`INNER_EVENT_ID_DO_FREQ_ACTION`进行调频事件的处理。

```
void SocPerfHandler:rocessEvent(const AppExecFwk::InnerEvent:ointer &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 `等等路径写入频率信息)



[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5