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"
23bool check(
bool condition,
const char* detail) {
25 ERROR(
"MEMORY-LOCK-TEST: FAIL " << detail);
31 bool permitsTotalPages(
size_t total,
bool)
const override {
32 return total <= maximumPages;
34 size_t maximumPages = ~size_t(0);
37class LockProbeFile final :
public File {
40 :
File(
String(
"memory-lock-probe"), 0, 0, 0, 1, nullptr,
42 m_Storage(
"Memory Lock Probe") {
45 ~LockProbeFile()
override {
46 shutdownFillCacheWriteback();
53 ByteSet(m_Storage.virtualAddress(), 0x49, getSize());
59 bool evict(
size_t offset) {
60 return cacheState().fill.evict(offset);
63 return __atomic_load_n(&physicalPageLoans(), __ATOMIC_ACQUIRE);
65 bool backingUnchanged() {
66 auto* bytes =
static_cast<const uint8_t*
>(m_Storage.virtualAddress());
67 for (
size_t n = 0; n < getSize(); ++n) {
75 uintptr_t
readBlock(uint64_t offset)
override {
76 if (offset >= getSize())
77 return FILE_BAD_BLOCK;
78 return reinterpret_cast<uintptr_t
>(m_Storage.virtualAddress()) + offset;
80 bool pinBlock(uint64_t offset)
override {
81 return offset < getSize();
90 for (
size_t attempt = 0; attempt < 256; ++attempt) {
93 if (!space.
isMapped(
reinterpret_cast<void*
>(address)))
100bool cleanupMappings(
MemoryMapManager& manager, TestAccount& account, LockProbeFile& file) {
103 const auto charge = account.charge();
104 return check(!charge.managedPages && !charge.rawPages && !file.loans(),
105 "mapping teardown retained a lock charge or file loan");
109 TestAccount& account) {
112 uintptr_t locked = 0;
113 uintptr_t control = 0;
114 physical_uintptr_t originalPhysical = 0;
115 bool passed = check(file.initialise(),
"compactor backing allocation");
120 if (!check(manager.
mapFile(&file, locked, pageSize, MemoryMappedObject::Read, 0,
false) &&
121 manager.
mapFile(&file, control, pageSize, MemoryMappedObject::Read, pageSize,
123 manager.faultIn(locked,
false) && manager.faultIn(control,
false),
124 "compactor mapping setup"))
127 space.
getMapping(
reinterpret_cast<void*
>(locked), originalPhysical, flags);
129 !file.evict(pageSize),
130 "resident shared mappings did not retain both cache loans"))
132 if (!check(manager.lockMemory(space, locked, pageSize, mode,
false) ==
133 MemoryLockStatus::Success &&
134 manager.hasLockedMemory(space, locked, pageSize) &&
135 !manager.hasLockedMemory(space, control, pageSize) &&
136 account.charge().managedPages == 1,
137 "single-page lock policy publication"))
141 if (!check(compactUntilAbsent(manager, space, control),
142 "actual compactor did not release the unlocked control"))
146 if (!check(space.
isMapped(
reinterpret_cast<void*
>(locked)) && file.loans() == 1,
147 "compactor removed the locked mapping or its backing loan"))
149 physical_uintptr_t physical = 0;
151 space.
getMapping(
reinterpret_cast<void*
>(locked), physical, flags);
152 auto* bytes =
reinterpret_cast<volatile uint8_t*
>(locked);
155 bytes[pageSize - 1] == 0x49 && !file.evict(0) && file.evict(pageSize),
156 "locked identity/data or cache eviction inhibition changed"))
158 if (!check(manager.lockMemory(space, locked, pageSize, MemoryLockMode::None,
false) ==
159 MemoryLockStatus::Success &&
160 !manager.hasLockedMemory(space, locked, pageSize) &&
161 !account.charge().managedPages,
162 "unlock retained policy or virtual-page charge"))
165 if (!check(compactUntilAbsent(manager, space, locked),
166 "unlock did not permit actual compaction"))
168 return check(!file.loans() && file.evict(0) && file.backingUnchanged(),
169 "unlock did not release the cache loan or changed backing bytes");
172 passed = cleanupMappings(manager, account, file) && passed;
174 NOTICE(
"MEMORY-LOCK-TEST: PASS compactor-"
175 << (mode == MemoryLockMode::Eager ?
"eager" :
"onfault")
176 <<
" locked-physical=retained control=evicted unlock=evicted");
183 uintptr_t address = 0;
184 physical_uintptr_t ownedPhysical = 0;
185 bool passed = check(file.initialise(),
"private backing allocation");
189 if (!check(manager.
mapFile(&file, address, pageSize,
190 MemoryMappedObject::Read | MemoryMappedObject::Write, 0,
true) &&
191 manager.faultIn(address,
false),
192 "private borrowed mapping setup"))
194 physical_uintptr_t borrowedPhysical = 0;
196 space.
getMapping(
reinterpret_cast<void*
>(address), borrowedPhysical, flags);
198 "private read did not borrow its file page"))
200#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
201 const size_t borrowedReferences =
202 PhysicalMemoryManager::pageReferenceCountForTest(borrowedPhysical);
204 if (!check(manager.lockMemory(space, address, pageSize, MemoryLockMode::OnFault,
false) ==
205 MemoryLockStatus::Success,
206 "private ONFAULT lock"))
208 physical_uintptr_t currentPhysical = 0;
209 space.
getMapping(
reinterpret_cast<void*
>(address), currentPhysical, flags);
212 "ONFAULT eagerly copied an already-resident private page"))
214 if (!check(manager.lockMemory(space, address, pageSize, MemoryLockMode::Eager,
false) ==
215 MemoryLockStatus::Success,
216 "private eager lock"))
218 space.
getMapping(
reinterpret_cast<void*
>(address), ownedPhysical, flags);
222 !file.loans() && account.charge().managedPages == 1,
223 "eager lock did not acquire exactly one writable private page"))
225#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
226 if (!check(PhysicalMemoryManager::pageReferenceCountForTest(ownedPhysical) == 1 &&
227 PhysicalMemoryManager::pageReferenceCountForTest(borrowedPhysical) ==
229 "eager copy changed backing ownership or leaked a private reference"))
232 auto* bytes =
reinterpret_cast<volatile uint8_t*
>(address);
233 for (
size_t n = 0; n < pageSize; ++n) {
234 if (!check(bytes[n] == 0x49,
"eager private copy lost source bytes"))
238 return check(file.backingUnchanged() && file.evict(0) && bytes[0] == 0x72 &&
239 bytes[pageSize - 1] == 0x49,
240 "private write or backing eviction changed the other owner");
243 passed = cleanupMappings(manager, account, file) && passed;
244#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
246 passed &= check(!PhysicalMemoryManager::pageReferenceCountForTest(ownedPhysical),
247 "private eager page survived mapping teardown");
251 "MEMORY-LOCK-TEST: PASS private-eager-copy onfault=borrowed eager=owned backing=unchanged");
256 size_t expectedPages,
const char* detail) {
259 const auto status = space.rawUserMemory().prepareAllLocks(MemoryLockMode::None, plan);
260 return check(status == MemoryLockStatus::Success && plan &&
261 plan.get()->eligiblePages() == expectedPages &&
262 account.charge().rawPages == expectedPages && !account.charge().managedPages,
268 account.maximumPages = pages;
273 const uintptr_t original =
reinterpret_cast<uintptr_t
>(space.
getEndOfHeap());
275 bool passed = [&]() {
276 if (!check(original && !(original & (pageSize - 1)),
"isolated heap alignment") ||
277 !rawState(manager, space, account, 0,
"isolated raw inventory is not empty"))
282 space.rawUserMemory().setCompleteInventory(
true);
283 account.maximumPages = 0;
284 if (!check(manager.lockAllMemory(space,
false, MemoryLockMode::None, MemoryLockMode::Eager,
285 false) == MemoryLockStatus::Success,
290 reinterpret_cast<uintptr_t
>(space.
getEndOfHeap()) == original,
291 "zero-quota heap admission changed the break") ||
292 !rawState(manager, space, account, 0,
"failed heap admission published a raw owner"))
294 quota(manager, account, 2);
295 if (!check(
reinterpret_cast<uintptr_t
>(
297 "two-page heap admission") ||
298 !rawState(manager, space, account, 2,
"heap admission did not charge two pages"))
300 for (
size_t offset = 0; offset < 2 * pageSize; offset += pageSize) {
301 void* page =
reinterpret_cast<void*
>(original + offset);
302 if (!check(space.
isMapped(page),
"eager heap page was not populated"))
304 physical_uintptr_t physical = 0;
309 "eager heap admission retained a write-faulting CoW page"))
311 auto* bytes =
reinterpret_cast<volatile uint8_t*
>(page);
312 if (!check(bytes[0] == 0 && bytes[pageSize - 1] == 0,
"eager heap page was not zeroed"))
315 if (!check(
reinterpret_cast<uintptr_t
>(space.
expandHeap(
317 "same-page heap growth") ||
318 !rawState(manager, space, account, 2,
"same-page growth charged an extra page"))
320 if (!check(
reinterpret_cast<uintptr_t
>(space.
expandHeap(-
static_cast<ssize_t
>(2 * pageSize),
322 original + 2 * pageSize &&
323 reinterpret_cast<uintptr_t
>(space.
getEndOfHeap()) == original &&
324 !space.
isMapped(
reinterpret_cast<void*
>(original)) &&
325 !space.
isMapped(
reinterpret_cast<void*
>(original + pageSize)),
326 "heap shrink did not retire its pages and break") ||
327 !rawState(manager, space, account, 0,
"heap shrink retained raw charge or inventory"))
330 quota(manager, account, 0);
331 if (!check(!thread->prepareInputUserStack(),
"zero-quota fallback stack was admitted") ||
332 !rawState(manager, space, account, 0,
"failed fallback stack published an owner"))
334 const size_t fallbackPages = (1024 * 1024) / pageSize;
335 quota(manager, account, fallbackPages);
336 if (!check(thread->prepareInputUserStack(),
"fallback stack admission") ||
337 !rawState(manager, space, account, fallbackPages,
"fallback stack charge or inventory"))
339 quota(manager, account, 0);
340 if (!check(thread->prepareInputUserStack(),
"existing fallback stack was allocated again"))
342 thread->retireInputUserStack();
343 thread->retireInputUserStack();
344 if (!rawState(manager, space, account, 0,
"fallback retirement retained charge or inventory"))
346 quota(manager, account, fallbackPages);
347 if (!check(thread->prepareInputUserStack(),
"fallback stack retry") ||
348 !rawState(manager, space, account, fallbackPages,
"fallback retry charge or inventory"))
350 thread->retireInputUserStack();
351 return rawState(manager, space, account, 0,
"fallback retry teardown");
354 thread->retireInputUserStack();
357 account.maximumPages = ~size_t(0);
358 account.publish(account.charge(), MemoryLockMode::None);
359 const uintptr_t current =
reinterpret_cast<uintptr_t
>(space.
getEndOfHeap());
360 if (current > original && current - original <= 2 * pageSize)
362 passed &= check(
reinterpret_cast<uintptr_t
>(space.
getEndOfHeap()) == original,
363 "raw fixture cleanup could not restore the heap");
365 passed = rawState(manager, space, account, 0,
"raw fixture final cleanup") && passed;
368 "MEMORY-LOCK-TEST: PASS raw-owners heap=quota-byte-growth-shrink "
369 "fallback=quota-retire-retry");
373int memoryLockWorker(
void* parameter) {
374 bool& passed = *
static_cast<bool*
>(parameter);
380 if (!check(!space.memoryLockAccount(),
"test address space already has a lock account"))
382 space.setMemoryLockAccount(&account);
383 manager.bindMemoryLockPolicy(space);
385 passed = compactorCase(MemoryLockMode::Eager, manager, space, account) &&
386 compactorCase(MemoryLockMode::OnFault, manager, space, account) &&
387 privateEagerCopy(manager, space, account) && rawOwnerFlows(manager, space, account);
391 space.rawUserMemory().clear();
392 const auto charge = account.charge();
393 passed &= check(!charge.managedPages && !charge.rawPages,
394 "test account retained charge at final teardown");
395 space.setMemoryLockAccount(
nullptr);
396 space.setUserMemoryPolicy(
nullptr);
402bool runMemoryLockRegressions() {
403 NOTICE(
"MEMORY-LOCK-TEST: BEGIN");
405 if (!check(process !=
nullptr,
"test process allocation"))
408 Thread*
worker =
new Thread(process, memoryLockWorker, &passed,
nullptr,
false,
true,
true);
410 const bool joined = started &&
worker->joinForCompletion();
411 if (started && !joined)
412 FATAL(
"MEMORY-LOCK-TEST: worker could not be joined safely");
416 passed = check(started && joined && passed,
"isolated worker") && passed;
418 NOTICE(
"MEMORY-LOCK-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)
void enableFillCacheWriteback()
virtual size_t getBlockSize() const
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
virtual void * getEndOfHeap()=0
virtual void * expandHeap(ssize_t incr, size_t flags)