Skip to content

调度整体说明

这篇文档定位为 进阶维护说明

如果你只是想改当前官网对外使用的线程写法,请优先看:

  1. 免费版 BPF自定义线程
  2. 付费版 BPF自定义线程
  3. 动态调频
  4. 场景分类 categories.json
  5. FAS 帧感知

本文按当前磁盘上的真实文件与源码实现整理,目标不是只解释几个字段,而是把这套模块从安装、启动、加载、热更新、运行时判定,到每类 JSON 的真实作用和常见改法一次讲清。

这份文档当前定位如下:

  1. 这是模块仓库当前唯一保留的总说明
  2. 它优先描述磁盘上的真实实现,不按旧规划文档口径回退
  3. 它同时服务三类场景:
    • 日常改配置
    • 接手维护运行时
    • 对照 APK 侧编辑器理解设备侧到底在吃什么

0. 先记住的 10 件事

如果你第一次接手这套调度,先记住下面 10 件事:

  1. 这不是单一 shell 调参脚本,而是 模式配置 + 线程专项 + FAS + 场景分类 + 运行时识别 五层叠加
  2. 日常最常改的主文件要按版本区分:
    • 免费版主看 bin/cpu/<SoC>.jsonbin/cpu/<拓扑>.jsonconfig/categories.json
    • 付费版再额外看 bin/cpu/fas.json
  3. bin/cpu/<拓扑>.json 虽然文件名还是普通 JSON,但当前主线实际承载的是 version: 3 的紧凑线程结构
  4. 前台识别当前主链不是只看 cpuset,而是 top-resumed-activity -> window -> activities -> /proc PID 多级回退
  5. FAS 当前主路径是 queueBuffer-only,不再回到旧 GPU 主路径
  6. 线程专项已经不只管前台应用,也能管 persistent 常驻规则
  7. persistent 常驻规则已经接入 eBPF 增量刷新,不再只靠粗暴轮询
  8. 功能开关的真实优先级是:模式默认值 -> 分类规则 -> 应用规则
  9. mode.txt 不是只有“当前模式”这一行,它还可以承载应用级模式覆盖
  10. 付费版 fas.json 里 APK 同步留下的远端元数据不参与运行时主逻辑,真正主线读取的是 defaultsapps

1. 当前仓库真实结构

模块仓库当前最关键的目录和文件如下:

路径作用
service.sh开机入口,等待系统启动后拉起模块运行时
customize.sh刷入安装期脚本,负责初始配置、环境处理、冲突组件处理
activity_diaodu.rc启动 bin/activity_diaodu 的外层脚本
bin/activity_diaodu设备侧实际运行的调度主二进制
bin/activity_diaodu.cpp主入口源码,包含路径常量、主循环拼装、模式文件读取等
bin/activity_config_loading.inc所有 JSON 的核心加载逻辑
bin/activity_mode_watch.inc模式与热更新监听逻辑
bin/activity_scene_foreground.inc前台包名、Activity、PID 识别逻辑
bin/activity_scene_policy.inc场景分类与类目策略逻辑
bin/activity_scheduler_internal.inc线程规则、角色规则、守护与重打动作逻辑
bin/activity_scheduler_runtime.inc线程调度运行时
bin/activity_dynamic_tuning.inc动态调频主逻辑
bin/activity_fas_sampling.incFAS 配置读取与 queueBuffer 采样逻辑
bin/activity_fas_runtime.incFAS 运行时控制逻辑
bin/cpu/模式、线程专项、FAS 配置目录
config/categories.json场景分类主配置
vtools/init_vtools.sh生成 /data/powercfg.json/data/powercfg.sh
action.sh卸载/清理相关脚本
appopt源码/eBPF 相关源码与产物

2. 安装链、启动链、运行链

2.1 安装阶段做了什么

刷入模块时,customize.sh 主要做这些事:

  1. 检查基础系统文件是否存在
  2. 屏蔽冲突项,例如 /system/vendor/bin/msm_irqbalance
  3. 初始化 /data/adb/modules/muronggameopt/config/mode.txt
  4. 调用 vtools/init_vtools.sh 生成 /data/powercfg.json/data/powercfg.sh
  5. 为模块目录、主二进制设置权限
  6. 检查并处理不同 ROM 的冲突服务:
    • com.oplus.cosa
    • com.xiaomi.joyose
    • com.vivo.gamewatch
    • oiface
  7. 复制 affinity.conf.defaultaffinity.conf

安装阶段额外要注意两点:

  1. mode.txt 首行是全局默认模式,后续每行可以写 包名 模式
  2. 当前仓库磁盘里没有看到 CPUcluster.sh 文件,但 customize.sh 仍引用了它;后续如果要继续整理安装链,这一处需要单独核实打包来源

2.2 开机启动链

当前启动顺序如下:

  1. service.sh 作为开机入口执行
  2. service.sh 最多循环等待系统启动完成
  3. 执行 vtools/init_vtools.sh
  4. 写入若干系统节点,例如:
    • /proc/sys/walt/sched_conservative_pl
    • /proc/oplus-votable/GAUGE_UPDATE/force_val
    • /proc/oplus-votable/GAUGE_UPDATE/force_active
    • /sys/module/cpufreq_bouncing/parameters/enable
  5. 调用 activity_diaodu.rc
  6. activity_diaodu.rc 解锁 cpufreq 目录权限后,后台启动 bin/activity_diaodu

2.3 主二进制初始化顺序

bin/activity_diaodu 启动后,初始化顺序大致是:

  1. 备份并清理旧日志
  2. 检查 root 权限
  3. 检测 CPU 型号与平台类型
  4. 重新挂载部分 /sys 为可写
  5. 检测 CPU 集群和 policy
  6. 加载 fas.json
  7. 加载线程专项配置
  8. 加载 categories.json
  9. 读取 mode.txt
  10. 按当前模式加载 SoC 模式配置
  11. 初始化 cluster policy
  12. 进入主循环并开启 inotify 热监听

3. 配置文件到底放哪

运行时最常用的真实路径如下:

