Skip to content

Commit

Permalink
CrOS: userspace swap: Small helper to find eligible VMAs
Browse files Browse the repository at this point in the history
This CL contains two very small helpers, the first which takes a
VMA and determines based on its properties if it would be eligible
for swapping. The second will return a vector of all VMAs which
are swap eligible for a given pid.

This will be used in the userspace swap mechanism which is coming
in a later CL, this is just split out to make reviewing everything
easier.

BUG=chromium:1067833

Change-Id: Ic60d65105b20dfa54cf221c92f3406016e68fc43
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2298161
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Commit-Queue: Brian Geffon <bgeffon@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789046}
  • Loading branch information
bgaff authored and Commit Bot committed Jul 16, 2020
1 parent 899ae8f commit 8b63738
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
3 changes: 3 additions & 0 deletions chromeos/memory/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ component("memory") {
"//chromeos/dbus/constants",
"//crypto",
"//crypto:platform",
"//services/resource_coordinator/public/cpp/memory_instrumentation",
"//third_party/zlib/google:compression_utils",
]
sources = [
Expand Down Expand Up @@ -56,12 +57,14 @@ source_set("unit_tests") {
"//base/test:test_support",
"//chromeos:chromeos_buildflags",
"//mojo/core/embedder",
"//services/resource_coordinator/public/cpp/memory_instrumentation",
"//testing/gmock",
"//testing/gtest",
]
sources = [
"pagemap_unittest.cc",
"userspace_swap/swap_storage_unittest.cc",
"userspace_swap/userfaultfd_unittest.cc",
"userspace_swap/userspace_swap_unittest.cc",
]
}
1 change: 1 addition & 0 deletions chromeos/memory/DEPS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# These deps should be only the required deps for //chromeos/memory
include_rules = [
"+third_party/zlib/google",
"+services/resource_coordinator",
]
62 changes: 62 additions & 0 deletions chromeos/memory/userspace_swap/userspace_swap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@

#include "chromeos/memory/userspace_swap/userspace_swap.h"

#include <vector>

#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/process/process_metrics.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chromeos/memory/userspace_swap/region.h"
#include "chromeos/memory/userspace_swap/swap_storage.h"
#include "chromeos/memory/userspace_swap/userfaultfd.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"

