mm: restore file data after partial huge-page MADV_DONTNEED

Private file mappings can acquire owned huge PMAs after a write breaks
copy-on-write. MemoryManager.Decommit treats those PMAs like private
anonymous memory when MADV_DONTNEED covers only part of a huge page. It
decommits the MemoryFile range in place, replacing discarded file data with
zeroes. Linux instead discards the private copy so later faults reload the
mapped file.

Limit the partial huge-page decommit optimization to anonymous VMAs. Let
file-backed private PMAs follow the normal invalidation path, which drops the
copied pages and restores file contents on the next fault.

Add a syscall test that modifies a huge private file mapping and applies
MADV_DONTNEED to an interior page.
This commit is contained in:
Ayush Ranjan
2026-08-21 19:44:24 +00:00
parent 11792a9979
commit 54e7c8ee0d
2 changed files with 31 additions and 4 deletions
+5 -4
View File
@@ -1132,9 +1132,10 @@ func (mm *MemoryManager) Decommit(addr hostarch.Addr, length uint64) error {
// - If at least one byte in ar is not covered by a vma, decommit the rest
// but return ENOMEM.
//
// - If we would invalidate only part of a huge page that we own (is not
// copy-on-write), use MemoryFile.Decommit() instead to keep the allocated
// huge page intact for future use.
// - If we would invalidate only part of a huge page backing a private
// anonymous mapping that we own (is not copy-on-write), use
// MemoryFile.Decommit() instead to keep the allocated huge page intact for
// future use.
didUnmapAS := false
pseg := mm.pmas.LowerBoundSegment(ar.Start)
vseg := mm.vmas.LowerBoundSegment(ar.Start)
@@ -1157,7 +1158,7 @@ func (mm *MemoryManager) Decommit(addr hostarch.Addr, length uint64) error {
}
for pseg.Ok() && pseg.Start() < vsegAR.End {
pma := pseg.ValuePtr()
if pma.huge && !mm.isPMACopyOnWriteLocked(vseg, pseg) {
if vma.mappable == nil && pma.huge && !mm.isPMACopyOnWriteLocked(vseg, pseg) {
psegAR := pseg.Range().Intersect(vsegAR)
if !psegAR.IsHugePageAligned() {
firstHugeStart := psegAR.Start.HugeRoundDown()
+26
View File
@@ -115,6 +115,32 @@ TEST(MadviseDontneedTest, CleansPrivateFilePage) {
ExpectAllMappingBytes(m, 4);
}
TEST(MadviseDontneedTest, CleansPrivateFileHugePageSubrange) {
constexpr char kFileValue = 4;
constexpr char kPrivateValue = 5;
TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
/* parent = */ GetAbsoluteTestTmpdir(),
/* content = */ std::string(kHugePageSize, kFileValue),
TempPath::kDefaultFileMode));
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR));
Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
Mmap(nullptr, kHugePageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
fd.get(), 0));
memset(m.ptr(), kPrivateValue, m.len());
ExpectAllMappingBytes(m, kPrivateValue);
char* const advised = static_cast<char*>(m.ptr()) + kPageSize;
ASSERT_THAT(madvise(advised, kPageSize, MADV_DONTNEED), SyscallSucceeds());
auto const v = m.view();
for (size_t i = 0; i < v.size(); ++i) {
EXPECT_EQ(v[i], i >= kPageSize && i < 2 * kPageSize ? kFileValue
: kPrivateValue)
<< "at offset " << i;
}
}
TEST(MadviseDontneedTest, DoesNotModifySharedFilePage) {
TempPath f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
/* parent = */ GetAbsoluteTestTmpdir(),