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 均在初始化過程的早期初始化階段掛載。
  • 任務設定檔 - 這些提供了一種抽象,將所需的功能與其實現的細節分開。 Android 框架使用SetTaskProfilesSetProcessProfiles API 將task_profiles.json檔案中所述的任務設定檔套用到進程或執行緒。 (這些 API 是 Android 11 及更高版本所獨有的。)

為了提供向後相容性,舊函數set_cpuset_policyset_sched_policyget_sched_policy提供相同的 API 和功能,但它們的實作已修改為使用任務設定檔。對於新用例,AOSP 建議使用新的任務設定檔 API,而不是舊的set_sched_policy函數。

Cgroups描述文件

Cgroup 在位於<ANDROID_BUILD_TOP>/system/core/libprocessgroup/profiles/下的cgroups.json檔案中進行了描述。每個控制器都在一個小節中進行描述,並且必須至少具有以下各項:

  • 名稱,由Controller字段定義。
  • 安裝路徑,由Path字段定義。
  • ModeUID (使用者 ID)和GID (群組 ID)描述了該路徑下檔案的擁有者和存取模式(均為可選)。
  • 可選屬性,設定為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 控制器)。 cgroups v2 層次結構中的所有控制器都安裝在同一位置。因此, Cgroups2部分有自己的PathModeUIDGID屬性來描述層次結構根的位置和屬性。 Cgroups2控制器路徑屬性是相對於該根路徑的。在 Android 12 及更高版本中,您可以定義一個 cgroup 控制器,透過將其設為true ,將路徑和模式指定為"Optional"

在早期初始化階段, cgroups.json檔案作為 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 檔案作為屬性清單中的條目。每個條目包含以下內容:

  • 名稱欄位 - 指定屬性的名稱。
  • 控制器欄位 - 按名稱引用cgroups.json檔案中的 cgroup 控制器。
  • 文件欄位 - 命名此控制器下的特定文件。

屬性是任務設定檔定義中的引用。在任務設定檔之外,當框架需要直接存取這些檔案時才使用它們,並且無法使用任務設定檔來抽象存取權限。在所有其他情況下,使用任務設定檔;它們在所需行為及其實作細節之間提供了更好的解耦。

設定檔部分包含任務設定檔定義,其中包含以下內容:

  • 名稱欄位 - 定義設定檔名稱。
  • 操作部分 - 列出套用設定檔時執行的一組操作。每個動作都有以下內容:

    • 指定操作的名稱字段
    • Params部分指定操作的一組參數

下表列出了支援的操作。

行動範圍描述
SetTimerSlack Slack定時器裕度(ns)
SetAttribute Name引用“屬性”部分中的屬性的名稱
Value要寫入由命名屬性表示的檔案的值
WriteFile FilePath文件路徑
Value要寫入檔案的值
JoinCgroup Controller cgroups.json中的 cgroup 控制器的名稱
Path cgroup 控制器層次結構中的子群組路徑

Android 12 及更高版本具有AggregateProfiles部分,其中包含聚合配置文件,每個配置文件都是一組一個或多個配置文件的別名。聚合設定檔定義包含以下內容:

  • 名稱欄位 - 指定聚合設定檔的名稱。
  • 設定檔欄位 - 列出聚合設定檔中包含的設定檔的名稱。

套用聚合設定檔時,所有包含的設定檔也會自動套用。聚合設定檔可以包含單獨的設定檔或其他聚合配置文件,只要不存在遞歸(包含其自身的設定檔)。

task_profiles 初始化語言指令

Android 初始化語言中的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 ) 下的 top-app 群組,這會導致將進程的 PID 寫入dev/cpuctl/top-app/tasks

在 Android 12 及更高版本中,請務必使用task_profiles指令遷移 cgroup 層次結構中的任務。它接受一個或多個參數,代表在task_profiles.json檔案中指定的設定檔的名稱。

每個 API 層級的任務設定檔

在 Android 12 及更高版本中,您可以修改或覆寫預設cgroups.jsontask_profiles.json檔案中的定義,可以根據 Android API 層級進行更改,也可以從供應商分區進行更改。

若要覆寫基於 API 層級的定義,裝置上必須存在下列檔案:

  • pro/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中,它現在包含所有與 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) SetTaskProfiles(int tid, const std::vector & profiles)

profiles中指定的任務設定檔套用到由執行緒 ID (tid) 使用其tid參數指定的執行緒。

bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector & profiles) SetProcessProfiles(uid_t uid, pid_t pid, const std::vector & profiles)

profiles中指定的任務設定檔套用到使用uidpid參數的使用者和進程 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變數設定為與該設定檔屬性關聯的檔案的路徑,以及使用tid參數由其執行緒 ID 指定的執行緒。

bool UsePerAppMemcg()

返回系統是否配置為使用每個應用程式記憶體 cgroup。