[self_freeze] Add function to allow compacting our own memory
We use MADV_PAGEOUT in order to swap out non-shared, non-file-backed pages that we don't need. This will be used when we go to background but are not frozen/compacted by App Freezer. This function is currently not called anywhere, so this CL should not change any behavior. Bug: 344547190 Change-Id: Ie2bd754ba06b5cae6769221396471eb74d495522 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5887346 Reviewed-by: Matthew Denton <mpdenton@chromium.org> Reviewed-by: Benoit Lize <lizeb@chromium.org> Reviewed-by: Brian Geffon <bgeffon@chromium.org> Commit-Queue: Thiabaud Engelbrecht <thiabaud@google.com> Cr-Commit-Position: refs/heads/main@{#1372231}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
f8e18fc22a
commit
90d66f8045
base/android
pre_freeze_background_memory_trimmer.ccpre_freeze_background_memory_trimmer.hpre_freeze_background_memory_trimmer_unittest.cc
sandbox/linux/seccomp-bpf-helpers
@ -4,6 +4,9 @@
|
||||
|
||||
#include "base/android/pre_freeze_background_memory_trimmer.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@ -15,12 +18,14 @@
|
||||
#include "base/feature_list.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/page_size.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/strings/strcat.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/task/thread_pool/thread_pool_instance.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/trace_event/base_tracing.h"
|
||||
|
||||
namespace base::android {
|
||||
namespace {
|
||||
@ -104,6 +109,29 @@ std::optional<uint64_t> Diff(std::optional<uint64_t> before,
|
||||
return after_value < before_value ? before_value - after_value : 0;
|
||||
}
|
||||
|
||||
bool IsMadvisePageoutSupported() {
|
||||
static bool supported = []() -> bool {
|
||||
#if defined(MADV_PAGEOUT)
|
||||
// To determine if MADV_PAGEOUT is supported we will try calling it with an
|
||||
// invalid memory area.
|
||||
// madvise(2) first checks the mode first, returning -EINVAL if it's
|
||||
// unknown. Next, it will always return 0 for a zero length VMA before
|
||||
// validating if it's mapped.
|
||||
// So, in this case, we can test for support with any page aligned address
|
||||
// with a zero length.
|
||||
int res =
|
||||
madvise(reinterpret_cast<void*>(base::GetPageSize()), 0, MADV_PAGEOUT);
|
||||
if (res < 0 && errno == -EINVAL)
|
||||
return false;
|
||||
PLOG_IF(ERROR, res < 0) << "Unexpected return from madvise";
|
||||
if (res == 0)
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}();
|
||||
return supported;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BASE_FEATURE(kOnPreFreezeMemoryTrim,
|
||||
@ -316,6 +344,91 @@ void PreFreezeBackgroundMemoryTrimmer::UnregisterMemoryMetricInternal(
|
||||
metrics_.erase(metrics_.begin() + index);
|
||||
}
|
||||
|
||||
// static
|
||||
bool PreFreezeBackgroundMemoryTrimmer::SelfCompactionIsSupported() {
|
||||
return IsMadvisePageoutSupported();
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<int64_t> PreFreezeBackgroundMemoryTrimmer::CompactSelf() {
|
||||
// MADV_PAGEOUT was only added in Linux 5.4, so do nothing in earlier
|
||||
// versions.
|
||||
if (!SelfCompactionIsSupported()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<debug::MappedMemoryRegion> regions;
|
||||
|
||||
std::string proc_maps;
|
||||
if (!debug::ReadProcMaps(&proc_maps) || !ParseProcMaps(proc_maps, ®ions)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (regions.size() == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// TODO(crbug.com/344547190): This may run for a long time. Add a way to
|
||||
// cancel this part-way through if we return to the foreground while this is
|
||||
// running.
|
||||
return CompactMemory(std::move(regions));
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<int64_t> PreFreezeBackgroundMemoryTrimmer::CompactRegion(
|
||||
debug::MappedMemoryRegion region) {
|
||||
#if defined(MADV_PAGEOUT)
|
||||
// Skip file-backed regions
|
||||
if (region.inode != 0 || region.dev_major != 0) {
|
||||
return 0;
|
||||
}
|
||||
// Skip shared regions
|
||||
if ((region.permissions & debug::MappedMemoryRegion::Permission::PRIVATE) ==
|
||||
0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TRACE_EVENT1("base", __PRETTY_FUNCTION__, "size", region.end - region.start);
|
||||
|
||||
int error = madvise(reinterpret_cast<void*>(region.start),
|
||||
region.end - region.start, MADV_PAGEOUT);
|
||||
|
||||
if (error < 0) {
|
||||
// We may fail on some regions, such as [vvar], or a locked region. It's
|
||||
// not worth it to try to filter these all out, so we just skip them, and
|
||||
// rely on metrics to verify that this is working correctly for most
|
||||
// regions.
|
||||
//
|
||||
// EINVAL could be [vvar] or a locked region. ENOMEM would be a moved or
|
||||
// unmapped region.
|
||||
if (errno != EINVAL && errno != ENOMEM) {
|
||||
PLOG(ERROR) << "Unexpected error from madvise.";
|
||||
return std::nullopt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return region.end - region.start;
|
||||
#else
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<int64_t> PreFreezeBackgroundMemoryTrimmer::CompactMemory(
|
||||
std::vector<debug::MappedMemoryRegion> regions) {
|
||||
TRACE_EVENT1("base", __PRETTY_FUNCTION__, "count", regions.size());
|
||||
int64_t total_bytes_processed = 0;
|
||||
for (const auto& region : regions) {
|
||||
const auto bytes_processed = CompactRegion(region);
|
||||
if (!bytes_processed) {
|
||||
return std::nullopt;
|
||||
}
|
||||
total_bytes_processed += bytes_processed.value();
|
||||
}
|
||||
return total_bytes_processed;
|
||||
}
|
||||
|
||||
void PreFreezeBackgroundMemoryTrimmer::PostMetricsTasksIfModern() {
|
||||
if (!SupportsModernTrim()) {
|
||||
return;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "base/debug/proc_maps_linux.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/memory/post_delayed_memory_reduction_task.h"
|
||||
@ -104,6 +105,12 @@ class BASE_EXPORT PreFreezeBackgroundMemoryTrimmer {
|
||||
static void UnregisterMemoryMetric(const PreFreezeMetric* metric)
|
||||
LOCKS_EXCLUDED(Instance().lock_);
|
||||
|
||||
static bool SelfCompactionIsSupported();
|
||||
|
||||
// Compacts the memory for the process, and returns the number of bytes
|
||||
// processed on success.
|
||||
static std::optional<int64_t> CompactSelf();
|
||||
|
||||
static void SetSupportsModernTrimForTesting(bool is_supported);
|
||||
static void ClearMetricsForTesting() LOCKS_EXCLUDED(lock_);
|
||||
size_t GetNumberOfPendingBackgroundTasksForTesting() const
|
||||
@ -114,6 +121,8 @@ class BASE_EXPORT PreFreezeBackgroundMemoryTrimmer {
|
||||
|
||||
static void OnPreFreezeForTesting() LOCKS_EXCLUDED(lock_) { OnPreFreeze(); }
|
||||
|
||||
static std::optional<int64_t> CompactRegion(debug::MappedMemoryRegion region);
|
||||
|
||||
// Called when Chrome is about to be frozen. Runs as many delayed tasks as
|
||||
// possible immediately, before we are frozen.
|
||||
static void OnPreFreeze() LOCKS_EXCLUDED(lock_);
|
||||
@ -128,6 +137,8 @@ class BASE_EXPORT PreFreezeBackgroundMemoryTrimmer {
|
||||
JNIEnv* env);
|
||||
friend class base::android::MemoryPurgeManagerAndroid;
|
||||
friend class base::OneShotDelayedBackgroundTimer;
|
||||
friend class PreFreezeBackgroundMemoryTrimmerTest;
|
||||
friend class PreFreezeSelfCompactionTest;
|
||||
|
||||
// We use our own implementation here, based on |PostCancelableDelayedTask|,
|
||||
// rather than relying on something like |base::OneShotTimer|, since
|
||||
@ -168,6 +179,9 @@ class BASE_EXPORT PreFreezeBackgroundMemoryTrimmer {
|
||||
|
||||
PreFreezeBackgroundMemoryTrimmer();
|
||||
|
||||
static std::optional<int64_t> CompactMemory(
|
||||
std::vector<debug::MappedMemoryRegion> regions);
|
||||
|
||||
void RegisterMemoryMetricInternal(const PreFreezeMetric* metric)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(lock_);
|
||||
|
||||
|
@ -4,8 +4,15 @@
|
||||
|
||||
#include "base/android/pre_freeze_background_memory_trimmer.h"
|
||||
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_temp_file.h"
|
||||
#include "base/memory/page_size.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/test/task_environment.h"
|
||||
@ -48,6 +55,44 @@ class MockMetric : public PreFreezeBackgroundMemoryTrimmer::PreFreezeMetric {
|
||||
|
||||
size_t MockMetric::count_ = 0;
|
||||
|
||||
std::optional<const debug::MappedMemoryRegion> GetMappedMemoryRegion(
|
||||
uintptr_t addr) {
|
||||
std::vector<debug::MappedMemoryRegion> regions;
|
||||
|
||||
std::string proc_maps;
|
||||
if (!debug::ReadProcMaps(&proc_maps) || !ParseProcMaps(proc_maps, ®ions)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const auto& region : regions) {
|
||||
if (region.start == addr) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<const debug::MappedMemoryRegion> GetMappedMemoryRegion(
|
||||
void* addr) {
|
||||
return GetMappedMemoryRegion(reinterpret_cast<uintptr_t>(addr));
|
||||
}
|
||||
|
||||
size_t CountResidentPagesInRange(void* addr, size_t size) {
|
||||
DCHECK((reinterpret_cast<uintptr_t>(addr) & (base::GetPageSize() - 1)) == 0);
|
||||
DCHECK(size % base::GetPageSize() == 0);
|
||||
|
||||
std::vector<unsigned char> pages(size / base::GetPageSize());
|
||||
|
||||
mincore(addr, size, &pages[0]);
|
||||
size_t tmp = 0;
|
||||
for (const auto& v : pages) {
|
||||
tmp += v & 0x01;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class PreFreezeBackgroundMemoryTrimmerTest : public testing::Test {
|
||||
@ -603,4 +648,119 @@ TEST_F(PreFreezeBackgroundMemoryTrimmerTest, TimerBoolTaskRunFromPreFreeze) {
|
||||
EXPECT_EQ(called_task_type.value(), MemoryReductionTaskContext::kProactive);
|
||||
}
|
||||
|
||||
TEST(PreFreezeSelfCompactionTest, Simple) {
|
||||
// MADV_PAGEOUT is only supported starting from Linux 5.4. So, on devices
|
||||
// don't support it, we bail out early. This is a known problem on some 32
|
||||
// bit devices.
|
||||
if (!PreFreezeBackgroundMemoryTrimmer::SelfCompactionIsSupported()) {
|
||||
GTEST_SKIP() << "No kernel support";
|
||||
}
|
||||
|
||||
const size_t kPageSize = base::GetPageSize();
|
||||
const size_t kNumPages = 24;
|
||||
const size_t size = kNumPages * kPageSize;
|
||||
|
||||
void* addr = nullptr;
|
||||
addr = mmap(nullptr, size, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1,
|
||||
0);
|
||||
ASSERT_NE(addr, MAP_FAILED);
|
||||
|
||||
// we touch the memory here to dirty it, so that it is definitely resident.
|
||||
memset((void*)addr, 1, size);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), kNumPages);
|
||||
|
||||
const auto region = GetMappedMemoryRegion(addr);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
const auto result =
|
||||
PreFreezeBackgroundMemoryTrimmer::CompactRegion(std::move(*region));
|
||||
ASSERT_EQ(result, size);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), 0u);
|
||||
|
||||
munmap(addr, size);
|
||||
}
|
||||
|
||||
TEST(PreFreezeSelfCompactionTest, File) {
|
||||
// MADV_PAGEOUT is only supported starting from Linux 5.4. So, on devices
|
||||
// don't support it, we bail out early. This is a known problem on some 32
|
||||
// bit devices.
|
||||
if (!PreFreezeBackgroundMemoryTrimmer::SelfCompactionIsSupported()) {
|
||||
GTEST_SKIP() << "No kernel support";
|
||||
}
|
||||
|
||||
const size_t kPageSize = base::GetPageSize();
|
||||
const size_t kNumPages = 2;
|
||||
const size_t size = kNumPages * kPageSize;
|
||||
|
||||
ScopedTempFile file;
|
||||
|
||||
ASSERT_TRUE(file.Create());
|
||||
|
||||
int fd = open(file.path().value().c_str(), O_RDWR);
|
||||
ASSERT_NE(fd, -1);
|
||||
|
||||
ASSERT_TRUE(base::WriteFile(file.path(), std::string(size, 1)));
|
||||
|
||||
void* addr = nullptr;
|
||||
addr = mmap(nullptr, size, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
ASSERT_NE(addr, MAP_FAILED);
|
||||
|
||||
// we touch the memory here to dirty it, so that it is definitely resident.
|
||||
memset((void*)addr, 2, size);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), kNumPages);
|
||||
|
||||
const auto region = GetMappedMemoryRegion(addr);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
const auto result =
|
||||
PreFreezeBackgroundMemoryTrimmer::CompactRegion(std::move(*region));
|
||||
ASSERT_EQ(result, 0);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), kNumPages);
|
||||
|
||||
munmap(addr, size);
|
||||
}
|
||||
|
||||
TEST(PreFreezeSelfCompactionTest, Locked) {
|
||||
// MADV_PAGEOUT is only supported starting from Linux 5.4. So, on devices
|
||||
// don't support it, we bail out early. This is a known problem on some 32
|
||||
// bit devices.
|
||||
if (!PreFreezeBackgroundMemoryTrimmer::SelfCompactionIsSupported()) {
|
||||
GTEST_SKIP() << "No kernel support";
|
||||
}
|
||||
|
||||
const size_t kPageSize = base::GetPageSize();
|
||||
// We use a small number of pages here because Android has a low limit on
|
||||
// the max locked size allowed (~64 KiB on many devices).
|
||||
const size_t kNumPages = 2;
|
||||
const size_t size = kNumPages * kPageSize;
|
||||
|
||||
void* addr = nullptr;
|
||||
addr = mmap(nullptr, size, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1,
|
||||
0);
|
||||
ASSERT_NE(addr, MAP_FAILED);
|
||||
|
||||
ASSERT_EQ(mlock(addr, size), 0);
|
||||
|
||||
// we touch the memory here to dirty it, so that it is definitely resident.
|
||||
memset((void*)addr, 1, size);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), kNumPages);
|
||||
|
||||
const auto region = GetMappedMemoryRegion(addr);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
const auto result =
|
||||
PreFreezeBackgroundMemoryTrimmer::CompactRegion(std::move(*region));
|
||||
ASSERT_EQ(result, 0);
|
||||
|
||||
EXPECT_EQ(CountResidentPagesInRange(addr, size), kNumPages);
|
||||
|
||||
munlock(addr, size);
|
||||
munmap(addr, size);
|
||||
}
|
||||
|
||||
} // namespace base::android
|
||||
|
@ -250,6 +250,15 @@ ResultExpr BaselinePolicyAndroid::EvaluateSyscall(int sysno) const {
|
||||
return RestrictAndroidIoctl(options_.allow_userfaultfd_ioctls);
|
||||
}
|
||||
|
||||
#if defined(MADV_PAGEOUT)
|
||||
if (sysno == __NR_madvise) {
|
||||
// Allow MADV_PAGEOUT
|
||||
const Arg<int> advice(2);
|
||||
return If(advice == MADV_PAGEOUT, Allow())
|
||||
.Else(BaselinePolicy::EvaluateSyscall(sysno));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ptrace is allowed so the crash reporter can fork in a renderer
|
||||
// and then ptrace the parent. https://crbug.com/933418
|
||||
if (sysno == __NR_ptrace) {
|
||||
|
Reference in New Issue
Block a user