Cgroup 抽象层

Android 10 及更高版本使用带有任务配置文件的控制组 (cgroup) 抽象层,开发者可以使用该抽象层来描述要应用于线程或进程的一组(或多组)限制。然后,系统会遵循任务配置文件的规定操作来选择一个或多个合适的 cgroup,通过这些 cgroup 来应用限制,并且可以更改底层的 cgroup 功能集,而不会影响更高的软件层。

关于 cgroup

Cgroup 提供了一种机制,用于将任务集(由进程、线程及其所有未来的子进程组成)聚合和划分为具有专门行为的分层组。Android 使用 cgroup 来控制和计算系统资源(例如 CPU 和内存使用情况和分配),并支持 Linux 内核 cgroups v1cgroups v2

Android 9 及更低版本

在 Android 9 及更低版本中,init.rc 初始化脚本包含可用 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 控制器的 path 和 mode 设置为 "Optional"(设置为 true)来定义该控制器。

cgroups.json 文件在初始化过程的 early-init 阶段被解析,并且 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 字段指定属性的名称。
  • 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_profiles init 语言命令

Android 12 及更高版本中提供了 Android Init Language 中的 task_profiles 命令,以方便特定进程的任务配置文件激活。它取代了用于在 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) 下的 top-app 组,这会导致将进程的 PID 写入 dev/cpuctl/top-app/tasks

始终使用 task_profiles 命令在 Android 12 及更高版本中迁移 cgroup 层次结构中的任务。它接受一个或多个参数,这些参数表示 task_profiles.json 文件中指定的配置文件的名称。

按 API 级别划分的任务配置文件

在 Android 12 及更高版本中,您可以根据 Android API 级别修改或替换默认 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/etc/cgroups.json
  • /vendor/etc/task_profiles.json

如果这些文件中的属性或配置文件定义使用的名称与默认文件中的名称相同,则文件(API 级别或供应商级别)定义将替换以前的定义。另请注意,供应商级别定义会替换 API 级别定义。如果新定义具有新名称,则属性或配置文件的集合将使用新定义进行修改。

Android 系统按以下顺序加载 cgrouptask_profile 文件

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

对现有 API 的更改

Android 10 及更高版本保留了函数 set_cpuset_policyset_sched_policyget_sched_policy,API 没有更改。但是,Android 10 将这些函数移动到 libprocessgroup 中,现在 libprocessgroup 包含所有 cgroup 相关的功能。

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

使用这些函数中的任何一个的模块都应在其 makefile 中添加对 libprocessgroup 库的依赖项。如果模块不使用任何其他 libcutils 功能,请从 makefile 中删除 libcutils 库依赖项。

任务配置文件 API

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

类型 API 和定义
bool SetTaskProfiles(int tid, const std::vector& profiles)
使用其 tid 参数,将 profiles 中指定的任务配置文件应用于由线程 ID (tid) 指定的线程。
bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles)
使用 uidpid 参数,将 profiles 中指定的任务配置文件应用于由其用户 ID 和进程 ID 指定的进程
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。