libprocessgroup: Add support for task profiles
am: 82b72a5667
Change-Id: If702ad510ae31619d53f82844d7beeff5ee56682
This commit is contained in:
commit
953472f577
14 changed files with 1333 additions and 469 deletions
|
|
@ -109,6 +109,7 @@ CHARGER_STATIC_LIBRARIES := \
|
||||||
libbase \
|
libbase \
|
||||||
libutils \
|
libutils \
|
||||||
libcutils \
|
libcutils \
|
||||||
|
libjsoncpp \
|
||||||
libprocessgroup \
|
libprocessgroup \
|
||||||
liblog \
|
liblog \
|
||||||
libm \
|
libm \
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ cc_defaults {
|
||||||
},
|
},
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"libseccomp_policy",
|
"libseccomp_policy",
|
||||||
"libprocessgroup",
|
|
||||||
"libavb",
|
"libavb",
|
||||||
"libprotobuf-cpp-lite",
|
"libprotobuf-cpp-lite",
|
||||||
"libpropertyinfoserializer",
|
"libpropertyinfoserializer",
|
||||||
|
|
@ -82,6 +81,7 @@ cc_defaults {
|
||||||
"liblog",
|
"liblog",
|
||||||
"liblogwrap",
|
"liblogwrap",
|
||||||
"liblp",
|
"liblp",
|
||||||
|
"libprocessgroup",
|
||||||
"libselinux",
|
"libselinux",
|
||||||
"libutils",
|
"libutils",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
#include <fs_mgr_vendor_overlay.h>
|
#include <fs_mgr_vendor_overlay.h>
|
||||||
#include <keyutils.h>
|
#include <keyutils.h>
|
||||||
#include <libavb/libavb.h>
|
#include <libavb/libavb.h>
|
||||||
|
#include <processgroup/processgroup.h>
|
||||||
#include <selinux/android.h>
|
#include <selinux/android.h>
|
||||||
|
|
||||||
#ifndef RECOVERY
|
#ifndef RECOVERY
|
||||||
|
|
@ -347,6 +348,17 @@ static Result<Success> console_init_action(const BuiltinArguments& args) {
|
||||||
return Success();
|
return Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Result<Success> SetupCgroupsAction(const BuiltinArguments&) {
|
||||||
|
// Have to create <CGROUPS_RC_DIR> using make_dir function
|
||||||
|
// for appropriate sepolicy to be set for it
|
||||||
|
make_dir(CGROUPS_RC_DIR, 0711);
|
||||||
|
if (!CgroupSetupCgroups()) {
|
||||||
|
return ErrnoError() << "Failed to setup cgroups";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success();
|
||||||
|
}
|
||||||
|
|
||||||
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
|
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
|
||||||
if (key.empty()) return;
|
if (key.empty()) return;
|
||||||
|
|
||||||
|
|
@ -682,6 +694,8 @@ int SecondStageMain(int argc, char** argv) {
|
||||||
// Nexus 9 boot time, so it's disabled by default.
|
// Nexus 9 boot time, so it's disabled by default.
|
||||||
if (false) DumpState();
|
if (false) DumpState();
|
||||||
|
|
||||||
|
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
|
||||||
|
|
||||||
am.QueueEventTrigger("early-init");
|
am.QueueEventTrigger("early-init");
|
||||||
|
|
||||||
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
|
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ test_libraries = [
|
||||||
"libcutils",
|
"libcutils",
|
||||||
"liblog",
|
"liblog",
|
||||||
"libbase",
|
"libbase",
|
||||||
|
"libjsoncpp",
|
||||||
"libprocessgroup",
|
"libprocessgroup",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ cc_library_headers {
|
||||||
|
|
||||||
cc_library {
|
cc_library {
|
||||||
srcs: [
|
srcs: [
|
||||||
|
"cgroup_map.cpp",
|
||||||
"processgroup.cpp",
|
"processgroup.cpp",
|
||||||
"sched_policy.cpp",
|
"sched_policy.cpp",
|
||||||
|
"task_profiles.cpp",
|
||||||
],
|
],
|
||||||
name: "libprocessgroup",
|
name: "libprocessgroup",
|
||||||
host_supported: true,
|
host_supported: true,
|
||||||
|
|
@ -29,7 +31,7 @@ cc_library {
|
||||||
},
|
},
|
||||||
shared_libs: [
|
shared_libs: [
|
||||||
"libbase",
|
"libbase",
|
||||||
"liblog",
|
"libjsoncpp",
|
||||||
],
|
],
|
||||||
// for cutils/android_filesystem_config.h
|
// for cutils/android_filesystem_config.h
|
||||||
header_libs: [
|
header_libs: [
|
||||||
|
|
|
||||||
405
libprocessgroup/cgroup_map.cpp
Normal file
405
libprocessgroup/cgroup_map.cpp
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#define LOG_NDEBUG 0
|
||||||
|
#define LOG_TAG "libprocessgroup"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/properties.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <cgroup_map.h>
|
||||||
|
#include <json/reader.h>
|
||||||
|
#include <json/value.h>
|
||||||
|
#include <processgroup/processgroup.h>
|
||||||
|
|
||||||
|
using android::base::GetBoolProperty;
|
||||||
|
using android::base::StringPrintf;
|
||||||
|
using android::base::unique_fd;
|
||||||
|
|
||||||
|
static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
|
||||||
|
|
||||||
|
static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
|
||||||
|
static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
|
||||||
|
static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.tasks";
|
||||||
|
|
||||||
|
static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid,
|
||||||
|
const std::string& gid) {
|
||||||
|
if (mode == 0) {
|
||||||
|
mode = 0755;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mkdir(path.c_str(), mode) != 0) {
|
||||||
|
/* chmod in case the directory already exists */
|
||||||
|
if (errno == EEXIST) {
|
||||||
|
if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
|
||||||
|
// /acct is a special case when the directory already exists
|
||||||
|
// TODO: check if file mode is already what we want instead of using EROFS
|
||||||
|
if (errno != EROFS) {
|
||||||
|
PLOG(ERROR) << "fchmodat() failed for " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PLOG(ERROR) << "mkdir() failed for " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passwd* uid_pwd = nullptr;
|
||||||
|
passwd* gid_pwd = nullptr;
|
||||||
|
|
||||||
|
if (!uid.empty()) {
|
||||||
|
uid_pwd = getpwnam(uid.c_str());
|
||||||
|
if (!uid_pwd) {
|
||||||
|
PLOG(ERROR) << "Unable to decode UID for '" << uid << "'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gid.empty()) {
|
||||||
|
gid_pwd = getpwnam(gid.c_str());
|
||||||
|
if (!gid_pwd) {
|
||||||
|
PLOG(ERROR) << "Unable to decode GID for '" << gid << "'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid_pwd && lchown(path.c_str(), uid_pwd->pw_uid, gid_pwd ? gid_pwd->pw_uid : -1) < 0) {
|
||||||
|
PLOG(ERROR) << "lchown() failed for " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* chown may have cleared S_ISUID and S_ISGID, chmod again */
|
||||||
|
if (mode & (S_ISUID | S_ISGID)) {
|
||||||
|
if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
|
||||||
|
PLOG(ERROR) << "fchmodat() failed for " << path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
|
||||||
|
std::vector<CgroupDescriptor> result;
|
||||||
|
std::string json_doc;
|
||||||
|
|
||||||
|
if (!android::base::ReadFileToString(CGROUPS_DESC_FILE, &json_doc)) {
|
||||||
|
LOG(ERROR) << "Failed to read task profiles from " << CGROUPS_DESC_FILE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Reader reader;
|
||||||
|
Json::Value root;
|
||||||
|
if (!reader.parse(json_doc, root)) {
|
||||||
|
LOG(ERROR) << "Failed to parse cgroups description: " << reader.getFormattedErrorMessages();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value cgroups = root["Cgroups"];
|
||||||
|
for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
|
||||||
|
std::string name = cgroups[i]["Controller"].asString();
|
||||||
|
descriptors->emplace(std::make_pair(
|
||||||
|
name,
|
||||||
|
CgroupDescriptor(1, name, cgroups[i]["Path"].asString(), cgroups[i]["Mode"].asInt(),
|
||||||
|
cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value cgroups2 = root["Cgroups2"];
|
||||||
|
descriptors->emplace(std::make_pair(
|
||||||
|
CGROUPV2_CONTROLLER_NAME,
|
||||||
|
CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
|
||||||
|
cgroups2["Mode"].asInt(), cgroups2["UID"].asString(),
|
||||||
|
cgroups2["GID"].asString())));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SetupCgroup(const CgroupDescriptor& descriptor) {
|
||||||
|
const CgroupController* controller = descriptor.controller();
|
||||||
|
|
||||||
|
// mkdir <path> [mode] [owner] [group]
|
||||||
|
if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
|
||||||
|
PLOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
if (controller->version() == 2) {
|
||||||
|
result = mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
|
||||||
|
nullptr);
|
||||||
|
} else {
|
||||||
|
// Unfortunately historically cpuset controller was mounted using a mount command
|
||||||
|
// different from all other controllers. This results in controller attributes not
|
||||||
|
// to be prepended with controller name. For example this way instead of
|
||||||
|
// /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
|
||||||
|
// the system currently expects.
|
||||||
|
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) {
|
||||||
|
PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
|
||||||
|
std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CgroupMap::CGROUPS_RC_FILE);
|
||||||
|
unique_fd fd(TEMP_FAILURE_RETRY(open(cgroup_rc_path.c_str(),
|
||||||
|
O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
|
||||||
|
S_IRUSR | S_IRGRP | S_IROTH)));
|
||||||
|
if (fd < 0) {
|
||||||
|
PLOG(ERROR) << "open() failed for " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgroupFile fl;
|
||||||
|
fl.version_ = 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 " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [name, descriptor] : descriptors) {
|
||||||
|
ret = TEMP_FAILURE_RETRY(write(fd, descriptor.controller(), sizeof(CgroupController)));
|
||||||
|
if (ret < 0) {
|
||||||
|
PLOG(ERROR) << "write() failed for " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgroupController::CgroupController(uint32_t version, const std::string& name,
|
||||||
|
const std::string& path) {
|
||||||
|
version_ = version;
|
||||||
|
strncpy(name_, name.c_str(), sizeof(name_) - 1);
|
||||||
|
name_[sizeof(name_) - 1] = '\0';
|
||||||
|
strncpy(path_, path.c_str(), sizeof(path_) - 1);
|
||||||
|
path_[sizeof(path_) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CgroupController::GetTasksFilePath(const std::string& path) const {
|
||||||
|
std::string tasks_path = path_;
|
||||||
|
|
||||||
|
if (!path.empty()) {
|
||||||
|
tasks_path += "/" + path;
|
||||||
|
}
|
||||||
|
return (version_ == 1) ? tasks_path + CGROUP_TASKS_FILE : tasks_path + CGROUP_TASKS_FILE_V2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CgroupController::GetProcsFilePath(const std::string& path, uid_t uid,
|
||||||
|
pid_t pid) const {
|
||||||
|
std::string proc_path(path_);
|
||||||
|
proc_path.append("/").append(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
|
||||||
|
std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
|
||||||
|
std::string content;
|
||||||
|
if (!android::base::ReadFileToString(file_name, &content)) {
|
||||||
|
LOG(ERROR) << "Failed to read " << file_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if group is null and tid exists return early because
|
||||||
|
// user is not interested in cgroup membership
|
||||||
|
if (group == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cg_tag = StringPrintf(":%s:", name_);
|
||||||
|
size_t start_pos = content.find(cg_tag);
|
||||||
|
if (start_pos == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_pos += cg_tag.length() + 1; // skip '/'
|
||||||
|
size_t end_pos = content.find('\n', start_pos);
|
||||||
|
if (end_pos == std::string::npos) {
|
||||||
|
*group = content.substr(start_pos, std::string::npos);
|
||||||
|
} else {
|
||||||
|
*group = content.substr(start_pos, end_pos - start_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
|
||||||
|
const std::string& path, mode_t mode, const std::string& uid,
|
||||||
|
const std::string& gid)
|
||||||
|
: controller_(version, name, path), mode_(mode), uid_(uid), gid_(gid) {}
|
||||||
|
|
||||||
|
CgroupMap::CgroupMap() : cg_file_data_(nullptr), cg_file_size_(0) {
|
||||||
|
if (!LoadRcFile()) {
|
||||||
|
PLOG(ERROR) << "CgroupMap::LoadRcFile called for [" << getpid() << "] failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CgroupMap::~CgroupMap() {
|
||||||
|
if (cg_file_data_) {
|
||||||
|
munmap(cg_file_data_, cg_file_size_);
|
||||||
|
cg_file_data_ = nullptr;
|
||||||
|
cg_file_size_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CgroupMap& CgroupMap::GetInstance() {
|
||||||
|
static CgroupMap instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupMap::LoadRcFile() {
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
if (cg_file_data_) {
|
||||||
|
// Data already initialized
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CGROUPS_RC_FILE);
|
||||||
|
unique_fd fd(TEMP_FAILURE_RETRY(open(cgroup_rc_path.c_str(), O_RDONLY | O_CLOEXEC)));
|
||||||
|
if (fd < 0) {
|
||||||
|
PLOG(ERROR) << "open() failed for " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fstat(fd, &sb) < 0) {
|
||||||
|
PLOG(ERROR) << "fstat() failed for " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cg_file_size_ = sb.st_size;
|
||||||
|
if (cg_file_size_ < sizeof(CgroupFile)) {
|
||||||
|
PLOG(ERROR) << "Invalid file format " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cg_file_data_ = (CgroupFile*)mmap(nullptr, cg_file_size_, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if (cg_file_data_ == MAP_FAILED) {
|
||||||
|
PLOG(ERROR) << "Failed to mmap " << cgroup_rc_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cg_file_data_->version_ != CgroupFile::FILE_CURR_VERSION) {
|
||||||
|
PLOG(ERROR) << cgroup_rc_path << " file version mismatch";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CgroupMap::Print() {
|
||||||
|
LOG(INFO) << "File version = " << cg_file_data_->version_;
|
||||||
|
LOG(INFO) << "File controller count = " << cg_file_data_->controller_count_;
|
||||||
|
|
||||||
|
LOG(INFO) << "Mounted cgroups:";
|
||||||
|
CgroupController* controller = (CgroupController*)(cg_file_data_ + 1);
|
||||||
|
for (int i = 0; i < cg_file_data_->controller_count_; i++, controller++) {
|
||||||
|
LOG(INFO) << "\t" << controller->name() << " ver " << controller->version() << " path "
|
||||||
|
<< controller->path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupMap::SetupCgroups() {
|
||||||
|
std::map<std::string, CgroupDescriptor> descriptors;
|
||||||
|
|
||||||
|
// load cgroups.json file
|
||||||
|
if (!ReadDescriptors(&descriptors)) {
|
||||||
|
PLOG(ERROR) << "Failed to load cgroup description file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup cgroups
|
||||||
|
for (const auto& [name, descriptor] : descriptors) {
|
||||||
|
if (!SetupCgroup(descriptor)) {
|
||||||
|
// issue a warning and proceed with the next cgroup
|
||||||
|
// TODO: mark the descriptor as invalid and skip it in WriteRcFile()
|
||||||
|
LOG(WARNING) << "Failed to setup " << name << " cgroup";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkdir <CGROUPS_RC_DIR> 0711 system system
|
||||||
|
if (!Mkdir(CGROUPS_RC_DIR, 0711, "system", "system")) {
|
||||||
|
PLOG(ERROR) << "Failed to create directory for <CGROUPS_RC_FILE> 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_FILE << " file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CGROUPS_RC_FILE);
|
||||||
|
// chmod 0644 <cgroup_rc_path>
|
||||||
|
if (fchmodat(AT_FDCWD, cgroup_rc_path.c_str(), 0644, AT_SYMLINK_NOFOLLOW) < 0) {
|
||||||
|
LOG(ERROR) << "fchmodat() failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CgroupController* CgroupMap::FindController(const std::string& name) const {
|
||||||
|
if (!cg_file_data_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip the file header to get to the first controller
|
||||||
|
CgroupController* controller = (CgroupController*)(cg_file_data_ + 1);
|
||||||
|
for (int i = 0; i < cg_file_data_->controller_count_; i++, controller++) {
|
||||||
|
if (name == controller->name()) {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
96
libprocessgroup/cgroup_map.h
Normal file
96
libprocessgroup/cgroup_map.h
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Minimal controller description to be mmapped into process address space
|
||||||
|
class CgroupController {
|
||||||
|
public:
|
||||||
|
CgroupController() {}
|
||||||
|
CgroupController(uint32_t version, const std::string& name, const std::string& path);
|
||||||
|
|
||||||
|
uint32_t version() const { return version_; }
|
||||||
|
const char* name() const { return name_; }
|
||||||
|
const char* path() const { return path_; }
|
||||||
|
|
||||||
|
std::string GetTasksFilePath(const std::string& path) const;
|
||||||
|
std::string GetProcsFilePath(const std::string& path, uid_t uid, pid_t pid) const;
|
||||||
|
bool GetTaskGroup(int tid, std::string* group) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr size_t CGROUP_NAME_BUF_SZ = 16;
|
||||||
|
static constexpr size_t CGROUP_PATH_BUF_SZ = 32;
|
||||||
|
|
||||||
|
uint32_t version_;
|
||||||
|
char name_[CGROUP_NAME_BUF_SZ];
|
||||||
|
char path_[CGROUP_PATH_BUF_SZ];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Complete controller description for mounting cgroups
|
||||||
|
class CgroupDescriptor {
|
||||||
|
public:
|
||||||
|
CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path,
|
||||||
|
mode_t mode, const std::string& uid, const std::string& gid);
|
||||||
|
|
||||||
|
const CgroupController* controller() const { return &controller_; }
|
||||||
|
mode_t mode() const { return mode_; }
|
||||||
|
std::string uid() const { return uid_; }
|
||||||
|
std::string gid() const { return gid_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CgroupController controller_;
|
||||||
|
mode_t mode_;
|
||||||
|
std::string uid_;
|
||||||
|
std::string gid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CgroupFile {
|
||||||
|
static constexpr uint32_t FILE_VERSION_1 = 1;
|
||||||
|
static constexpr uint32_t FILE_CURR_VERSION = FILE_VERSION_1;
|
||||||
|
|
||||||
|
uint32_t version_;
|
||||||
|
uint32_t controller_count_;
|
||||||
|
CgroupController controllers_[];
|
||||||
|
};
|
||||||
|
|
||||||
|
class CgroupMap {
|
||||||
|
public:
|
||||||
|
static constexpr const char* CGROUPS_RC_FILE = "cgroup.rc";
|
||||||
|
|
||||||
|
// Selinux policy ensures only init process can successfully use this function
|
||||||
|
static bool SetupCgroups();
|
||||||
|
|
||||||
|
static CgroupMap& GetInstance();
|
||||||
|
|
||||||
|
const CgroupController* FindController(const std::string& name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CgroupFile* cg_file_data_;
|
||||||
|
size_t cg_file_size_;
|
||||||
|
|
||||||
|
CgroupMap();
|
||||||
|
~CgroupMap();
|
||||||
|
|
||||||
|
bool LoadRcFile();
|
||||||
|
void Print();
|
||||||
|
};
|
||||||
|
|
@ -14,14 +14,28 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _PROCESSGROUP_H_
|
#pragma once
|
||||||
#define _PROCESSGROUP_H_
|
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
#include <sys/cdefs.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
__BEGIN_DECLS
|
__BEGIN_DECLS
|
||||||
|
|
||||||
|
static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
|
||||||
|
static constexpr const char* CGROUPS_RC_DIR = "/dev/cgroup_info";
|
||||||
|
|
||||||
|
bool CgroupSetupCgroups();
|
||||||
|
bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
|
||||||
|
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
|
||||||
|
bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
|
||||||
|
|
||||||
|
bool UsePerAppMemcg();
|
||||||
|
|
||||||
|
bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles);
|
||||||
|
bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
|
||||||
|
|
||||||
// Return 0 and removes the cgroup if there are no longer any processes in it.
|
// Return 0 and removes the cgroup if there are no longer any processes in it.
|
||||||
// Returns -1 in the case of an error occurring or if there are processes still running
|
// Returns -1 in the case of an error occurring or if there are processes still running
|
||||||
// even after retrying for up to 200ms.
|
// even after retrying for up to 200ms.
|
||||||
|
|
@ -42,5 +56,3 @@ bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes);
|
||||||
void removeAllProcessGroups(void);
|
void removeAllProcessGroups(void);
|
||||||
|
|
||||||
__END_DECLS
|
__END_DECLS
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2018 The Android Open Source Project
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -67,13 +67,13 @@ extern int set_sched_policy(int tid, SchedPolicy policy);
|
||||||
* On platforms which support gettid(), zero tid means current thread.
|
* On platforms which support gettid(), zero tid means current thread.
|
||||||
* Return value: 0 for success, or -1 for error and set errno.
|
* Return value: 0 for success, or -1 for error and set errno.
|
||||||
*/
|
*/
|
||||||
extern int get_sched_policy(int tid, SchedPolicy *policy);
|
extern int get_sched_policy(int tid, SchedPolicy* policy);
|
||||||
|
|
||||||
/* Return a displayable string corresponding to policy.
|
/* Return a displayable string corresponding to policy.
|
||||||
* Return value: non-NULL NUL-terminated name of unspecified length;
|
* Return value: non-NULL NUL-terminated name of unspecified length;
|
||||||
* the caller is responsible for displaying the useful part of the string.
|
* the caller is responsible for displaying the useful part of the string.
|
||||||
*/
|
*/
|
||||||
extern const char *get_sched_policy_name(SchedPolicy policy);
|
extern const char* get_sched_policy_name(SchedPolicy policy);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
@ -43,8 +43,8 @@
|
||||||
#include <android-base/stringprintf.h>
|
#include <android-base/stringprintf.h>
|
||||||
#include <android-base/strings.h>
|
#include <android-base/strings.h>
|
||||||
#include <cutils/android_filesystem_config.h>
|
#include <cutils/android_filesystem_config.h>
|
||||||
|
|
||||||
#include <processgroup/processgroup.h>
|
#include <processgroup/processgroup.h>
|
||||||
|
#include <task_profiles.h>
|
||||||
|
|
||||||
using android::base::GetBoolProperty;
|
using android::base::GetBoolProperty;
|
||||||
using android::base::StartsWith;
|
using android::base::StartsWith;
|
||||||
|
|
@ -53,16 +53,103 @@ using android::base::WriteStringToFile;
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
static const char kCpuacctCgroup[] = "/acct";
|
|
||||||
static const char kMemoryCgroup[] = "/dev/memcg/apps";
|
|
||||||
|
|
||||||
#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
|
#define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
|
||||||
|
|
||||||
|
bool CgroupSetupCgroups() {
|
||||||
|
return CgroupMap::SetupCgroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path) {
|
||||||
|
const CgroupController* controller = CgroupMap::GetInstance().FindController(cgroup_name);
|
||||||
|
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
*path = controller->path();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
|
||||||
|
const TaskProfiles& tp = TaskProfiles::GetInstance();
|
||||||
|
const ProfileAttribute* attr = tp.GetAttribute(attr_name);
|
||||||
|
|
||||||
|
if (attr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
*path = StringPrintf("%s/%s", attr->controller()->path(), attr->file_name().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
|
||||||
|
const TaskProfiles& tp = TaskProfiles::GetInstance();
|
||||||
|
const ProfileAttribute* attr = tp.GetAttribute(attr_name);
|
||||||
|
|
||||||
|
if (attr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attr->GetPathForTask(tid, path)) {
|
||||||
|
PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UsePerAppMemcg() {
|
||||||
|
bool low_ram_device = GetBoolProperty("ro.config.low_ram", false);
|
||||||
|
return GetBoolProperty("ro.config.per_app_memcg", low_ram_device);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isMemoryCgroupSupported() {
|
static bool isMemoryCgroupSupported() {
|
||||||
static bool memcg_supported = !access("/dev/memcg/memory.limit_in_bytes", F_OK);
|
std::string cgroup_name;
|
||||||
|
static bool memcg_supported = (CgroupMap::GetInstance().FindController("memory") != nullptr);
|
||||||
|
|
||||||
return memcg_supported;
|
return memcg_supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
|
||||||
|
const TaskProfiles& tp = TaskProfiles::GetInstance();
|
||||||
|
|
||||||
|
for (const auto& name : profiles) {
|
||||||
|
const TaskProfile* profile = tp.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles) {
|
||||||
|
const TaskProfiles& tp = TaskProfiles::GetInstance();
|
||||||
|
|
||||||
|
for (const auto& name : profiles) {
|
||||||
|
const TaskProfile* profile = tp.GetProfile(name);
|
||||||
|
if (profile != nullptr) {
|
||||||
|
if (!profile->ExecuteForTask(tid)) {
|
||||||
|
PLOG(WARNING) << "Failed to apply " << name << " task profile";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PLOG(WARNING) << "Failed to find " << name << "task profile";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
|
static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
|
||||||
return StringPrintf("%s/uid_%d", cgroup, uid);
|
return StringPrintf("%s/uid_%d", cgroup, uid);
|
||||||
}
|
}
|
||||||
|
|
@ -103,11 +190,21 @@ static void RemoveUidProcessGroups(const std::string& uid_path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAllProcessGroups()
|
void removeAllProcessGroups() {
|
||||||
{
|
|
||||||
LOG(VERBOSE) << "removeAllProcessGroups()";
|
LOG(VERBOSE) << "removeAllProcessGroups()";
|
||||||
for (const char* cgroup_root_path : {kCpuacctCgroup, kMemoryCgroup}) {
|
|
||||||
std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path), closedir);
|
std::vector<std::string> cgroups;
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
if (CgroupGetControllerPath("cpuacct", &path)) {
|
||||||
|
cgroups.push_back(path);
|
||||||
|
}
|
||||||
|
if (CgroupGetControllerPath("memory", &path)) {
|
||||||
|
cgroups.push_back(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::string cgroup_root_path : cgroups) {
|
||||||
|
std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path.c_str()), closedir);
|
||||||
if (root == NULL) {
|
if (root == NULL) {
|
||||||
PLOG(ERROR) << "Failed to open " << cgroup_root_path;
|
PLOG(ERROR) << "Failed to open " << cgroup_root_path;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -121,7 +218,7 @@ void removeAllProcessGroups()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto path = StringPrintf("%s/%s", cgroup_root_path, dir->d_name);
|
auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
|
||||||
RemoveUidProcessGroups(path);
|
RemoveUidProcessGroups(path);
|
||||||
LOG(VERBOSE) << "Removing " << path;
|
LOG(VERBOSE) << "Removing " << path;
|
||||||
if (rmdir(path.c_str()) == -1) PLOG(WARNING) << "Failed to remove " << path;
|
if (rmdir(path.c_str()) == -1) PLOG(WARNING) << "Failed to remove " << path;
|
||||||
|
|
@ -130,6 +227,21 @@ void removeAllProcessGroups()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
|
||||||
|
if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chown(path.c_str(), uid, gid) == -1) {
|
||||||
|
int saved_errno = errno;
|
||||||
|
rmdir(path.c_str());
|
||||||
|
errno = saved_errno;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns number of processes killed on success
|
// Returns number of processes killed on success
|
||||||
// Returns 0 if there are no processes in the process cgroup left to kill
|
// Returns 0 if there are no processes in the process cgroup left to kill
|
||||||
// Returns -1 on error
|
// Returns -1 on error
|
||||||
|
|
@ -200,10 +312,16 @@ static int DoKillProcessGroupOnce(const char* cgroup, uid_t uid, int initialPid,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
|
static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
|
||||||
|
std::string cpuacct_path;
|
||||||
|
std::string memory_path;
|
||||||
|
|
||||||
|
CgroupGetControllerPath("cpuacct", &cpuacct_path);
|
||||||
|
CgroupGetControllerPath("memory", &memory_path);
|
||||||
|
|
||||||
const char* cgroup =
|
const char* cgroup =
|
||||||
(!access(ConvertUidPidToPath(kCpuacctCgroup, uid, initialPid).c_str(), F_OK))
|
(!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
|
||||||
? kCpuacctCgroup
|
? cpuacct_path.c_str()
|
||||||
: kMemoryCgroup;
|
: memory_path.c_str();
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
|
@ -258,44 +376,22 @@ int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
|
||||||
return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/);
|
return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
|
int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
|
||||||
if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
|
std::string cgroup;
|
||||||
return false;
|
if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
|
||||||
}
|
CgroupGetControllerPath("memory", &cgroup);
|
||||||
|
|
||||||
if (chown(path.c_str(), uid, gid) == -1) {
|
|
||||||
int saved_errno = errno;
|
|
||||||
rmdir(path.c_str());
|
|
||||||
errno = saved_errno;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isPerAppMemcgEnabled() {
|
|
||||||
static bool per_app_memcg =
|
|
||||||
GetBoolProperty("ro.config.per_app_memcg", GetBoolProperty("ro.config.low_ram", false));
|
|
||||||
return per_app_memcg;
|
|
||||||
}
|
|
||||||
|
|
||||||
int createProcessGroup(uid_t uid, int initialPid, bool memControl)
|
|
||||||
{
|
|
||||||
const char* cgroup;
|
|
||||||
if (isMemoryCgroupSupported() && (memControl || isPerAppMemcgEnabled())) {
|
|
||||||
cgroup = kMemoryCgroup;
|
|
||||||
} else {
|
} else {
|
||||||
cgroup = kCpuacctCgroup;
|
CgroupGetControllerPath("cpuacct", &cgroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto uid_path = ConvertUidToPath(cgroup, uid);
|
auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
|
||||||
|
|
||||||
if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
|
if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
|
||||||
PLOG(ERROR) << "Failed to make and chown " << uid_path;
|
PLOG(ERROR) << "Failed to make and chown " << uid_path;
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, initialPid);
|
auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid);
|
||||||
|
|
||||||
if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
|
if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
|
||||||
PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
|
PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
|
||||||
|
|
@ -313,13 +409,17 @@ int createProcessGroup(uid_t uid, int initialPid, bool memControl)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool SetProcessGroupValue(uid_t uid, int pid, const std::string& file_name, int64_t value) {
|
static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
|
||||||
if (!isMemoryCgroupSupported()) {
|
if (!isMemoryCgroupSupported()) {
|
||||||
PLOG(ERROR) << "Memcg is not mounted.";
|
PLOG(ERROR) << "Memcg is not mounted.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto path = ConvertUidPidToPath(kMemoryCgroup, uid, pid) + file_name;
|
std::string path;
|
||||||
|
if (!CgroupGetAttributePathForTask(attr_name, tid, &path)) {
|
||||||
|
PLOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!WriteStringToFile(std::to_string(value), path)) {
|
if (!WriteStringToFile(std::to_string(value), path)) {
|
||||||
PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
|
PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
|
||||||
|
|
@ -328,14 +428,14 @@ static bool SetProcessGroupValue(uid_t uid, int pid, const std::string& file_nam
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setProcessGroupSwappiness(uid_t uid, int pid, int swappiness) {
|
bool setProcessGroupSwappiness(uid_t, int pid, int swappiness) {
|
||||||
return SetProcessGroupValue(uid, pid, "/memory.swappiness", swappiness);
|
return SetProcessGroupValue(pid, "MemSwappiness", swappiness);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setProcessGroupSoftLimit(uid_t uid, int pid, int64_t soft_limit_in_bytes) {
|
bool setProcessGroupSoftLimit(uid_t, int pid, int64_t soft_limit_in_bytes) {
|
||||||
return SetProcessGroupValue(uid, pid, "/memory.soft_limit_in_bytes", soft_limit_in_bytes);
|
return SetProcessGroupValue(pid, "MemSoftLimit", soft_limit_in_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setProcessGroupLimit(uid_t uid, int pid, int64_t limit_in_bytes) {
|
bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) {
|
||||||
return SetProcessGroupValue(uid, pid, "/memory.limit_in_bytes", limit_in_bytes);
|
return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,372 +1,81 @@
|
||||||
/*
|
/*
|
||||||
** Copyright 2007, The Android Open Source Project
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
**
|
*
|
||||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
** you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
** You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
**
|
*
|
||||||
** http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
**
|
*
|
||||||
** Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
** See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
** limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <processgroup/sched_policy.h>
|
#include <processgroup/sched_policy.h>
|
||||||
|
|
||||||
#define LOG_TAG "SchedPolicy"
|
#define LOG_TAG "SchedPolicy"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <android-base/macros.h>
|
#include <android-base/logging.h>
|
||||||
#include <log/log.h>
|
#include <android-base/threads.h>
|
||||||
|
#include <cgroup_map.h>
|
||||||
|
#include <processgroup/processgroup.h>
|
||||||
|
|
||||||
|
using android::base::GetThreadId;
|
||||||
|
|
||||||
/* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
|
/* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
|
||||||
* Call this any place a SchedPolicy is used as an input parameter.
|
* Call this any place a SchedPolicy is used as an input parameter.
|
||||||
* Returns the possibly re-mapped policy.
|
* Returns the possibly re-mapped policy.
|
||||||
*/
|
*/
|
||||||
static inline SchedPolicy _policy(SchedPolicy p)
|
static inline SchedPolicy _policy(SchedPolicy p) {
|
||||||
{
|
return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
|
||||||
return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
int set_cpuset_policy(int tid, SchedPolicy policy) {
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sched.h>
|
|
||||||
#include <sys/prctl.h>
|
|
||||||
|
|
||||||
#define POLICY_DEBUG 0
|
|
||||||
|
|
||||||
// timer slack value in nS enforced when the thread moves to background
|
|
||||||
#define TIMER_SLACK_BG 40000000
|
|
||||||
#define TIMER_SLACK_FG 50000
|
|
||||||
|
|
||||||
static pthread_once_t the_once = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static int __sys_supports_timerslack = -1;
|
|
||||||
|
|
||||||
// File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error
|
|
||||||
static int system_bg_cpuset_fd = -1;
|
|
||||||
static int bg_cpuset_fd = -1;
|
|
||||||
static int fg_cpuset_fd = -1;
|
|
||||||
static int ta_cpuset_fd = -1; // special cpuset for top app
|
|
||||||
static int rs_cpuset_fd = -1; // special cpuset for screen off restrictions
|
|
||||||
|
|
||||||
// File descriptors open to /dev/stune/../tasks, setup by initialize, or -1 on error
|
|
||||||
static int bg_schedboost_fd = -1;
|
|
||||||
static int fg_schedboost_fd = -1;
|
|
||||||
static int ta_schedboost_fd = -1;
|
|
||||||
static int rt_schedboost_fd = -1;
|
|
||||||
|
|
||||||
/* Add tid to the scheduling group defined by the policy */
|
|
||||||
static int add_tid_to_cgroup(int tid, int fd)
|
|
||||||
{
|
|
||||||
if (fd < 0) {
|
|
||||||
SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// specialized itoa -- works for tid > 0
|
|
||||||
char text[22];
|
|
||||||
char *end = text + sizeof(text) - 1;
|
|
||||||
char *ptr = end;
|
|
||||||
*ptr = '\0';
|
|
||||||
while (tid > 0) {
|
|
||||||
*--ptr = '0' + (tid % 10);
|
|
||||||
tid = tid / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, ptr, end - ptr) < 0) {
|
|
||||||
/*
|
|
||||||
* If the thread is in the process of exiting,
|
|
||||||
* don't flag an error
|
|
||||||
*/
|
|
||||||
if (errno == ESRCH)
|
|
||||||
return 0;
|
|
||||||
SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
|
|
||||||
ptr, strerror(errno), fd);
|
|
||||||
errno = EINVAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
If CONFIG_CPUSETS for Linux kernel is set, "tasks" can be found under
|
|
||||||
/dev/cpuset mounted in init.rc; otherwise, that file does not exist
|
|
||||||
even though the directory, /dev/cpuset, is still created (by init.rc).
|
|
||||||
|
|
||||||
A couple of other candidates (under cpuset mount directory):
|
|
||||||
notify_on_release
|
|
||||||
release_agent
|
|
||||||
|
|
||||||
Yet another way to decide if cpuset is enabled is to parse
|
|
||||||
/proc/self/status and search for lines begin with "Mems_allowed".
|
|
||||||
|
|
||||||
If CONFIG_PROC_PID_CPUSET is set, the existence "/proc/self/cpuset" can
|
|
||||||
be used to decide if CONFIG_CPUSETS is set, so we don't have a dependency
|
|
||||||
on where init.rc mounts cpuset. That's why we'd better require this
|
|
||||||
configuration be set if CONFIG_CPUSETS is set.
|
|
||||||
|
|
||||||
In older releases, this was controlled by build-time configuration.
|
|
||||||
*/
|
|
||||||
bool cpusets_enabled() {
|
|
||||||
static bool enabled = (access("/dev/cpuset/tasks", F_OK) == 0);
|
|
||||||
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Similar to CONFIG_CPUSETS above, but with a different configuration
|
|
||||||
CONFIG_CGROUP_SCHEDTUNE that's in Android common Linux kernel and Linaro
|
|
||||||
Stable Kernel (LSK), but not in mainline Linux as of v4.9.
|
|
||||||
|
|
||||||
In older releases, this was controlled by build-time configuration.
|
|
||||||
*/
|
|
||||||
bool schedboost_enabled() {
|
|
||||||
static bool enabled = (access("/dev/stune/tasks", F_OK) == 0);
|
|
||||||
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __initialize() {
|
|
||||||
const char* filename;
|
|
||||||
|
|
||||||
if (cpusets_enabled()) {
|
|
||||||
if (!access("/dev/cpuset/tasks", W_OK)) {
|
|
||||||
|
|
||||||
filename = "/dev/cpuset/foreground/tasks";
|
|
||||||
fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/cpuset/background/tasks";
|
|
||||||
bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/cpuset/system-background/tasks";
|
|
||||||
system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/cpuset/top-app/tasks";
|
|
||||||
ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/cpuset/restricted/tasks";
|
|
||||||
rs_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
|
|
||||||
if (schedboost_enabled()) {
|
|
||||||
filename = "/dev/stune/top-app/tasks";
|
|
||||||
ta_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/stune/foreground/tasks";
|
|
||||||
fg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/stune/background/tasks";
|
|
||||||
bg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
filename = "/dev/stune/rt/tasks";
|
|
||||||
rt_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", getpid());
|
|
||||||
__sys_supports_timerslack = !access(buf, W_OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the path under the requested cgroup subsystem (if it exists)
|
|
||||||
*
|
|
||||||
* The data from /proc/<pid>/cgroup looks (something) like:
|
|
||||||
* 2:cpu:/bg_non_interactive
|
|
||||||
* 1:cpuacct:/
|
|
||||||
*
|
|
||||||
* We return the part after the "/", which will be an empty string for
|
|
||||||
* the default cgroup. If the string is longer than "bufLen", the string
|
|
||||||
* will be truncated.
|
|
||||||
*/
|
|
||||||
static int getCGroupSubsys(int tid, const char* subsys, char* buf, size_t bufLen)
|
|
||||||
{
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
char pathBuf[32];
|
|
||||||
char lineBuf[256];
|
|
||||||
FILE *fp;
|
|
||||||
|
|
||||||
snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid);
|
|
||||||
if (!(fp = fopen(pathBuf, "re"))) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) {
|
|
||||||
char *next = lineBuf;
|
|
||||||
char *found_subsys;
|
|
||||||
char *grp;
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
/* Junk the first field */
|
|
||||||
if (!strsep(&next, ":")) {
|
|
||||||
goto out_bad_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(found_subsys = strsep(&next, ":"))) {
|
|
||||||
goto out_bad_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(found_subsys, subsys)) {
|
|
||||||
/* Not the subsys we're looking for */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(grp = strsep(&next, ":"))) {
|
|
||||||
goto out_bad_data;
|
|
||||||
}
|
|
||||||
grp++; /* Drop the leading '/' */
|
|
||||||
len = strlen(grp);
|
|
||||||
grp[len-1] = '\0'; /* Drop the trailing '\n' */
|
|
||||||
|
|
||||||
if (bufLen <= len) {
|
|
||||||
len = bufLen - 1;
|
|
||||||
}
|
|
||||||
strncpy(buf, grp, len);
|
|
||||||
buf[len] = '\0';
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SLOGE("Failed to find subsys %s", subsys);
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
out_bad_data:
|
|
||||||
SLOGE("Bad cgroup data {%s}", lineBuf);
|
|
||||||
fclose(fp);
|
|
||||||
return -1;
|
|
||||||
#else
|
|
||||||
errno = ENOSYS;
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_sched_policy(int tid, SchedPolicy *policy)
|
|
||||||
{
|
|
||||||
if (tid == 0) {
|
if (tid == 0) {
|
||||||
tid = gettid();
|
tid = GetThreadId();
|
||||||
}
|
|
||||||
pthread_once(&the_once, __initialize);
|
|
||||||
|
|
||||||
char grpBuf[32];
|
|
||||||
|
|
||||||
grpBuf[0] = '\0';
|
|
||||||
if (schedboost_enabled()) {
|
|
||||||
if (getCGroupSubsys(tid, "schedtune", grpBuf, sizeof(grpBuf)) < 0) return -1;
|
|
||||||
}
|
|
||||||
if ((grpBuf[0] == '\0') && cpusets_enabled()) {
|
|
||||||
if (getCGroupSubsys(tid, "cpuset", grpBuf, sizeof(grpBuf)) < 0) return -1;
|
|
||||||
}
|
|
||||||
if (grpBuf[0] == '\0') {
|
|
||||||
*policy = SP_FOREGROUND;
|
|
||||||
} else if (!strcmp(grpBuf, "foreground")) {
|
|
||||||
*policy = SP_FOREGROUND;
|
|
||||||
} else if (!strcmp(grpBuf, "system-background")) {
|
|
||||||
*policy = SP_SYSTEM;
|
|
||||||
} else if (!strcmp(grpBuf, "background")) {
|
|
||||||
*policy = SP_BACKGROUND;
|
|
||||||
} else if (!strcmp(grpBuf, "top-app")) {
|
|
||||||
*policy = SP_TOP_APP;
|
|
||||||
} else {
|
|
||||||
errno = ERANGE;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int set_cpuset_policy(int tid, SchedPolicy policy)
|
|
||||||
{
|
|
||||||
// in the absence of cpusets, use the old sched policy
|
|
||||||
if (!cpusets_enabled()) {
|
|
||||||
return set_sched_policy(tid, policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tid == 0) {
|
|
||||||
tid = gettid();
|
|
||||||
}
|
}
|
||||||
policy = _policy(policy);
|
policy = _policy(policy);
|
||||||
pthread_once(&the_once, __initialize);
|
|
||||||
|
|
||||||
int fd = -1;
|
|
||||||
int boost_fd = -1;
|
|
||||||
switch (policy) {
|
switch (policy) {
|
||||||
case SP_BACKGROUND:
|
case SP_BACKGROUND:
|
||||||
fd = bg_cpuset_fd;
|
return SetTaskProfiles(tid,
|
||||||
boost_fd = bg_schedboost_fd;
|
{"HighEnergySaving", "ProcessCapacityLow", "TimerSlackHigh"})
|
||||||
break;
|
? 0
|
||||||
case SP_FOREGROUND:
|
: -1;
|
||||||
case SP_AUDIO_APP:
|
case SP_FOREGROUND:
|
||||||
case SP_AUDIO_SYS:
|
case SP_AUDIO_APP:
|
||||||
fd = fg_cpuset_fd;
|
case SP_AUDIO_SYS:
|
||||||
boost_fd = fg_schedboost_fd;
|
return SetTaskProfiles(tid,
|
||||||
break;
|
{"HighPerformance", "ProcessCapacityHigh", "TimerSlackNormal"})
|
||||||
case SP_TOP_APP :
|
? 0
|
||||||
fd = ta_cpuset_fd;
|
: -1;
|
||||||
boost_fd = ta_schedboost_fd;
|
case SP_TOP_APP:
|
||||||
break;
|
return SetTaskProfiles(tid,
|
||||||
case SP_SYSTEM:
|
{"MaxPerformance", "ProcessCapacityMax", "TimerSlackNormal"})
|
||||||
fd = system_bg_cpuset_fd;
|
? 0
|
||||||
break;
|
: -1;
|
||||||
case SP_RESTRICTED:
|
case SP_SYSTEM:
|
||||||
fd = rs_cpuset_fd;
|
return SetTaskProfiles(tid, {"ServiceCapacityLow", "TimerSlackNormal"}) ? 0 : -1;
|
||||||
break;
|
case SP_RESTRICTED:
|
||||||
default:
|
return SetTaskProfiles(tid, {"ServiceCapacityRestricted", "TimerSlackNormal"}) ? 0 : -1;
|
||||||
boost_fd = fd = -1;
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (add_tid_to_cgroup(tid, fd) != 0) {
|
|
||||||
if (errno != ESRCH && errno != ENOENT)
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schedboost_enabled()) {
|
|
||||||
if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
|
|
||||||
if (errno != ESRCH && errno != ENOENT)
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_timerslack_ns(int tid, unsigned long slack) {
|
int set_sched_policy(int tid, SchedPolicy policy) {
|
||||||
// v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
|
|
||||||
// TODO: once we've backported this, log if the open(2) fails.
|
|
||||||
if (__sys_supports_timerslack) {
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", tid);
|
|
||||||
int fd = open(buf, O_WRONLY | O_CLOEXEC);
|
|
||||||
if (fd != -1) {
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%lu", slack);
|
|
||||||
if (write(fd, buf, len) != len) {
|
|
||||||
SLOGE("set_timerslack_ns write failed: %s\n", strerror(errno));
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
|
|
||||||
if ((tid == 0) || (tid == gettid())) {
|
|
||||||
if (prctl(PR_SET_TIMERSLACK, slack) == -1) {
|
|
||||||
SLOGE("set_timerslack_ns prctl failed: %s\n", strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int set_sched_policy(int tid, SchedPolicy policy)
|
|
||||||
{
|
|
||||||
if (tid == 0) {
|
if (tid == 0) {
|
||||||
tid = gettid();
|
tid = GetThreadId();
|
||||||
}
|
}
|
||||||
policy = _policy(policy);
|
policy = _policy(policy);
|
||||||
pthread_once(&the_once, __initialize);
|
|
||||||
|
|
||||||
#if POLICY_DEBUG
|
#if POLICY_DEBUG
|
||||||
char statfile[64];
|
char statfile[64];
|
||||||
|
|
@ -376,91 +85,116 @@ int set_sched_policy(int tid, SchedPolicy policy)
|
||||||
snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
|
snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
|
||||||
memset(thread_name, 0, sizeof(thread_name));
|
memset(thread_name, 0, sizeof(thread_name));
|
||||||
|
|
||||||
int fd = open(statfile, O_RDONLY | O_CLOEXEC);
|
unique_fd fd(TEMP_FAILURE_RETRY(open(statfile, O_RDONLY | O_CLOEXEC)));
|
||||||
if (fd >= 0) {
|
if (fd >= 0) {
|
||||||
int rc = read(fd, statline, 1023);
|
int rc = read(fd, statline, 1023);
|
||||||
close(fd);
|
|
||||||
statline[rc] = 0;
|
statline[rc] = 0;
|
||||||
char *p = statline;
|
char* p = statline;
|
||||||
char *q;
|
char* q;
|
||||||
|
|
||||||
for (p = statline; *p != '('; p++);
|
for (p = statline; *p != '('; p++)
|
||||||
|
;
|
||||||
p++;
|
p++;
|
||||||
for (q = p; *q != ')'; q++);
|
for (q = p; *q != ')'; q++)
|
||||||
|
;
|
||||||
|
|
||||||
strncpy(thread_name, p, (q-p));
|
strncpy(thread_name, p, (q - p));
|
||||||
}
|
}
|
||||||
switch (policy) {
|
switch (policy) {
|
||||||
case SP_BACKGROUND:
|
|
||||||
SLOGD("vvv tid %d (%s)", tid, thread_name);
|
|
||||||
break;
|
|
||||||
case SP_FOREGROUND:
|
|
||||||
case SP_AUDIO_APP:
|
|
||||||
case SP_AUDIO_SYS:
|
|
||||||
case SP_TOP_APP:
|
|
||||||
SLOGD("^^^ tid %d (%s)", tid, thread_name);
|
|
||||||
break;
|
|
||||||
case SP_SYSTEM:
|
|
||||||
SLOGD("/// tid %d (%s)", tid, thread_name);
|
|
||||||
break;
|
|
||||||
case SP_RT_APP:
|
|
||||||
SLOGD("RT tid %d (%s)", tid, thread_name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SLOGD("??? tid %d (%s)", tid, thread_name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (schedboost_enabled()) {
|
|
||||||
int boost_fd = -1;
|
|
||||||
switch (policy) {
|
|
||||||
case SP_BACKGROUND:
|
case SP_BACKGROUND:
|
||||||
boost_fd = bg_schedboost_fd;
|
SLOGD("vvv tid %d (%s)", tid, thread_name);
|
||||||
break;
|
break;
|
||||||
case SP_FOREGROUND:
|
case SP_FOREGROUND:
|
||||||
case SP_AUDIO_APP:
|
case SP_AUDIO_APP:
|
||||||
case SP_AUDIO_SYS:
|
case SP_AUDIO_SYS:
|
||||||
boost_fd = fg_schedboost_fd;
|
|
||||||
break;
|
|
||||||
case SP_TOP_APP:
|
case SP_TOP_APP:
|
||||||
boost_fd = ta_schedboost_fd;
|
SLOGD("^^^ tid %d (%s)", tid, thread_name);
|
||||||
|
break;
|
||||||
|
case SP_SYSTEM:
|
||||||
|
SLOGD("/// tid %d (%s)", tid, thread_name);
|
||||||
break;
|
break;
|
||||||
case SP_RT_APP:
|
case SP_RT_APP:
|
||||||
boost_fd = rt_schedboost_fd;
|
SLOGD("RT tid %d (%s)", tid, thread_name);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
boost_fd = -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
default:
|
||||||
|
SLOGD("??? tid %d (%s)", tid, thread_name);
|
||||||
if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
|
break;
|
||||||
if (errno != ESRCH && errno != ENOENT)
|
}
|
||||||
return -errno;
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
|
switch (policy) {
|
||||||
|
case SP_BACKGROUND:
|
||||||
|
return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh"}) ? 0 : -1;
|
||||||
|
case SP_FOREGROUND:
|
||||||
|
case SP_AUDIO_APP:
|
||||||
|
case SP_AUDIO_SYS:
|
||||||
|
return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal"}) ? 0 : -1;
|
||||||
|
case SP_TOP_APP:
|
||||||
|
return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal"}) ? 0 : -1;
|
||||||
|
case SP_RT_APP:
|
||||||
|
return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal"}) ? 0 : -1;
|
||||||
|
default:
|
||||||
|
return SetTaskProfiles(tid, {"TimerSlackNormal"}) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
bool cpusets_enabled() {
|
||||||
|
static bool enabled = (CgroupMap::GetInstance().FindController("cpuset") != nullptr);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/* Stubs for non-Android targets. */
|
bool schedboost_enabled() {
|
||||||
|
static bool enabled = (CgroupMap::GetInstance().FindController("schedtune") != nullptr);
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
int set_sched_policy(int /*tid*/, SchedPolicy /*policy*/) {
|
static int getCGroupSubsys(int tid, const char* subsys, std::string& subgroup) {
|
||||||
|
const CgroupController* controller = CgroupMap::GetInstance().FindController(subsys);
|
||||||
|
|
||||||
|
if (!controller) return -1;
|
||||||
|
|
||||||
|
if (!controller->GetTaskGroup(tid, &subgroup)) {
|
||||||
|
PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_sched_policy(int /*tid*/, SchedPolicy* policy) {
|
int get_sched_policy(int tid, SchedPolicy* policy) {
|
||||||
*policy = SP_SYSTEM_DEFAULT;
|
if (tid == 0) {
|
||||||
|
tid = GetThreadId();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string group;
|
||||||
|
if (schedboost_enabled()) {
|
||||||
|
if (getCGroupSubsys(tid, "schedtune", group) < 0) return -1;
|
||||||
|
}
|
||||||
|
if (group.empty() && cpusets_enabled()) {
|
||||||
|
if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace hardcoded directories
|
||||||
|
if (group.empty()) {
|
||||||
|
*policy = SP_FOREGROUND;
|
||||||
|
} else if (group == "foreground") {
|
||||||
|
*policy = SP_FOREGROUND;
|
||||||
|
} else if (group == "system-background") {
|
||||||
|
*policy = SP_SYSTEM;
|
||||||
|
} else if (group == "background") {
|
||||||
|
*policy = SP_BACKGROUND;
|
||||||
|
} else if (group == "top-app") {
|
||||||
|
*policy = SP_TOP_APP;
|
||||||
|
} else if (group == "restricted") {
|
||||||
|
*policy = SP_RESTRICTED;
|
||||||
|
} else {
|
||||||
|
errno = ERANGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char* get_sched_policy_name(SchedPolicy policy) {
|
const char* get_sched_policy_name(SchedPolicy policy) {
|
||||||
policy = _policy(policy);
|
policy = _policy(policy);
|
||||||
static const char* const kSchedPolicyNames[] = {
|
static const char* const kSchedPolicyNames[] = {
|
||||||
|
|
|
||||||
373
libprocessgroup/task_profiles.cpp
Normal file
373
libprocessgroup/task_profiles.cpp
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#define LOG_NDEBUG 0
|
||||||
|
#define LOG_TAG "libprocessgroup"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <task_profiles.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <android-base/file.h>
|
||||||
|
#include <android-base/logging.h>
|
||||||
|
#include <android-base/stringprintf.h>
|
||||||
|
#include <android-base/threads.h>
|
||||||
|
|
||||||
|
#include <cutils/android_filesystem_config.h>
|
||||||
|
|
||||||
|
#include <json/reader.h>
|
||||||
|
#include <json/value.h>
|
||||||
|
|
||||||
|
using android::base::GetThreadId;
|
||||||
|
using android::base::StringPrintf;
|
||||||
|
using android::base::unique_fd;
|
||||||
|
using android::base::WriteStringToFile;
|
||||||
|
|
||||||
|
#define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
|
||||||
|
|
||||||
|
bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
|
||||||
|
std::string subgroup;
|
||||||
|
if (!controller_->GetTaskGroup(tid, &subgroup)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subgroup.empty()) {
|
||||||
|
*path = StringPrintf("%s/%s", controller_->path(), file_name_.c_str());
|
||||||
|
} else {
|
||||||
|
*path = StringPrintf("%s/%s/%s", controller_->path(), subgroup.c_str(), file_name_.c_str());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetClampsAction::ExecuteForProcess(uid_t, pid_t) const {
|
||||||
|
// TODO: add support when kernel supports util_clamp
|
||||||
|
LOG(WARNING) << "SetClampsAction::ExecuteForProcess is not supported";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetClampsAction::ExecuteForTask(int) const {
|
||||||
|
// TODO: add support when kernel supports util_clamp
|
||||||
|
LOG(WARNING) << "SetClampsAction::ExecuteForTask is not supported";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetTimerSlackAction::IsTimerSlackSupported(int tid) {
|
||||||
|
auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
|
||||||
|
|
||||||
|
return (access(file.c_str(), W_OK) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetTimerSlackAction::ExecuteForTask(int tid) const {
|
||||||
|
static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
|
||||||
|
|
||||||
|
// v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
|
||||||
|
// TODO: once we've backported this, log if the open(2) fails.
|
||||||
|
if (sys_supports_timerslack) {
|
||||||
|
auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
|
||||||
|
if (!WriteStringToFile(std::to_string(slack_), file)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
|
||||||
|
return ExecuteForTask(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetAttributeAction::ExecuteForTask(int tid) const {
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
if (!attribute_->GetPathForTask(tid, &path)) {
|
||||||
|
PLOG(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
|
||||||
|
return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCgroupAction::SetCgroupAction(const CgroupController* c, const std::string& p)
|
||||||
|
: controller_(c), path_(p) {
|
||||||
|
// cache file descriptor only if path is app independent
|
||||||
|
if (IsAppDependentPath(path_)) {
|
||||||
|
// file descriptor is not cached
|
||||||
|
fd_.reset(-2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string tasks_path = c->GetTasksFilePath(p.c_str());
|
||||||
|
|
||||||
|
if (access(tasks_path.c_str(), W_OK) != 0) {
|
||||||
|
// file is not accessible
|
||||||
|
fd_.reset(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_fd fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
|
||||||
|
if (fd < 0) {
|
||||||
|
PLOG(ERROR) << "Failed to cache fd '" << tasks_path << "'";
|
||||||
|
fd_.reset(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_ = std::move(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
|
||||||
|
if (tid <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string value = std::to_string(tid);
|
||||||
|
|
||||||
|
if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
|
||||||
|
// If the thread is in the process of exiting, don't flag an error
|
||||||
|
if (errno != ESRCH) {
|
||||||
|
PLOG(ERROR) << "JoinGroup failed to write '" << value << "'; fd=" << fd;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
|
||||||
|
if (fd_ >= 0) {
|
||||||
|
// fd is cached, reuse it
|
||||||
|
if (!AddTidToCgroup(pid, fd_)) {
|
||||||
|
PLOG(ERROR) << "Failed to add task into cgroup";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd_ == -1) {
|
||||||
|
// no permissions to access the file, ignore
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is app-dependent path, file descriptor is not cached
|
||||||
|
std::string procs_path = controller_->GetProcsFilePath(path_.c_str(), 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 << ": " << strerror(errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!AddTidToCgroup(pid, tmp_fd)) {
|
||||||
|
PLOG(ERROR) << "Failed to add task into cgroup";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetCgroupAction::ExecuteForTask(int tid) const {
|
||||||
|
if (fd_ >= 0) {
|
||||||
|
// fd is cached, reuse it
|
||||||
|
if (!AddTidToCgroup(tid, fd_)) {
|
||||||
|
PLOG(ERROR) << "Failed to add task into cgroup";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd_ == -1) {
|
||||||
|
// no permissions to access the file, ignore
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// application-dependent path can't be used with tid
|
||||||
|
PLOG(ERROR) << "Application profile can't be applied to a thread";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskProfile::ExecuteForProcess(uid_t uid, pid_t pid) const {
|
||||||
|
for (const auto& element : elements_) {
|
||||||
|
if (!element->ExecuteForProcess(uid, pid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskProfile::ExecuteForTask(int tid) const {
|
||||||
|
if (tid == 0) {
|
||||||
|
tid = GetThreadId();
|
||||||
|
}
|
||||||
|
for (const auto& element : elements_) {
|
||||||
|
if (!element->ExecuteForTask(tid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskProfiles& TaskProfiles::GetInstance() {
|
||||||
|
static TaskProfiles instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskProfiles::TaskProfiles() {
|
||||||
|
if (!Load(CgroupMap::GetInstance())) {
|
||||||
|
LOG(ERROR) << "TaskProfiles::Load for [" << getpid() << "] failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskProfiles::Load(const CgroupMap& cg_map) {
|
||||||
|
std::string json_doc;
|
||||||
|
|
||||||
|
if (!android::base::ReadFileToString(TASK_PROFILE_DB_FILE, &json_doc)) {
|
||||||
|
LOG(ERROR) << "Failed to read task profiles from " << TASK_PROFILE_DB_FILE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Reader reader;
|
||||||
|
Json::Value root;
|
||||||
|
if (!reader.parse(json_doc, root)) {
|
||||||
|
LOG(ERROR) << "Failed to parse task profiles: " << reader.getFormattedErrorMessages();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value attr = root["Attributes"];
|
||||||
|
for (Json::Value::ArrayIndex i = 0; i < attr.size(); ++i) {
|
||||||
|
std::string name = attr[i]["Name"].asString();
|
||||||
|
std::string ctrlName = attr[i]["Controller"].asString();
|
||||||
|
std::string file_name = attr[i]["File"].asString();
|
||||||
|
|
||||||
|
if (attributes_.find(name) == attributes_.end()) {
|
||||||
|
const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
|
||||||
|
if (controller) {
|
||||||
|
attributes_[name] = std::make_unique<ProfileAttribute>(controller, file_name);
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "Controller " << ctrlName << " is not found";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "Attribute " << name << " is already defined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::string> params;
|
||||||
|
|
||||||
|
Json::Value profilesVal = root["Profiles"];
|
||||||
|
for (Json::Value::ArrayIndex i = 0; i < profilesVal.size(); ++i) {
|
||||||
|
Json::Value profileVal = profilesVal[i];
|
||||||
|
|
||||||
|
std::string profileName = profileVal["Name"].asString();
|
||||||
|
Json::Value actions = profileVal["Actions"];
|
||||||
|
auto profile = std::make_unique<TaskProfile>();
|
||||||
|
|
||||||
|
for (Json::Value::ArrayIndex actIdx = 0; actIdx < actions.size(); ++actIdx) {
|
||||||
|
Json::Value actionVal = actions[actIdx];
|
||||||
|
std::string actionName = actionVal["Name"].asString();
|
||||||
|
Json::Value paramsVal = actionVal["Params"];
|
||||||
|
if (actionName == "JoinCgroup") {
|
||||||
|
std::string ctrlName = paramsVal["Controller"].asString();
|
||||||
|
std::string path = paramsVal["Path"].asString();
|
||||||
|
|
||||||
|
const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
|
||||||
|
if (controller) {
|
||||||
|
profile->Add(std::make_unique<SetCgroupAction>(controller, path));
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "JoinCgroup: controller " << ctrlName << " is not found";
|
||||||
|
}
|
||||||
|
} else if (actionName == "SetTimerSlack") {
|
||||||
|
std::string slackValue = paramsVal["Slack"].asString();
|
||||||
|
char* end;
|
||||||
|
unsigned long slack;
|
||||||
|
|
||||||
|
slack = strtoul(slackValue.c_str(), &end, 10);
|
||||||
|
if (end > slackValue.c_str()) {
|
||||||
|
profile->Add(std::make_unique<SetTimerSlackAction>(slack));
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetTimerSlack: invalid parameter: " << slackValue;
|
||||||
|
}
|
||||||
|
} else if (actionName == "SetAttribute") {
|
||||||
|
std::string attrName = paramsVal["Name"].asString();
|
||||||
|
std::string attrValue = paramsVal["Value"].asString();
|
||||||
|
|
||||||
|
auto iter = attributes_.find(attrName);
|
||||||
|
if (iter != attributes_.end()) {
|
||||||
|
profile->Add(
|
||||||
|
std::make_unique<SetAttributeAction>(iter->second.get(), attrValue));
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetAttribute: unknown attribute: " << attrName;
|
||||||
|
}
|
||||||
|
} else if (actionName == "SetClamps") {
|
||||||
|
std::string boostValue = paramsVal["Boost"].asString();
|
||||||
|
std::string clampValue = paramsVal["Clamp"].asString();
|
||||||
|
char* end;
|
||||||
|
unsigned long boost;
|
||||||
|
|
||||||
|
boost = strtoul(boostValue.c_str(), &end, 10);
|
||||||
|
if (end > boostValue.c_str()) {
|
||||||
|
unsigned long clamp = strtoul(clampValue.c_str(), &end, 10);
|
||||||
|
if (end > clampValue.c_str()) {
|
||||||
|
profile->Add(std::make_unique<SetClampsAction>(boost, clamp));
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetClamps: invalid parameter " << clampValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "SetClamps: invalid parameter: " << boostValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "Unknown profile action: " << actionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profiles_[profileName] = std::move(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskProfile* TaskProfiles::GetProfile(const std::string& name) const {
|
||||||
|
auto iter = profiles_.find(name);
|
||||||
|
|
||||||
|
if (iter != profiles_.end()) {
|
||||||
|
return iter->second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
|
||||||
|
auto iter = attributes_.find(name);
|
||||||
|
|
||||||
|
if (iter != attributes_.end()) {
|
||||||
|
return iter->second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
140
libprocessgroup/task_profiles.h
Normal file
140
libprocessgroup/task_profiles.h
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <android-base/unique_fd.h>
|
||||||
|
#include <cgroup_map.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_; }
|
||||||
|
|
||||||
|
bool GetPathForTask(int tid, std::string* path) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const CgroupController* controller_;
|
||||||
|
std::string file_name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abstract profile element
|
||||||
|
class ProfileAction {
|
||||||
|
public:
|
||||||
|
virtual ~ProfileAction() {}
|
||||||
|
|
||||||
|
// Default implementations will fail
|
||||||
|
virtual bool ExecuteForProcess(uid_t, pid_t) const { return -1; };
|
||||||
|
virtual bool ExecuteForTask(int) const { return -1; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Profile actions
|
||||||
|
class SetClampsAction : public ProfileAction {
|
||||||
|
public:
|
||||||
|
SetClampsAction(int boost, int clamp) noexcept : boost_(boost), clamp_(clamp) {}
|
||||||
|
|
||||||
|
virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
|
||||||
|
virtual bool ExecuteForTask(int tid) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int boost_;
|
||||||
|
int clamp_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SetTimerSlackAction : public ProfileAction {
|
||||||
|
public:
|
||||||
|
SetTimerSlackAction(unsigned long slack) noexcept : slack_(slack) {}
|
||||||
|
|
||||||
|
virtual bool ExecuteForTask(int tid) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long slack_;
|
||||||
|
|
||||||
|
static bool IsTimerSlackSupported(int tid);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set attribute profile element
|
||||||
|
class SetAttributeAction : public ProfileAction {
|
||||||
|
public:
|
||||||
|
SetAttributeAction(const ProfileAttribute* attribute, const std::string& value)
|
||||||
|
: attribute_(attribute), value_(value) {}
|
||||||
|
|
||||||
|
virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
|
||||||
|
virtual bool ExecuteForTask(int tid) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ProfileAttribute* attribute_;
|
||||||
|
std::string value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set cgroup profile element
|
||||||
|
class SetCgroupAction : public ProfileAction {
|
||||||
|
public:
|
||||||
|
SetCgroupAction(const CgroupController* c, const std::string& p);
|
||||||
|
|
||||||
|
virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
|
||||||
|
virtual bool ExecuteForTask(int tid) const;
|
||||||
|
|
||||||
|
const CgroupController* controller() const { return controller_; }
|
||||||
|
std::string path() const { return path_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const CgroupController* controller_;
|
||||||
|
std::string path_;
|
||||||
|
android::base::unique_fd fd_;
|
||||||
|
|
||||||
|
static bool IsAppDependentPath(const std::string& path);
|
||||||
|
static bool AddTidToCgroup(int tid, int fd);
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskProfile {
|
||||||
|
public:
|
||||||
|
TaskProfile() {}
|
||||||
|
|
||||||
|
void Add(std::unique_ptr<ProfileAction> e) { elements_.push_back(std::move(e)); }
|
||||||
|
|
||||||
|
bool ExecuteForProcess(uid_t uid, pid_t pid) const;
|
||||||
|
bool ExecuteForTask(int tid) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<ProfileAction>> elements_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskProfiles {
|
||||||
|
public:
|
||||||
|
// Should be used by all users
|
||||||
|
static TaskProfiles& GetInstance();
|
||||||
|
|
||||||
|
const TaskProfile* GetProfile(const std::string& name) const;
|
||||||
|
const ProfileAttribute* GetAttribute(const std::string& name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::unique_ptr<TaskProfile>> profiles_;
|
||||||
|
std::map<std::string, std::unique_ptr<ProfileAttribute>> attributes_;
|
||||||
|
|
||||||
|
TaskProfiles();
|
||||||
|
|
||||||
|
bool Load(const CgroupMap& cg_map);
|
||||||
|
};
|
||||||
|
|
@ -11,6 +11,7 @@ import /vendor/etc/init/hw/init.${ro.hardware}.rc
|
||||||
import /init.usb.configfs.rc
|
import /init.usb.configfs.rc
|
||||||
import /init.${ro.zygote}.rc
|
import /init.${ro.zygote}.rc
|
||||||
|
|
||||||
|
# Cgroups are mounted right before early-init using list from /etc/cgroups.json
|
||||||
on early-init
|
on early-init
|
||||||
# Mount shared so changes propagate into child namespaces
|
# Mount shared so changes propagate into child namespaces
|
||||||
# Do this before other processes are started from init. Otherwise,
|
# Do this before other processes are started from init. Otherwise,
|
||||||
|
|
@ -30,14 +31,8 @@ on early-init
|
||||||
# Set the security context of /postinstall if present.
|
# Set the security context of /postinstall if present.
|
||||||
restorecon /postinstall
|
restorecon /postinstall
|
||||||
|
|
||||||
# Mount cgroup mount point for cpu accounting
|
|
||||||
mount cgroup none /acct nodev noexec nosuid cpuacct
|
|
||||||
chmod 0555 /acct
|
|
||||||
mkdir /acct/uid
|
mkdir /acct/uid
|
||||||
|
|
||||||
# root memory control cgroup, used by lmkd
|
|
||||||
mkdir /dev/memcg 0700 root system
|
|
||||||
mount cgroup none /dev/memcg nodev noexec nosuid memory
|
|
||||||
# memory.pressure_level used by lmkd
|
# memory.pressure_level used by lmkd
|
||||||
chown root system /dev/memcg/memory.pressure_level
|
chown root system /dev/memcg/memory.pressure_level
|
||||||
chmod 0040 /dev/memcg/memory.pressure_level
|
chmod 0040 /dev/memcg/memory.pressure_level
|
||||||
|
|
@ -69,8 +64,6 @@ on init
|
||||||
symlink /system/vendor /vendor
|
symlink /system/vendor /vendor
|
||||||
|
|
||||||
# Create energy-aware scheduler tuning nodes
|
# Create energy-aware scheduler tuning nodes
|
||||||
mkdir /dev/stune
|
|
||||||
mount cgroup none /dev/stune nodev noexec nosuid schedtune
|
|
||||||
mkdir /dev/stune/foreground
|
mkdir /dev/stune/foreground
|
||||||
mkdir /dev/stune/background
|
mkdir /dev/stune/background
|
||||||
mkdir /dev/stune/top-app
|
mkdir /dev/stune/top-app
|
||||||
|
|
@ -164,8 +157,6 @@ on init
|
||||||
chmod 0400 /proc/net/fib_trie
|
chmod 0400 /proc/net/fib_trie
|
||||||
|
|
||||||
# Create cgroup mount points for process groups
|
# Create cgroup mount points for process groups
|
||||||
mkdir /dev/cpuctl
|
|
||||||
mount cgroup none /dev/cpuctl nodev noexec nosuid cpu
|
|
||||||
chown system system /dev/cpuctl
|
chown system system /dev/cpuctl
|
||||||
chown system system /dev/cpuctl/tasks
|
chown system system /dev/cpuctl/tasks
|
||||||
chmod 0666 /dev/cpuctl/tasks
|
chmod 0666 /dev/cpuctl/tasks
|
||||||
|
|
@ -173,9 +164,6 @@ on init
|
||||||
write /dev/cpuctl/cpu.rt_runtime_us 950000
|
write /dev/cpuctl/cpu.rt_runtime_us 950000
|
||||||
|
|
||||||
# sets up initial cpusets for ActivityManager
|
# sets up initial cpusets for ActivityManager
|
||||||
mkdir /dev/cpuset
|
|
||||||
mount cpuset none /dev/cpuset nodev noexec nosuid
|
|
||||||
|
|
||||||
# this ensures that the cpusets are present and usable, but the device's
|
# this ensures that the cpusets are present and usable, but the device's
|
||||||
# init.rc must actually set the correct cpus
|
# init.rc must actually set the correct cpus
|
||||||
mkdir /dev/cpuset/foreground
|
mkdir /dev/cpuset/foreground
|
||||||
|
|
@ -237,8 +225,6 @@ on init
|
||||||
# This is needed by any process that uses socket tagging.
|
# This is needed by any process that uses socket tagging.
|
||||||
chmod 0644 /dev/xt_qtaguid
|
chmod 0644 /dev/xt_qtaguid
|
||||||
|
|
||||||
mkdir /dev/cg2_bpf
|
|
||||||
mount cgroup2 cg2_bpf /dev/cg2_bpf nodev noexec nosuid
|
|
||||||
chown root root /dev/cg2_bpf
|
chown root root /dev/cg2_bpf
|
||||||
chmod 0600 /dev/cg2_bpf
|
chmod 0600 /dev/cg2_bpf
|
||||||
mount bpf bpf /sys/fs/bpf nodev noexec nosuid
|
mount bpf bpf /sys/fs/bpf nodev noexec nosuid
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue