8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/process/Process.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/MemoryRegion.h"
13#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/UserMemoryPolicy.h"
16#include "pedigree/kernel/processor/VirtualAddressSpace.h"
17#include "pedigree/kernel/utilities/utility.h"
19#include "modules/system/vfs/File.h"
23using Status = MemoryMapManager::FileRemapStatus;
25bool check(
bool condition,
const char* detail) {
27 ERROR(
"REMAP-FILE-PAGES-TEST: FAIL " << detail);
33 bool permitsTotalPages(
size_t,
bool)
const override {
38class RemapProbeFile final :
public File {
40 static constexpr size_t Pages = 8;
42 :
File(
String(
"remap-file-pages-probe"), 0, 0, 0, 1, nullptr,
44 m_Storage(
"Remap File Pages Probe") {
47 ~RemapProbeFile()
override {
48 shutdownFillCacheWriteback();
55 for (
size_t n = 0; n < Pages; ++n)
56 ByteSet(
static_cast<uint8_t*
>(m_Storage.virtualAddress()) + n * page, pattern(n), page);
59 static uint8_t pattern(
size_t page) {
66 return !rejectMapping;
71 if (physical != ~physical_uintptr_t(0) && index < Pages)
72 __atomic_add_fetch(&m_Loans[index], 1, __ATOMIC_RELEASE);
78 __atomic_sub_fetch(&m_Loans[index], 1, __ATOMIC_RELEASE);
81 size_t loansAt(
size_t page)
const {
82 return __atomic_load_n(&m_Loans[page], __ATOMIC_ACQUIRE);
85 return __atomic_load_n(&physicalPageLoans(), __ATOMIC_ACQUIRE);
87 bool evict(
size_t page) {
93 for (
size_t n = 0; n < Pages; ++n) {
99 bool rejectMapping =
false;
102 uintptr_t
readBlock(uint64_t offset)
override {
103 if (offset >= getSize())
104 return FILE_BAD_BLOCK;
105 return reinterpret_cast<uintptr_t
>(m_Storage.virtualAddress()) + offset;
107 bool pinBlock(uint64_t offset)
override {
108 return offset < getSize();
114 size_t m_Loans[Pages] = {};
117bool mapProbe(
MemoryMapManager& manager, RemapProbeFile& file, uintptr_t& address,
size_t length,
118 size_t offset = 0, MemoryLockMode mode = MemoryLockMode::None) {
119 return manager.
mapFile(&file, address, length, MemoryMappedObject::Read, offset,
false,
120 MemoryMapManager::Placement::Hint,
nullptr,
121 MemoryMappedObject::Read | MemoryMappedObject::Write,
126bool borrowed(
VirtualAddressSpace& space, uintptr_t address, physical_uintptr_t& physical) {
127 if (!space.
isMapped(
reinterpret_cast<void*
>(address)))
130 space.
getMapping(
reinterpret_cast<void*
>(address), physical, flags);
135bool sameBorrowed(
VirtualAddressSpace& space, uintptr_t address, physical_uintptr_t expected) {
136 physical_uintptr_t current = 0;
137 return borrowed(space, address, current) && current == expected;
140bool hasPattern(uintptr_t address,
size_t filePage) {
141 const auto* bytes =
reinterpret_cast<volatile uint8_t*
>(address);
143 return bytes[0] == RemapProbeFile::pattern(filePage) &&
144 bytes[page - 1] == RemapProbeFile::pattern(filePage);
147bool cleanup(
MemoryMapManager& manager, TestAccount& account, RemapProbeFile& file) {
150 account.publish(account.charge(), MemoryLockMode::None);
151 return check(!account.charge().managedPages && !account.charge().rawPages && file.balanced(),
152 "teardown retained charge or an offset-specific loan");
159 bool passed = check(file.initialise(),
"slice backing allocation");
163 physical_uintptr_t original[3] = {};
164 if (!check(mapProbe(manager, file, base, 3 * page),
"slice mapping setup"))
166 for (
size_t n = 0; n < 3; ++n) {
167 if (!check(manager.faultIn(base + n * page,
false) &&
168 borrowed(space, base + n * page, original[n]) && file.loansAt(n) == 1,
169 "slice initial PTE and loan"))
172 file.rejectMapping =
true;
173 if (!check(manager.remapFilePages(base + page, page, 4 * page,
false) == Status::PolicyDenied,
174 "backing policy rejection"))
176 file.rejectMapping =
false;
177 for (
size_t n = 0; n < 3; ++n) {
178 if (!check(sameBorrowed(space, base + n * page, original[n]) && file.loansAt(n) == 1 &&
179 hasPattern(base + n * page, n),
180 "rejected replacement changed a victim"))
183 if (!check(manager.remapFilePages(base + page, page, 4 * page,
true) == Status::Success &&
184 !space.
isMapped(
reinterpret_cast<void*
>(base + page)) && file.loans() == 2 &&
185 !file.loansAt(1) && !file.loansAt(4) && file.evict(1),
186 "NONBLOCK did not retire the old offset and leave a lazy replacement"))
188 if (!check(sameBorrowed(space, base, original[0]) &&
189 sameBorrowed(space, base + 2 * page, original[2]) && file.loansAt(0) == 1 &&
190 file.loansAt(2) == 1 && hasPattern(base, 0) && hasPattern(base + 2 * page, 2),
191 "slice replacement changed unaffected physical pages or loans"))
193 physical_uintptr_t replacement = 0;
194 if (!check(manager.faultIn(base + page,
false) && borrowed(space, base + page, replacement) &&
195 file.loansAt(4) == 1 && hasPattern(base + page, 4),
196 "lazy replacement fault used the old offset"))
198 if (!check(manager.remapFilePages(base + page, page, 5 * page,
false) == Status::Success &&
199 borrowed(space, base + page, replacement) && !file.loansAt(4) &&
200 file.loansAt(5) == 1 && file.loans() == 3 && hasPattern(base + page, 5) &&
201 sameBorrowed(space, base, original[0]) &&
202 sameBorrowed(space, base + 2 * page, original[2]),
203 "default remap did not populate its new offset or preserve slices"))
208 passed = cleanup(manager, account, file) && passed;
211 "REMAP-FILE-PAGES-TEST: PASS slices policy=preserved old-loans=retired "
212 "neighbors=retained nonblock=lazy default=populated");
220 bool passed = check(file.initialise(),
"staging-limit backing allocation");
225 const size_t length = (VirtualAddressSpace::MaximumRemapPages + 1) * page;
226 physical_uintptr_t first = 0, second = 0;
227 if (!check(mapProbe(manager, file, base, length) && manager.faultIn(base,
false) &&
228 manager.faultIn(base + page,
false) && borrowed(space, base, first) &&
229 borrowed(space, base + page, second),
230 "staging-limit mapping setup"))
232 return check(manager.remapFilePages(base + page, page, 4 * page,
true) == Status::NoMemory &&
233 sameBorrowed(space, base, first) &&
234 sameBorrowed(space, base + page, second) && file.loansAt(0) == 1 &&
235 file.loansAt(1) == 1 && file.loans() == 2 && !file.loansAt(4) &&
236 hasPattern(base, 0) && hasPattern(base + page, 1) &&
237 !account.charge().managedPages,
238 "failed staging changed PTEs, backing offsets, loans, or charge");
241 passed = cleanup(manager, account, file) && passed;
243 NOTICE(
"REMAP-FILE-PAGES-TEST: PASS staging-limit resident-pages=2 victims=preserved");
248 for (
size_t attempt = 0; attempt < 256; ++attempt) {
250 if (!space.
isMapped(
reinterpret_cast<void*
>(address)))
258 TestAccount& account) {
260 uintptr_t target = 0, control = 0;
262 const bool onfault = mode == MemoryLockMode::OnFault;
263 physical_uintptr_t replacement = 0;
264 bool passed = check(file.initialise(),
"locked backing allocation");
269 if (!check(mapProbe(manager, file, target, page, 0, mode) &&
270 mapProbe(manager, file, control, page, 2 * page) &&
271 manager.faultIn(target,
false) && manager.faultIn(control,
false),
272 "locked mapping setup"))
275 account.publish(account.charge(), MemoryLockMode::OnFault);
276 if (!check(manager.remapFilePages(target, page, 4 * page, !onfault) == Status::Success &&
277 !file.loansAt(0) && file.loansAt(2) == 1 &&
278 account.charge().managedPages == 1 &&
279 manager.hasLockedMemory(space, target, page),
280 "locked replacement changed charge or retained old offset loan"))
283 if (!check(!space.
isMapped(
reinterpret_cast<void*
>(target)) && !file.loansAt(4) &&
285 "FUTURE ONFAULT remap populated despite its lazy policy"))
287 if (!check(manager.faultIn(target,
false),
"ONFAULT replacement fault"))
290 if (!check(borrowed(space, target, replacement) && file.loansAt(4) == 1 &&
291 hasPattern(target, 4),
292 "locked replacement did not populate the requested file page"))
295 if (!check(compactUntilAbsent(manager, space, control),
296 "actual compactor did not evict the unlocked control"))
300 if (!check(sameBorrowed(space, target, replacement) && file.loansAt(4) == 1 &&
301 !file.loansAt(2) && file.loans() == 1 && !file.evict(4) &&
302 hasPattern(target, 4) && account.charge().managedPages == 1,
303 "actual compactor retired the locked replacement or its new-offset loan"))
305 if (!check(manager.lockMemory(space, target, page, MemoryLockMode::None,
false) ==
306 MemoryLockStatus::Success &&
307 !account.charge().managedPages,
308 "replacement unlock retained charge"))
311 if (!check(compactUntilAbsent(manager, space, target),
312 "unlocked replacement retained its PTE"))
315 return check(!file.loansAt(4) && file.balanced() && file.evict(4),
316 "unlocked replacement retained its new-offset PTE or loan");
319 passed = cleanup(manager, account, file) && passed;
321 NOTICE(
"REMAP-FILE-PAGES-TEST: PASS "
322 << (onfault ?
"onfault" :
"eager")
323 <<
" replacement=retained control=evicted unlock=evicted");
327int remapFilePagesWorker(
void* parameter) {
328 bool& passed = *
static_cast<bool*
>(parameter);
334 if (!check(!space.memoryLockAccount(),
"test address space already has a lock account"))
336 space.setMemoryLockAccount(&account);
337 manager.bindMemoryLockPolicy(space);
339 passed = slicedRetirement(manager, space, account) && stagingLimit(manager, space, account) &&
340 lockedReplacement(MemoryLockMode::OnFault, manager, space, account) &&
341 lockedReplacement(MemoryLockMode::Eager, manager, space, account);
345 space.rawUserMemory().clear();
346 passed &= check(!account.charge().managedPages && !account.charge().rawPages,
347 "final account teardown");
348 space.setMemoryLockAccount(
nullptr);
349 space.setUserMemoryPolicy(
nullptr);
355bool runRemapFilePagesRegressions() {
356 NOTICE(
"REMAP-FILE-PAGES-TEST: BEGIN");
358 if (!check(process !=
nullptr,
"test process allocation"))
361 Thread*
worker =
new Thread(process, remapFilePagesWorker, &passed,
nullptr,
false,
true,
true);
363 const bool joined = started &&
worker->joinForCompletion();
364 if (started && !joined)
365 FATAL(
"REMAP-FILE-PAGES-TEST: worker could not be joined safely");
369 passed = check(started && joined && passed,
"isolated worker") && passed;
371 NOTICE(
"REMAP-FILE-PAGES-TEST: END PASS");
Memory-mapped file interface.
virtual uintptr_t readBlock(uint64_t location)
virtual void unpinBlock(uint64_t location)
void evict(uint64_t location)
virtual physical_uintptr_t getPhysicalPage(size_t offset)
void enableFillCacheWriteback()
virtual bool allowMapping(bool shared, bool writeRequested, bool &mayWrite)
virtual size_t getBlockSize() const
virtual void returnPhysicalPage(size_t offset)
virtual MUST_USE_RESULT bool pinBlock(uint64_t location)
static MemoryMapManager & instance()
MemoryMappedObject * mapFile(File *pFile, uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms, size_t offset=0, bool bCopyOnWrite=true)
Special memory entity in the kernel's virtual address space.
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() PURE
static ProcessorInformation & information()
static Scheduler & instance()
static const size_t CopyOnWrite
static const size_t Borrowed
virtual bool isMapped(void *virtualAddress)=0
static const size_t KernelMode
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static const size_t Write