路径说明
/data/adb/modules/muronggameopt/config/mode.txt当前模式状态文件,同时支持应用级模式覆盖
/data/adb/modules/muronggameopt/config/categories.json场景分类配置
/data/adb/modules/muronggameopt/bin/cpu/fas.json付费版 FAS 配置;免费版当前没有这条独立主文件
/data/adb/modules/muronggameopt/bin/cpu/<SoC>.jsonSoC 模式配置
/data/adb/modules/muronggameopt/bin/cpu/<拓扑>.json线程专项主配置
/data/adb/modules/muronggameopt/bin/cpu/<SoC>.scheduler.json可选会话兜底配置
/data/powercfg.json外部场景控制描述文件
/data/powercfg.sh外部场景写回入口脚本

4. 这套调度真正分成哪几层

可以把当前系统理解成下面五层:

  1. 模式层
    • 定义 powersave / balance / performance / fast
    • 负责频率、governor、cpuset、模式级动态调频
  2. 线程专项层
    • 决定某个应用或常驻进程里的关键线程怎么绑核、怎么上优先级、怎么做 RT
  3. FAS 层
    • queueBuffer 节奏推断目标帧率并驱动频率控制
  4. 场景分类层
    • 按游戏、桌面、相机、扫码、小程序等类目给默认策略
  5. 运行时识别层
    • 负责前台包名、前台 Activity、前台 PID、persistent 常驻规则与 eBPF 增量刷新

最容易记的理解方式:

  1. SoC JSON 决定模式地基
  2. 拓扑 JSON 决定线程专项
  3. 付费版 fas.json 决定 FPS 档位与 FAS 控制参数;免费版当前没有这层独立文件
  4. categories.json 决定“这是什么场景”和“这一类默认偏什么”

5. 当前真实加载顺序

5.1 模式主配置

SoC 模式配置读取路径:

  1. bin/cpu/<SoC>.json

例如:

  1. bin/cpu/SM8850.json
  2. bin/cpu/SM8750.json
  3. bin/cpu/MT6991.json

这类文件决定:

  1. cpu_policies
  2. cpuset
  3. 模式级 dynamic_tuning
  4. 模式级 features

5.2 线程专项配置

线程专项配置按下面顺序找:

  1. bin/cpu/<SoC>.threads.json
  2. bin/cpu/<拓扑>.threads.json
  3. bin/cpu/<拓扑>.json

例如 SM88506+2 设备,顺序就是:

  1. bin/cpu/SM8850.threads.json
  2. bin/cpu/6+2.threads.json
  3. bin/cpu/6+2.json

当前磁盘上的真实情况是:

  1. *.threads.json 不一定存在
  2. 6+2.json6+1.json4+4.json 这类拓扑文件就是当前线程专项主路径

5.3 场景分类配置

场景分类当前固定读取:

  1. config/categories.json

5.4 FAS 配置

FAS 当前固定读取:

  1. bin/cpu/fas.json

这条独立主文件当前按付费版主线理解;免费版当前磁盘里没有这条独立文件。

5.5 会话兜底配置

代码支持:

  1. bin/cpu/<SoC>.scheduler.json

但磁盘上不一定有;没有时就回到代码默认值,再叠加分类和应用覆盖。

6. 哪些改动能热生效,哪些不能

这是当前最容易踩坑的一块。

6.1 当前已接 inotify 热监听的文件

运行时当前明确监听这些更新:

  1. config/mode.txt
  2. config/categories.json
  3. config/runtime_license.json
  4. bin/cpu/fas.json(付费版)
  5. bin/cpu/<SoC>.json 这类当前 CPU 型号对应的模式配置文件
  6. 当前实际生效的线程专项配置文件
    • bin/cpu/<SoC>.threads.json
    • bin/cpu/<拓扑>.threads.json
    • bin/cpu/<拓扑>.json 三者里当前命中的那一个
  7. 当前实际生效的 bin/cpu/<SoC>.scheduler.json

6.2 热生效后的动作

对应行为如下:

  1. mode.txt
    • 立即重读模式
    • 立即重新加载当前模式的 SoC 配置
  2. categories.json
    • 立即重载场景分类规则
  3. fas.json(付费版)
    • 立即重载 FAS 应用配置
  4. 改当前 CPU 型号对应的 SoC 配置
    • 立即重载模式配置
    • 重新初始化 cluster policy
  5. 改当前实际生效的线程专项配置
    • 立即重载线程规则
    • 恢复旧线程已施加的 nice / scheduler 调整
    • 重置当前调度会话
    • 清理线程缓存、学习样本和 eBPF 目标映射
    • 下一轮重新识别前台 PID 和线程
  6. 改当前实际生效的 scheduler.json
    • 立即重载会话默认值
    • 走同一条线程运行时重置路径,避免旧会话继续残留

6.3 仍然要注意的地方

虽然线程专项现在已经支持热更新,但仍要注意两点:

  1. 只有“当前实际命中的线程配置路径”会被当成线程专项配置热更新
  2. 如果你改的是另一个未命中的备选文件,运行时不会自动去切主配置源

另外,下面这些情况下仍建议主动重启服务或设备:

  1. 你同时改了大量线程规则和运行时代码
  2. 你怀疑旧进程已经带着旧策略运行太久
  3. 你想验证完整冷启动路径而不是单纯热更新路径

7. mode.txt 的真实语义

mode.txt 不是只保存一个“当前模式名”,它真实结构是:

txt
powersave
com.tencent.tmgp.sgame balance
com.miHoYo.Yuanshen balance
com.tencent.tmgp.cf balance

含义如下:

  1. 第一行是全局默认模式
  2. 后续每一行是 包名 模式
  3. 运行时会先读第一行,再检查后续是否存在针对当前前台包名的覆盖

逐行解释:

  1. 第一行
    • 必须是全局默认模式
    • 当前前台没有命中应用覆盖时,就吃这一行
  2. 后续每一行
    • 格式是 包名 模式
    • 只对对应包名生效
  3. 如果同一个包写了多次
    • 维护时不建议依赖重复写法
    • 最稳妥的做法是只保留一条

当前支持的模式值只有:

  1. powersave
  2. balance
  3. performance
  4. fast

所以如果想给单独某个应用强制走更高模式,不一定非要改 SoC JSON,也可以先改 mode.txt 的应用覆盖。

8. 模式主配置 bin/cpu/<SoC>.json

8.1 这类文件负责什么

