Android-cgroup使用指南

cgroup

在网上查到cgroup的相关信息大多都是基于Linux的,很少有从Android的角度qu详细的描述怎么使用,在学习的过程中走了不少弯路,就记录一下。

cgroupControl Group的缩写,是Linux内核提供的一种机制,主要用于CPU,memory,I/O等。

Android中的cgroup

  • 阅读前提:对cgroup有一个基础的概念,在网上看了一些资料,对于Android上面的使用,有一些疑问。

  • 准备:最好是本地有一份Android源代码,对源代码和C/C++有基本的了解。

基础的说明文档

源代码中kernel目录下,Documentation/admin-guide/cgroup-v1目录下有很多.rst说明文件,网络上的很多千篇一律的文章基本都翻译于此,blkio,cpusets,freezer,memcg很多不同的说明文档也基本表明了cgroup的基本控制范围。

cgroup的基础

相信通过阅读以上的说明文档,你已经对cgroup有了一定的了解。既然Cgroup是通过将任务ID存放在同一个分组之中,然后再通过优先级的形式进行memoryI/OCPU等模块的限制,那么分组和限制就是全部的重点。我们首先从分组聊起。

cgroup的分组

分组首先要有规则,在目前的计算机的世界中硬件在大多数情况下还是没有办法满足所有任务瞬时的进行任务操作,并且很多任务是连续的,所以任务总有优先级的概念,Android中并不例外。Android中基本的任务优先级是根据进程ADJ进行判断的。我们从这里看起。关于ADJ相关的计算和流程大家搜索一下,网上还是有挺多这样的文章的,我们直接跟踪到最后一步和cgroup相关的地方。

JAVA 复制代码
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
@GuardedBy("mService")
private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
        long nowElapsed) {
    if (app.waitingToKill != null && app.curReceivers.isEmpty()
            && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
        app.kill(app.waitingToKill, ApplicationExitInfo.REASON_USER_REQUESTED,
                ApplicationExitInfo.SUBREASON_UNKNOWN, true);
        success = false;
    } else {
        int processGroup;
        switch (curSchedGroup) {
            case ProcessList.SCHED_GROUP_BACKGROUND:
                processGroup = THREAD_GROUP_BACKGROUND;
                break;
            case ProcessList.SCHED_GROUP_TOP_APP:
            case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
                processGroup = THREAD_GROUP_TOP_APP;
                break;
            case ProcessList.SCHED_GROUP_RESTRICTED:
                processGroup = THREAD_GROUP_RESTRICTED;
                break;
            default:
                processGroup = THREAD_GROUP_DEFAULT;
                break;
        }
        mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
                0 /* unused */, app.pid, processGroup, app.processName));
}
Java 复制代码
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
    final int pid = msg.arg1;
    final int group = msg.arg2;
    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
                + msg.obj + " to " + group);
    }
    try {
        setProcessGroup(pid, group);
    } catch (Exception e) {
        if (DEBUG_ALL) {
            Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }
    return true;
});

以上是OomAdjuster.java中,在计算完成之后,applyOomAdjLocked函数调用mProcessGroupHandler,其中主要调用的函数就是setProcessGroup,将根据优先级计算出的processGroup和对应的pid对应存储,这个就是我们要说的一个关键函数。

  • process.java
Java 复制代码
public static final native void setProcessGroup(int pid, int group)
        throws IllegalArgumentException, SecurityException;
  • android_util_Process.cpp
C++ 复制代码
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
    ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
    char proc_path[255];

    if (!verifyGroup(env, grp)) {
        return;
    }

    if (grp == SP_FOREGROUND) {
        signalExceptionForGroupError(env, EINVAL, pid);
        return;
    }

    if (grp < 0) {
        grp = SP_FOREGROUND;
    }

    if (kDebugPolicy) {
        char cmdline[32];
        int fd;

        strcpy(cmdline, "unknown");

        sprintf(proc_path, "/proc/%d/cmdline", pid);
        fd = open(proc_path, O_RDONLY | O_CLOEXEC);
        if (fd >= 0) {
            ssize_t rc = read(fd, cmdline, sizeof(cmdline) - 1);
            if (rc < 0) {
                ALOGE("read /proc/%d/cmdline (%s)", pid, strerror(errno));
            } else {
                cmdline[rc] = 0;
            }
            close(fd);
        }

        if (grp == SP_BACKGROUND) {
            ALOGD("setProcessGroup: vvv pid %d (%s)", pid, cmdline);
        } else {
            ALOGD("setProcessGroup: ^^^ pid %d (%s)", pid, cmdline);
        }
    }

    if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
        signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
}

