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