SoC 模式配置主要负责:

  1. 各个模式下每个簇的频率上下限
  2. governor 与 governor 参数
  3. cpuset
  4. 模式级 dynamic_tuning
  5. 模式级 features

8.2 顶层结构

典型结构:

json
{
  "version": "5.8",
  "powersave": {},
  "balance": {},
  "performance": {},
  "fast": {}
}

字段说明:

字段含义
version配置版本标记
powersave省电模式
balance均衡模式
performance性能模式
fast极速模式

逐字段解释:

  1. version
    • 配置文件版本标记
    • 当前主要用于人工识别和同步版本,不直接决定运行时走哪条逻辑
  2. powersave
    • 省电模式总配置
  3. balance
    • 均衡模式总配置
  4. performance
    • 性能模式总配置
  5. fast
    • 极速模式总配置

理解要点:

  1. 这四个模式对象结构相同
  2. 只是频率上限、cpuset、动态调频参数不同

8.3 模式对象常见字段

json
{
  "cpu_policies": [],
  "cpuset": {},
  "dynamic_tuning": {},
  "features": {}
}
字段含义
cpu_policies每个簇或每个 policy 的频率与 governor 配置
cpusetbackground / foreground / topapp 等组的 CPU 范围
dynamic_tuning模式级动态调频默认参数
features模式级功能开关默认值

8.4 features

当前模式级功能开关要按版本区分:

  1. 免费版主线:scheduler_masterbpf_threadscene_categorybase_profile
  2. 付费版主线:scheduler_masterdynamic_tuningfascustom_threaddynamic_thread_schedulerscene_categorybase_profile

这层是总默认值,后面还会被分类和应用专项覆盖。

逐字段解释:

字段作用典型理解
scheduler_master是否默认开启调度总控关掉后,模式级调度主控能力整体停用
dynamic_tuning付费版是否默认开启动态调频主链关掉后,这个模式只保留频率基线,不再让动态调频主动调天花板
fas付费版是否默认允许 FAS 工作关掉后,即使 fas.json 里有应用档位,也不会进入 FAS 主链
bpf_thread免费版是否默认允许 BPF自定义线程工作免费版当前主线字段
custom_thread付费版是否默认允许静态自定义线程规则工作控制 main / gfx / render / worker / other / extra:[pattern] / default_action 这条静态规则链
dynamic_thread_scheduler付费版是否默认允许动态线程运行时工作控制线程学习、守护和动态重打
scene_category是否默认允许场景分类覆写关掉后不再吃 categories.json 分类覆写
base_profile是否默认允许模式基础档写入关掉后基础模式动作不参与写入

组合理解:

  1. dynamic_tuning=false, fas=true 表示允许 FAS,但不允许动态调频主链
  2. 免费版看 bpf_thread=true
  3. 付费版静态线程规则看 custom_thread=true
  4. 付费版动态线程运行时看 dynamic_thread_scheduler=true

8.5 cpu_policies

常见结构:

json
{
  "cluster": 0,
  "governor": "performance",
  "max_freq": 4608000,
  "min_freq": 1977600,
  "dynamic_tuning": {
    "target_loads": "70 2419200:82 3187200:90",
    "target_load": 70,
    "load_margin": 5,
    "debug_log": 0
  },
  "params": {}
}

字段说明:

字段含义
cluster簇编号,从 0 开始
governorgovernor 名称
max_freq当前模式该簇的最高频率
min_freq当前模式该簇的最低频率(非FAS模式下的底线)
optimum_freq【新增】当前模式该簇的“甜点频率”(FAS驻留频段)
dynamic_tuning该簇自己的动态调频参数
params额外 governor 参数

逐字段解释:

  1. cluster
    • 指向哪个 CPU 簇
    • 不是逻辑优先级,而是实际簇编号
  2. governor
    • 决定这一簇走哪种 governor 逻辑
    • 它会影响后面 params 字段到底有没有意义
  3. max_freq
    • 当前模式下该簇可达到的最高频率
    • 动态调频、FAS、boost 都不会突破这个模式上限
  4. min_freq
    • 当前模式下该簇的最低频率地板
    • 注意:在 FAS 模式下,此参数会被忽略,FAS 拥有彻底探底至硬件最低频率(如 384MHz)的权限以追求极致省电。
  5. optimum_freq
    • 当前模式该簇的“甜点频率”
    • FAS 模式下,当未发生严重掉帧时,频率会被极力拉长停留在该数值附近,实现功耗与性能的最佳平衡。
  6. dynamic_tuning
    • 该簇自己的动态调频参数
    • 它的优先级高于模式级总默认值
  7. params
    • governor 私有参数补丁区
    • 更适合微调,不适合当主逻辑总线

8.6 dynamic_tuning

当前主要字段:

字段含义
enabled是否默认开启动态调频
target_loads分段目标负载表
target_load默认目标负载
load_margin负载容差
debug_log是否打开更多辅助日志

当前实现里的一个重要细节:

  1. debug_log 打开后,不只是调频日志会多
  2. debug_log 还会让 debug_log.txtfas_log.txtaffinity_log.txtstats_log.txt 这类辅助日志真正开始写内容

逐字段解释:

  1. enabled
    • 当前模式或当前簇是否允许动态调频主链真正接管
    • 这是动态调频总开关,不是仅调日志
  2. target_loads
    • 分段目标负载表
    • 频率越高,可以配越严格的目标负载
  3. target_load
    • 没命中更细分段时的默认目标负载
    • 值越小越激进,越容易抬频
  4. load_margin
    • 目标负载的容差
    • 值越大越保守,频率变化更平缓
  5. debug_log
    • 是否开启更多调试日志
    • 打开后更适合排查,不适合长期常开

8.7 cpuset

常见字段:

  1. background
  2. systembackground
  3. foreground
  4. topapp

逐字段解释:

  1. background
    • 普通后台线程组可用 CPU
  2. systembackground
    • 系统后台组可用 CPU
  3. foreground
    • 前台应用普通线程组可用 CPU
  4. topapp
    • 顶层前台应用热点线程主要可用 CPU

理解要点:

  1. cpuset 是模式地基,不是专项线程策略
  2. 它会影响系统线程默认可跑范围,但不会替代线程专项的精确绑核

