当前位置:   article > 正文

Android 中 cgroup抽象层详解_cgroup android

cgroup android

源码基于:Android R

0. 前言

在之前的博文《Android中app freezer原理》一文中,我们看到冻结器的enable、freeze、unfreeze 都是通过 cgroup 的机制进行处理。

本文将介绍下 Android 中 cgroup 的抽象层基本信息和使用方式。

1. cgroups 简介

cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。

cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:

  • cpu:主要限制进程的 cpu 使用率;
  • cpuaat:可以统计 cgroups 中的进程的 cpu 使用报告;
  • cpuset:可以为 cgroups 中的进程分配单独的 cpu 节点或内存节点;
  • memory:可以限制进程的 memory 使用量;
  • blkio:可以限制进程的块设备 io;
  • devices:可以控制进程能够访问某些设备;
  • freezer:可以挂起或恢复 cgroups 中的进程;
  • net_cls:可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制;
  • ns:可以使不同的 cgroups 下面的进程使用不同的 namespace;

Android 使用 cgroups 来控制和考量如 CPU、memory 等系统资源的使用和分配情况,并支持 Linux 内核 cgroup v1cgroup v2 版本。

2. Android cgroup 抽象层简介

Android Q(10) 或更高版本通过 task profiles 使用 cgroup 抽象层,task profiles 可以用来描述应用于某个线程或进程的一个set 或 sets 的限制。系统依照 task profiles 的规定选择一个或多个适当的 cgroups。通过这种限制,可以对底层的 cgroup 功能集进行更改,而不会影响较高的软件层。

Android P(9) 和更低版本,可用的 cgroups 以及他们的挂载点、版本都会在 init.rc 中设定。虽然这些信息可以更改,但 Android framework 的设定 (基于init.rc) 是一组特定的 cgroups 存在于特定的位置,并具有特定版本和子group 层级。这限制了选择下一个 cgroup 版本使用的能力,也限制了更改 cgroup 层级去使用新功能的能力。

在 Android Q(10) 或更高版本,将 cgroups 和 task profiles 搭配使用:

  • cgroup 配置:开发人员在 cgroups.json 文件中 cgroups 描述 cgroups 配置,以此定义 cgroups 组以及他们的挂载点和 attibutes。所有的 cgroups 将在 early-init 阶段被挂载上。
  • task profiles:这些配置文件提供了一种抽象概念,将必需的功能与该功能的实现详情分离。Android framework 使用 SetTaskProfiles 和 SetProcessProfiles 接口(这些API 是 Android R 或更高版本独有),按照 task_profiles.json 文件中描述将 task profiles 应用到一个进程或一个线程。

3. cgroups.json

文件路径:system/core/libprocessgroup/profiles/cgroups.json

  1. system/core/libprocessgroup/profiles/cgroups.json
  2. {
  3. "Cgroups": [
  4. {
  5. "Controller": "blkio",
  6. "Path": "/dev/blkio",
  7. "Mode": "0755",
  8. "UID": "system",
  9. "GID": "system"
  10. },
  11. {
  12. "Controller": "cpu",
  13. "Path": "/dev/cpuctl",
  14. "Mode": "0755",
  15. "UID": "system",
  16. "GID": "system"
  17. },
  18. {
  19. "Controller": "cpuacct",
  20. "Path": "/acct",
  21. "Mode": "0555"
  22. },
  23. {
  24. "Controller": "cpuset",
  25. "Path": "/dev/cpuset",
  26. "Mode": "0755",
  27. "UID": "system",
  28. "GID": "system"
  29. },
  30. {
  31. "Controller": "memory",
  32. "Path": "/dev/memcg",
  33. "Mode": "0700",
  34. "UID": "root",
  35. "GID": "system"
  36. },
  37. {
  38. "Controller": "schedtune",
  39. "Path": "/dev/stune",
  40. "Mode": "0755",
  41. "UID": "system",
  42. "GID": "system"
  43. }
  44. ],
  45. "Cgroups2": {
  46. "Path": "/sys/fs/cgroup",
  47. "Mode": "0755",
  48. "UID": "system",
  49. "GID": "system",
  50. "Controllers": [
  51. {
  52. "Controller": "freezer",
  53. "Path": "freezer",
  54. "Mode": "0755",
  55. "UID": "system",
  56. "GID": "system"
  57. }
  58. ]
  59. }
  60. }

cgroup v1 和 cgroup v2 描述的规则不一样。

