一、理论介绍
1. 新版本Android不建议在 init.rc 初始化脚本来安装其他 cgroup,需要在 cgroups.json 文件中描述相应的组,并在 task_profiles.json 文件中定义新的任务配置文件以使用这些 cgroup 来执行操作。
2. cgroups.json cgroup 描述文件
cgroup 设置 - 开发者在其 cgroups.json 文件中描述 cgroup 设置,以便定义 cgroup 组及其装载位置和属性。所有 cgroup 均会在初始化过程的 early-init 阶段装载。
//system/core/libprocessgroup/profiles/cgroups.json { "Cgroups": [ ... { "Controller": "cpu", "Path": "/dev/cpuctl", "Mode": "0755", "UID": "system", "GID": "system" }, ... { "Controller": "memory", "Path": "/dev/memcg", "Mode": "0755", "UID": "system", "GID": "system", "Optional": true } ... ], "Cgroups2": { "Path": "/sys/fs/cgroup", "Mode": "0755", "UID": "system", "GID": "system", "Controllers": [ { "Controller": "freezer", "Path": ".", "Mode": "0755", "UID": "system", "GID": "system" } ] } }
注意,cgroup1 和 cgroup2 的挂载点是不一样的!"Cgroups" 描述 cgroup v1 控制器,"Cgroups2" 描述 cgroup v2 控制器。cgroup v2 层次结构中的所有控制器均在同一位置装载。因此,Cgroups2 部分有自己的 Path、Mode、UID 和 GID 属性,用于描述层次结构的根的位置和属性。Cgroups2 下的 Controllers 的 Path 属性是相对于该根路径而言的。在 Android 12 及更高版本中,您可以将已指定路径和模式的 cgroup 控制器定义为 "Optional"(方法是将其设置为 true)。
各个控制器在子部分中描述,且必须至少包含以下内容:
名称:由 Controller 字段定义。
装载路径:由 Path 字段定义。
Mode、UID、GID:用于描述该路径下文件的所有者模式和访问模式(均为可选字段)。
Optional 属性: 将其设置为 true,让系统忽略内核不支持装载的 cgroup 控制器引起的装载错误。
系统会在 init 进程的 early-init 阶段解析 cgroups.json 文件,并在指定位置装载 cgroup。如果以后需要获取 cgroup 装载位置,请使用 CgroupGetControllerPath API 函数。
3. task_profiles.json 任务配置文件
任务配置文件 - 这些配置文件提供了一种抽象概念,将必需的功能与该功能的实现详情分离。Android 框架使用 SetTaskProfiles 和 SetProcessProfiles API,按照 task_profiles.json 文件中的描述将任务配置文件应用于进程或线程(这些 API 是 Android 11 及更高版本所独有的)。它会打包到机器的 /etc/task_profiles.json。
//system/core/libprocessgroup/profiles/task_profiles.json { "Attributes": [ ... { "Name": "UClampMin", "Controller": "cpu", "File": "cpu.uclamp.min" }, { "Name": "UClampMax", "Controller": "cpu", "File": "cpu.uclamp.max" }, { "Name": "UClampLatencySensitive", "Controller": "cpu", "File": "cpu.uclamp.latency_sensitive" }, ... ], "Profiles": [ { "Name": "HighEnergySaving", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "background" //若Path指定为""空,就表示根组 } } ] }, ... { "Name": "NormalPerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "system" } } ] }, { "Name": "ServicePerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "system-background" } } ] }, { "Name": "HighPerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "foreground" } } ] }, { "Name": "MaxPerformance", //这个名字就表示join cpu top-app 分组。 "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "top-app" } } ] }, { "Name": "RealtimePerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "rt" } } ] }, { "Name": "CameraServicePerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "camera-daemon" } } ] }, ... { "Name": "NNApiHALPerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "nnapi-hal" } } ] }, ... "Name": "Dex2oatPerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "dex2oat" } } ] }, ... ], "AggregateProfiles": [ { "Name": "SCHED_SP_DEFAULT", "Profiles": [ "TimerSlackNormal" ] }, { "Name": "SCHED_SP_BACKGROUND", "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ] }, { "Name": "SCHED_SP_FOREGROUND", "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ] }, { "Name": "SCHED_SP_TOP_APP", "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ] }, { "Name": "SCHED_SP_SYSTEM", "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ] }, { "Name": "SCHED_SP_RT_APP", "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ] }, { "Name": "SCHED_SP_DISPLAY", "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ] }, { "Name": "CPUSET_SP_DEFAULT", "Profiles": [ "TimerSlackNormal" ] }, ... { "Name": "Dex2OatBootComplete", "Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ] } ] }
注:这里只列出了 cpu cgroup 相关的。
该文件用于描述要应用于进程或线程的一组特定操作。这组操作与一个配置文件名称相关联,后者在 SetTaskProfiles 和 SetProcessProfiles 调用中用于调用配置文件操作。
(1) Attributes 列表中每个条目都包含
Name - 指定 Attribute 的名称。 Controller - 按名称引用 cgroups.json 文件中的 cgroup 控制器。 File - 为相应控制器下的特定文件命名。
Attributes 是任务配置文件定义中的引用。在任务配置文件之外,仅当框架需要直接访问相应文件且无法使用任务配置文件抽象访问时,才应使用属性。在所有其他情况下,请使用任务配置文件;它们可以更好地分离所需行为及其实现详情。
(2) Profiles 列表每个条目包含
Name - 定义配置文件的名称。 Actions - 列出了应用配置文件后执行的一组操作,每项操作都包含以下几项: Name:用于指定操作 Params:用于指定操作的一组参数
下表列出了受支持的操作和参数的匹配关系:
操作 参数 说明 -------------------------------------------------------------- SetTimerSlack Slack 定时器可宽延时间(以纳秒为单位) -------------------------------------------------------------- SetAttribute Name 引用 Attributes 部分中某一属性的名称 Value 要写入到由指定属性表示的文件的值 -------------------------------------------------------------- WriteFile FilePath 文件的路径 Value 要写入文件的值 -------------------------------------------------------------- JoinCgroup Controller cgroups.json 中的 cgroup 控制器的名称 Path cgroup 控制器层次结构中的子组路径 --------------------------------------------------------------
注:SetTimerSlack 修改的是 /proc/<tid>/timerslack_ns 文件。
(3) AggregateProfiles 列表
Android 12 及更高版本具有一个 AggregateProfiles 部分,其中包含聚合配置文件,每个聚合配置文件都是一个或多个配置文件的别名。聚合配置文件定义由以下内容组成:
Name - 指定聚合配置文件的名称。
Profiles - 列出聚合配置文件中包含的配置文件的名称。
应用聚合配置文件时,也会自动应用所含的所有配置文件。只要没有递归(包括其自身的配置文件),聚合配置文件就可以包含单个配置文件或其他聚合配置文件。
4. task_profile init 语言命令
Android Init 语言中的 task_profiles 命令可用于 Android 12 及更高版本,促进为特定进程激活任务配置文件。它取代了用于在 cgroup 之间迁移进程的 writepid 命令(在 Android 12 中已弃用)。借助 task_profiles 命令,您可以灵活地更改底层实现,而不会影响上层。在以下示例中,这两个命令实际上会执行相同的操作:
writepid /dev/cpuctl/top-app/tasks //在 Android 12 中已弃用 - 用于将当前任务的 PID 写入 /dev/cpuctl/top-app/tasks 文件。 task_profiles MaxPerformance //将当前进程联接到 "cpu" 控制器 (cpuctl) 下的热门应用组中,这会将进程的 PID 写入 dev/cpuctl/top-app/tasks。
在 Android 12 及更高版本中,请务必使用 task_profiles 命令迁移 cgroup 层次结构中的任务。它接受一个或多个参数,参数yoga task_profiles.json 文件中指定的配置文件的名称表示。
5. 按 API 级别任务配置文件
在 Android 12 及更高版本中,您可以根据 Android API 级别或从 vendor 分区,修改或替换默认 cgroups.json 和 task_profiles.json 文件中的定义。
如需根据 API 级别替换定义,设备上必须存在以下文件:
/system/etc/task_profiles/cgroups_<API level>.json //此文件用于特定于 API 级别的 cgroup。 /system/etc/task_profiles/task_profiles_<API level>.json //此文件用于特定于 API 级别的配置文件。
如需从 vendor 分区中替换定义,设备上必须存在以下文件:
/vendor/etc/cgroups.json
/vendor/etc/task_profiles.json
如果这些文件中的某个属性或配置文件定义使用与默认文件中相同的名称,该文件(API 级别或供应商级别)定义将替换先前的定义。另请注意,供应商级别定义会替换 API 级别定义。如果新定义有新名称,则将使用新定义修改属性集或配置文件集。
Android 系统会按照以下顺序加载 cgroup 和 task_profile 文件:
(1) 默认 cgroups.json 和 task_profiles.json 文件。
(2) 特定于 API 级别的文件(如果存在)。
(3) vendor 分区文件(如果存在)。
6. 对现有 API 的更改
Android 10 及更高版本保留了 set_cpuset_policy、set_sched_policy 和 get_sched_policy 函数,而未对 API 进行任何更改。不过,Android 10 将这些函数移到了 libprocessgroup 中(其中现在包含所有与 cgroup 相关的功能)。
虽然 cutils/sched_policy.h 头文件仍然存在,但为了避免破坏现有代码,请确保新代码改为包含新的 processgroup/sched_policy.h 头文件。
使用上述任何函数的模块应该将对 libprocessgroup 库的依赖项添加到其 makefile 中。如果某个模块不使用任何其他 libcutils 功能,请从 makefile 中删除相应的 libcutils 库依赖项。
7. 任务配置文件 API
下表定义了 processgroup/processgroup.h 中的私有 API:
//将 profiles 中指定的任务配置文件应用于由线程 ID (tid) 使用其 tid 参数指定的线程。 bool SetTaskProfiles(int tid, const std::vector& profiles) //将 profiles 中指定的任务配置文件应用于由其用户 ID 和进程 ID 使用 uid 和 pid 参数指定的进程 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles) //返回值指示 cgroup_name 指定的 cgroup 控制器是否存在;如果为 true,则将 path 变量设为该 cgroup 的根 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path) //返回值指示 attr_name 指定的配置文件属性是否存在;如果为 true,则将 path 变量设为与该配置文件属性相关联的文件的路径。 bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) //返回值指示 attr_name 指定的配置文件属性是否存在;如果为 true,则将 path 变量设为与该配置文件属性相关联的文件的路径,以及由其线程 ID 使用 tid 参数指定的线程。 bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) //返回值指示系统是否配置为使用应用级别内存 cgroup。 bool UsePerAppMemcg()
二、使用举例
1. media.omx rc文件中配置
//frameworks/av/services/mediacodec/android.hardware.media.omx@1.0-service.rc service vendor.media.omx /vendor/bin/hw/android.hardware.media.omx@1.0-service class main user mediacodec group camera drmrpc mediadrm audio ioprio rt 4 task_profiles ProcessCapacityHigh //这个表示加入到cpuset的foreground分组。
在 task_profiles.json 文件中配置如下:
{ "Name": "ProcessCapacityHigh", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpuset", "Path": "foreground" } } ] },
查看服务主线程cpuset分组:
# ps -AT | grep omx mediacodec 2050 2050 1 54752 1788 binder_wait_for_work 0 S omx@1.0-service # cat /proc/2050/cgroup 5:freezer:/ 4:memory:/ 3:cpuset:/foreground //看起来是生效的! 2:cpu:/ 1:blkio:/ 0::/uid_1064/pid_2050
可以看到其下所有服务都在foreground分组:
# P=2050; for T in `ls /proc/$P/task`; do echo $T; cat /proc/$P/task/$T/comm; cat /proc/$P/task/$T/cgroup | grep cpuset; done; 2050 omx@1.0-service 3:cpuset:/foreground 2143 Binder:2050_1 3:cpuset:/foreground 2161 Binder:2050_2 3:cpuset:/foreground 2226 HwBinder:2050_1 3:cpuset:/foreground 3350 HwBinder:2050_2 3:cpuset:/foreground
三、实验
1. 修改 task_profiles.json,让切换为top-app分组的任务出现在 top-app/main_top_app 目录下
# adb pull /etc/task_profiles.json . //按下面改动 # adb push task_profiles.json /etc/ /dev/cpuctl/top-app # mkdir main_top_app /dev/cpuctl/top-app # chown system:system main_top_app /dev/cpuctl/top-app/main_top_app # chown system:system cgroup.procs /dev/cpuctl/top-app/main_top_app # chown system:system tasks /dev/cpuctl/top-app/main_top_app # stop /dev/cpuctl/top-app/main_top_app # start //测试: /dev/cpuctl/top-app/main_top_app # cat cgroup.procs 8908 14060
改动参考 "MaxPerformance" 添加 "MaxMainPerformance",并添加在 "AggregateProfiles" 属性下:
{ "Name": "MaxMainPerformance", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "cpu", "Path": "top-app/main_top_app" } } ] }, { "Name": "SCHED_SP_TOP_APP", "Profiles": [ "MaxPerformance", "MaxMainPerformance", "MaxIoPriority", "TimerSlackNormal" ] },
四、代码走读
1. profile 是如何被设置生效的
Process.java 的JNI android_util_Process.cpp 中通过 SetTaskProfiles() 函数来设置进程Cgroup配置。比如 SetTaskProfiles(tid, THREAD_GROUP_BACKGROUND, true);
/* 传参grp 为Process.java 中定义的 "THREAD_GROUP_*" */ void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint grp) { ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp); if (!verifyGroup(env, grp)) { return; } int res = SetTaskProfiles(tid, {get_sched_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1; if (res != NO_ERROR) { signalExceptionForGroupError(env, -res, tid); } } //system/core/libprocessgroup/sched_policy.cpp const char* get_sched_policy_profile_name(SchedPolicy policy) { /* * sched profile array for: * SP_DEFAULT(-1), SP_BACKGROUND(0), SP_FOREGROUND(1), * SP_SYSTEM(2), SP_AUDIO_APP(3), SP_AUDIO_SYS(4), * SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7) * index is policy + 1 * this need keep in sync with SchedPolicy enum */ static constexpr const char* kSchedProfiles[SP_CNT + 1] = { "SCHED_SP_DEFAULT", "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND", "SCHED_SP_SYSTEM", "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND", "SCHED_SP_TOP_APP", "SCHED_SP_RT_APP", "SCHED_SP_DEFAULT", }; if (policy < SP_DEFAULT || policy >= SP_CNT) { return nullptr; } return kSchedProfiles[policy + 1]; }
这个函数将上层传参"THREAD_GROUP_*"转换为 task_profiles.json 中 "AggregateProfiles" 条目下对应的cgroup profile的值。比如上层传参 THREAD_GROUP_BACKGROUND,就对应 SCHED_SP_BACKGROUND,对应的 HighEnergySaving(Join cpu background), LowIoPriority(Join blkio background), SetTimerSlack(Set TimerSlack=5000000) 三个都生效。
cpuset的 “SP_*” 与 “CPUSET_SP_*”的对应关系如下:
//system/core/libprocessgroup/sched_policy.cpp const char* get_cpuset_policy_profile_name(SchedPolicy policy) { /* * cpuset profile array for: * SP_DEFAULT(-1), SP_BACKGROUND(0), SP_FOREGROUND(1), * SP_SYSTEM(2), SP_AUDIO_APP(3), SP_AUDIO_SYS(4), * SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7) * index is policy + 1 * this need keep in sync with SchedPolicy enum */ static constexpr const char* kCpusetProfiles[SP_CNT + 1] = { "CPUSET_SP_DEFAULT", "CPUSET_SP_BACKGROUND", "CPUSET_SP_FOREGROUND", "CPUSET_SP_SYSTEM", "CPUSET_SP_FOREGROUND", "CPUSET_SP_FOREGROUND", "CPUSET_SP_TOP_APP", "CPUSET_SP_DEFAULT", "CPUSET_SP_RESTRICTED", }; if (policy < SP_DEFAULT || policy >= SP_CNT) { return nullptr; } return kCpusetProfiles[policy + 1]; }
具体实现如下:
//system/core/libprocessgroup/processgroup.cpp 如传参 profiles=SCHED_SP_BACKGROUND bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) { return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache); } //system/core/libprocessgroup/task_profiles.cpp bool TaskProfiles::SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) for (const auto& name : profiles) { TaskProfile* profile = GetProfile(name); if (profile != nullptr) { if (use_fd_cache) { profile->EnableResourceCaching(); } if (!profile->ExecuteForTask(tid)) { PLOG(WARNING) << "Failed to apply " << name << " task profile"; } } else { PLOG(WARNING) << "Failed to find " << name << "task profile"; } } return true; }
参考:
1. Cgroup 抽象层: https://source.android.com/docs/core/perf/cgroups?hl=zh-cn
Control Groups version 1:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/#control-groups-version-1
Control Group v2:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
Android Init Language:https://android.googlesource.com/platform/system/core/+/c5c532fc312c9e5a2f2b8fecbfc535af4ffcd245/init/README.md