建议:

  1. 这层是模式总地基,不要把应用专项需求直接塞到这里
  2. 想做某个游戏的个别热线程绑核,优先用线程专项,不要反过来全局改坏模式基线

9. 线程专项 bin/cpu/<拓扑>.json

9.1 这类文件现在到底是什么

这类文件例如:

  1. bin/cpu/6+2.json
  2. bin/cpu/6+1.json
  3. bin/cpu/4+4.json

虽然文件名还是普通 JSON,但当前主线实际承载的是 version: 3 的紧凑线程结构。

9.2 顶层结构

json
{
  "version": 3,
  "apps": [
    {
      "friendly": "王者荣耀",
      "packages": ["com.tencent.tmgp.sgame"],
      "scope": "foreground",
      "main": { "match": ["UnityMain"], "cpus": "6-7", "fifo": 1 },
      "gfx": { "match": ["UnityGfx*"], "cpus": "4-5", "rr": 10 },
      "worker": { "match": ["*worker*"], "limit": 2, "cpus": "4-5", "rr": 5 },
      "extra:[binder:*]": { "limit": 8, "cpus": "0-5" },
      "other": { "cpus": "1-4" }
    }
  ]
}
字段含义
version当前紧凑线程结构版本,建议保持 3
apps应用级或常驻级专项规则主体

9.3 apps[] 里的公共字段

当前两版共同主线先看这些字段:

字段作用
friendly规则显示名,方便日志和 APK 编辑器识别
packages包名列表,支持一条规则绑定多个包
scope作用域;不写通常视为前台,persistent/global/system 走常驻路径
main / gfx / render / worker / other固定角色规则
extra:[pattern]手写补充线程规则,适合单独抓特殊线程名

要按版本理解两点:

  1. 免费版当前最稳妥的主线就是维护这些公共字段和各角色对象
  2. 付费版会再额外支持应用级 featuressessiondefault_action

9.4 apps 规则到底怎么理解

apps 里的每一项,不一定是一条“单应用规则”,也可能是一条“多包名共用规则组”。

也就是说:

  1. 一条规则可以挂一个包名
  2. 一条规则也可以挂一组包名
  3. APK 侧现在已经支持按应用展开查看,但底层仍然是规则组结构

典型结构:

json
{
  "friendly": "王者荣耀",
  "packages": ["com.tencent.tmgp.sgame"],
  "scope": "persistent",
  "main": { "match": ["UnityMain"], "limit": 1, "cpus": "6-7", "fifo": 1 },
  "worker": { "match": ["*worker*"], "limit": 2, "cpus": "4-5", "rr": 5 },
  "extra:[binder:*]": { "limit": 8, "cpus": "0-5" },
  "other": { "cpus": "1-4" }
}

如果你维护的是付费版,还可以按需再叠:

  1. features
  2. session
  3. default_action

9.5 friendly

用途:

  1. 给规则取可读名
  2. 便于日志、APK 编辑器和人工维护识别

9.6 packages

当前线程专项加载器读取的是:

  1. packages

注意:

  1. 线程专项当前不是像 FAS 那样同时兼容 package + packages
  2. 线程专项这里应优先写 packages

运行时匹配细节:

  1. 读取时会统一转小写
  2. 支持显式包名
  3. 支持带 *? 的通配
  4. 非通配情况下,当前实现也接受“完整相等或包含命中”

建议:

  1. 游戏专项尽量写完整包名
  2. persistent 规则尽量写显式系统进程包名
  3. 非必要不要用太宽的包含式关键词

9.7 scope

当前运行时理解如下:

写法含义
不写前台作用域
persistent常驻进程作用域
global当前也会按 persistent 对待
system当前也会按 persistent 对待

所以当前代码实际口径是:

  1. persistent
  2. global
  3. system

这三种写法都会进常驻路径。

9.8 features

这层要按版本理解:

  1. 免费版当前主线不需要在线程专项 apps[] 里依赖这层
  2. 付费版才更常见应用级 features 覆盖

当前付费侧支持字段:

  1. dynamic_tuning
  2. fas
  3. custom_thread
  4. dynamic_thread_scheduler

优先级:

  1. 模式默认值
  2. 分类级 features
  3. 应用级 features

逐字段解释:

字段作用典型效果
dynamic_tuning控制这个应用是否允许动态调频主链false 时,此应用会禁止动态调频,即使模式默认开启
fas控制这个应用是否允许 FASfalse 时,此应用不会进入 FAS 逻辑
custom_thread控制这个应用是否允许静态自定义线程规则false 时,不吃 main / gfx / render / worker / other / extra:[pattern] / default_action 这条静态规则链
dynamic_thread_scheduler控制这个应用是否允许动态线程运行时false 时,不做线程学习、守护和动态重打

组合示例:

  1. dynamic_tuning=false, fas=true, custom_thread=true, dynamic_thread_scheduler=true
    • 允许 FAS
    • 允许静态自定义线程规则
    • 允许动态线程运行时
    • 禁止动态调频
  2. dynamic_tuning=true, fas=false
    • 允许动态调频
    • 禁止 FAS

9.9 session

这层同样主要按付费版主线理解。

当前支持字段:

  1. learning_duration_ms
  2. guard_interval_ms

并且当前加载器带下限校验:

  1. learning_duration_ms 必须 >= 10000
  2. guard_interval_ms 必须 >= 100

优先级:

  1. 代码默认值
  2. scheduler.json 兜底
  3. 分类级 session
  4. 应用级 session

逐字段解释:

字段作用说明
learning_duration_ms学习期时长运行时会在这段时间内观察线程特征,再锁定更稳定的分配
guard_interval_ms守护重打间隔锁定后多久允许重新检查并补打一次规则

理解要点:

  1. learning_duration_ms 越长,越保守
  2. guard_interval_ms 越短,越容易频繁重打

9.10 default_action

这是真实执行的兜底动作,不是注释。

当前紧凑结构里,other 是更常见的主兜底角色;如果付费侧还写了应用级 default_action,它会在角色规则没命中时作为额外兜底入口。

常见字段:

  1. clusters
  2. cpus
  3. mask_hex
  4. nice
  5. scheduler

