Android-cgroup使用指南

cgroup

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

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相关的地方。

/** 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));
}


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
public static final native void setProcessGroup(int pid, int group)
        throws IllegalArgumentException, SecurityException;

  • android_util_Process.cpp
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
#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()

初始化

未命名文件.png

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
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

{
  "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的文档中也有说明。

ControllerPathModeUIDGID
控制器名称路径权限指定路径目录下文件的权限组group ID 同前

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

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。

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

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
bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, true);
}

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

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;
}

接着看构造函数。

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

"Cgroups": [
    {
      "Controller": "cpu",
      "Path": "/dev/cpuctl",
      "Mode": "0755",
      "UID": "system",
      "GID": "system"
    }
]

{
    "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" ]
        }
    ]
}

操作参数说明
SetTimerSlackSlack定时器可宽延时间(以纳秒为单位)
SetAttributeName引用 Attributes 部分中某一属性的名称
Value要写入到由指定属性表示的文件的值
SetAttributeName引用 Attributes 部分中某一属性的名称
Value要写入到由指定属性表示的文件的值
WriteFileFilePath文件的路径
Value要写入文件的值
JoinCgroupControllercgroups.json 中的 cgroup 控制器的名称
Pathcgroup 控制器层次结构中的子组路径

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

  • sched_policy.cpp
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];
}

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进程是怎么分组的,我们简单的看一个例子

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

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了。

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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值