cgroup
在网上查到cgroup
的相关信息大多都是基于Linux
的,很少有从Android
的角度去详细的描述怎么使用,在学习的过程中走了不少弯路,就记录一下。
cgroup
是Control 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存放在同一个分组之中,然后再通过优先级的形式进行memory
,I/O
,CPU
等模块的限制,那么分组和限制就是全部的重点。我们首先从分组聊起。
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()
。
初始化
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版本.json
,vendor
中最后加载,也是最高优先级,会覆盖之前的相同名称的配置,也就是在厂商自己配置的模块中优先级最高,所以大家如果有客制化的需求直接定义在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
的文档中也有说明。
Controller | Path | Mode | UID | GID |
---|---|---|---|---|
控制器名称 | 路径 | 权限 | 指定路径目录下文件的权限组 | 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;
}
这里我们可以看到是同时的去解析加载了Cgroups
和Cgroups2
。也就是两个版本之间并没冲突,如果我们想要去切换版本,只需要在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.json
和cgroup.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" ]
}
]
}
操作 | 参数 | 说明 |
---|---|---|
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
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开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题