Cgroup 抽象层

Android 10 及更高版本将对照组 (cgroup) 抽象层和任务配置文件搭配使用,让开发者能够使用它们来描述应用于某个线程或进程的一组(或多组)限制。然后,系统按照任务配置文件的规定操作来选择一个或多个适当的 cgroup;通过这种方式,系统可以应用各种限制,并对底层 cgroup 功能集进行更改,而不会影响较高的软件层。

cgroup 简介

cgroup 提供一种机制,可将任务集(包括进程、线程及其所有未来的子级)聚合并分区到具有专门行为的层级组中。Android 使用 cgroup 控制及考量 CPU 和内存等系统资源的使用和分配情况,并支持 Linux 内核 cgroup v1cgroup v2

Android 9 及更低版本

在 Android 9 及更低版本中,init.rc 初始化脚本包含一组可用的 cgroup 及其装载点和版本。虽然 cgroup 可以更改,但 Android 框架的设定是一组特定的 cgroup 存在于特定位置,并具有特定的版本和子组层次结构(基于脚本)。这就限制了选择要使用的下一个 cgroup 版本的能力,也限制了更改 cgroup 层次结构以使用新功能的能力。

Android 10 及更高版本

Android 10 及更高版本将 cgroup 与任务配置文件搭配使用:

  • cgroup 设置。开发者在其 cgroups.json 文件中描述 cgroup 设置,以便定义 cgroup 组及其装载位置和属性。所有 cgroup 均会在初始化过程的 early-init 阶段装载。
  • 任务配置文件。这些配置文件提供了一种抽象概念,将必需的功能与该功能的实现详情分离。Android 框架使用 SetTaskProfilesSetProcessProfiles API,按照 task_profiles.json 文件中的描述将任务配置文件应用于进程或线程(这些 API 是 Android 11 及更高版本所独有的)。

为了实现向后兼容,旧版函数 set_cpuset_policyset_sched_policyget_sched_policy 提供相同的 API 和功能,但其实现已修改为使用任务配置文件。对于新的用例,AOSP 建议使用新的任务配置文件 API,而不是旧版 set_sched_policy 函数。

cgroup 描述文件

cgroup 在 cgroups.json 文件(位于 <ANDROID_BUILD_TOP>/system/core/libprocessgroup/profiles/ 下)中描述。各个控制器在子部分中描述,且必须至少包含以下内容:

  • 名称,由 Controller 字段定义。
  • 装载路径,由 Path 字段定义。
  • ModeUID(用户 ID)和 GID(组 ID),用于描述该路径下文件的所有者模式和访问模式(均为可选字段)。
  • Optional 属性,将其设置为 true,让系统忽略内核不支持装载的 cgroup 控制器引起的装载错误。

示例 cgroups.json 文件

以下示例展示了 cgroup v1 (Cgroups) 控制器和 cgroup v2 (Cgroups2) 控制器的描述以及各自的路径。

{
  "Cgroups": [
    {
      "Controller": "cpu",
      "Path": "/dev/cpuctl",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "memory",
      "Path": "/dev/memcg",
      "Mode": "0700",
      "Optional": true
    }
  ],
 "Cgroups2": {
   "Path": "/sys/fs/cgroup",
   "Mode": "0755",
   "UID": "system",
   "GID": "system",
   "Controllers": [
     {
       "Controller": "freezer",
       "Path": ".",
       "Mode": "0755",
       "UID": "system",
       "GID": "system"
     }
   ]
 }
}

该示例文件包含两个部分:Cgroups(描述 cgroup v1 控制器)和 Cgroups2(描述 cgroup v2 控制器)。cgroup v2 层次结构中的所有控制器均在同一位置装载。因此,Cgroups2 部分有自己的 PathModeUIDGID 属性,用于描述层次结构的根的位置和属性。Cgroups2 下的 ControllersPath 属性是相对于该根路径而言的。在 Android 12 及更高版本中,您可以将已指定路径和模式的 cgroup 控制器定义为 "Optional"(方法是将其设置为 true)。

系统会在 init 进程的 early-init 阶段解析 cgroups.json 文件,并在指定位置装载 cgroup。如果以后需要获取 cgroup 装载位置,请使用 CgroupGetControllerPath API 函数。

任务配置文件

task_profiles.json 文件位于 <ANDROID_BUILD_TOP>/system/core/libprocessgroup/profiles/ 下。该文件用于描述要应用于进程或线程的一组特定操作。这组操作与一个配置文件名称相关联,后者在 SetTaskProfilesSetProcessProfiles 调用中用于调用配置文件操作。

示例 task_profiles.json 文件