对于 cgroup v1,必须拥有:

  • Controller:指定 cgroups 子系统名称,之后 task profiles 中设定需要依赖该名称;
  • Path:指定挂载的路径,有了该路径 task profiles 下才可以指定文件名;
  • Mode:用于指定Path 目录下文件的执行 mode;
  • UID:指定 user ID,指定Path 目录下文件的owner;
  • GID:指定 group ID,指定Path 目录下文件的owner;

对于 cgroup v2,基本同 v1,Controllers 中定义了子 cgroup,这些都挂载在同一个目录下。子 cgroup 中的 Path 是相对于根 Path。例如这里 freezer 的 Path 设定了 freezer,就是在 根 Path /sys/fs/cgroup/ 目录下创建一个目录 freezer。

另外 cgroups.json 文件可能不止一个:

  1. /system/core/libprocessgroup/profiles/cgroups.json //默认文件
  2. /system/core/libprocessgroup/profiles/cgroups_<API level>.json //API级别的文件,R版本没有,S版本很多
  3. /vendor/xxx/cgroups.json //vendor自定义文件

这三种文件加载顺序是:默认 -> API 级别 -> vendor,于是就存在一个覆盖的流程,只要后面的文件中定义的 Controller 值与前面的相同,就会覆盖前者的定义。

4. task profiles

文件路径:system/core/libprocessgroup/profiles/task_profiles.json

  1. {
  2. "Attributes": [
  3. {
  4. "Name": "MemSoftLimit",
  5. "Controller": "memory",
  6. "File": "memory.soft_limit_in_bytes"
  7. },
  8. {
  9. "Name": "MemSwappiness",
  10. "Controller": "memory",
  11. "File": "memory.swappiness"
  12. },
  13. {
  14. "Name": "FreezerState",
  15. "Controller": "freezer",
  16. "File": "cgroup.freeze"
  17. }
  18. ],
  19. "Profiles": [
  20. {
  21. "Name": "Frozen",
  22. "Actions": [
  23. {
  24. "Name": "JoinCgroup",
  25. "Params":
  26. {
  27. "Controller": "freezer",
  28. "Path": ""
  29. }
  30. }
  31. ]
  32. },
  33. {
  34. "Name": "TimerSlackHigh",
  35. "Actions": [
  36. {
  37. "Name": "SetTimerSlack",
  38. "Params":
  39. {
  40. "Slack": "40000000"
  41. }
  42. }
  43. ]
  44. },
  45. {
  46. "Name": "PerfBoost",
  47. "Actions": [
  48. {
  49. "Name": "SetClamps",
  50. "Params":
  51. {
  52. "Boost": "50%",
  53. "Clamp": "0"
  54. }
  55. }
  56. ]
  57. },
  58. {
  59. "Name": "HighMemoryUsage",
  60. "Actions": [
  61. {
  62. "Name": "SetAttribute",
  63. "Params":
  64. {
  65. "Name": "MemSoftLimit",
  66. "Value": "512MB"
  67. }
  68. },
  69. {
  70. "Name": "SetAttribute",
  71. "Params":
  72. {
  73. "Name": "MemSwappiness",
  74. "Value": "100"
  75. }
  76. }
  77. ]
  78. },
  79. {
  80. "Name": "FreezerEnabled",
  81. "Actions": [
  82. {
  83. "Name": "SetAttribute",
  84. "Params":
  85. {
  86. "Name": "FreezerState",
  87. "Value": "1"
  88. }
  89. }
  90. ]
  91. }
  92. ],
  93. "AggregateProfiles": [
  94. {
  95. "Name": "SCHED_SP_DEFAULT",
  96. "Profiles": [ "TimerSlackNormal" ]
  97. },
  98. {
  99. "Name": "SCHED_SP_BACKGROUND",
  100. "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
  101. },
  102. {
  103. "Name": "SCHED_SP_FOREGROUND",
  104. "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
  105. },
  106. {
  107. "Name": "SCHED_SP_TOP_APP",
  108. "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
  109. },
  110. ...
  111. ]
  112. }

整个文件配置由一个大括号包含,总体由三部分组成:

  • Attributes
  • Profiles
  • AggregateProfiles

另外,task_profiles.json 文件也不止一个:

  1. system/core/libprocessgroup/profiles/task_profiles.json //默认
  2. system/core/libprocessgroup/profiles/task_profiles_<API level>.json //API级别的文件,R版本没有,S有很多
  3. vendor/xxx/task_profiles.json //vendor配置