这是一个典型的JNI函数,我们查看最终的调用函数SetProcessProfilesCached,这个函数就定义在我们最终要讲述的动态库中libprocessgroup

libprocessgroup

这个动态库的源代码定义在system/core/libprocessgroup,也是Android中Cgroup的主角,我们跟着相关的代码来一点点的解析Cgroup。

启动

  • system/core/init/init.cpp
C++ 复制代码
#include <processgroup/processgroup.h>
#include <processgroup/setup.h>

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 {};
}
int SecondStageMain(int argc, char** argv) {
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    InitializeSubcontext();

    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

    LoadBootScripts(am, sm);

    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();

    // Make the GSI status available before scripts start running.
    auto is_running = android::gsi::IsGsiRunning() ? "1" : "0";
    SetProperty(gsi::kGsiBootedProp, is_running);
    auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
    SetProperty(gsi::kGsiInstalledProp, is_installed);

    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
    am.QueueEventTrigger("early-init");
}

init.cpp开始调用SetupCgroupsAction(),然后调用processgroup中的CgroupSetup()

初始化

C++ 复制代码
bool CgroupSetup() {
    using namespace android::cgrouprc;

    std::map<std::string, CgroupDescriptor> descriptors;

    ...
    // load cgroups.json file
    // 第一步
    if (!ReadDescriptors(&descriptors)) {
        LOG(ERROR) << "Failed to load cgroup description file";
        return false;
    }

    // setup cgroups
    for (auto& [name, descriptor] : descriptors) {
        //第三步
        if (SetupCgroup(descriptor)) {
            descriptor.set_mounted(true);
        } else {
            // issue a warning and proceed with the next cgroup
            LOG(WARNING) << "Failed to setup " << name << " cgroup";
        }
    }

    // mkdir <CGROUPS_RC_DIR> 0711 system system
    //WriteRcFile的前置步骤
    if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
        LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";
        return false;
    }

    // Generate <CGROUPS_RC_FILE> file which can be directly mmapped into
    // process memory. This optimizes performance, memory usage
    // and limits infrormation shared with unprivileged processes
    // to the minimum subset of information from cgroups.json
    // 第四步
    if (!WriteRcFile(descriptors)) {
        LOG(ERROR) << "Failed to write " << CGROUPS_RC_PATH << " file";
        return false;
    }

    // chmod 0644 <CGROUPS_RC_PATH>
    if (fchmodat(AT_FDCWD, CGROUPS_RC_PATH, 0644, AT_SYMLINK_NOFOLLOW) < 0) {
        PLOG(ERROR) << "fchmodat() failed";
        return false;
    }

    return true;
}
  • 解析cgroups.json
C++ 复制代码
static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";

static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";

static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
    // load system cgroup descriptors
    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
        return false;
    }

    // load API-level specific system cgroups descriptors if available
    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
    if (api_level > 0) {
        std::string api_cgroups_path =
                android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
        if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
            if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
                return false;
            }
        }
    }

    // load vendor cgroup descriptors if the file exists
    if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
        //第二步
        !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
        return false;
    }

    return true;
}

这里可以根据整个流程看到是定义了三个目录,先加载系统中定义的cgroups.json,在加载cgroups api版本.jsonvendor中最后加载,也是最高优先级,会覆盖之前的相同名称的配置,也就是在厂商自己配置的模块中优先级最高,所以大家如果有客制化的需求直接定义在vendor即可。下面聊一下cgroup.json

json 复制代码
{
  "Cgroups": [
    {
      "Controller": "blkio",
      "Path": "/dev/blkio",
      "Mode": "0775",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "cpu",
      "Path": "/dev/cpuctl",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "cpuset",
      "Path": "/dev/cpuset",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    },
    {
      "Controller": "memory",
      "Path": "/dev/memcg",
      "Mode": "0700",
      "UID": "root",
      "GID": "system",
      "Optional": true
    }
  ],
  "Cgroups2": {
    "Path": "/sys/fs/cgroup",
    "Mode": "0775",
    "UID": "system",
    "GID": "system",
    "Controllers": [
      {
        "Controller": "freezer",
        "Path": "."
      }
    ]
  }
}