{
  "Attributes": [
    {
      "Name": "MemSoftLimit",
      "Controller": "memory",
      "File": "memory.soft_limit_in_bytes"
    },
    {
      "Name": "MemSwappiness",
      "Controller": "memory",
      "File": "memory.swappiness"
    }
  ],
  "Profiles": [
    {
      "Name": "MaxPerformance",
      "Actions" : [
        {
          "Name" : "JoinCgroup",
          "Params" :
          {
            "Controller": "schedtune",
            "Path": "top-app"
          }
        }
      ]
    },
    {
      "Name": "TimerSlackHigh",
      "Actions" : [
        {
          "Name" : "SetTimerSlack",
          "Params" :
          {
            "Slack": "40000000"
          }
        }
      ]
    },
    {
      "Name": "LowMemoryUsage",
      "Actions" : [
        {
          "Name" : "SetAttribute",
          "Params" :
          {
            "Name" : "MemSoftLimit",
            "Value" : "16MB"
          }
        },
        {
          "Name" : "SetAttribute",
          "Params" :
          {
            "Name" : "MemSwappiness",
            "Value" : "150"

          }
        }
      ]
    }
  ]
  "AggregateProfiles": [
     {
       "Name": "SCHED_SP_DEFAULT",
       "Profiles": [ "TimerSlackHigh", "MaxPerformance" ]
     },
     {
       "Name": "SCHED_SP_BACKGROUND",
       "Profiles": [ "LowMemoryUsage" ]
     }
}

为特定 cgroup 文件分配名称,作为 Attributes 列表中的条目。每个条目都包含以下内容:

  • Name 字段用于指定 Attribute 的名称。
  • Controller 字段用于按名称引用 cgroups.json 文件中的 cgroup 控制器。
  • File 字段用于为相应控制器下的特定文件命名。

Attributes 是任务配置文件定义中的引用。在任务配置文件之外,仅当框架需要直接访问相应文件且无法使用任务配置文件抽象访问时,才应使用属性。在所有其他情况下,请使用任务配置文件;它们可以更好地分离所需行为及其实现详情。

Profiles 部分使用以下字段来包含任务配置文件定义:

  • Name 字段用于定义配置文件的名称。
  • Actions 部分列出了应用配置文件后执行的一组操作。每项操作都包含以下几项:

    • Name 字段用于指定操作。
    • Params 部分用于指定操作的一组参数。

表格中列出了受支持的操作:

操作 参数 说明
SetTimerSlack Slack 定时器可宽延时间(以纳秒为单位)
SetAttribute Name 引用 Attributes 部分中某一属性的名称
Value 要写入到由指定属性表示的文件的值
WriteFileFilePath文件的路径
Value要写入文件的值
JoinCgroup Controller cgroups.json 中的 cgroup 控制器的名称
Path cgroup 控制器层次结构中的子组路径

Android 12 及更高版本具有一个 AggregateProfiles 部分,其中包含聚合配置文件,每个聚合配置文件都是一个或多个配置文件的别名。聚合配置文件定义由以下内容组成:

  • Name 字段用于指定聚合配置文件的名称。
  • Profiles 字段列出了聚合配置文件中包含的配置文件的名称。

应用聚合配置文件时,也会自动应用所含的所有配置文件。只要没有递归(包括其自身的配置文件),聚合配置文件就可以包含单个配置文件或其他聚合配置文件。

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 层次结构中的任务。它接受一个或多个参数,表示 task_profiles.json 文件中指定的配置文件的名称。

按 API 级别任务配置文件

在 Android 12 及更高版本中,您可以根据 Android API 级别或从 vendor 分区,修改或替换默认 cgroups.jsontask_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 系统会按照以下顺序加载 cgrouptask_profile 文件:

  1. 默认 cgroups.jsontask_profiles.json 文件。
  2. 特定于 API 级别的文件(如果存在)。
  3. vendor 分区文件(如果存在)。

对现有 API 的更改

Android 10 及更高版本保留了 set_cpuset_policyset_sched_policyget_sched_policy 函数,而未对 API 进行任何更改。不过,Android 10 将这些函数移到了 libprocessgroup 中(其中现在包含所有与 cgroup 相关的功能)。

虽然 cutils/sched_policy.h 头文件仍然存在,但为了避免破坏现有代码,请确保新代码改为包含新的 processgroup/sched_policy.h 头文件。

使用上述任何函数的模块应该将对 libprocessgroup 库的依赖项添加到其 makefile 中。如果某个模块不使用任何其他 libcutils 功能,请从 makefile 中删除相应的 libcutils 库依赖项。

任务配置文件 API

下表定义了 processgroup/processgroup.h 中的私有 API:

类型 API 和定义
bool SetTaskProfiles(int tid, const std::vector& profiles)
profiles 中指定的任务配置文件应用于由线程 ID (tid) 使用其 tid 参数指定的线程。
bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles)
profiles 中指定的任务配置文件应用于由其用户 ID 和进程 ID 使用 uidpid 参数指定的进程
bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path)
返回 cgroup_name 指定的 cgroup 控制器是否存在;并且如果为 true,将 path 变量设置为该 cgroup 的根
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path)
返回 attr_name 指定的配置文件属性是否存在;并且如果为 true,将 path 变量设置为与该配置文件属性相关联的文件的路径。
bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path)
返回 attr_name 指定的配置文件属性是否存在;并且如果为 true,将 path 变量设置为与该配置文件属性以及由其线程 ID 使用 tid 参数指定的线程相关联的文件的路径。
bool UsePerAppMemcg()
返回系统是否配置为使用应用级别内存 cgroup。