加载、覆盖的顺序同cgroups.json,按照 Name 来匹配,只要两个文件中定义同名项,后者就会覆盖前者的定义。 

4.1 Attributes 段

Attributes 中 cgroups 中特定的文件。

Attributes 是task profiles 文件定义中的引用。在 task profiles 之外,只有当 framework 请求直接访问这些文件,且无法使用 task profiles 抽象访问时。其他情况下,使用 task profiles,它可以更好地分离所需行为及其实现详情。

Attributes 中每一项包含:

  • Name: 该 Attribute 的名称,profiles 中引用时使用该Name 值;
  • Controller:引用 cgroups.json 文件中的一个 cgroup controller,引用 cgroup 的Controller 值;
  • File:在 cgroup Controller 所在的目录下的一个特殊文件;

如上面:

  1. "Attributes": [
  2. {
  3. "Name": "FreezerState",
  4. "Controller": "freezer",
  5. "File": "cgroup.freeze"
  6. }
  7. ],

用的是 Controller 为 freezer 的 cgroup,从上面第 3 节中得知,它采用 cgroups v2 的格式,cgroup Path 为 /sys/fs/cgroup/freezer/,这里定义的 attribute 指定的是该目录下 cgroup.freeze 文件。

 

在代码中,通过 ProfileAttribute 类来管理每个 Attribute:

  1. system/core/libprocessgroup/task_profiles.h
  2. class ProfileAttribute {
  3. public:
  4. ProfileAttribute(const CgroupController& controller, const std::string& file_name)
  5. : controller_(controller), file_name_(file_name) {}
  6. const CgroupController* controller() const { return &controller_; }
  7. const std::string& file_name() const { return file_name_; }
  8. void Reset(const CgroupController& controller, const std::string& file_name);
  9. bool GetPathForTask(int tid, std::string* path) const;
  10. private:
  11. CgroupController controller_;
  12. std::string file_name_;
  13. };

4.2 Profiles 段

每一项定义包含:

  • Name:指定 profile name;
  • Actions:罗列该 profile 被应用时,需要执行的 actions 集合,每个 action 包含:
    • Name:需要执行的 action 类别;
    • Params:该 action 所需的参数的集合;

下面来看下Actions 中 Name 可选的类别及其Params 配置:

ActionParameterDescription
SetTimerSlackSlack定时器可宽延的时间,单位为 ns
SetAttributeName引用Attributes 中的某一个属性的名称
Value要写入到attribute指定文件中的数据
WriteFileFilePath文件路径
Value要写入到文件的值
JoinCgroupController文件 cgroups.json 中的cgroup名称
Pathcgroup 层次结构中的额子组路径

 

4.2.1 SetTimerSlack

SetTimerSlack 只有一个参数 Slack,这个参数对应 /proc/PID/timerslack_ns 节点。TimerSlack 是Linux 系统为了降低系统功耗、避免 timer 时间参差不齐、过于频繁的唤醒 cpu,而设置的一种对齐策略。这个值关系到进程的定时器,如 select、epoll_wait、sleep 等API 的唤醒时间。

在Linux 4.6+ 版本,都是支持 /proc/PID/timerslack_ns 节点。

具体参考:https://cloud.tencent.com/developer/article/1836285

 

在代码中,通过 SetTimerSlackAction 类来管理该 profile:

  1. system/core/libprocessgroup/task_profiles.cpp
  2. bool SetTimerSlackAction::ExecuteForTask(int tid) const {
  3. static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
  4. if (sys_supports_timerslack) {
  5. auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
  6. if (!WriteStringToFile(std::to_string(slack_), file)) {
  7. if (errno == ENOENT) {
  8. // This happens when process is already dead
  9. return true;
  10. }
  11. PLOG(ERROR) << "set_timerslack_ns write failed";
  12. }
  13. }
  14. // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
  15. if (tid == 0 || tid == GetThreadId()) {
  16. if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
  17. PLOG(ERROR) << "set_timerslack_ns prctl failed";
  18. }
  19. }
  20. return true;
  21. }

4.2.2 SetAttribute

SetAttribute则跟 task_profiles.json 中的Attributes挂钩起来,对应了SetAttributeAction。

SetAttribute 有两个参数,Name 指的就是之前定义的 Attribute 的名称,Value 则是往 Attribute 对应的cgroup 的子节点写入的值。