其中cgroup就是v1版本,Cgroups2就是v2版本,版本之间的区别这里就不做大的讲述。网上讲述这个的很多,还有之前的kernel的文档中也有说明。

Controller Path Mode UID GID
控制器名称 路径 权限 指定路径目录下文件的权限组 group ID 同前

确定了加载文件,接下来我们看如何解析它的代码

C++ 复制代码
static bool ReadDescriptorsFromFile(const std::string& file_name,
                                    std::map<std::string, CgroupDescriptor>* descriptors) {
    std::vector<CgroupDescriptor> result;
    std::string json_doc;

    if (!android::base::ReadFileToString(file_name, &json_doc)) {
        PLOG(ERROR) << "Failed to read task profiles from " << file_name;
        return false;
    }

    ...
    if (root.isMember("Cgroups")) {
        const Json::Value& cgroups = root["Cgroups"];
        for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
            std::string name = cgroups[i]["Controller"].asString();
            MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
        }
    }

    if (root.isMember("Cgroups2")) {
        const Json::Value& cgroups2 = root["Cgroups2"];
        std::string root_path = cgroups2["Path"].asString();
        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_CONTROLLER_NAME, "", 2);

        const Json::Value& childGroups = cgroups2["Controllers"];
        for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
            std::string name = childGroups[i]["Controller"].asString();
            MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
        }
    }

    return true;
}

这里我们可以看到是同时的去解析加载了CgroupsCgroups2。也就是两个版本之间并没冲突,如果我们想要去切换版本,只需要在cgroup.json中去做不同的配置即可。这里也要判断一个地方就是kernel的版本以及kernel中的配置,如果配置了不适用cgroupv1,那我们就必须要使用v2。

c++ 复制代码
static bool SetupCgroup(const CgroupDescriptor& descriptor) {
   const format::CgroupController* controller = descriptor.controller();

   int result;
   if (controller->version() == 2) {
       result = 0;
       if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
           // /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
           // try to create again in case the mount point is changed
           if (!Mkdir(controller->path(), 0, "", "")) {
               LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
               return false;
           }
...
           if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
                     "memory_recursiveprot") < 0) {
               LOG(INFO) << "Mounting memcg with memory_recursiveprot failed. Retrying without.";
               if (mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
                         nullptr) < 0) {
                   PLOG(ERROR) << "Failed to mount cgroup v2";
               }
           }

           // selinux permissions change after mounting, so it's ok to change mode and owner now
           if (!ChangeDirModeAndOwner(controller->path(), descriptor.mode(), descriptor.uid(),
                                      descriptor.gid())) {
               LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
               result = -1;
           }
       } else {
           if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
               LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
               return false;
           }

           if (controller->flags() & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
               std::string str = std::string("+") + controller->name();
               std::string path = std::string(controller->path()) + "/cgroup.subtree_control";

               if (!base::WriteStringToFile(str, path)) {
                   LOG(ERROR) << "Failed to activate controller " << controller->name();
                   return false;
               }
           }
       }
   } else {
       // mkdir <path> [mode] [owner] [group]
       if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
           LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
           return false;
       }

...
       if (!strcmp(controller->name(), "cpuset")) {
           // mount cpuset none /dev/cpuset nodev noexec nosuid
           result = mount("none", controller->path(), controller->name(),
                          MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
       } else {
           // mount cgroup none <path> nodev noexec nosuid <controller>
           result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
                          controller->name());
       }
   }

   if (result < 0) {
       bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;

       if (optional && errno == EINVAL) {
           // Optional controllers are allowed to fail to mount if kernel does not support them
           LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
       } else {
           PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
           return false;
       }
   }

   return true;
}

上面的代码主要就是针对不同的子系统去创建挂载对应的目录。

  • 常见的CGroup子系统
子系统名称 功能
cpuset 多核心CPU,可以根据cgroup分配CPU核心和内存节点
cpu 根据配置影响任务的CPU使用率
cpuacct 产生cgroup任务的CPU资源使用报告
memory 可以限制进程的 memory 使用量
blkio 限制cgroup任务中不同快设备的输入/输出控制
freezer 暂停或者恢复任务--冷冻
net_cls 可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制

现在就剩下最后一步,将cgroup.json写入CGROUPS_RC_PATH

