赞
踩
源码基于:Android R
在之前的博文《Android中app freezer原理》一文中,我们看到冻结器的enable、freeze、unfreeze 都是通过 cgroup 的机制进行处理。
本文将介绍下 Android 中 cgroup 的抽象层基本信息和使用方式。
cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。
cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:
Android 使用 cgroups 来控制和考量如 CPU、memory 等系统资源的使用和分配情况,并支持 Linux 内核 cgroup v1 和 cgroup v2 版本。
在 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 搭配使用:
文件路径:system/core/libprocessgroup/profiles/cgroups.json
- system/core/libprocessgroup/profiles/cgroups.json
-
- {
- "Cgroups": [
- {
- "Controller": "blkio",
- "Path": "/dev/blkio",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "cpu",
- "Path": "/dev/cpuctl",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "cpuacct",
- "Path": "/acct",
- "Mode": "0555"
- },
- {
- "Controller": "cpuset",
- "Path": "/dev/cpuset",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "memory",
- "Path": "/dev/memcg",
- "Mode": "0700",
- "UID": "root",
- "GID": "system"
- },
- {
- "Controller": "schedtune",
- "Path": "/dev/stune",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- }
- ],
- "Cgroups2": {
- "Path": "/sys/fs/cgroup",
- "Mode": "0755",
- "UID": "system",
- "GID": "system",
- "Controllers": [
- {
- "Controller": "freezer",
- "Path": "freezer",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- }
- ]
- }
- }
cgroup v1 和 cgroup v2 描述的规则不一样。
对于 cgroup v1,必须拥有:
对于 cgroup v2,基本同 v1,Controllers 中定义了子 cgroup,这些都挂载在同一个目录下。子 cgroup 中的 Path 是相对于根 Path。例如这里 freezer 的 Path 设定了 freezer,就是在 根 Path /sys/fs/cgroup/ 目录下创建一个目录 freezer。
另外 cgroups.json 文件可能不止一个:
- /system/core/libprocessgroup/profiles/cgroups.json //默认文件
- /system/core/libprocessgroup/profiles/cgroups_<API level>.json //API级别的文件,R版本没有,S版本很多
- /vendor/xxx/cgroups.json //vendor自定义文件
这三种文件加载顺序是:默认 -> API 级别 -> vendor,于是就存在一个覆盖的流程,只要后面的文件中定义的 Controller 值与前面的相同,就会覆盖前者的定义。
文件路径:system/core/libprocessgroup/profiles/task_profiles.json
- {
- "Attributes": [
- {
- "Name": "MemSoftLimit",
- "Controller": "memory",
- "File": "memory.soft_limit_in_bytes"
- },
- {
- "Name": "MemSwappiness",
- "Controller": "memory",
- "File": "memory.swappiness"
- },
- {
- "Name": "FreezerState",
- "Controller": "freezer",
- "File": "cgroup.freeze"
- }
- ],
-
- "Profiles": [
- {
- "Name": "Frozen",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "freezer",
- "Path": ""
- }
- }
- ]
- },
- {
- "Name": "TimerSlackHigh",
- "Actions": [
- {
- "Name": "SetTimerSlack",
- "Params":
- {
- "Slack": "40000000"
- }
- }
- ]
- },
- {
- "Name": "PerfBoost",
- "Actions": [
- {
- "Name": "SetClamps",
- "Params":
- {
- "Boost": "50%",
- "Clamp": "0"
- }
- }
- ]
- },
- {
- "Name": "HighMemoryUsage",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "MemSoftLimit",
- "Value": "512MB"
- }
- },
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "MemSwappiness",
- "Value": "100"
- }
- }
- ]
- },
- {
- "Name": "FreezerEnabled",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "1"
- }
- }
- ]
- }
- ],
-
- "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" ]
- },
- ...
- ]
- }
整个文件配置由一个大括号包含,总体由三部分组成:
另外,task_profiles.json 文件也不止一个:
- system/core/libprocessgroup/profiles/task_profiles.json //默认
- system/core/libprocessgroup/profiles/task_profiles_<API level>.json //API级别的文件,R版本没有,S有很多
- vendor/xxx/task_profiles.json //vendor配置
加载、覆盖的顺序同cgroups.json,按照 Name 来匹配,只要两个文件中定义同名项,后者就会覆盖前者的定义。
Attributes 中 cgroups 中特定的文件。
Attributes 是task profiles 文件定义中的引用。在 task profiles 之外,只有当 framework 请求直接访问这些文件,且无法使用 task profiles 抽象访问时。其他情况下,使用 task profiles,它可以更好地分离所需行为及其实现详情。
Attributes 中每一项包含:
如上面:
- "Attributes": [
- {
- "Name": "FreezerState",
- "Controller": "freezer",
- "File": "cgroup.freeze"
- }
- ],
用的是 Controller 为 freezer 的 cgroup,从上面第 3 节中得知,它采用 cgroups v2 的格式,cgroup Path 为 /sys/fs/cgroup/freezer/,这里定义的 attribute 指定的是该目录下 cgroup.freeze 文件。
在代码中,通过 ProfileAttribute 类来管理每个 Attribute:
- system/core/libprocessgroup/task_profiles.h
-
- class ProfileAttribute {
- public:
- ProfileAttribute(const CgroupController& controller, const std::string& file_name)
- : controller_(controller), file_name_(file_name) {}
-
- const CgroupController* controller() const { return &controller_; }
- const std::string& file_name() const { return file_name_; }
- void Reset(const CgroupController& controller, const std::string& file_name);
-
- bool GetPathForTask(int tid, std::string* path) const;
-
- private:
- CgroupController controller_;
- std::string file_name_;
- };
每一项定义包含:
下面来看下Actions 中 Name 可选的类别及其Params 配置:
Action | Parameter | Description |
---|---|---|
SetTimerSlack | Slack | 定时器可宽延的时间,单位为 ns |
SetAttribute | Name | 引用Attributes 中的某一个属性的名称 |
Value | 要写入到attribute指定文件中的数据 | |
WriteFile | FilePath | 文件路径 |
Value | 要写入到文件的值 | |
JoinCgroup | Controller | 文件 cgroups.json 中的cgroup名称 |
Path | cgroup 层次结构中的额子组路径 |
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:
- system/core/libprocessgroup/task_profiles.cpp
-
- bool SetTimerSlackAction::ExecuteForTask(int tid) const {
- static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
-
- if (sys_supports_timerslack) {
- auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
- if (!WriteStringToFile(std::to_string(slack_), file)) {
- if (errno == ENOENT) {
- // This happens when process is already dead
- return true;
- }
- PLOG(ERROR) << "set_timerslack_ns write failed";
- }
- }
-
- // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
- if (tid == 0 || tid == GetThreadId()) {
- if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
- PLOG(ERROR) << "set_timerslack_ns prctl failed";
- }
- }
-
- return true;
- }
SetAttribute则跟 task_profiles.json 中的Attributes挂钩起来,对应了SetAttributeAction。
SetAttribute 有两个参数,Name 指的就是之前定义的 Attribute 的名称,Value 则是往 Attribute 对应的cgroup 的子节点写入的值。
在代码中,通过 SetAttributeAction 类来管理 SetAttribute 的profile:
- system/core/libprocessgroup/task_profiles.cpp
-
- bool SetAttributeAction::ExecuteForTask(int tid) const {
- std::string path;
-
- if (!attribute_->GetPathForTask(tid, &path)) {
- LOG(ERROR) << "Failed to find cgroup for tid " << tid;
- return false;
- }
-
- if (!WriteStringToFile(value_, path)) {
- PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
- return false;
- }
-
- return true;
- }
类中会有一个成员变量 attribute,类型为 ProfileAttribute。
代码中可以获知,先根据 Attribute 中的path,再将 value 写入文件节点中。
JoinCgroup 只有两个参数Controller 和 Path,Controller 指的是 cgroups 的 subsystem,Path 则是指该 subsystem 下的路径,也就是子 cgroup。通过该配置,将设置成这个profile 的进程或线程加入到该 subsystem 的子 cgroup中,受这个cgroup 的资源限制。
在代码中通过 SetCgroupAction 类来管理这个profile。
例如上面的:
- {
- "Attributes": [
- ...
- ],
-
- "Profiles": [
- {
- "Name": "Frozen",
- "Actions": [
- {
- "Name": "JoinCgroup",
- "Params":
- {
- "Controller": "freezer",
- "Path": ""
- }
- }
- ]
- }
- ],
-
- "AggregateProfiles": [
- ...
- ]
- }
这里配置的 profile 名字为 Frozen,利用的是Cgroup Controller 为 freezer,Path 为空。
也就是说该 profile 需要使用 /sys/fs/cgroup/freezer/ 目录下的某个子 cgroup 文件。具体看系统调用。通过查找,系统在 CachedAppOptimizer 类中会调用 Process.setProcessFrozen(),进而调用到 jni android_util_Process_setProcessFrozen() 接口:
- frameworks/base/core/jni/android_util_Process.cpp
-
- void android_os_Process_setProcessFrozen(
- JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
- {
- bool success = true;
-
- if (freeze) {
- success = SetProcessProfiles(uid, pid, {"Frozen"});
- } else {
- success = SetProcessProfiles(uid, pid, {"Unfrozen"});
- }
-
- if (!success) {
- signalExceptionForGroupError(env, EINVAL, pid);
- }
- }
当进程进行 freeze 或 unfreeze 的时候,会调用 SetProcessProfiles(),精细是 SetCgroupAction 类型的profile,最终调用 ExecuteForProcess():
- system/core/libprocessgroup/task_profiles.cpp
-
- bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
- std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
- unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
- if (tmp_fd < 0) {
- PLOG(WARNING) << "Failed to open " << procs_path;
- return false;
- }
- if (!AddTidToCgroup(pid, tmp_fd)) {
- LOG(ERROR) << "Failed to add task into cgroup";
- return false;
- }
-
- return true;
- }
通过函数,先通过 Controller 的 GetProcsFilePath() 接口获取该profile 需要修改的path,参数为该 profile 配置的 Path:
- system/core/libprocessgroup/cgroup_map.cpp
- std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
- pid_t pid) const {
- std::string proc_path(path());
- proc_path.append("/").append(rel_path);
- proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
- proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
-
- return proc_path.append(CGROUP_PROCS_FILE);
- }
最终写的文件就是 CGROUP_PROCS_FILE,也就是 cgroup.procs 文件。
在 Android 12 或更高版本中,task_profiles.json 文件中还包含了 AggregateProfiles 段。
这里定义一个或多个 profile 的别名,由一下内容组成:
当一个 aggregate profile 被应用时,里面包含的所有的 profile 都会被自动的应用。
如上面:
- "AggregateProfiles": [
- {
- "Name": "SCHED_SP_FOREGROUND",
- "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
- },
- ...
- ]
当应用 SCHED_SP_FOREGROUND 这个 aggregate profile时,里面包含的所有的 profiles (High
Performance、HighIoPriority、TimerSlackNormal) 都会被应用。
另外,如果没有递归,aggregate profiles 中可以包含单独的profiles 或其他 aggregate profiles。
在 init 启动的第二阶段会调用:
- system/core/init/init.cpp
-
- int SecondStageMain(int argc, char** argv) {
- ...
- am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
- ...
- }
- system/core/init/init.cpp
-
- static Result<void> SetupCgroupsAction(const BuiltinArguments&) {
- // Have to create <CGROUPS_RC_DIR> using make_dir function
- // for appropriate sepolicy to be set for it
- make_dir(android::base::Dirname(CGROUPS_RC_PATH), 0711);
- if (!CgroupSetup()) {
- return ErrnoError() << "Failed to setup cgroups";
- }
-
- return {};
- }
创建一个 CGROUPS_RC_PATH 文件:/dev/cgroup_info/cgroup.rc
之后将 cgroups.json 文件的信息写入到 cgroup.rc 文件中,以供 task_profiles 读取controller 信息。
通过代码,我们其实可以清晰看到,TaskProfiles 类在构造的时候开始解析 task_profile.json:
- syste/core/libprocessgroup/task_profiles.cpp
-
- TaskProfiles::TaskProfiles() {
- // load system task profiles
- if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
- LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
- }
-
- // load vendor task profiles if the file exists
- if (!access(TASK_PROFILE_DB_VENDOR_FILE, F_OK) &&
- !Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_VENDOR_FILE)) {
- LOG(ERROR) << "Loading " << TASK_PROFILE_DB_VENDOR_FILE << " for [" << getpid()
- << "] failed";
- }
- }
主要通过 Load() 去解析两个文件:
在Load() 中会分别解析 task_profiles.json 文件中的 Attributes、Profiles、AggregateProfiles 段 。这里暂不过多剖析。我们在 task profiles 解析完成之后,系统通过 SetProcessProfiles() 或 SetTaskProfiles() 来达到应用 profile 的目的。
- system/core/libprocessgroup/processgroup.cpp
-
- bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
- return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles);
- }
这是一个全局的函数,通过 TaskProfiles 的单例调用 task profiles 下的SetProcessProfiles():
- system/core/libprocessgroup/task_profiles.cpp
-
- bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
- const std::vector<std::string>& profiles) {
- for (const auto& name : profiles) {
- TaskProfile* profile = GetProfile(name);
- if (profile != nullptr) {
- if (!profile->ExecuteForProcess(uid, pid)) {
- PLOG(WARNING) << "Failed to apply " << name << " process profile";
- }
- } else {
- PLOG(WARNING) << "Failed to find " << name << "process profile";
- }
- }
- return true;
- }
进一步通过 profiles 的name,确定精细的 profile,进而调用 ExecuteForProcess() 函数,如上面的第 4.2.3 节,最终精细的就是 SetCgroupAction 这个 profile。
流程大致如下:
- system/core/libprocessgroup/processgroup.cpp
-
- bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
- return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
- }
具体的流程同 SetProcessProfiles() 函数,最终调用的是 profile action 的ExecuteForTask() 函数。
至此,关于Android 中 cgroup 的抽象层大致讲述完了,代码逻辑很清晰,主要的内核代码在后期会详细剖析。这里总结下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。