逐字段解释:

  1. clusters
    • 让兜底线程优先落到哪些簇
  2. cpus
    • 让兜底线程明确落到哪些 CPU
  3. mask_hex
    • 用十六进制掩码表达 CPU 集
  4. nice
    • 兜底线程的常规优先级偏移
  5. scheduler
    • 兜底线程的实时调度策略

理解要点:

  1. default_action 是“角色规则没命中时的最后动作”
  2. 它真的会执行,不是注释字段

9.11 main / gfx / render / worker / other

当前固定角色就是这五类:

  1. main
  2. gfx
  3. render
  4. worker
  5. other

可以这样理解:

  1. main
    • 主线程入口
  2. gfx
    • 图形和渲染前置线程
  3. render
    • Render 或更接近提交链的线程
  4. worker
    • 高负载工作线程
  5. other
    • 当前紧凑结构里的主要兜底角色

9.12 extra:[pattern]

这就是当前主线里对旧 custom 思路的替代写法。

理解方式:

  1. key 本身带线程模式,例如 extra:[binder:*]
  2. value 仍然是和普通角色相同的角色对象
  3. 适合补抓某个特殊线程名,而不是再套一层 custom -> selectors -> action

9.13 match

当前角色对象和 extra:[pattern] 里最常见的字段是:

  1. match
  2. limit
  3. cpus
  4. clusters
  5. nice
  6. rr
  7. fifo

其中 match 用来写线程名模式列表,例如:

  1. 普通包含:"UnityMain"
  2. 通配匹配:"UnityGfx*""*worker*"
  3. 精确匹配:"=renderengine"
  4. 排名后缀:"RenderThread@1""*worker*@1-2"

理解要点:

  1. match 直接贴线程名模式,不再拆成旧 selectors.patterns
  2. @1@1-2 这类后缀用于同类线程里抓第几个

9.14 limit

limit 表示这个角色最多抓几个线程。

最常见理解:

  1. main 通常保持 1
  2. workerextra:[pattern] 更常需要显式限额
  3. limit 和宽通配一起使用时要特别谨慎

9.15 cpus / clusters

这两类字段都是在指定线程落点:

  1. cpus
    • 直接写 CPU 编号范围,例如 6-74-5
  2. clusters
    • 按簇写目标,例如 littlemidbigprime

建议:

  1. 优先用 clusters 表达意图
  2. 只有非常明确要写死编号时再用 cpus
  3. 同一条规则不要同时写得过于冲突

9.16 nice / rr / fifo

这三项决定优先级和 RT 方式:

  1. nice
    • 常规优先级偏移
  2. rr
    • SCHED_RR 优先级,值就是 RT 优先级
  3. fifo
    • SCHED_FIFO 优先级,值就是 RT 优先级

建议:

  1. rrfifo 更适合作为默认 RT 试探
  2. fifo 只给极少数关键线程
  3. 不要把大量 worker 线程推到高优先级 RT

9.17 main 的真实语义

main 的意思不是“所有同名线程全部吃进主线程规则”。

当前真实语义是:

  1. main 只占主规则名额
  2. 同线程名出现多条时,不会把所有同名线程都算成 main
  3. 剩余线程还可以继续流向 extra:[pattern]other

9.18 线程专项最常见的坑

下面这些最容易把规则写坏:

  1. 把宽泛的 thread-* 和真正主线程关键字塞到同一个角色里
  2. 上一条还叠 limit: 1
  3. 误以为旧 threads / custom / selectors / action / safety 仍是当前主线
  4. 给大量 workerfifo
  5. 对 persistent 常驻规则使用过宽通配

10. config/categories.json

10.1 这类配置负责什么

场景分类负责:

  1. 识别当前前台属于哪一类
  2. 给该类提供默认功能开关
  3. 给该类提供默认会话参数
  4. 给该类提供默认线程动作兜底

10.2 顶层结构

当前是数组,每一项是一条分类规则。

json
[
  {
    "friendly": "王者荣耀",
    "packages": ["com.tencent.tmgp.sgame"],
    "game": true,
    "features": {},
    "session": {},
    "default_action": {},
    "category": "HonorOfKings"
  }
]

10.3 字段说明

字段含义
friendly可读名
category类目标识,供代码和日志使用
game是否属于游戏家族
packages包名匹配列表
activitiesActivity 匹配列表
processes进程名匹配列表,支持 :tool:peak 等后缀
features分类级功能开关,付费版更常见
session分类级会话参数,付费版更常见
default_action分类级线程动作兜底,两版都常见

逐字段解释:

  1. friendly
    • 这条分类规则的人类可读名
  2. category
    • 代码里的类目标识
    • 不是纯显示字段
  3. game
    • 是否把这类前台视为游戏家族
  4. packages
    • 包名命中源
  5. activities
    • Activity 命中源
  6. processes
    • 进程命中源,适合 :tool:peak:appbrand
  7. features
    • 这类前台的默认功能开关,付费版更常见
  8. session
    • 这类前台的默认学习期/守护期,付费版更常见
  9. default_action
    • 这类前台在线程专项没命中时的兜底动作

特别说明:

  1. category
    • 不只是标签
    • 还是场景策略里的实际分流键
  2. game
    • 用来把该条规则标记进游戏家族
    • 更偏运行时辅助判断,不是单纯展示字段
  3. 免费版当前最常见主线是 friendly / category / packages / activities / processes / default_action
  4. 付费版才更常用分类级 features / session 覆盖

10.4 当前匹配优先级

当前分类匹配顺序是:

  1. 先看 processes
  2. 再看 activities
  3. 最后看 packages

这点很重要,因为:

  1. 同一个包可能同时承载普通页面、扫码页面、小程序页面
  2. 仅靠包名不够时,必须靠进程名或 Activity 把它们拆开

10.5 当前已实际使用的类目

当前代码和配置里已实际用到的类目包括:

  1. HonorOfKings
  2. GenshinImpact
  3. Game
  4. Launcher
  5. Scanner
  6. Camera
  7. MiniProgram
  8. ScrollOpt
  9. WhiteList

这些类目当前可以这样理解:

  1. HonorOfKings
    • 王者荣耀专项类目
  2. GenshinImpact
    • 原神专项类目
  3. Game
    • 普通游戏类目
  4. Launcher
    • 桌面、启动器、回桌面快速操作类
  5. Scanner
    • 扫码识别类场景
  6. Camera
    • 相机与拍摄类场景
  7. MiniProgram
    • 小程序与轻应用子进程场景
  8. ScrollOpt
    • 偏滑动流畅性优化类
  9. WhiteList
    • 白名单保护类,不希望被误干预