namespace chromeos {
namespace memory {
namespace userspace_swap {

namespace {

using memory_instrumentation::mojom::VmRegion;

// NOTE: Descriptions for these feature params can be found in the userspace
// swap header file for the UserspaceSwapConfig struct.
const base::Feature kUserspaceSwap{"UserspaceSwapEnabled",
Expand Down Expand Up @@ -171,6 +178,61 @@ CHROMEOS_EXPORT bool KernelSupportsUserspaceSwap() {
return userfault_fd_supported && mremap_dontunmap_supported;
}

CHROMEOS_EXPORT bool IsVMASwapEligible(
const memory_instrumentation::mojom::VmRegionPtr& vma) {
// We only conisder VMAs which are Private Anonymous
// Readable/Writable that aren't locked and a certain size.
uint32_t target_perms =
VmRegion::kProtectionFlagsRead | VmRegion::kProtectionFlagsWrite;
if (vma->protection_flags != target_perms)
return false;

if (!vma->mapped_file.empty())
return false;

if (vma->byte_locked > 0)
return false;

// It must be within the VMA size bounds configured.
const auto& config = UserspaceSwapConfig::Get();
if (vma->size_in_bytes < config.vma_region_minimum_size_bytes ||
vma->size_in_bytes > config.vma_region_maximum_size_bytes)
return false;

return true;
}

CHROMEOS_EXPORT bool GetAllSwapEligibleVMAs(base::PlatformThreadId pid,
std::vector<Region>* regions) {
DCHECK(regions);
regions->clear();

const auto& config = UserspaceSwapConfig::Get();

std::vector<memory_instrumentation::mojom::VmRegionPtr> vmas =
memory_instrumentation::OSMetrics::GetProcessMemoryMaps(pid);

if (vmas.empty()) {
return false;
}

// Only consider VMAs which match our criteria.
for (const auto& v : vmas) {
if (IsVMASwapEligible(v)) {
regions->push_back(Region(static_cast<uintptr_t>(v->start_address),
static_cast<uintptr_t>(v->size_in_bytes)));
}
}

// We can shuffle the VMA maps (if configured) so we don't always start from
// the same VMA on subsequent swaps.
if (config.shuffle_maps_on_swap) {
base::RandomShuffle(regions->begin(), regions->end());
}

return true;
}

} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
23 changes: 23 additions & 0 deletions chromeos/memory/userspace_swap/userspace_swap.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

#include <sys/mman.h>
#include <cstdint>
#include <vector>

#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chromeos/chromeos_export.h"
#include "chromeos/memory/userspace_swap/region.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom.h"

#ifndef MREMAP_DONTUNMAP
#define MREMAP_DONTUNMAP 4
Expand Down Expand Up @@ -117,6 +120,26 @@ struct CHROMEOS_EXPORT UserspaceSwapConfig {
// the rendererer UserspaceSwapImpl.
CHROMEOS_EXPORT bool KernelSupportsUserspaceSwap();

// A swap eligible VMA is one that meets the required swapping criteria,
// which are:
// - RW protections
// - Not file backed (Anonymous)
// - Not shared (Private)
// - Contains no locked memory
// - Meets the size constraints set by vma_region_min_size_bytes
// and vma_region_max_size_bytes
CHROMEOS_EXPORT bool IsVMASwapEligible(
const memory_instrumentation::mojom::VmRegionPtr& vma);

// GetAllSwapEligibleVMAs will return a vector of regions which are swap
// eligible, these regions are NOT "swap region" sized they are the VMAs and as
// such must then be split in the appropriate region size by the userspace swap
// mechanism. On error it will return false and errno will be set appropriately.
//
// This vector may be shuffled if shuffle_maps_on_swap has been set to true.
CHROMEOS_EXPORT bool GetAllSwapEligibleVMAs(base::PlatformThreadId pid,
std::vector<Region>* regions);

} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
Expand Down
115 changes: 115 additions & 0 deletions chromeos/memory/userspace_swap/userspace_swap_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/memory/userspace_swap/userspace_swap.h"

#include <string>

#include "chromeos/memory/userspace_swap/region.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {
namespace memory {
namespace userspace_swap {

namespace {
using memory_instrumentation::mojom::VmRegion;
using memory_instrumentation::mojom::VmRegionPtr;

// DefaultEligibleRegion is a region which is eligible by default which can be
// made ineligible by changing one or more of the properties.
VmRegion DefaultEligibleRegion() {
VmRegion r;
r.start_address = 0xF00F00BA4;
r.size_in_bytes =
UserspaceSwapConfig::Get().vma_region_minimum_size_bytes + 1;
r.protection_flags =
VmRegion::kProtectionFlagsRead | VmRegion::kProtectionFlagsWrite;

// These aren't necessary but we list them to be explicit about the
// requirements.
r.byte_locked = 0;
r.mapped_file = std::string();

return r;
}

} // namespace

TEST(EligibleVMA, DefaultIsEligible) {
ASSERT_TRUE(IsVMASwapEligible(DefaultEligibleRegion().Clone()));
}

TEST(EligibleVMA, SharedIsNotEligible) {
auto r = DefaultEligibleRegion();
r.protection_flags |= VmRegion::kProtectionFlagsMayshare;

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, ProtNoneIsNotEligible) {
auto r = DefaultEligibleRegion();
r.protection_flags = 0;

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, WrOnlyIsNotEligible) {
auto r = DefaultEligibleRegion();
r.protection_flags = VmRegion::kProtectionFlagsWrite;

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, RdOnlyIsNotEligible) {
auto r = DefaultEligibleRegion();
r.protection_flags = VmRegion::kProtectionFlagsRead;

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, ExecIsNotEligible) {
auto r = DefaultEligibleRegion();
r.protection_flags |= VmRegion::kProtectionFlagsExec;

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, FileBackedIsNotEligible) {
auto r = DefaultEligibleRegion();
r.mapped_file = "/some/file/foo.so";

ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, AnyLockedRegionIsNotEligible) {
auto r = DefaultEligibleRegion();
r.byte_locked = 20 << 10; // Any non-zero locked will do.
ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, RegionTooSmallIsNotEligible) {
auto r = DefaultEligibleRegion();
r.size_in_bytes =
UserspaceSwapConfig::Get().vma_region_minimum_size_bytes - 1;
ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(EligibleVMA, RegionTooLargeIsNotEligible) {
auto r = DefaultEligibleRegion();
r.size_in_bytes =
UserspaceSwapConfig::Get().vma_region_maximum_size_bytes + 1;
ASSERT_FALSE(IsVMASwapEligible(r.Clone()));
}

TEST(GetAllSwapEligibleVMAs, SimpleVerification) {
std::vector<Region> regions;
ASSERT_TRUE(GetAllSwapEligibleVMAs(getpid(), &regions));
ASSERT_GT(regions.size(), 0u);
}

} // namespace userspace_swap
} // namespace memory
} // namespace chromeos

0 comments on commit 8b63738

Please sign in to comment.