在代码中,通过 SetAttributeAction 类来管理 SetAttribute 的profile:

  1. system/core/libprocessgroup/task_profiles.cpp
  2. bool SetAttributeAction::ExecuteForTask(int tid) const {
  3. std::string path;
  4. if (!attribute_->GetPathForTask(tid, &path)) {
  5. LOG(ERROR) << "Failed to find cgroup for tid " << tid;
  6. return false;
  7. }
  8. if (!WriteStringToFile(value_, path)) {
  9. PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
  10. return false;
  11. }
  12. return true;
  13. }

类中会有一个成员变量 attribute,类型为 ProfileAttribute。

代码中可以获知,先根据 Attribute 中的path,再将 value 写入文件节点中。

 

4.2.3 JoinCgroup

JoinCgroup 只有两个参数Controller 和 Path,Controller 指的是 cgroups 的 subsystem,Path 则是指该 subsystem 下的路径,也就是子 cgroup。通过该配置,将设置成这个profile 的进程或线程加入到该 subsystem 的子 cgroup中,受这个cgroup 的资源限制。

在代码中通过 SetCgroupAction 类来管理这个profile。

例如上面的:

  1. {
  2. "Attributes": [
  3. ...
  4. ],
  5. "Profiles": [
  6. {
  7. "Name": "Frozen",
  8. "Actions": [
  9. {
  10. "Name": "JoinCgroup",
  11. "Params":
  12. {
  13. "Controller": "freezer",
  14. "Path": ""
  15. }
  16. }
  17. ]
  18. }
  19. ],
  20. "AggregateProfiles": [
  21. ...
  22. ]
  23. }

这里配置的 profile 名字为 Frozen,利用的是Cgroup Controller 为 freezer,Path 为空。

也就是说该 profile 需要使用 /sys/fs/cgroup/freezer/ 目录下的某个子 cgroup 文件。具体看系统调用。通过查找,系统在 CachedAppOptimizer 类中会调用 Process.setProcessFrozen(),进而调用到 jni android_util_Process_setProcessFrozen() 接口:

  1. frameworks/base/core/jni/android_util_Process.cpp
  2. void android_os_Process_setProcessFrozen(
  3. JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
  4. {
  5. bool success = true;
  6. if (freeze) {
  7. success = SetProcessProfiles(uid, pid, {"Frozen"});
  8. } else {
  9. success = SetProcessProfiles(uid, pid, {"Unfrozen"});
  10. }
  11. if (!success) {
  12. signalExceptionForGroupError(env, EINVAL, pid);
  13. }
  14. }

当进程进行 freeze 或 unfreeze 的时候,会调用 SetProcessProfiles(),精细是 SetCgroupAction 类型的profile,最终调用 ExecuteForProcess():

  1. system/core/libprocessgroup/task_profiles.cpp
  2. bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
  3. std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
  4. unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
  5. if (tmp_fd < 0) {
  6. PLOG(WARNING) << "Failed to open " << procs_path;
  7. return false;
  8. }
  9. if (!AddTidToCgroup(pid, tmp_fd)) {
  10. LOG(ERROR) << "Failed to add task into cgroup";
  11. return false;
  12. }
  13. return true;
  14. }

通过函数,先通过 Controller 的 GetProcsFilePath() 接口获取该profile 需要修改的path,参数为该 profile 配置的 Path:

  1. system/core/libprocessgroup/cgroup_map.cpp
  2. std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
  3. pid_t pid) const {
  4. std::string proc_path(path());
  5. proc_path.append("/").append(rel_path);
  6. proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
  7. proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
  8. return proc_path.append(CGROUP_PROCS_FILE);
  9. }

 最终写的文件就是 CGROUP_PROCS_FILE,也就是 cgroup.procs 文件。

4.3 AggregateProfiles 段

在 Android 12 或更高版本中,task_profiles.json 文件中还包含了 AggregateProfiles 段。

这里定义一个或多个 profile 的别名,由一下内容组成:

  • Name:指定aggregate profile 的名称;
  • Profiles:该 aggregate profile 包含的 profil 名称集合;

当一个 aggregate profile 被应用时,里面包含的所有的 profile 都会被自动的应用。

如上面:

  1. "AggregateProfiles": [
  2. {
  3. "Name": "SCHED_SP_FOREGROUND",
  4. "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
  5. },
  6. ...
  7. ]

当应用 SCHED_SP_FOREGROUND 这个 aggregate profile时,里面包含的所有的 profiles (High

Performance、HighIoPriority、TimerSlackNormal) 都会被应用。

另外,如果没有递归,aggregate profiles 中可以包含单独的profiles 或其他 aggregate profiles。