10.6 典型使用场景

最典型的几种:

  1. com.tencent.mobileqq:tool 识别扫一扫
  2. com.tencent.mobileqq:peak 识别相机相关页面
  3. com.tencent.mm:appbrandcom.tencent.mm:toolsmp 识别小程序
  4. 桌面通过 Launcher 包名和小程序启动 Activity 识别

10.7 分类级 features

这层主要按付费版主线理解。

当前支持字段与应用级一致:

  1. dynamic_tuning
  2. fas
  3. custom_thread
  4. dynamic_thread_scheduler

优先级:

  1. 模式默认值
  2. 分类级 features
  3. 应用级 features

逐字段解释与线程专项 features 一致:

  1. dynamic_tuning
    • 控制这类前台是否默认允许动态调频
  2. fas
    • 控制这类前台是否默认允许 FAS
  3. custom_thread
    • 控制这类前台是否默认允许静态自定义线程规则
  4. dynamic_thread_scheduler
    • 控制这类前台是否默认允许动态线程运行时

10.8 分类级 session

这层也主要按付费版主线理解。

支持字段:

  1. learning_duration_ms
  2. guard_interval_ms

这层主要用于给整类应用提供默认学习期和守护期,再由应用级 session 覆盖。

逐字段解释:

  1. learning_duration_ms
    • 这类前台默认学习期
  2. guard_interval_ms
    • 这类前台默认守护重打间隔

10.9 分类级 default_action

作用是:

  1. 当前前台没命中应用级线程专项时
  2. 仍然能给该类前台提供一个兜底线程动作

当前这层已经实际接入,不是摆设。

它的字段写法和线程专项 default_action 一样,常见也是:

  1. clusters
  2. cpus
  3. mask_hex
  4. nice
  5. scheduler

11. bin/cpu/fas.json

11.1 这类配置负责什么

fas.json 负责:

  1. FAS 的默认 FPS 档位参数
  2. 特定应用支持哪些 target_fps
  3. 每个档位的 margin_fps
  4. 每个档位的 kp
  5. 自动档位识别时的兜底档

它不负责:

  1. 线程绑核
  2. RT
  3. cpuset
  4. 模式总频率上限

11.2 顶层结构

json
{
  "defaults": {},
  "apps": [],
  "projects": [],
  "_murong_remote": {}
}

当前运行时真正会读取的主字段是:

  1. defaults
  2. apps

当前 projects_murong_remote 更像 APK 同步或远端元数据,不是运行时主控制入口。

逐字段解释:

  1. defaults
    • FAS 的全局默认参数
  2. apps
    • 应用级 FAS 规则主体
  3. projects
    • APK 同步或远端项目描述信息
    • 当前主运行时不依赖它来决定 FAS 档位
  4. _murong_remote
    • 远端同步元数据
    • 当前主运行时不把它当控制字段

11.3 defaults

当前常见字段:

字段含义
default_target_fps默认目标帧率
base_margin_fps基础帧率容差
kp默认比例控制系数

逐字段解释:

  1. default_target_fps
    • 某个应用没有显式定义 FPS 档位时的默认目标帧率
  2. base_margin_fps
    • 默认允许的帧率误差
  3. kp
    • 默认比例控制强度
    • 越大通常越激进

11.4 apps

当前 apps 同时兼容两种包名写法:

  1. package
  2. packages

其中:

  1. package 可以是单包名
  2. package 也可以是逗号分隔字符串
  3. packages 可以是数组
  4. 运行时会统一收集并去重

逐字段解释:

  1. friendly
    • 应用可读名
  2. package
    • 单包名或逗号分隔包名
  3. packages
    • 数组写法的多包名
  4. fps_profiles
    • 这个应用支持的 FPS 档位细分参数
  5. kp
    • 应用级默认 kp
  6. fallback
    • 应用级默认兜底标记

理解要点:

  1. friendly 只提升可读性,不参与实际包名匹配
  2. packagepackages 最终都会被统一成包名集合
  3. kpfallback 如果同时在应用级和档位级都写,维护时更建议以档位级为准去理解

典型结构:

json
{
  "friendly": "王者荣耀",
  "package": "com.tencent.tmgp.sgame",
  "fps_profiles": []
}

或者:

json
{
  "friendly": "永劫无间手游",
  "packages": [
    "com.netease.l22",
    "com.netease.lztys"
  ],
  "fps_profiles": []
}

11.5 fps_profiles

典型结构:

json
{
  "target_fps": 144,
  "margin_fps": 2.5,
  "kp": 2.8,
  "fallback": true,
  "sweet_spot_threshold": 600,
  "avalanche_threshold": 800
}

字段说明:

字段含义
target_fps目标帧率
margin_fps对应档位帧率容差
kp对应档位比例控制系数
fallback自动档位识别时的兜底档
sweet_spot_threshold【新增】甜点驻留阈值(默认600)
avalanche_threshold【新增】防雪崩突破阈值(默认800)

重要参数进阶理解:

  1. sweet_spot_thresholdavalanche_threshold

    • 这两个参数决定了 FAS 对掉帧的容忍度与激进程度
    • 0 ~ sweet_spot_threshold:常规稳态。频率会被限制在 min_freqoptimum_freq (甜点频率) 之间平滑游走,极致省电
    • sweet_spot_threshold ~ avalanche_threshold:长尾预警。主力核开始向 max_freq 冲刺,但基础小核(如0-5簇)依然会“躺平”在甜点频率不凑热闹。
    • > avalanche_threshold:雪崩救场。所有核心(包括基础小核)解除封印,强制突破甜点频率限制,并配合底线保护机制瞬间拉满,彻底杜绝大掉帧
    • 调校建议:电量焦虑党可调高阈值(如 800/950),电竞死忠党可调低阈值(如 400/600)。
  2. FAS 与动态调频(眼睛)的联动机制

    • 纯 FAS 就像“蒙着眼睛踩油门”,只看帧率不看负载。为了解决这一痛点,现在 FAS 会将动态调频作为**“动态天花板 (Ceiling)”**。
    • 当控制压力 < avalanche_threshold 时,如果 FAS 算出的目标频率高于当前 CPU 物理负载允许的天花板,将强制被天花板压制。这意味着如果游戏发生“自旋空转”假死,频率绝不会乱飙,实现 1+1>2 的省电效果。
    • 只有当压力突破 avalanche_threshold 时,FAS 才会一脚踢开天花板,强制满血救场。