C++ 复制代码
static constexpr const char* CGROUPS_RC_PATH = "/dev/cgroup_info/cgroup.rc";
static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
    unique_fd fd(TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
                                         S_IRUSR | S_IRGRP | S_IROTH)));
    if (fd < 0) {
        PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH;
        return false;
    }

    format::CgroupFile fl;
    fl.version_ = format::CgroupFile::FILE_CURR_VERSION;
    fl.controller_count_ = descriptors.size();
    int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl)));
    if (ret < 0) {
        PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
        return false;
    }

    for (const auto& [name, descriptor] : descriptors) {
        ret = TEMP_FAILURE_RETRY(
                write(fd, descriptor.controller(), sizeof(format::CgroupController)));
        if (ret < 0) {
            PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
            return false;
        }
    }

    return true;
}

到此为止,libprocessgroup的初始化启动也已经完成,我们了解了它的启动流程和启动时机。在回头去看如何将不同的任务放入到不同的groups中。

分组

我们从前文提到的SetProcessProfilesCached,我们就是根据这个函数定位到了libprocessgroup动态库中。

  • processgroup.cpp
C++ 复制代码
bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, true);
}

这个函数中并没有做什么实际的逻辑,接着追踪。我们首先要看GetInstance()

C++ 复制代码
TaskProfiles& TaskProfiles::GetInstance() {
    // Deliberately leak this object to avoid a race between destruction on
    // process exit and concurrent access from another thread.
    static auto* instance = new TaskProfiles;
    return *instance;
}

接着看构造函数。

C++ 复制代码
static constexpr const char* TASK_PROFILE_DB_FILE = "/etc/task_profiles.json";
static constexpr const char* TASK_PROFILE_DB_VENDOR_FILE = "/vendor/etc/task_profiles.json";

static constexpr const char* TEMPLATE_TASK_PROFILE_API_FILE =
        "/etc/task_profiles/task_profiles_%u.json";

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 API-level specific system task profiles if available
    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
    if (api_level > 0) {
        std::string api_profiles_path =
                android::base::StringPrintf(TEMPLATE_TASK_PROFILE_API_FILE, api_level);
        if (!access(api_profiles_path.c_str(), F_OK) || errno != ENOENT) {
            if (!Load(CgroupMap::GetInstance(), api_profiles_path)) {
                LOG(ERROR) << "Loading " << api_profiles_path << " 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";
    }
}

上面的代码就略微熟悉了,和cgroup.json的加载方式同出一辙。如果有定制化的需求最好是在vendor目录中客制化即可。下面的重点就是将task_profiles.jsoncgroup.json中的字段搭配起来。

举个例子,以子系统cpu举例,它的挂载目录为/dev/cpuctl,配置在cgroup.json

json 复制代码
"Cgroups": [
    {
      "Controller": "cpu",
      "Path": "/dev/cpuctl",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    }
]
json 复制代码
{
    "Attributes": [
        {
          "Name": "UClampMax",
          "Controller": "cpu",
          "File": "cpu.uclamp.max"
        }
    ],
    "Profiles": [
        {
          "Name": "HighEnergySaving",
          "Actions": [
            {
              "Name": "JoinCgroup",
              "Params":
              {
                "Controller": "cpu",
                "Path": "background"
              }
            }
          ]
        }
    ],
    "AggregateProfiles": [
        {
          "Name": "SCHED_SP_BACKGROUND",
          "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
        }
    ]
}
操作 参数 说明
SetTimerSlack Slack 定时器可宽延时间(以纳秒为单位)
SetAttribute Name 引用 Attributes 部分中某一属性的名称
Value 要写入到由指定属性表示的文件的值
SetAttribute Name 引用 Attributes 部分中某一属性的名称
Value 要写入到由指定属性表示的文件的值
WriteFile FilePath 文件的路径
Value 要写入文件的值
JoinCgroup Controller cgroups.json 中的 cgroup 控制器的名称
Path cgroup 控制器层次结构中的子组路径

这里需要查看代码进行分析的地方就是AggregateProfiles中的配置。我们看一下SCHED_SP_BACKGROUND的使用处就会豁然开朗。

  • sched_policy.cpp
C++ 复制代码
const char* get_sched_policy_profile_name(SchedPolicy policy) {
    /*
     *  sched profile array for:
     *  SP_DEFAULT(-1), SP_BACKGROUND(0), SP_FOREGROUND(1),
     *  SP_SYSTEM(2), SP_AUDIO_APP(3), SP_AUDIO_SYS(4),
     *  SP_TOP_APP(5), SP_RT_APP(6), SP_RESTRICTED(7)
     *  index is policy + 1
     *  this need keep in sync with SchedPolicy enum
     */
    static constexpr const char* kSchedProfiles[SP_CNT + 1] = {
            "SCHED_SP_DEFAULT", "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND",
            "SCHED_SP_SYSTEM",  "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
            "SCHED_SP_TOP_APP", "SCHED_SP_RT_APP",     "SCHED_SP_DEFAULT"};
    if (policy < SP_DEFAULT || policy >= SP_CNT) {
        return nullptr;
    }
    return kSchedProfiles[policy + 1];
}
arduino 复制代码
int set_sched_policy(int tid, SchedPolicy policy) {
    if (tid == 0) {
        tid = GetThreadId();
    }
    policy = _policy(policy);

...
    switch (policy) {
        case SP_BACKGROUND:
            return SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true) ? 0 : -1;
        case SP_FOREGROUND:
        case SP_AUDIO_APP:
        case SP_AUDIO_SYS:
            return SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
        case SP_TOP_APP:
            return SetTaskProfiles(tid, {"SCHED_SP_TOP_APP"}, true) ? 0 : -1;
        case SP_SYSTEM:
            return SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
        case SP_RT_APP:
            return SetTaskProfiles(tid, {"SCHED_SP_RT_APP"}, true) ? 0 : -1;
        default:
            return SetTaskProfiles(tid, {"SCHED_SP_DEFAULT"}, true) ? 0 : -1;
    }

    return 0;
}

