diff --git a/healthd/Android.mk b/healthd/Android.mk index 2127b9603..823ed0699 100644 --- a/healthd/Android.mk +++ b/healthd/Android.mk @@ -109,6 +109,7 @@ CHARGER_STATIC_LIBRARIES := \ libbase \ libutils \ libcutils \ + libjsoncpp \ libprocessgroup \ liblog \ libm \ diff --git a/init/Android.bp b/init/Android.bp index 67688f225..639d8d1b9 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -60,7 +60,6 @@ cc_defaults { }, static_libs: [ "libseccomp_policy", - "libprocessgroup", "libavb", "libprotobuf-cpp-lite", "libpropertyinfoserializer", @@ -82,6 +81,7 @@ cc_defaults { "liblog", "liblogwrap", "liblp", + "libprocessgroup", "libselinux", "libutils", ], diff --git a/init/init.cpp b/init/init.cpp index 4f4a15f1d..5a3cc155a 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #ifndef RECOVERY @@ -347,6 +348,17 @@ static Result console_init_action(const BuiltinArguments& args) { return Success(); } +static Result SetupCgroupsAction(const BuiltinArguments&) { + // Have to create 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) { if (key.empty()) return; @@ -682,6 +694,8 @@ int SecondStageMain(int argc, char** argv) { // Nexus 9 boot time, so it's disabled by default. if (false) DumpState(); + am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups"); + am.QueueEventTrigger("early-init"); // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... diff --git a/libcutils/tests/Android.bp b/libcutils/tests/Android.bp index 72ae55921..fb9bbdd92 100644 --- a/libcutils/tests/Android.bp +++ b/libcutils/tests/Android.bp @@ -59,6 +59,7 @@ test_libraries = [ "libcutils", "liblog", "libbase", + "libjsoncpp", "libprocessgroup", ] diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp index d04a79a66..d97f09fad 100644 --- a/libprocessgroup/Android.bp +++ b/libprocessgroup/Android.bp @@ -16,8 +16,10 @@ cc_library_headers { cc_library { srcs: [ + "cgroup_map.cpp", "processgroup.cpp", "sched_policy.cpp", + "task_profiles.cpp", ], name: "libprocessgroup", host_supported: true, @@ -29,7 +31,7 @@ cc_library { }, shared_libs: [ "libbase", - "liblog", + "libjsoncpp", ], // for cutils/android_filesystem_config.h header_libs: [ diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp new file mode 100644 index 000000000..1b5f217c7 --- /dev/null +++ b/libprocessgroup/cgroup_map.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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* descriptors) { + std::vector 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 [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 nodev noexec nosuid + 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& 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(""), std::to_string(uid)); + proc_path = regex_replace(proc_path, std::regex(""), 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 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 0711 system system + if (!Mkdir(CGROUPS_RC_DIR, 0711, "system", "system")) { + PLOG(ERROR) << "Failed to create directory for file"; + return false; + } + + // Generate 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 + 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; +} diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h new file mode 100644 index 000000000..ba2caf742 --- /dev/null +++ b/libprocessgroup/cgroup_map.h @@ -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 +#include + +#include +#include +#include + +// 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(); +}; diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h index 2412f3c99..6f973b832 100644 --- a/libprocessgroup/include/processgroup/processgroup.h +++ b/libprocessgroup/include/processgroup/processgroup.h @@ -14,14 +14,28 @@ * limitations under the License. */ -#ifndef _PROCESSGROUP_H_ -#define _PROCESSGROUP_H_ +#pragma once #include #include +#include +#include __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& profiles); +bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& profiles); + // 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 // even after retrying for up to 200ms. @@ -42,5 +56,3 @@ bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes); void removeAllProcessGroups(void); __END_DECLS - -#endif diff --git a/libprocessgroup/include/processgroup/sched_policy.h b/libprocessgroup/include/processgroup/sched_policy.h index 79a32fdeb..3c498da91 100644 --- a/libprocessgroup/include/processgroup/sched_policy.h +++ b/libprocessgroup/include/processgroup/sched_policy.h @@ -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"); * 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. * 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 value: non-NULL NUL-terminated name of unspecified length; * 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 } diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp index 8d2ac3d57..e9dec1261 100644 --- a/libprocessgroup/processgroup.cpp +++ b/libprocessgroup/processgroup.cpp @@ -25,12 +25,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -43,8 +43,8 @@ #include #include #include - #include +#include using android::base::GetBoolProperty; using android::base::StartsWith; @@ -53,16 +53,103 @@ using android::base::WriteStringToFile; using namespace std::chrono_literals; -static const char kCpuacctCgroup[] = "/acct"; -static const char kMemoryCgroup[] = "/dev/memcg/apps"; - #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 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; } +bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector& 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& 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) { 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()"; - for (const char* cgroup_root_path : {kCpuacctCgroup, kMemoryCgroup}) { - std::unique_ptr root(opendir(cgroup_root_path), closedir); + + std::vector 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 root(opendir(cgroup_root_path.c_str()), closedir); if (root == NULL) { PLOG(ERROR) << "Failed to open " << cgroup_root_path; } else { @@ -121,7 +218,7 @@ void removeAllProcessGroups() 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); LOG(VERBOSE) << "Removing " << 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 0 if there are no processes in the process cgroup left to kill // 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) { + std::string cpuacct_path; + std::string memory_path; + + CgroupGetControllerPath("cpuacct", &cpuacct_path); + CgroupGetControllerPath("memory", &memory_path); + const char* cgroup = - (!access(ConvertUidPidToPath(kCpuacctCgroup, uid, initialPid).c_str(), F_OK)) - ? kCpuacctCgroup - : kMemoryCgroup; + (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK)) + ? cpuacct_path.c_str() + : memory_path.c_str(); 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*/); } -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; -} - -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; +int createProcessGroup(uid_t uid, int initialPid, bool memControl) { + std::string cgroup; + if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) { + CgroupGetControllerPath("memory", &cgroup); } 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)) { PLOG(ERROR) << "Failed to make and chown " << uid_path; 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)) { 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; } -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()) { PLOG(ERROR) << "Memcg is not mounted."; 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)) { 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; } -bool setProcessGroupSwappiness(uid_t uid, int pid, int swappiness) { - return SetProcessGroupValue(uid, pid, "/memory.swappiness", swappiness); +bool setProcessGroupSwappiness(uid_t, int pid, int swappiness) { + return SetProcessGroupValue(pid, "MemSwappiness", swappiness); } -bool setProcessGroupSoftLimit(uid_t uid, int pid, int64_t soft_limit_in_bytes) { - return SetProcessGroupValue(uid, pid, "/memory.soft_limit_in_bytes", soft_limit_in_bytes); +bool setProcessGroupSoftLimit(uid_t, int pid, int64_t soft_limit_in_bytes) { + return SetProcessGroupValue(pid, "MemSoftLimit", soft_limit_in_bytes); } -bool setProcessGroupLimit(uid_t uid, int pid, int64_t limit_in_bytes) { - return SetProcessGroupValue(uid, pid, "/memory.limit_in_bytes", limit_in_bytes); +bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) { + return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes); } diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp index f95d7e48f..4c8aa6dd7 100644 --- a/libprocessgroup/sched_policy.cpp +++ b/libprocessgroup/sched_policy.cpp @@ -1,372 +1,81 @@ /* -** Copyright 2007, 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. -*/ + * 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. + */ #include #define LOG_TAG "SchedPolicy" #include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include +#include + +using android::base::GetThreadId; /* 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. * Returns the possibly re-mapped policy. */ -static inline SchedPolicy _policy(SchedPolicy p) -{ - return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p; +static inline SchedPolicy _policy(SchedPolicy p) { + return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p; } -#if defined(__ANDROID__) - -#include -#include -#include - -#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//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) -{ +int set_cpuset_policy(int tid, SchedPolicy policy) { if (tid == 0) { - tid = gettid(); - } - 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(); + tid = GetThreadId(); } policy = _policy(policy); - pthread_once(&the_once, __initialize); - int fd = -1; - int boost_fd = -1; switch (policy) { - case SP_BACKGROUND: - fd = bg_cpuset_fd; - boost_fd = bg_schedboost_fd; - break; - case SP_FOREGROUND: - case SP_AUDIO_APP: - case SP_AUDIO_SYS: - fd = fg_cpuset_fd; - boost_fd = fg_schedboost_fd; - break; - case SP_TOP_APP : - fd = ta_cpuset_fd; - boost_fd = ta_schedboost_fd; - break; - case SP_SYSTEM: - fd = system_bg_cpuset_fd; - break; - case SP_RESTRICTED: - fd = rs_cpuset_fd; - break; - default: - boost_fd = fd = -1; - 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; - } + case SP_BACKGROUND: + return SetTaskProfiles(tid, + {"HighEnergySaving", "ProcessCapacityLow", "TimerSlackHigh"}) + ? 0 + : -1; + case SP_FOREGROUND: + case SP_AUDIO_APP: + case SP_AUDIO_SYS: + return SetTaskProfiles(tid, + {"HighPerformance", "ProcessCapacityHigh", "TimerSlackNormal"}) + ? 0 + : -1; + case SP_TOP_APP: + return SetTaskProfiles(tid, + {"MaxPerformance", "ProcessCapacityMax", "TimerSlackNormal"}) + ? 0 + : -1; + case SP_SYSTEM: + return SetTaskProfiles(tid, {"ServiceCapacityLow", "TimerSlackNormal"}) ? 0 : -1; + case SP_RESTRICTED: + return SetTaskProfiles(tid, {"ServiceCapacityRestricted", "TimerSlackNormal"}) ? 0 : -1; + default: + break; } return 0; } -static void set_timerslack_ns(int tid, unsigned long slack) { - // v4.6+ kernels support the /proc//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//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) -{ +int set_sched_policy(int tid, SchedPolicy policy) { if (tid == 0) { - tid = gettid(); + tid = GetThreadId(); } policy = _policy(policy); - pthread_once(&the_once, __initialize); #if POLICY_DEBUG char statfile[64]; @@ -376,91 +85,116 @@ int set_sched_policy(int tid, SchedPolicy policy) snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid); 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) { int rc = read(fd, statline, 1023); - close(fd); statline[rc] = 0; - char *p = statline; - char *q; + char* p = statline; + char* q; - for (p = statline; *p != '('; p++); + for (p = statline; *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) { - 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: - boost_fd = bg_schedboost_fd; + SLOGD("vvv tid %d (%s)", tid, thread_name); break; case SP_FOREGROUND: case SP_AUDIO_APP: case SP_AUDIO_SYS: - boost_fd = fg_schedboost_fd; - break; 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; case SP_RT_APP: - boost_fd = rt_schedboost_fd; - break; - default: - boost_fd = -1; + SLOGD("RT tid %d (%s)", tid, thread_name); break; - } - - if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) { - if (errno != ESRCH && errno != ENOENT) - return -errno; - } + default: + SLOGD("??? tid %d (%s)", tid, thread_name); + break; + } +#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; } -#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; } -int get_sched_policy(int /*tid*/, SchedPolicy* policy) { - *policy = SP_SYSTEM_DEFAULT; +int get_sched_policy(int tid, SchedPolicy* policy) { + 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; } -#endif - const char* get_sched_policy_name(SchedPolicy policy) { policy = _policy(policy); static const char* const kSchedPolicyNames[] = { diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp new file mode 100644 index 000000000..eb50f8539 --- /dev/null +++ b/libprocessgroup/task_profiles.cpp @@ -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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +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//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//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("", 0) != std::string::npos || path.find("", 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(controller, file_name); + } else { + LOG(WARNING) << "Controller " << ctrlName << " is not found"; + } + } else { + LOG(WARNING) << "Attribute " << name << " is already defined"; + } + } + + std::map 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(); + + 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(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(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(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(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; +} diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h new file mode 100644 index 000000000..684762aa1 --- /dev/null +++ b/libprocessgroup/task_profiles.h @@ -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 +#include +#include +#include +#include + +#include +#include + +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 e) { elements_.push_back(std::move(e)); } + + bool ExecuteForProcess(uid_t uid, pid_t pid) const; + bool ExecuteForTask(int tid) const; + + private: + std::vector> 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> profiles_; + std::map> attributes_; + + TaskProfiles(); + + bool Load(const CgroupMap& cg_map); +}; diff --git a/rootdir/init.rc b/rootdir/init.rc index 7aba3dce7..c6e211603 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -11,6 +11,7 @@ import /vendor/etc/init/hw/init.${ro.hardware}.rc import /init.usb.configfs.rc import /init.${ro.zygote}.rc +# Cgroups are mounted right before early-init using list from /etc/cgroups.json on early-init # Mount shared so changes propagate into child namespaces # 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. restorecon /postinstall - # Mount cgroup mount point for cpu accounting - mount cgroup none /acct nodev noexec nosuid cpuacct - chmod 0555 /acct 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 chown root system /dev/memcg/memory.pressure_level chmod 0040 /dev/memcg/memory.pressure_level @@ -69,8 +64,6 @@ on init symlink /system/vendor /vendor # 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/background mkdir /dev/stune/top-app @@ -164,8 +157,6 @@ on init chmod 0400 /proc/net/fib_trie # 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/tasks chmod 0666 /dev/cpuctl/tasks @@ -173,9 +164,6 @@ on init write /dev/cpuctl/cpu.rt_runtime_us 950000 # 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 # init.rc must actually set the correct cpus mkdir /dev/cpuset/foreground @@ -237,8 +225,6 @@ on init # This is needed by any process that uses socket tagging. 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 chmod 0600 /dev/cg2_bpf mount bpf bpf /sys/fs/bpf nodev noexec nosuid