逐字段解释:

  1. target_fps
    • 这一档要维持的目标 FPS
  2. margin_fps
    • 这一档允许的误差
  3. kp
    • 这一档的比例控制强度
  4. fallback
    • 当自动识别不稳定或未命中时,回退到这一档

建议:

  1. 同一游戏尽量把常见 FPS 档写全
  2. 一条应用只保留一个 fallback: true
  3. 高刷档通常要给更宽的 margin_fps

11.6 当前 FAS 实现口径

理解当前 FAS 时,记住这几条:

  1. 当前主链是 queueBuffer-only
  2. 不再走旧 GPU 主路径
  3. 目标帧率已经不是“掉到多少就认多少”
  4. 现在已有高档优先、降档滞回、切档清窗口
  5. fas.json 只是给 FAS 提供目标档位和参数,不取代线程专项和模式调频

12. scheduler.json

代码当前支持:

  1. bin/cpu/<SoC>.scheduler.json

建议它只承载会话参数兜底,不要继续往里塞新总线。

推荐结构:

json
{
  "default": {
    "learning_duration_ms": 90000,
    "guard_interval_ms": 700
  },
  "performance": {
    "learning_duration_ms": 120000,
    "guard_interval_ms": 1000
  }
}

作用:

  1. 给所有模式一个总默认会话参数
  2. 再给具体模式做覆盖
  3. 最后仍然会被分类和应用级 session 覆盖

逐字段解释:

  1. default
    • 全模式通用会话默认值
  2. powersave / balance / performance / fast
    • 对应模式下的会话覆盖
  3. learning_duration_ms
    • 当前模式的默认学习期
  4. guard_interval_ms
    • 当前模式的默认守护间隔

理解要点:

  1. scheduler.json 当前只建议做“会话默认值层”
  2. 不建议再往里面继续塞线程动作、FAS 参数或分类信息

13. 前台识别、场景识别、persistent 与 eBPF

13.1 前台识别当前主链

当前前台识别主链是:

  1. dumpsys activity top-resumed-activity
  2. dumpsys window windows
  3. dumpsys activity activities
  4. /proc + oom_score_adj + cgroup 选前台 PID
  5. cpuset top-app 只作为辅助

13.2 为什么不是只看包名

因为当前系统不只是识别“前台是哪个应用”,还要进一步识别:

  1. 是普通前台
  2. 还是扫码页
  3. 还是相机页
  4. 还是小程序子进程
  5. 还是桌面启动器相关场景

13.3 persistent 常驻规则

当前 persistent 路径已经是实装能力,不是计划项。

主要特点:

  1. 适合长期存活的系统进程
  2. 不依赖前台切换才生效
  3. 会额外扫描指定包名对应进程
  4. 配合 eBPF 事件做增量刷新

13.4 eBPF 在当前系统里的职责

当前 eBPF 不是单独的“全能调度器”,它主要做两件事:

  1. 前台应用侧
    • 聚合前台 TGID 的线程 runtime
    • 帮助跟踪渲染热点线程
    • 采样 queueBuffer
  2. 常驻规则侧
    • 监听 fork / exec / sched_setaffinity
    • 命中后快速重扫 persistent 规则进程

也就是说:

  1. eBPF 负责更快知道“线程变了”
  2. 真正决定“怎么打动作”的还是 JSON 和用户态运行时

14. 优先级关系

14.1 功能开关优先级

  1. 模式默认值
  2. 分类级 features
  3. 应用级 features

14.2 会话参数优先级

  1. 代码默认值
  2. scheduler.json
  3. 分类级 session
  4. 应用级 session

14.3 线程动作优先级

  1. 应用级角色规则 main / gfx / render / worker
  2. 应用级 extra:[pattern]
  3. 应用级 other
  4. 应用级 default_action(付费版可选)
  5. 分类级 default_action
  6. 代码默认模板

14.4 常驻规则刷新理解

当前可以这样理解:

  1. 常规 /proc 扫描负责兜底
  2. eBPF 事件负责加速重扫
  3. 官调冲突时,前台线程规则和 persistent 路径会一起暂停重打

15. APK 侧界面与模块侧文件怎么对应

为了防止“软件端看着能改,但不知道实际改到哪个文件”,当前可以这样对应:

APK 侧入口模块侧文件
应用策略config/mode.txt
CPU 调度配置bin/cpu/<SoC>.json
线程分配bin/cpu/<拓扑>.json
场景分类规则config/categories.json
FASbin/cpu/fas.json(付费版)

理解要点:

  1. APK 负责“让人能改”
  2. 模块负责“让机器真跑”
  3. 只改 APK 页面文案,不改模块字段,最终不会生效

15.1 软件里“功能总开关”到底关在哪里

很多人会把首页“功能开关”里的这几项和对应 JSON 文件混在一起:

  1. 动态调频
  2. FAS
  3. BPF自定义线程
  4. 场景分类

当前 APK 里,这几项总开关并不是分别直接写到 fas.jsonthreads.jsoncategories.json 里,而是统一写回:

  1. bin/cpu/<SoC>.json
  2. 当前模式对象下的 features

也就是实际写回位置是:

json
{
  "powersave": {
    "features": {}
  },
  "balance": {
    "features": {}
  },
  "performance": {
    "features": {}
  },
  "fast": {
    "features": {}
  }
}

比如当前模式如果是 balance,那首页总开关改的是:

  1. balance.features.scheduler_master
  2. balance.features.dynamic_tuning
  3. balance.features.fas
  4. 免费版主线是 balance.features.bpf_thread
  5. 付费版静态线程规则主线是 balance.features.custom_thread
  6. 付费版动态线程运行时主线是 balance.features.dynamic_thread_scheduler
  7. balance.features.scene_category
  8. balance.features.base_profile

要特别注意一个实现细节:

  1. 动态调频 总开关除了写 features.dynamic_tuning
  2. 还会同步写当前模式下的 dynamic_tuning.enabled