这里也回到了最初我们追踪java层代码的地方,前文是调用SetProcessProfiles,这里是调用SetTaskProfiles。分组的基础逻辑,和Androidframeworks的关联也已经说明清楚,这里还有一个小小的疑问点,就是Natvie进程是怎么分组的,我们简单的看一个例子

rc 复制代码
service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service.mediatek
    class hal
    user audioserver
    # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
    group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub system sdcard_rw
    capabilities BLOCK_SUSPEND SYS_NICE
    ioprio rt 4
    task_profiles ProcessCapacityHigh HighPerformance
    onrestart restart audioserver

如果我们要创建一个Native进程需要将其放入分组中就需要 task_profiles ProcessCapacityHigh HighPerformance。可是小小的脑袋中还是有些迷惑,一句脚本就可以了,当然也有大量的代码在支撑这个工作。

我们看下脚本的解析地方service_parser.cpp

c 复制代码
const KeywordMap<ServiceParser::OptionParser>& ServiceParser::GetParserMap() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const KeywordMap<ServiceParser::OptionParser> parser_map = {
        ...

        {"task_profiles",           {1,     kMax, &ServiceParser::ParseTaskProfiles}}
        ...
    };
    // clang-format on
    return parser_map;
}

小结

充分的理解了上面的代码之后,我们就知道了cgroups的代码实现处,了解了android上层与其的关联和cgroup的子模块,理解了大致的控制范围。就可以尝试去使用cgroups了。

当然还有很多难题在后面,我们的配置是否有效,如何高效的调试。如何和我们工作中的业务进行深入的定制化配置都是我们需要思考的地方。

后面可能会在写一篇,讲述一下自己在工作中使用cgroup如何解决实际的问题。时间就未可知了。

相关推荐
alexhilton9 分钟前
使用用例(Use Case)以让Android代码更简洁
android·kotlin·android jetpack
峥嵘life15 分钟前
Android xml的Preference设置visibility=“gone“ 无效分析解决
android·xml
wei_work@39 分钟前
【linux】简单的shell脚本练习
linux·运维·服务器
DeepSeek忠实粉丝1 小时前
微调篇--超长文本微调训练
人工智能·程序员·llm
AI大模型1 小时前
构建可调用外部工具的AI助手:LangChain函数调用与API集成详解
程序员·langchain·llm
Jooolin1 小时前
【编程史】Git是啥?它和GitHub关系是?
linux·git·github
用户2018792831671 小时前
通俗故事:驱动二进制文件在AOSP中的角色
android
穷人小水滴1 小时前
在 Termux 中签名 apk 文件
android·linux·apk
用户2018792831671 小时前
android王国的 “城堡攻防战”
android
还是一只小牛1 小时前
探秘 React Native:线程探索及桥优化
android·前端