5. cgroups 初始化

在 init 启动的第二阶段会调用:

  1. system/core/init/init.cpp
  2. int SecondStageMain(int argc, char** argv) {
  3. ...
  4. am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
  5. ...
  6. }
  1. system/core/init/init.cpp
  2. static Result<void> SetupCgroupsAction(const BuiltinArguments&) {
  3. // Have to create <CGROUPS_RC_DIR> using make_dir function
  4. // for appropriate sepolicy to be set for it
  5. make_dir(android::base::Dirname(CGROUPS_RC_PATH), 0711);
  6. if (!CgroupSetup()) {
  7. return ErrnoError() << "Failed to setup cgroups";
  8. }
  9. return {};
  10. }

创建一个 CGROUPS_RC_PATH 文件:/dev/cgroup_info/cgroup.rc

之后将 cgroups.json 文件的信息写入到 cgroup.rc 文件中,以供 task_profiles 读取controller 信息。

 

6. Task profiles

通过代码,我们其实可以清晰看到,TaskProfiles 类在构造的时候开始解析 task_profile.json:

  1. syste/core/libprocessgroup/task_profiles.cpp
  2. TaskProfiles::TaskProfiles() {
  3. // load system task profiles
  4. if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
  5. LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
  6. }
  7. // load vendor task profiles if the file exists
  8. if (!access(TASK_PROFILE_DB_VENDOR_FILE, F_OK) &&
  9. !Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_VENDOR_FILE)) {
  10. LOG(ERROR) << "Loading " << TASK_PROFILE_DB_VENDOR_FILE << " for [" << getpid()
  11. << "] failed";
  12. }
  13. }

主要通过 Load() 去解析两个文件:

  • TASK_PROFILE_DB_FILE (/etc/task_profiles.json)
  • TASK_PROFILE_DB_VENDOR_FILE (/vendor/etc/task_profiles.json)

在Load() 中会分别解析 task_profiles.json 文件中的 Attributes、Profiles、AggregateProfiles 段 。这里暂不过多剖析。我们在 task profiles 解析完成之后,系统通过 SetProcessProfiles() 或 SetTaskProfiles() 来达到应用 profile 的目的。

6.1 SetProcessProfiles()

  1. system/core/libprocessgroup/processgroup.cpp
  2. bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
  3. return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles);
  4. }

这是一个全局的函数,通过 TaskProfiles 的单例调用 task profiles 下的SetProcessProfiles():

  1. system/core/libprocessgroup/task_profiles.cpp
  2. bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
  3. const std::vector<std::string>& profiles) {
  4. for (const auto& name : profiles) {
  5. TaskProfile* profile = GetProfile(name);
  6. if (profile != nullptr) {
  7. if (!profile->ExecuteForProcess(uid, pid)) {
  8. PLOG(WARNING) << "Failed to apply " << name << " process profile";
  9. }
  10. } else {
  11. PLOG(WARNING) << "Failed to find " << name << "process profile";
  12. }
  13. }
  14. return true;
  15. }

进一步通过 profiles 的name,确定精细的 profile,进而调用 ExecuteForProcess() 函数,如上面的第 4.2.3 节,最终精细的就是 SetCgroupAction 这个 profile。

流程大致如下:

6.2 SetTaskProfiles()

  1. system/core/libprocessgroup/processgroup.cpp
  2. bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
  3. return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
  4. }

具体的流程同 SetProcessProfiles() 函数,最终调用的是 profile action 的ExecuteForTask() 函数。

 

 

至此,关于Android 中 cgroup 的抽象层大致讲述完了,代码逻辑很清晰,主要的内核代码在后期会详细剖析。这里总结下:

  • 通过 cgroups.json 配置 cgroup 的所有子系统,Controller 的名称会在后面 Attributes 或 Profiles 中使用到。另外,这样的 cgroups.json 文件可能还有很多,有加载顺序,也就有了覆盖;
  • 通过 task_profiles.json 配置所有活动,利用之前的 cgroups.json 中定义的子系统,进一步定义Attributes、Profiles 及 AggregateProfiles。同样的,也有加载顺序,也就有了覆盖;
  • cgroups.json 的解析是在 init 的第二阶段完成;
  • 系统会创建一个 TaskProfiles 的单例,管理所有的 profile,而 profile 中也维护着对应的 actions;
  • 通过接口 SetProcessProfiles() 来应用特定的 profile 到进程上;
  • 通过接口 SetTaskProfiles() 来应用特定的 profile 到线程上;

 

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号