Commit b00164ba authored by Brian Geffon's avatar Brian Geffon Committed by Commit Bot

CrOS: userspace_swap: Add a region intersection helper.

We need a way to determine how an operation on a range which
may overlap with a region intersects. The kernel will notify for
ranges of memory which may span several regions or even be a portion
of a region. This new helper determines how they overlap and returns
the pieces that remain along with the intersection.

Bug: chromium:1067833
Change-Id: I83ab545ec6176105047762e9ba0d7af208255553
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2453796Reviewed-by: default avatarMatthew Denton <mpdenton@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Brian Geffon <bgeffon@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814887}
parent a6f4b03b
...@@ -43,6 +43,7 @@ component("userspace_swap") { ...@@ -43,6 +43,7 @@ component("userspace_swap") {
"//third_party/zlib/google:compression_utils", "//third_party/zlib/google:compression_utils",
] ]
sources = [ sources = [
"region.cc",
"region.h", "region.h",
"swap_storage.cc", "swap_storage.cc",
"swap_storage.h", "swap_storage.h",
...@@ -67,6 +68,7 @@ source_set("unit_tests") { ...@@ -67,6 +68,7 @@ source_set("unit_tests") {
"//testing/gtest", "//testing/gtest",
] ]
sources = [ sources = [
"region_unittest.cc",
"swap_storage_unittest.cc", "swap_storage_unittest.cc",
"userfaultfd_unittest.cc", "userfaultfd_unittest.cc",
"userspace_swap_unittest.cc", "userspace_swap_unittest.cc",
......
// Copyright 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/region.h"
#include <sys/uio.h>
#include <cstdint>
#include <ostream>
#include <vector>
#include "base/containers/span.h"
#include "base/strings/string_piece.h"
namespace chromeos {
namespace memory {
namespace userspace_swap {
// AsIovec will return the iovec representation of this Region.
struct iovec CHROMEOS_EXPORT Region::AsIovec() const {
return {.iov_base = reinterpret_cast<void*>(address), .iov_len = length};
}
base::StringPiece CHROMEOS_EXPORT Region::AsStringPiece() const {
return base::StringPiece(reinterpret_cast<char*>(address), length);
}
RegionOverlap CHROMEOS_EXPORT
Region::CalculateRegionOverlap(const Region& range) const {
RegionOverlap overlap;
// We have four possible situations here related to how a region actually fits
// within a range, as only a portion of a region we manage may have been
// removed or unmapped. When situations such as 2, 3, and 4 happen we need to
// deal with them and we will do that by restoring the remaining portion of
// the region.
//
// [ region_start region_end ]
// 1. [ range_start range_end ]
// 2. [range_start range_end]
// 3. [range_start range_end]
// 4. [range_start range_end]
//
// Given these scenarios RemainingRegions will populate |before|
// and |after| if there are portions which were unaffected by an
// operation. This allows the Removed/Unmapped callbacks to determine how to
// restore any memory that was not removed or unmapped.
uintptr_t region_start = address;
uintptr_t region_end = address + length;
uintptr_t range_start = range.address;
uintptr_t range_end = range.address + range.length;
CHECK_LE(range_start, range_end);
CHECK_LE(region_start, region_end);
if (range_end < region_start || range_start > region_end) {
return overlap; // There is no overlap
}
// Since we only care about the pieces that related to this region we will
// clamp range_start and range_end.
range_start = std::max(range_start, region_start);
range_end = std::min(range_end, region_end);
if (range_start > region_start) {
overlap.before = Region(region_start, range_start - region_start);
}
overlap.intersection = Region(range_start, range_end - range_start);
if (range_end < region_end) {
overlap.after = Region(range_end, region_end - range_end);
}
return overlap;
}
// Easily print a region to a stream, useful for debugging.
std::ostream& CHROMEOS_EXPORT operator<<(std::ostream& os,
const Region& region) {
os << "[" << reinterpret_cast<void*>(region.address) << "-"
<< reinterpret_cast<void*>(region.address + region.length) << "]";
return os;
}
RegionOverlap::RegionOverlap() = default;
RegionOverlap::~RegionOverlap() = default;
RegionOverlap::RegionOverlap(const RegionOverlap&) = default;
} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
...@@ -7,16 +7,21 @@ ...@@ -7,16 +7,21 @@
#include <sys/uio.h> #include <sys/uio.h>
#include <cstdint> #include <cstdint>
#include <ostream>
#include <vector> #include <vector>
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/numerics/checked_math.h" #include "base/numerics/checked_math.h"
#include "base/optional.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "chromeos/chromeos_export.h"
namespace chromeos { namespace chromeos {
namespace memory { namespace memory {
namespace userspace_swap { namespace userspace_swap {
struct RegionOverlap;
// A region describes a block of memory. // A region describes a block of memory.
class Region { class Region {
public: public:
...@@ -26,13 +31,10 @@ class Region { ...@@ -26,13 +31,10 @@ class Region {
Region() = default; Region() = default;
Region(Region&&) = default; Region(Region&&) = default;
Region(const Region&) = default; Region(const Region&) = default;
~Region() = default;
Region& operator=(const Region&) = default; Region& operator=(const Region&) = default;
Region& operator=(Region&&) = default; Region& operator=(Region&&) = default;
// To avoid requiring callers to cast pointers or integral types to Regions,
// we're flexible and allow any pointer type or any integral type. We convert
// them to the types needed for a Region. This simplifies calling code
// tremendously. Static asserts enforce that the types are valid.
template <typename Address, typename Length> template <typename Address, typename Length>
Region(Address* address, Length length) Region(Address* address, Length length)
: address(reinterpret_cast<uintptr_t>(const_cast<Address*>(address))), : address(reinterpret_cast<uintptr_t>(const_cast<Address*>(address))),
...@@ -66,29 +68,56 @@ class Region { ...@@ -66,29 +68,56 @@ class Region {
Region(const std::vector<T>& vec) Region(const std::vector<T>& vec)
: Region(vec.data(), vec.size() * sizeof(T)) {} : Region(vec.data(), vec.size() * sizeof(T)) {}
// AsIovec will return the iovec representation of this Region.
struct iovec AsIovec() const {
return {.iov_base = reinterpret_cast<void*>(address), .iov_len = length};
}
base::StringPiece AsStringPiece() const {
return base::StringPiece(reinterpret_cast<char*>(address), length);
}
template <typename T> template <typename T>
base::span<T> AsSpan() const { base::span<T> AsSpan() const {
return base::span<T>(reinterpret_cast<T*>(address), length); return base::span<T>(reinterpret_cast<T*>(address), length);
} }
struct iovec CHROMEOS_EXPORT AsIovec() const;
base::StringPiece CHROMEOS_EXPORT AsStringPiece() const;
bool operator<(const Region& other) const { bool operator<(const Region& other) const {
// Because the standard library treats equality as !less(a,b) && !less(b,a) // Because the standard library treats equality as !less(a,b) &&
// our definition of less than will be that this has to be FULLY before // !less(b,a) our definition of less than will be that this has to be
// other. Overlapping regions are not allowed and are explicitly checked // FULLY before other. Overlapping regions are not allowed and are
// before inserting by using find() any overlap would return equal, this // explicitly checked before inserting by using find() any overlap would
// also has the property that you can search for a Region of length 1 to // return equal, this also has the property that you can search for a
// find the mapping for a fault. // Region of length 1 to find the mapping for a fault.
return ((address + length - 1) < other.address); return ((address + length - 1) < other.address);
} }
// CalculateRegionOverlap can be used to determine how a |range| overlaps with
// this region. There are five possible outcomes:
// 1. |range| does not overlap at all with this region, in this situation the
// returned RegionOverlap will have none of the members with values.
// 2. |range| fully covers this region, in this situaton before and after in
// the RegionOverlap will be empty and intersection will be identical to this
// region.
// 3. |range| overlaps from the start of of this region, in this case before
// will be empty and intersection will contain the overlapped portion and
// after will contain the piece that did not intersect.
// 4. |range| overlaps from the end of this region, In this case before will
// contain the piece which does not intersect, intersection will contain the
// portion that overlaps and after will be empty.
// 5. |range| is fully within this region, in this situation all fields will
// be set, before will contain the part before the intersection, intersection
// will contain an area equal to range, and after will contain the portion
// which doesn't intersect after range.
CHROMEOS_EXPORT RegionOverlap
CalculateRegionOverlap(const Region& range) const;
friend std::ostream& operator<<(std::ostream& os, const Region& region);
};
struct CHROMEOS_EXPORT RegionOverlap {
RegionOverlap();
~RegionOverlap();
RegionOverlap(const RegionOverlap&);
base::Optional<Region> before;
base::Optional<Region> intersection;
base::Optional<Region> after;
}; };
} // namespace userspace_swap } // namespace userspace_swap
......
// 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/region.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace memory {
namespace userspace_swap {
// This test validates the behavior of CalculateOverlap when a region and a
// range don't overlap at all.
TEST(Region, OverlapNone) {
Region region(1000, 100); // Region from [1000, 1100]
// Calculate no overlap ahead of the region.
Region range(900, 99); // Range from [900,1000]
RegionOverlap overlap = region.CalculateRegionOverlap(range);
ASSERT_FALSE(overlap.before);
ASSERT_FALSE(overlap.intersection);
ASSERT_FALSE(overlap.after);
// Calculate no overlap beyond the region.
Region range2(1101, 99); // Range from [1101, 1200]
RegionOverlap overlap2 = region.CalculateRegionOverlap(range2);
ASSERT_FALSE(overlap2.before);
ASSERT_FALSE(overlap2.intersection);
ASSERT_FALSE(overlap2.after);
}
// This test validates the behavior of CalculateOverlap when range fully covers
// the region.
TEST(Region, OverlapFull) {
Region region(1000, 100); // Region from [1000, 1100]
Region range(900, 300); // Range from [900,1200]
// And the intersection->should be the same as region.
RegionOverlap overlap = region.CalculateRegionOverlap(range);
ASSERT_FALSE(overlap.before);
ASSERT_FALSE(overlap.after);
ASSERT_TRUE(overlap.intersection);
EXPECT_EQ(overlap.intersection->address, region.address);
EXPECT_EQ(overlap.intersection->length, region.length);
}
// This validates the behavior when the range overlaps from the start of the
// range and the end piece of region remains.
TEST(Region, OverlapEndRemains) {
Region region(1000, 100); // Region from [1000, 1100]
Region range(900, 150); // Range from [900,1050]
RegionOverlap overlap = region.CalculateRegionOverlap(range);
ASSERT_FALSE(overlap.before);
ASSERT_TRUE(overlap.intersection);
ASSERT_TRUE(overlap.after);
EXPECT_EQ(overlap.intersection->address, region.address);
EXPECT_EQ(overlap.intersection->length, 50u);
EXPECT_EQ(overlap.after->length, 50u);
}
// This validates the behavior when the range overlaps from the end of the
// range and the start piece of region remains.
TEST(Region, OverlapStartRemains) {
Region region(1000, 100); // Region from [1000, 1100]
Region range(1050, 150); // Range from [1050,1200]
RegionOverlap overlap = region.CalculateRegionOverlap(range);
ASSERT_FALSE(overlap.after);
ASSERT_TRUE(overlap.intersection);
ASSERT_TRUE(overlap.before);
EXPECT_EQ(overlap.intersection->address, region.address + 50);
EXPECT_EQ(overlap.intersection->length, 50u);
EXPECT_EQ(overlap.before->length, 50u);
}
// This test validates a range which is fully within a region.
TEST(Region, OverlapWithinRegion) {
Region region(1000, 100); // Region from [1000, 1100]
Region range(1050, 25); // Range from [1050,1075]
RegionOverlap overlap = region.CalculateRegionOverlap(range);
ASSERT_TRUE(overlap.intersection);
ASSERT_TRUE(overlap.before);
ASSERT_TRUE(overlap.after);
EXPECT_EQ(overlap.intersection->address, region.address + 50);
EXPECT_EQ(overlap.intersection->length, 25u);
EXPECT_EQ(overlap.before->length, 50u);
EXPECT_EQ(overlap.after->length, 25u);
}
} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment