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
This commit is contained in:
parent
e94eb5140c
commit
d7157c22af
5 changed files with 121 additions and 70 deletions
|
@ -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 <value>`
|
||||
> 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 <value>` and `memcg.limit_percent <value>`
|
||||
> 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 <value>`
|
||||
> 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 <value>`
|
||||
> Sets the child's memory.soft_limit_in_bytes to the specified value (only if memcg is mounted),
|
||||
|
|
|
@ -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<Success> Service::ParseMemcgLimitInBytes(std::vector<std::string>&& args)
|
|||
return Success();
|
||||
}
|
||||
|
||||
Result<Success> Service::ParseMemcgLimitPercent(std::vector<std::string>&& args) {
|
||||
if (!ParseInt(args[1], &limit_percent_, 0)) {
|
||||
return Error() << "limit_percent value must be equal or greater than 0";
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
Result<Success> Service::ParseMemcgLimitProperty(std::vector<std::string>&& args) {
|
||||
limit_property_ = std::move(args[1]);
|
||||
return Success();
|
||||
}
|
||||
|
||||
Result<Success> Service::ParseMemcgSoftLimitInBytes(std::vector<std::string>&& 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<Success> 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<Success> 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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,8 @@ class Service {
|
|||
Result<Success> ParseOomScoreAdjust(std::vector<std::string>&& args);
|
||||
Result<Success> ParseOverride(std::vector<std::string>&& args);
|
||||
Result<Success> ParseMemcgLimitInBytes(std::vector<std::string>&& args);
|
||||
Result<Success> ParseMemcgLimitPercent(std::vector<std::string>&& args);
|
||||
Result<Success> ParseMemcgLimitProperty(std::vector<std::string>&& args);
|
||||
Result<Success> ParseMemcgSoftLimitInBytes(std::vector<std::string>&& args);
|
||||
Result<Success> ParseMemcgSwappiness(std::vector<std::string>&& args);
|
||||
Result<Success> ParseNamespace(std::vector<std::string>&& 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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<DIR, decltype(&closedir)> 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<DIR, decltype(&closedir)> 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<FILE, decltype(&fclose)> 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<int>(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;
|
||||
|
|
Loading…
Reference in a new issue