|
1 | 1 | // Copyright (c) 2024 Pyarelal Knowles, MIT License |
2 | 2 |
|
| 3 | +#include <algorithm> |
3 | 4 | #include <cstdio> |
| 5 | +#include <decodeless/mappedfile.hpp> |
4 | 6 | #include <filesystem> |
5 | 7 | #include <fstream> |
6 | 8 | #include <gtest/gtest.h> |
7 | | -#include <decodeless/mappedfile.hpp> |
8 | 9 | #include <ostream> |
9 | 10 | #include <span> |
10 | 11 |
|
@@ -226,7 +227,7 @@ TEST_F(MappedFileFixture, LinuxResize) { |
226 | 227 |
|
227 | 228 | #endif |
228 | 229 |
|
229 | | -TEST_F(MappedFileFixture, ResizeMemory) { |
| 230 | +TEST(MappedMemory, ResizeMemory) { |
230 | 231 | const char str[] = "hello world!"; |
231 | 232 | { |
232 | 233 | resizable_memory file(0, 10000); |
@@ -270,7 +271,7 @@ TEST_F(MappedFileFixture, ResizeMemory) { |
270 | 271 | } |
271 | 272 | } |
272 | 273 |
|
273 | | -TEST_F(MappedFileFixture, ResizeMemoryExtended) { |
| 274 | +TEST(MappedMemory, ResizeMemoryExtended) { |
274 | 275 | size_t nextBytes = 1; |
275 | 276 | resizable_memory memory(nextBytes, 1llu << 32); // 4gb of virtual memory pls |
276 | 277 | void* data = memory.data(); |
@@ -425,3 +426,58 @@ TEST_F(MappedFileFixture, Readme) { |
425 | 426 | fs::remove(tmpFile2); |
426 | 427 | EXPECT_FALSE(fs::exists(tmpFile2)); |
427 | 428 | } |
| 429 | + |
| 430 | +#ifndef _WIN32 |
| 431 | +std::vector<uint8_t> getResidency(void* base, size_t size) { |
| 432 | + std::vector<unsigned char> result(size / getpagesize(), 0u); |
| 433 | + int ret = mincore(base, size, result.data()); |
| 434 | + if (ret != 0) |
| 435 | + throw detail::LastError(); |
| 436 | + return result; |
| 437 | +} |
| 438 | + |
| 439 | +TEST(MappedMemory, PageResidencyAfterDecommit) { |
| 440 | + const size_t page_size = getpagesize(); |
| 441 | + const size_t reserve_size = page_size * 64; // 64 pages total |
| 442 | + const size_t commit_size = page_size * 4; // We'll use 4 pages |
| 443 | + |
| 444 | + // Reserve virtual address space (uncommitted, inaccessible) |
| 445 | + void* base = |
| 446 | + mmap(nullptr, reserve_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); |
| 447 | + ASSERT_NE(base, MAP_FAILED) << "Failed to mmap reserved space"; |
| 448 | + EXPECT_TRUE(std::ranges::all_of(getResidency(base, commit_size), |
| 449 | + [](uint8_t c) { return (c & 1u) == 0; })); |
| 450 | + |
| 451 | + // Commit a portion with PROT_READ | PROT_WRITE |
| 452 | + int prot_result = mprotect(base, commit_size, PROT_READ | PROT_WRITE); |
| 453 | + ASSERT_EQ(prot_result, 0) << "Failed to mprotect committed region"; |
| 454 | + EXPECT_TRUE(std::ranges::all_of(getResidency(base, commit_size), |
| 455 | + [](uint8_t c) { return (c & 1u) == 0; })); |
| 456 | + |
| 457 | + // Touch the memory to ensure it's backed by RAM |
| 458 | + std::span committed(static_cast<std::byte*>(base), commit_size); |
| 459 | + std::ranges::fill(committed, std::byte(0xAB)); |
| 460 | + |
| 461 | + // Verify pages are resident using mincore |
| 462 | + EXPECT_TRUE(std::ranges::all_of(getResidency(base, commit_size), |
| 463 | + [](uint8_t c) { return (c & 1u) == 1; })); |
| 464 | + |
| 465 | + // Decommit |
| 466 | + #if 0 |
| 467 | + void* remap = mmap(base, commit_size, PROT_NONE, |
| 468 | + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0); |
| 469 | + ASSERT_EQ(remap, base) << "Failed to remap to decommit pages"; |
| 470 | + #else |
| 471 | + // See MADV_FREE discussion here: https://github.com/golang/go/issues/42330 |
| 472 | + prot_result = mprotect(base, commit_size, PROT_NONE); |
| 473 | + ASSERT_EQ(prot_result, 0) << "Failed to mprotect committed region back to PROT_NONE"; |
| 474 | + int madvise_result = madvise(base, commit_size, MADV_DONTNEED); |
| 475 | + ASSERT_EQ(madvise_result, 0) << "Failed to release pages with madvise"; |
| 476 | + #endif |
| 477 | + EXPECT_TRUE(std::ranges::all_of(getResidency(base, commit_size), |
| 478 | + [](uint8_t c) { return (c & 1u) == 0; })); |
| 479 | + |
| 480 | + // Cleanup |
| 481 | + munmap(base, reserve_size); |
| 482 | +} |
| 483 | +#endif |
0 commit comments