也就是说,首页总开关当前真实对应关系如下:

APK 开关名实际写回字段作用理解
总控<当前模式>.features.scheduler_master总闸门;关掉后调度主控能力整体停用
动态调频<当前模式>.features.dynamic_tuning + <当前模式>.dynamic_tuning.enabled控制当前模式是否允许动态调频主链运行
FAS<当前模式>.features.fas控制当前模式是否允许 FAS 主链运行
BPF自定义线程免费版看 <当前模式>.features.bpf_thread控制免费版线程规则总开关
自定义线程付费版看 <当前模式>.features.custom_thread控制付费版静态线程规则是否生效
动态线程付费版看 <当前模式>.features.dynamic_thread_scheduler控制付费版线程学习、守护和动态重打
场景分类<当前模式>.features.scene_category控制当前模式是否允许吃 categories.json 的分类覆写
模式基础<当前模式>.features.base_profile控制当前模式基础档是否参与写入

要这样理解这几个“总开关”:

  1. 动态调频 总开关不是去改 fas.json
  2. FAS 总开关不是去删 fas.json 里的应用配置
  3. BPF自定义线程 总开关不是去改线程规则文件
  4. 场景分类 总开关不是去改 categories.json 内容本身

它们本质上都是:

  1. 当前模式级别的运行时准入开关
  2. 决定“允不允许这条能力在当前模式下参与调度”

所以如果你的目标不是“当前模式下一键关总开关”,而是“改单独配置内容”,对应关系应该是:

  1. 想改单独动态调频参数:
    • bin/cpu/<SoC>.json
    • 主要看 <mode>.dynamic_tuning
  2. 想改单独 FAS 档位、应用列表:
    • bin/cpu/fas.json
  3. 想改单独 BPF自定义线程规则、绑核规则、RT 规则:
    • bin/cpu/<拓扑>.json
  4. 想改单独场景分类命中条件、分类级 features/session/default_action:
    • config/categories.json

再说得更直接一点:

  1. 首页“功能开关”改的是当前模式的总阀门
  2. 配置页里的 cpu.json / fas.json / 线程分配 / 场景分类规则 改的是各条能力自己的配置内容

16. 日志、辅助文件、运行时产物

16.1 当前主要日志路径

路径说明
/data/adb/modules/muronggameopt/config/log.txt主日志
/data/adb/modules/muronggameopt/config/debug_log.txt动态调频辅助日志
/data/adb/modules/muronggameopt/config/fas_log.txtFAS 辅助日志
/data/adb/modules/muronggameopt/config/affinity_log.txt线程绑核与优先级辅助日志
/data/adb/modules/muronggameopt/config/stats_log.txt运行时统计日志
/data/adb/modules/muronggameopt/config/log_backups/日志备份目录

16.2 哪些日志默认会比较安静

辅助日志并不是一直全量写。

当前实现里,下面这些更依赖 debug_log

  1. debug_log.txt
  2. fas_log.txt
  3. affinity_log.txt
  4. stats_log.txt

所以如果你在调动态调频、FAS、绑核,却发现辅助日志很少,先检查当前模式或 cluster 的 dynamic_tuning.debug_log 是否打开。

16.3 运行时还会写什么

当前运行时还会写:

  1. trace marker
    • /sys/kernel/tracing/trace_marker
    • /sys/kernel/debug/tracing/trace_marker
  2. /data/powercfg.json
  3. /data/powercfg.sh

也就是说,当前系统不仅有文本日志,也有面向 systrace/atrace 的即时打点能力。

17. 最常见的改法

17.1 新增一个游戏专项

建议顺序:

  1. 先在 bin/cpu/<拓扑>.jsonapps 里加规则
  2. friendly
  3. packages
  4. 先写 main / gfx / render / worker
  5. 再按需要补 extra:[pattern]other
  6. 付费版再按需要补 features / session / default_action
  7. 如果这游戏要单独 FAS 档位,再去 bin/cpu/fas.jsonfps_profiles
  8. 如果它需要独立场景默认策略,再去 config/categories.json 补一条类目或专用条目

17.2 新增一个前台类目

建议顺序:

  1. config/categories.json 新增一条规则
  2. 先写 friendly
  3. 必写 category
  4. 按需要写 packages / activities / processes
  5. 先写 default_action;付费版再按需要写 features / session

17.3 给某个应用临时做强绑核

优先改:

  1. <角色>.clusters
  2. 必要时再用 <角色>.cpus

不建议上来就全局改 cpuset

17.4 给某类应用默认走中高簇

优先改:

  1. categories.json 里的 default_action.clusters

例如:

json
{
  "default_action": {
    "clusters": ["middle", "big"]
  }
}

17.5 给某个常驻系统进程做专项

建议:

  1. 在线程专项里新增一条 apps 规则
  2. packages 写显式进程包名
  3. scopepersistent
  4. 谨慎设置关键角色、other 和 RT 参数

18. 当前边界与维护建议

18.1 当前明确不做的事

  1. 不引入热反馈调度
  2. 不恢复 SurfaceFlinger/gfxinfo 旧回退链
  3. 不为了极限帧率把所有模式长期锁高功耗
  4. 不把一切场景逻辑全塞回线程角色层

18.2 当前维护建议

  1. 改字段前先确认 APK 侧是否也要同步
  2. 改运行时逻辑前先以磁盘真实状态为准,不要按旧设计稿脑补
  3. 想改模式和频率,优先看 bin/cpu/<SoC>.json
  4. 想改线程专项,优先看 bin/cpu/<拓扑>.json
  5. 想改分类,优先看 config/categories.json
  6. 想改 FAS 档位,优先看 bin/cpu/fas.json
  7. 想让改动即时生效,先确认这类文件是否被 inotify 监听
  8. 线程专项现在已支持热生效,但大改后仍建议顺手验证一次完整冷启动路径

19. 一句话总记忆

如果你只想记住一段最短的话:

  1. 模式和频率改 bin/cpu/<SoC>.json
  2. 线程专项改 bin/cpu/<拓扑>.json
  3. 场景分类改 config/categories.json
  4. FAS 档位改 bin/cpu/fas.json
  5. 当前模式状态和应用模式覆盖改 config/mode.txt