From d7157c22afd3fef378d0105287ae0b60deef9665 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Tue, 30 Oct 2018 15:49:33 -0700 Subject: [PATCH] Introduce additional service options for controlling memory cgroups. The memcg.limit_percent option can be used to limit the cgroup's max RSS to the given value as a percentage of the device's physical memory. The memcg.limit_property option specifies the name of a property that can be used to control the cgroup's max RSS. These new options correspond to the arguments to the limitProcessMemory function in frameworks/av/media/libmedia/MediaUtils.cpp; this will allow us to add these options to the rc files for the programs that call this function and then remove the callers in a later change. There is also a change in semantics: the memcg.* options now have an effect on all devices which support memory cgroups, not just those with ro.config.low_ram or ro.config.per_app_memcg set to true. This change also brings the semantics in line with the documentation, so it looks like the previous semantics were unintentional. Change-Id: I9495826de6e477b952e23866743b5fa600adcacb Bug: 118642754 --- init/README.md | 13 +- init/service.cpp | 50 ++++++-- init/service.h | 11 +- .../include/processgroup/processgroup.h | 4 +- libprocessgroup/processgroup.cpp | 113 +++++++++--------- 5 files changed, 121 insertions(+), 70 deletions(-) diff --git a/init/README.md b/init/README.md index 6c51b3735..2c531df60 100644 --- a/init/README.md +++ b/init/README.md @@ -235,9 +235,16 @@ runs the service. to "123,124,125". Since keycodes are handled very early in init, only PRODUCT_DEFAULT_PROPERTY_OVERRIDES properties can be used. -`memcg.limit_in_bytes ` -> Sets the child's memory.limit_in_bytes to the specified value (only if memcg is mounted), - which must be equal or greater than 0. +`memcg.limit_in_bytes ` and `memcg.limit_percent ` +> Sets the child's memory.limit_in_bytes to the minimum of `limit_in_bytes` + bytes and `limit_percent` which is interpreted as a percentage of the size + of the device's physical memory (only if memcg is mounted). + Values must be equal or greater than 0. + +`memcg.limit_property ` +> Sets the child's memory.limit_in_bytes to the value of the specified property + (only if memcg is mounted). This property will override the values specified + via `memcg.limit_in_bytes` and `memcg.limit_percent`. `memcg.soft_limit_in_bytes ` > Sets the child's memory.soft_limit_in_bytes to the specified value (only if memcg is mounted), diff --git a/init/service.cpp b/init/service.cpp index 7f4942373..1bda7ecfb 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -235,9 +235,6 @@ Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid, ioprio_pri_(0), priority_(0), oom_score_adjust_(-1000), - swappiness_(-1), - soft_limit_in_bytes_(-1), - limit_in_bytes_(-1), start_order_(0), args_(args) {} @@ -630,6 +627,18 @@ Result Service::ParseMemcgLimitInBytes(std::vector&& args) return Success(); } +Result Service::ParseMemcgLimitPercent(std::vector&& args) { + if (!ParseInt(args[1], &limit_percent_, 0)) { + return Error() << "limit_percent value must be equal or greater than 0"; + } + return Success(); +} + +Result Service::ParseMemcgLimitProperty(std::vector&& args) { + limit_property_ = std::move(args[1]); + return Success(); +} + Result Service::ParseMemcgSoftLimitInBytes(std::vector&& args) { if (!ParseInt(args[1], &soft_limit_in_bytes_, 0)) { return Error() << "soft_limit_in_bytes value must be equal or greater than 0"; @@ -783,6 +792,10 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const { {"keycodes", {1, kMax, &Service::ParseKeycodes}}, {"memcg.limit_in_bytes", {1, 1, &Service::ParseMemcgLimitInBytes}}, + {"memcg.limit_percent", + {1, 1, &Service::ParseMemcgLimitPercent}}, + {"memcg.limit_property", + {1, 1, &Service::ParseMemcgLimitProperty}}, {"memcg.soft_limit_in_bytes", {1, 1, &Service::ParseMemcgSoftLimitInBytes}}, {"memcg.swappiness", @@ -1001,11 +1014,13 @@ Result Service::Start() { start_order_ = next_start_order_++; process_cgroup_empty_ = false; - errno = -createProcessGroup(uid_, pid_); + bool use_memcg = swappiness_ != -1 || soft_limit_in_bytes_ != -1 || limit_in_bytes_ != -1 || + limit_percent_ != -1 || !limit_property_.empty(); + errno = -createProcessGroup(uid_, pid_, use_memcg); if (errno != 0) { PLOG(ERROR) << "createProcessGroup(" << uid_ << ", " << pid_ << ") failed for service '" << name_ << "'"; - } else { + } else if (use_memcg) { if (swappiness_ != -1) { if (!setProcessGroupSwappiness(uid_, pid_, swappiness_)) { PLOG(ERROR) << "setProcessGroupSwappiness failed"; @@ -1018,8 +1033,29 @@ Result Service::Start() { } } - if (limit_in_bytes_ != -1) { - if (!setProcessGroupLimit(uid_, pid_, limit_in_bytes_)) { + size_t computed_limit_in_bytes = limit_in_bytes_; + if (limit_percent_ != -1) { + long page_size = sysconf(_SC_PAGESIZE); + long num_pages = sysconf(_SC_PHYS_PAGES); + if (page_size > 0 && num_pages > 0) { + size_t max_mem = SIZE_MAX; + if (size_t(num_pages) < SIZE_MAX / size_t(page_size)) { + max_mem = size_t(num_pages) * size_t(page_size); + } + computed_limit_in_bytes = + std::min(computed_limit_in_bytes, max_mem / 100 * limit_percent_); + } + } + + if (!limit_property_.empty()) { + // This ends up overwriting computed_limit_in_bytes but only if the + // property is defined. + computed_limit_in_bytes = android::base::GetUintProperty( + limit_property_, computed_limit_in_bytes, SIZE_MAX); + } + + if (computed_limit_in_bytes != size_t(-1)) { + if (!setProcessGroupLimit(uid_, pid_, computed_limit_in_bytes)) { PLOG(ERROR) << "setProcessGroupLimit failed"; } } diff --git a/init/service.h b/init/service.h index c7beee926..49b09ceca 100644 --- a/init/service.h +++ b/init/service.h @@ -154,6 +154,8 @@ class Service { Result ParseOomScoreAdjust(std::vector&& args); Result ParseOverride(std::vector&& args); Result ParseMemcgLimitInBytes(std::vector&& args); + Result ParseMemcgLimitPercent(std::vector&& args); + Result ParseMemcgLimitProperty(std::vector&& args); Result ParseMemcgSoftLimitInBytes(std::vector&& args); Result ParseMemcgSwappiness(std::vector&& args); Result ParseNamespace(std::vector&& args); @@ -213,9 +215,12 @@ class Service { int oom_score_adjust_; - int swappiness_; - int soft_limit_in_bytes_; - int limit_in_bytes_; + int swappiness_ = -1; + int soft_limit_in_bytes_ = -1; + + int limit_in_bytes_ = -1; + int limit_percent_ = -1; + std::string limit_property_; bool process_cgroup_empty_ = false; diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h index 9fa4154ab..2412f3c99 100644 --- a/libprocessgroup/include/processgroup/processgroup.h +++ b/libprocessgroup/include/processgroup/processgroup.h @@ -31,8 +31,10 @@ int killProcessGroup(uid_t uid, int initialPid, int signal); // that it only returns 0 in the case that the cgroup exists and it contains no processes. int killProcessGroupOnce(uid_t uid, int initialPid, int signal); -int createProcessGroup(uid_t uid, int initialPid); +int createProcessGroup(uid_t uid, int initialPid, bool memControl = false); +// Set various properties of a process group. For these functions to work, the process group must +// have been created by passing memControl=true to createProcessGroup. bool setProcessGroupSwappiness(uid_t uid, int initialPid, int swappiness); bool setProcessGroupSoftLimit(uid_t uid, int initialPid, int64_t softLimitInBytes); bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes); diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp index 0a42053df..9df8dd96f 100644 --- a/libprocessgroup/processgroup.cpp +++ b/libprocessgroup/processgroup.cpp @@ -53,49 +53,31 @@ using android::base::WriteStringToFile; using namespace std::chrono_literals; -#define MEM_CGROUP_PATH "/dev/memcg/apps" -#define MEM_CGROUP_TASKS "/dev/memcg/apps/tasks" -#define ACCT_CGROUP_PATH "/acct" +static const char kCpuacctCgroup[] = "/acct"; +static const char kMemoryCgroup[] = "/dev/memcg/apps"; #define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs" -std::once_flag init_path_flag; - -static const std::string& GetCgroupRootPath() { - static std::string cgroup_root_path; - std::call_once(init_path_flag, [&]() { - // low-ram devices use per-app memcg by default, unlike high-end ones - bool low_ram_device = GetBoolProperty("ro.config.low_ram", false); - bool per_app_memcg = - GetBoolProperty("ro.config.per_app_memcg", low_ram_device); - if (per_app_memcg) { - // Check if mem cgroup is mounted, only then check for - // write-access to avoid SELinux denials - cgroup_root_path = - (access(MEM_CGROUP_TASKS, F_OK) || access(MEM_CGROUP_PATH, W_OK) ? - ACCT_CGROUP_PATH : MEM_CGROUP_PATH); - } else { - cgroup_root_path = ACCT_CGROUP_PATH; - } - }); - return cgroup_root_path; +static bool isMemoryCgroupSupported() { + static bool memcg_supported = !access("/dev/memcg/memory.limit_in_bytes", F_OK); + return memcg_supported; } -static std::string ConvertUidToPath(uid_t uid) { - return StringPrintf("%s/uid_%d", GetCgroupRootPath().c_str(), uid); +static std::string ConvertUidToPath(const char* cgroup, uid_t uid) { + return StringPrintf("%s/uid_%d", cgroup, uid); } -static std::string ConvertUidPidToPath(uid_t uid, int pid) { - return StringPrintf("%s/uid_%d/pid_%d", GetCgroupRootPath().c_str(), uid, pid); +static std::string ConvertUidPidToPath(const char* cgroup, uid_t uid, int pid) { + return StringPrintf("%s/uid_%d/pid_%d", cgroup, uid, pid); } -static int RemoveProcessGroup(uid_t uid, int pid) { +static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid) { int ret; - auto uid_pid_path = ConvertUidPidToPath(uid, pid); + auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid); ret = rmdir(uid_pid_path.c_str()); - auto uid_path = ConvertUidToPath(uid); + auto uid_path = ConvertUidToPath(cgroup, uid); rmdir(uid_path.c_str()); return ret; @@ -124,25 +106,26 @@ static void RemoveUidProcessGroups(const std::string& uid_path) { void removeAllProcessGroups() { LOG(VERBOSE) << "removeAllProcessGroups()"; - const auto& cgroup_root_path = GetCgroupRootPath(); - std::unique_ptr root(opendir(cgroup_root_path.c_str()), closedir); - if (root == NULL) { - PLOG(ERROR) << "Failed to open " << cgroup_root_path; - } else { - dirent* dir; - while ((dir = readdir(root.get())) != nullptr) { - if (dir->d_type != DT_DIR) { - continue; - } + for (const char* cgroup_root_path : {kCpuacctCgroup, kMemoryCgroup}) { + std::unique_ptr root(opendir(cgroup_root_path), closedir); + if (root == NULL) { + PLOG(ERROR) << "Failed to open " << cgroup_root_path; + } else { + dirent* dir; + while ((dir = readdir(root.get())) != nullptr) { + if (dir->d_type != DT_DIR) { + continue; + } - if (!StartsWith(dir->d_name, "uid_")) { - continue; - } + if (!StartsWith(dir->d_name, "uid_")) { + continue; + } - 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; + auto path = StringPrintf("%s/%s", cgroup_root_path, dir->d_name); + RemoveUidProcessGroups(path); + LOG(VERBOSE) << "Removing " << path; + if (rmdir(path.c_str()) == -1) PLOG(WARNING) << "Failed to remove " << path; + } } } } @@ -150,8 +133,8 @@ void removeAllProcessGroups() // 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 -static int DoKillProcessGroupOnce(uid_t uid, int initialPid, int signal) { - auto path = ConvertUidPidToPath(uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE; +static int DoKillProcessGroupOnce(const char* cgroup, uid_t uid, int initialPid, int signal) { + auto path = ConvertUidPidToPath(cgroup, uid, initialPid) + PROCESSGROUP_CGROUP_PROCS_FILE; std::unique_ptr fd(fopen(path.c_str(), "re"), fclose); if (!fd) { PLOG(WARNING) << "Failed to open process cgroup uid " << uid << " pid " << initialPid; @@ -217,11 +200,16 @@ static int DoKillProcessGroupOnce(uid_t uid, int initialPid, int signal) { } static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) { + const char* cgroup = + (!access(ConvertUidPidToPath(kCpuacctCgroup, uid, initialPid).c_str(), F_OK)) + ? kCpuacctCgroup + : kMemoryCgroup; + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); int retry = retries; int processes; - while ((processes = DoKillProcessGroupOnce(uid, initialPid, signal)) > 0) { + while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) { LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid; if (retry > 0) { std::this_thread::sleep_for(5ms); @@ -251,7 +239,7 @@ static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid << " in " << static_cast(ms) << "ms"; } - return RemoveProcessGroup(uid, initialPid); + return RemoveProcessGroup(cgroup, uid, initialPid); } else { if (retries > 0) { LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid @@ -285,16 +273,29 @@ static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t return true; } -int createProcessGroup(uid_t uid, int initialPid) +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) { - auto uid_path = ConvertUidToPath(uid); + const char* cgroup; + if (isMemoryCgroupSupported() && (memControl || isPerAppMemcgEnabled())) { + cgroup = kMemoryCgroup; + } else { + cgroup = kCpuacctCgroup; + } + + auto uid_path = ConvertUidToPath(cgroup, 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(uid, initialPid); + auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, initialPid); if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) { PLOG(ERROR) << "Failed to make and chown " << uid_pid_path; @@ -313,12 +314,12 @@ int createProcessGroup(uid_t uid, int initialPid) } static bool SetProcessGroupValue(uid_t uid, int pid, const std::string& file_name, int64_t value) { - if (GetCgroupRootPath() != MEM_CGROUP_PATH) { + if (!isMemoryCgroupSupported()) { PLOG(ERROR) << "Memcg is not mounted."; return false; } - auto path = ConvertUidPidToPath(uid, pid) + file_name; + auto path = ConvertUidPidToPath(kMemoryCgroup, uid, pid) + file_name; if (!WriteStringToFile(std::to_string(value), path)) { PLOG(ERROR) << "Failed to write '" << value << "' to " << path;