libprocessgroup: Remove cgroup.rc file

The cgroup.rc file was introduced in 192aee782 ("libprocessgroup: Add
support for task profiles") back with the initial support for task
profiles. It was intended to optimize performance associated with cgroup
operations. However over time, supporting this file led to making
libprocessgroup code more complicated (such as the cgrouprc LLNDK
interface), and the file ended up getting mmaped into nearly every
process on Android even though only a handful of them actually use it.
Replacing this file with reading and parsing of cgroup information on
demand allows us to simplify and shrink libprocessgroup, and eliminates
thousands of unused mappings without negatively affecting boot time or
other performance metrics.

Bug: 349105928
Test: Verified with memcg v2 and MaxActivationDepth 1 on Cuttlefish, Raven, and Mokey
Change-Id: Ic3f01fdf7fda89a56ab80657e1cf4573156273e6
This commit is contained in:
T.J. Mercier 2024-08-08 16:11:21 +00:00
parent 8dd9d45534
commit ae4ce8ccc5
23 changed files with 365 additions and 389 deletions

View file

@ -85,9 +85,9 @@ cc_library_static {
"libsnapshot_cow",
"liburing",
"libprocessgroup",
"libprocessgroup_util",
"libjsoncpp",
"libcgrouprc",
"libcgrouprc_format",
],
include_dirs: ["bionic/libc/kernel"],
export_include_dirs: ["include"],
@ -129,9 +129,9 @@ cc_defaults {
"libsnapshot_cow",
"libsnapuserd",
"libprocessgroup",
"libprocessgroup_util",
"libjsoncpp",
"libcgrouprc",
"libcgrouprc_format",
"libsnapuserd_client",
"libz",
"liblz4",
@ -221,9 +221,9 @@ cc_defaults {
"libsnapshot_cow",
"libsnapuserd",
"libprocessgroup",
"libprocessgroup_util",
"libjsoncpp",
"libcgrouprc",
"libcgrouprc_format",
"liburing",
"libz",
],
@ -319,7 +319,6 @@ cc_binary_host {
"libprocessgroup",
"libjsoncpp",
"libcgrouprc",
"libcgrouprc_format",
"liburing",
"libz",
],

View file

@ -163,7 +163,6 @@ libinit_cc_defaults {
"libavb",
"libavf_cc_flags",
"libbootloader_message",
"libcgrouprc_format",
"liblmkd_utils",
"liblz4",
"libzstd",

View file

@ -636,9 +636,6 @@ static Result<void> SetupCgroupsAction(const BuiltinArguments&) {
LOG(INFO) << "Cgroups support in kernel is not enabled";
return {};
}
// Have to create <CGROUPS_RC_DIR> using make_dir function
// for appropriate sepolicy to be set for it
make_dir(android::base::Dirname(CGROUPS_RC_PATH), 0711);
if (!CgroupSetup()) {
return ErrnoError() << "Failed to setup cgroups";
}

View file

@ -301,7 +301,7 @@ cc_defaults {
android: {
static_executable: true,
static_libs: [
"libcgrouprc_format",
"libprocessgroup_util",
] + test_libraries + always_static_test_libraries,
},
not_windows: {

View file

@ -17,7 +17,7 @@ soong_config_module_type {
libprocessgroup_flag_aware_cc_defaults {
name: "libprocessgroup_build_flags_cc",
cpp_std: "gnu++20",
cpp_std: "gnu++23",
soong_config_variables: {
memcg_v2_force_enabled: {
cflags: [
@ -116,5 +116,6 @@ cc_test {
],
static_libs: [
"libgmock",
"libprocessgroup_util",
],
}

View file

@ -151,7 +151,7 @@ bool CgroupMap::LoadRcFile() {
void CgroupMap::Print() const {
if (!loaded_) {
LOG(ERROR) << "CgroupMap::Print called for [" << getpid()
<< "] failed, RC file was not initialized properly";
<< "] failed, cgroups were not initialized properly";
return;
}
LOG(INFO) << "File version = " << ACgroupFile_getVersion();
@ -221,7 +221,7 @@ int CgroupMap::ActivateControllers(const std::string& path) const {
if (__builtin_available(android 36, *)) {
max_activation_depth = ACgroupController_getMaxActivationDepth(controller);
}
const int depth = util::GetCgroupDepth(ACgroupController_getPath(controller), path);
const int depth = GetCgroupDepth(ACgroupController_getPath(controller), path);
if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) {
std::string str("+");

View file

@ -49,7 +49,8 @@ cc_library {
"libbase",
],
static_libs: [
"libcgrouprc_format",
"libjsoncpp",
"libprocessgroup_util",
],
stubs: {
symbol_file: "libcgrouprc.map.txt",

View file

@ -14,93 +14,51 @@
* limitations under the License.
*/
#include <sys/mman.h>
#include <sys/stat.h>
#include <memory>
#include <iterator>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <android/cgrouprc.h>
#include <processgroup/processgroup.h>
#include <processgroup/util.h>
#include "cgrouprc_internal.h"
using android::base::StringPrintf;
using android::base::unique_fd;
using android::cgrouprc::format::CgroupController;
using android::cgrouprc::format::CgroupFile;
static CgroupFile* LoadRcFile() {
struct stat sb;
unique_fd fd(TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_RDONLY | O_CLOEXEC)));
if (fd < 0) {
PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH;
static CgroupDescriptorMap* LoadDescriptors() {
CgroupDescriptorMap* descriptors = new CgroupDescriptorMap;
if (!ReadDescriptors(descriptors)) {
LOG(ERROR) << "Failed to load cgroup description file";
return nullptr;
}
if (fstat(fd, &sb) < 0) {
PLOG(ERROR) << "fstat() failed for " << CGROUPS_RC_PATH;
return nullptr;
}
size_t file_size = sb.st_size;
if (file_size < sizeof(CgroupFile)) {
LOG(ERROR) << "Invalid file format " << CGROUPS_RC_PATH;
return nullptr;
}
CgroupFile* file_data = (CgroupFile*)mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
if (file_data == MAP_FAILED) {
PLOG(ERROR) << "Failed to mmap " << CGROUPS_RC_PATH;
return nullptr;
}
if (file_data->version_ != CgroupFile::FILE_CURR_VERSION) {
LOG(ERROR) << CGROUPS_RC_PATH << " file version mismatch";
munmap(file_data, file_size);
return nullptr;
}
auto expected = sizeof(CgroupFile) + file_data->controller_count_ * sizeof(CgroupController);
if (file_size != expected) {
LOG(ERROR) << CGROUPS_RC_PATH << " file has invalid size, expected " << expected
<< ", actual " << file_size;
munmap(file_data, file_size);
return nullptr;
}
return file_data;
return descriptors;
}
static CgroupFile* GetInstance() {
static const CgroupDescriptorMap* GetInstance() {
// Deliberately leak this object (not munmap) to avoid a race between destruction on
// process exit and concurrent access from another thread.
static auto* file = LoadRcFile();
return file;
static const CgroupDescriptorMap* descriptors = LoadDescriptors();
return descriptors;
}
uint32_t ACgroupFile_getVersion() {
auto file = GetInstance();
if (file == nullptr) return 0;
return file->version_;
static constexpr uint32_t FILE_VERSION_1 = 1;
auto descriptors = GetInstance();
if (descriptors == nullptr) return 0;
// There has only ever been one version, and there will be no more since cgroup.rc is no more
return FILE_VERSION_1;
}
uint32_t ACgroupFile_getControllerCount() {
auto file = GetInstance();
if (file == nullptr) return 0;
return file->controller_count_;
auto descriptors = GetInstance();
if (descriptors == nullptr) return 0;
return descriptors->size();
}
const ACgroupController* ACgroupFile_getController(uint32_t index) {
auto file = GetInstance();
if (file == nullptr) return nullptr;
CHECK(index < file->controller_count_);
auto descriptors = GetInstance();
if (descriptors == nullptr) return nullptr;
CHECK(index < descriptors->size());
// Although the object is not actually an ACgroupController object, all ACgroupController_*
// functions implicitly convert ACgroupController* back to CgroupController* before invoking
// member functions.
return static_cast<ACgroupController*>(&file->controllers_[index]);
const CgroupController* p = std::next(descriptors->begin(), index)->second.controller();
return static_cast<const ACgroupController*>(p);
}

View file

@ -16,9 +16,6 @@
#pragma once
#include <android/cgrouprc.h>
#include <processgroup/cgroup_controller.h>
#include <processgroup/format/cgroup_controller.h>
#include <processgroup/format/cgroup_file.h>
struct ACgroupController : android::cgrouprc::format::CgroupController {};
struct ACgroupController : CgroupController {};

View file

@ -23,17 +23,4 @@ cc_library_static {
vendor_ramdisk_available: true,
recovery_available: true,
native_bridge_supported: true,
srcs: [
"cgroup_controller.cpp",
],
cflags: [
"-Wall",
"-Werror",
],
export_include_dirs: [
"include",
],
shared_libs: [
"libbase",
],
}

View file

@ -57,7 +57,7 @@ __BEGIN_DECLS
bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
static constexpr const char* CGROUPS_RC_PATH = "/dev/cgroup_info/cgroup.rc";
[[deprecated]] static constexpr const char* CGROUPS_RC_PATH = "/dev/cgroup_info/cgroup.rc";
bool UsePerAppMemcg();

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* Copyright (C) 2024 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.
@ -16,23 +16,6 @@
#pragma once
#include <cstdint>
#include <string>
#include <processgroup/format/cgroup_controller.h>
namespace android {
namespace cgrouprc {
namespace format {
struct CgroupFile {
uint32_t version_;
uint32_t controller_count_;
CgroupController controllers_[];
static constexpr uint32_t FILE_VERSION_1 = 1;
static constexpr uint32_t FILE_CURR_VERSION = FILE_VERSION_1;
};
} // namespace format
} // namespace cgrouprc
} // namespace android
static const std::string CGROUP_V2_ROOT_DEFAULT = "/sys/fs/cgroup";

View file

@ -33,7 +33,6 @@ cc_library_shared {
"libjsoncpp",
],
static_libs: [
"libcgrouprc_format",
"libprocessgroup_util",
],
header_libs: [

View file

@ -21,10 +21,7 @@
#include <sys/stat.h>
#include <processgroup/format/cgroup_controller.h>
namespace android {
namespace cgrouprc {
#include <processgroup/cgroup_controller.h>
// Complete controller description for mounting cgroups
class CgroupDescriptor {
@ -33,7 +30,7 @@ class CgroupDescriptor {
mode_t mode, const std::string& uid, const std::string& gid, uint32_t flags,
uint32_t max_activation_depth);
const format::CgroupController* controller() const { return &controller_; }
const CgroupController* controller() const { return &controller_; }
mode_t mode() const { return mode_; }
std::string uid() const { return uid_; }
std::string gid() const { return gid_; }
@ -41,11 +38,8 @@ class CgroupDescriptor {
void set_mounted(bool mounted);
private:
format::CgroupController controller_;
CgroupController controller_;
mode_t mode_ = 0;
std::string uid_;
std::string gid_;
};
} // namespace cgrouprc
} // namespace android

View file

@ -22,45 +22,28 @@
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <optional>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <android/cgrouprc.h>
#include <json/reader.h>
#include <json/value.h>
#include <processgroup/format/cgroup_file.h>
#include <processgroup/cgroup_descriptor.h>
#include <processgroup/processgroup.h>
#include <processgroup/setup.h>
#include <processgroup/util.h>
#include "../build_flags.h"
#include "cgroup_descriptor.h"
using android::base::GetUintProperty;
using android::base::StringPrintf;
using android::base::unique_fd;
namespace android {
namespace cgrouprc {
#include "../internal.h"
static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
static const std::string CGROUP_V2_ROOT_DEFAULT = "/sys/fs/cgroup";
static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid,
const std::string& gid, bool permissive_mode = false) {
uid_t pw_uid = -1;
@ -148,149 +131,15 @@ static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid,
return true;
}
static void MergeCgroupToDescriptors(std::map<std::string, CgroupDescriptor>* descriptors,
const Json::Value& cgroup, const std::string& name,
const std::string& root_path, int cgroups_version) {
const std::string cgroup_path = cgroup["Path"].asString();
std::string path;
if (!root_path.empty()) {
path = root_path;
if (cgroup_path != ".") {
path += "/";
path += cgroup_path;
}
} else {
path = cgroup_path;
}
uint32_t controller_flags = 0;
if (cgroup["NeedsActivation"].isBool() && cgroup["NeedsActivation"].asBool()) {
controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
}
if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
}
uint32_t max_activation_depth = UINT32_MAX;
if (cgroup.isMember("MaxActivationDepth")) {
max_activation_depth = cgroup["MaxActivationDepth"].asUInt();
}
CgroupDescriptor descriptor(
cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags,
max_activation_depth);
auto iter = descriptors->find(name);
if (iter == descriptors->end()) {
descriptors->emplace(name, descriptor);
} else {
iter->second = descriptor;
}
}
static const bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
static bool ReadDescriptorsFromFile(const std::string& file_name,
std::map<std::string, CgroupDescriptor>* descriptors) {
std::vector<CgroupDescriptor> result;
std::string json_doc;
if (!android::base::ReadFileToString(file_name, &json_doc)) {
PLOG(ERROR) << "Failed to read task profiles from " << file_name;
return false;
}
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value root;
std::string errorMessage;
if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage;
return false;
}
if (root.isMember("Cgroups")) {
const Json::Value& cgroups = root["Cgroups"];
for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
std::string name = cgroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") continue;
MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
}
}
bool memcgv2_present = false;
std::string root_path;
if (root.isMember("Cgroups2")) {
const Json::Value& cgroups2 = root["Cgroups2"];
root_path = cgroups2["Path"].asString();
MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2);
const Json::Value& childGroups = cgroups2["Controllers"];
for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
std::string name = childGroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") memcgv2_present = true;
MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
}
}
if (force_memcg_v2 && !memcgv2_present) {
LOG(INFO) << "Forcing memcg to v2 hierarchy";
Json::Value memcgv2;
memcgv2["Controller"] = "memory";
memcgv2["NeedsActivation"] = true;
memcgv2["Path"] = ".";
memcgv2["Optional"] = true; // In case of cgroup_disabled=memory, so we can still boot
MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
}
return true;
}
static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
// load system cgroup descriptors
if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
return false;
}
// load API-level specific system cgroups descriptors if available
unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
if (api_level > 0) {
std::string api_cgroups_path =
android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
return false;
}
}
}
// load vendor cgroup descriptors if the file exists
if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
!ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
return false;
}
return true;
}
// To avoid issues in sdk_mac build
#if defined(__ANDROID__)
static bool IsOptionalController(const format::CgroupController* controller) {
static bool IsOptionalController(const CgroupController* controller) {
return controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
}
static bool MountV2CgroupController(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
const CgroupController* controller = descriptor.controller();
// /sys/fs/cgroup is created by cgroup2 with specific selinux permissions,
// try to create again in case the mount point is changed
@ -324,7 +173,7 @@ static bool MountV2CgroupController(const CgroupDescriptor& descriptor) {
}
static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
const CgroupController* controller = descriptor.controller();
if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
@ -338,7 +187,7 @@ static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
std::string path = controller->path();
path += "/cgroup.subtree_control";
if (!base::WriteStringToFile(str, path)) {
if (!android::base::WriteStringToFile(str, path)) {
if (IsOptionalController(controller)) {
PLOG(INFO) << "Failed to activate optional controller " << controller->name()
<< " at " << path;
@ -353,7 +202,7 @@ static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
}
static bool MountV1CgroupController(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
const CgroupController* controller = descriptor.controller();
// mkdir <path> [mode] [owner] [group]
if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
@ -388,7 +237,7 @@ static bool MountV1CgroupController(const CgroupDescriptor& descriptor) {
}
static bool SetupCgroup(const CgroupDescriptor& descriptor) {
const format::CgroupController* controller = descriptor.controller();
const CgroupController* controller = descriptor.controller();
if (controller->version() == 2) {
if (!strcmp(controller->name(), CGROUPV2_HIERARCHY_NAME)) {
@ -410,35 +259,6 @@ static bool SetupCgroup(const CgroupDescriptor&) {
#endif
static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
unique_fd fd(TEMP_FAILURE_RETRY(open(CGROUPS_RC_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
S_IRUSR | S_IRGRP | S_IROTH)));
if (fd < 0) {
PLOG(ERROR) << "open() failed for " << CGROUPS_RC_PATH;
return false;
}
format::CgroupFile fl;
fl.version_ = format::CgroupFile::FILE_CURR_VERSION;
fl.controller_count_ = descriptors.size();
int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl)));
if (ret < 0) {
PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
return false;
}
for (const auto& [name, descriptor] : descriptors) {
ret = TEMP_FAILURE_RETRY(
write(fd, descriptor.controller(), sizeof(format::CgroupController)));
if (ret < 0) {
PLOG(ERROR) << "write() failed for " << CGROUPS_RC_PATH;
return false;
}
}
return true;
}
CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
const std::string& path, mode_t mode, const std::string& uid,
const std::string& gid, uint32_t flags,
@ -458,9 +278,6 @@ void CgroupDescriptor::set_mounted(bool mounted) {
controller_.set_flags(flags);
}
} // namespace cgrouprc
} // namespace android
static std::optional<bool> MGLRUDisabled() {
const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
std::string content;
@ -472,9 +289,8 @@ static std::optional<bool> MGLRUDisabled() {
return content == "0x0000";
}
static std::optional<bool> MEMCGDisabled(
const std::map<std::string, android::cgrouprc::CgroupDescriptor>& descriptors) {
std::string cgroup_v2_root = android::cgrouprc::CGROUP_V2_ROOT_DEFAULT;
static std::optional<bool> MEMCGDisabled(const CgroupDescriptorMap& descriptors) {
std::string cgroup_v2_root = CGROUP_V2_ROOT_DEFAULT;
const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
if (it == descriptors.end()) {
LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
@ -495,14 +311,10 @@ static std::optional<bool> MEMCGDisabled(
return content.find("memory") == std::string::npos;
}
static bool CreateV2SubHierarchy(
const std::string& path,
const std::map<std::string, android::cgrouprc::CgroupDescriptor>& descriptors) {
using namespace android::cgrouprc;
static bool CreateV2SubHierarchy(const std::string& path, const CgroupDescriptorMap& descriptors) {
const auto cgv2_iter = descriptors.find(CGROUPV2_HIERARCHY_NAME);
if (cgv2_iter == descriptors.end()) return false;
const android::cgrouprc::CgroupDescriptor cgv2_descriptor = cgv2_iter->second;
const CgroupDescriptor cgv2_descriptor = cgv2_iter->second;
if (!Mkdir(path, cgv2_descriptor.mode(), cgv2_descriptor.uid(), cgv2_descriptor.gid())) {
PLOG(ERROR) << "Failed to create directory for " << path;
@ -512,10 +324,10 @@ static bool CreateV2SubHierarchy(
// Activate all v2 controllers in path so they can be activated in
// children as they are created.
for (const auto& [name, descriptor] : descriptors) {
const format::CgroupController* controller = descriptor.controller();
const CgroupController* controller = descriptor.controller();
std::uint32_t flags = controller->flags();
std::uint32_t max_activation_depth = controller->max_activation_depth();
const int depth = util::GetCgroupDepth(controller->path(), path);
const int depth = GetCgroupDepth(controller->path(), path);
if (controller->version() == 2 && name != CGROUPV2_HIERARCHY_NAME &&
flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION && depth < max_activation_depth) {
@ -535,22 +347,13 @@ static bool CreateV2SubHierarchy(
}
bool CgroupSetup() {
using namespace android::cgrouprc;
std::map<std::string, CgroupDescriptor> descriptors;
CgroupDescriptorMap descriptors;
if (getpid() != 1) {
LOG(ERROR) << "Cgroup setup can be done only by init process";
return false;
}
// Make sure we do this only one time. No need for std::call_once because
// init is a single-threaded process
if (access(CGROUPS_RC_PATH, F_OK) == 0) {
LOG(WARNING) << "Attempt to call CgroupSetup() more than once";
return true;
}
// load cgroups.json file
if (!ReadDescriptors(&descriptors)) {
LOG(ERROR) << "Failed to load cgroup description file";
@ -559,15 +362,18 @@ bool CgroupSetup() {
// setup cgroups
for (auto& [name, descriptor] : descriptors) {
if (SetupCgroup(descriptor)) {
descriptor.set_mounted(true);
} else {
if (descriptor.controller()->flags() & CGROUPRC_CONTROLLER_FLAG_MOUNTED) {
LOG(WARNING) << "Attempt to call CgroupSetup() more than once";
return true;
}
if (!SetupCgroup(descriptor)) {
// issue a warning and proceed with the next cgroup
LOG(WARNING) << "Failed to setup " << name << " cgroup";
}
}
if (force_memcg_v2) {
if (android::libprocessgroup_flags::force_memcg_v2()) {
if (MGLRUDisabled().value_or(false)) {
LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
<< "Global reclaim performance will suffer.";
@ -593,26 +399,5 @@ bool CgroupSetup() {
}
}
// mkdir <CGROUPS_RC_DIR> 0711 system system
if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";
return false;
}
// Generate <CGROUPS_RC_FILE> file which can be directly mmapped into
// process memory. This optimizes performance, memory usage
// and limits infrormation shared with unprivileged processes
// to the minimum subset of information from cgroups.json
if (!WriteRcFile(descriptors)) {
LOG(ERROR) << "Failed to write " << CGROUPS_RC_PATH << " file";
return false;
}
// chmod 0644 <CGROUPS_RC_PATH>
if (fchmodat(AT_FDCWD, CGROUPS_RC_PATH, 0644, AT_SYMLINK_NOFOLLOW) < 0) {
PLOG(ERROR) << "fchmodat() failed";
return false;
}
return true;
}

View file

@ -37,8 +37,16 @@ cc_library_static {
"include",
],
srcs: [
"cgroup_controller.cpp",
"cgroup_descriptor.cpp",
"util.cpp",
],
shared_libs: [
"libbase",
],
static_libs: [
"libjsoncpp",
],
defaults: ["libprocessgroup_build_flags_cc"],
}

View file

@ -14,11 +14,9 @@
* limitations under the License.
*/
#include <processgroup/format/cgroup_controller.h>
#include <processgroup/cgroup_controller.h>
namespace android {
namespace cgrouprc {
namespace format {
#include <cstring>
CgroupController::CgroupController(uint32_t version, uint32_t flags, const std::string& name,
const std::string& path, uint32_t max_activation_depth)
@ -54,8 +52,4 @@ const char* CgroupController::path() const {
void CgroupController::set_flags(uint32_t flags) {
flags_ = flags;
}
} // namespace format
} // namespace cgrouprc
} // namespace android
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 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 <processgroup/cgroup_descriptor.h>
#include <processgroup/util.h> // For flag values
CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
const std::string& path, mode_t mode, const std::string& uid,
const std::string& gid, uint32_t flags,
uint32_t max_activation_depth)
: controller_(version, flags, name, path, max_activation_depth),
mode_(mode),
uid_(uid),
gid_(gid) {}
void CgroupDescriptor::set_mounted(bool mounted) {
uint32_t flags = controller_.flags();
if (mounted) {
flags |= CGROUPRC_CONTROLLER_FLAG_MOUNTED;
} else {
flags &= ~CGROUPRC_CONTROLLER_FLAG_MOUNTED;
}
controller_.set_flags(flags);
}

View file

@ -20,11 +20,7 @@
#include <cstdint>
#include <string>
namespace android {
namespace cgrouprc {
namespace format {
// Minimal controller description to be mmapped into process address space
// Minimal controller description
struct CgroupController {
public:
CgroupController() = default;
@ -48,8 +44,4 @@ struct CgroupController {
uint32_t max_activation_depth_ = UINT32_MAX;
char name_[CGROUP_NAME_BUF_SZ] = {};
char path_[CGROUP_PATH_BUF_SZ] = {};
};
} // namespace format
} // namespace cgrouprc
} // namespace android
};

View file

@ -0,0 +1,45 @@
/*
* 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 <cstdint>
#include <string>
#include <sys/stat.h>
#include <processgroup/cgroup_controller.h>
// 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, uint32_t flags,
uint32_t max_activation_depth);
const CgroupController* controller() const { return &controller_; }
mode_t mode() const { return mode_; }
std::string uid() const { return uid_; }
std::string gid() const { return gid_; }
void set_mounted(bool mounted);
private:
CgroupController controller_;
mode_t mode_ = 0;
std::string uid_;
std::string gid_;
};

View file

@ -16,10 +16,18 @@
#pragma once
#include <map>
#include <string>
namespace util {
#include "cgroup_descriptor.h"
// Duplicated from cgrouprc.h. Don't depend on libcgrouprc here.
#define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1
#define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2
#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4
unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path);
} // namespace util
using CgroupControllerName = std::string;
using CgroupDescriptorMap = std::map<CgroupControllerName, CgroupDescriptor>;
bool ReadDescriptors(CgroupDescriptorMap* descriptors);

View file

@ -18,8 +18,6 @@
#include "gtest/gtest.h"
using util::GetCgroupDepth;
TEST(EmptyInputs, bothEmpty) {
EXPECT_EQ(GetCgroupDepth({}, {}), 0);
}

View file

@ -18,9 +18,33 @@
#include <algorithm>
#include <iterator>
#include <optional>
#include <string_view>
#include <mntent.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <json/reader.h>
#include <json/value.h>
#include "../build_flags.h"
#include "../internal.h"
using android::base::GetUintProperty;
namespace {
constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
// This should match the publicly declared value in processgroup.h,
// but we don't want this library to depend on libprocessgroup.
constexpr std::string CGROUPV2_HIERARCHY_NAME_INTERNAL = "cgroup2";
const char SEP = '/';
std::string DeduplicateAndTrimSeparators(const std::string& path) {
@ -42,9 +66,135 @@ std::string DeduplicateAndTrimSeparators(const std::string& path) {
return ret;
}
void MergeCgroupToDescriptors(CgroupDescriptorMap* descriptors, const Json::Value& cgroup,
const std::string& name, const std::string& root_path,
int cgroups_version) {
const std::string cgroup_path = cgroup["Path"].asString();
std::string path;
if (!root_path.empty()) {
path = root_path;
if (cgroup_path != ".") {
path += "/";
path += cgroup_path;
}
} else {
path = cgroup_path;
}
uint32_t controller_flags = 0;
if (cgroup["NeedsActivation"].isBool() && cgroup["NeedsActivation"].asBool()) {
controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
}
if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
}
uint32_t max_activation_depth = UINT32_MAX;
if (cgroup.isMember("MaxActivationDepth")) {
max_activation_depth = cgroup["MaxActivationDepth"].asUInt();
}
CgroupDescriptor descriptor(
cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags,
max_activation_depth);
auto iter = descriptors->find(name);
if (iter == descriptors->end()) {
descriptors->emplace(name, descriptor);
} else {
iter->second = descriptor;
}
}
bool ReadDescriptorsFromFile(const std::string& file_name, CgroupDescriptorMap* descriptors) {
static constexpr bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
std::vector<CgroupDescriptor> result;
std::string json_doc;
if (!android::base::ReadFileToString(file_name, &json_doc)) {
PLOG(ERROR) << "Failed to read task profiles from " << file_name;
return false;
}
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value root;
std::string errorMessage;
if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage;
return false;
}
if (root.isMember("Cgroups")) {
const Json::Value& cgroups = root["Cgroups"];
for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
std::string name = cgroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") continue;
MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
}
}
bool memcgv2_present = false;
std::string root_path;
if (root.isMember("Cgroups2")) {
const Json::Value& cgroups2 = root["Cgroups2"];
root_path = cgroups2["Path"].asString();
MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME_INTERNAL, "", 2);
const Json::Value& childGroups = cgroups2["Controllers"];
for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
std::string name = childGroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") memcgv2_present = true;
MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
}
}
if (force_memcg_v2 && !memcgv2_present) {
LOG(INFO) << "Forcing memcg to v2 hierarchy";
Json::Value memcgv2;
memcgv2["Controller"] = "memory";
memcgv2["NeedsActivation"] = true;
memcgv2["Path"] = ".";
memcgv2["Optional"] = true; // In case of cgroup_disabled=memory, so we can still boot
MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
}
return true;
}
using MountDir = std::string;
using MountOpts = std::string;
static std::optional<std::map<MountDir, MountOpts>> ReadCgroupV1Mounts() {
FILE* fp = setmntent("/proc/mounts", "r");
if (fp == nullptr) {
PLOG(ERROR) << "Failed to read mounts";
return std::nullopt;
}
std::map<MountDir, MountOpts> mounts;
const std::string_view CGROUP_V1_TYPE = "cgroup";
for (mntent* mentry = getmntent(fp); mentry != nullptr; mentry = getmntent(fp)) {
if (mentry->mnt_type && CGROUP_V1_TYPE == mentry->mnt_type &&
mentry->mnt_dir && mentry->mnt_opts) {
mounts[mentry->mnt_dir] = mentry->mnt_opts;
}
}
endmntent(fp);
return mounts;
}
} // anonymous namespace
namespace util {
unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
const std::string deduped_root = DeduplicateAndTrimSeparators(controller_root);
@ -56,4 +206,47 @@ unsigned int GetCgroupDepth(const std::string& controller_root, const std::strin
return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(), SEP);
}
} // namespace util
bool ReadDescriptors(CgroupDescriptorMap* descriptors) {
// load system cgroup descriptors
if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
return false;
}
// load API-level specific system cgroups descriptors if available
unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
if (api_level > 0) {
std::string api_cgroups_path =
android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
return false;
}
}
}
// load vendor cgroup descriptors if the file exists
if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
!ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
return false;
}
// check for v1 mount/usability status
std::optional<std::map<MountDir, MountOpts>> v1Mounts;
for (auto& [name, descriptor] : *descriptors) {
const CgroupController* const controller = descriptor.controller();
if (controller->version() != 1) continue;
// Read only once, and only if we have at least one v1 controller
if (!v1Mounts) {
v1Mounts = ReadCgroupV1Mounts();
if (!v1Mounts) return false;
}
if (const auto it = v1Mounts->find(controller->path()); it != v1Mounts->end()) {
if (it->second.contains(controller->name())) descriptor.set_mounted(true);
}
}
return true;
}