8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/PerProcessorScheduler.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PageFaultHandler.h"
15#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18#include "pedigree/kernel/time/Time.h"
19#include "pedigree/kernel/utilities/utility.h"
22bool check(
bool condition,
const char* detail,
const char* test =
"pagefault-handler-lifetime") {
27 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
31struct RegistryDispatchContext;
32RegistryDispatchContext* g_RegistryDispatchContext =
nullptr;
33void dispatchWhileWriterLocked();
34void dispatchWhileDeferredScopeLocked();
38 explicit RegistryDispatchHandler(RegistryDispatchContext& context) : m_Context(context) {}
40 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
43 RegistryDispatchContext& m_Context;
46struct RegistryDispatchContext {
54 deferredScopeRequested(0),
55 deferredScopeHookCalls(0),
56 deferredScopeDispatches(0),
58 unregisterSucceeded(0) {}
61 RegistryDispatchHandler handler;
73bool RegistryDispatchHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
75 if (m_Context.writerRequested.compareAndSwap(1, 2)) {
76 m_Context.registry->withMutationLockForTest(dispatchWhileWriterLocked);
78 if (m_Context.deferredScopeRequested.compareAndSwap(1, 2)) {
80 dispatchWhileDeferredScopeLocked);
85void dispatchWhileWriterLocked() {
86 RegistryDispatchContext* context = g_RegistryDispatchContext;
91 context->writerHookCalls += 1;
92 if (context->registry->dispatchHandlerForTest(&context->handler)) {
93 context->writerDispatches += 1;
97void dispatchWhileDeferredScopeLocked() {
98 RegistryDispatchContext* context = g_RegistryDispatchContext;
103 context->deferredScopeHookCalls += 1;
104 if (context->registry->dispatchHandlerForTest(&context->handler)) {
105 context->deferredScopeDispatches += 1;
109bool lockIndependentDispatch() {
110 constexpr const char* Test =
"pagefault-lock-reentry";
112 RegistryDispatchContext context(registry);
115 g_RegistryDispatchContext = &context;
116 context.writerRequested = 1;
117 const bool writerDispatched = registry->dispatchHandlerForTest(&context.handler);
118 context.deferredScopeRequested = 1;
119 const bool deferredScopeDispatched = registry->dispatchHandlerForTest(&context.handler);
120 g_RegistryDispatchContext =
nullptr;
122 const bool cleaned = registered && registry->
unregisterHandler(&context.handler);
124 passed &= check(registered,
"the test handler could not be registered", Test);
125 passed &= check(writerDispatched && context.writerHookCalls == 1 && context.writerDispatches == 1,
126 "dispatch or callback return waited for the registry writer lock", Test);
127 passed &= check(deferredScopeDispatched && context.deferredScopeHookCalls == 1 &&
128 context.deferredScopeDispatches == 1 && context.calls == 4,
129 "dispatch entered the interrupted Thread's cleanup publication lock", Test);
130 passed &= check(registry->activeDispatchCountForTest(&context.handler) == 0,
131 "nested dispatch leaked a callback hazard", Test);
132 passed &= check(cleaned,
"the test handler could not be removed", Test);
134 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-lock-reentry");
140 RegistryDispatchContext* context = g_RegistryDispatchContext;
141 if (!context || handler != &context->handler || !context->prePinHookCalls.compareAndSwap(0, 1)) {
145 if (context->registry->unregisterHandler(&context->handler)) {
146 context->unregisterSucceeded += 1;
150bool admissionCloseRevalidation() {
151 constexpr const char* Test =
"pagefault-admission-close-race";
153 RegistryDispatchContext context(registry);
155 const size_t callsBeforeDispatch = context.calls;
157 g_RegistryDispatchContext = &context;
158 PageFaultHandler::setHandlerPrePinHook(unregisterBeforePin);
159 const bool staleDispatch = registry->dispatchHandlerForTest(&context.handler);
160 PageFaultHandler::setHandlerPrePinHook(
nullptr);
161 g_RegistryDispatchContext =
nullptr;
166 passed &= check(registered,
"the test handler could not be registered", Test);
167 passed &= check(context.prePinHookCalls == 1 && context.unregisterSucceeded == 1,
168 "unregister did not retire the pre-pin publication", Test);
169 passed &= check(!staleDispatch && context.calls == callsBeforeDispatch,
170 "a stale pre-pin snapshot entered the retired callback", Test);
171 passed &= check(registry->activeDispatchCountForTest(&context.handler) == 0,
172 "rejected admission leaked a callback hazard", Test);
173 passed &= check(reused && cleaned,
"the revalidated slot could not be reused and removed", Test);
175 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-admission-close-race");
180struct AtomicReopenContext;
181AtomicReopenContext* g_AtomicReopenContext =
nullptr;
185 explicit AtomicReopenHandler(AtomicReopenContext& context) : m_Context(context) {}
187 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
190 AtomicReopenContext& m_Context;
193struct AtomicReopenContext {
195 : registry(registry),
202 selfRemovalRejected(0),
203 atomicRemovalReturned(0),
204 atomicRemovalRejected(0),
208 AtomicReopenHandler handler;
220bool AtomicReopenHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
221 m_Context.handlerCalls += 1;
222 if (m_Context.phase !=
static_cast<size_t>(2)) {
223 m_Context.failures += 1;
227 if (!m_Context.registry->unregisterHandler(
this)) {
228 m_Context.selfRemovalRejected += 1;
231 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
232 while (!m_Context.atomicRemovalReturned && Time::getTicks() < deadline) {
235 if (!m_Context.atomicRemovalReturned) {
236 m_Context.failures += 1;
242 AtomicReopenContext* context = g_AtomicReopenContext;
243 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
247 context->pinHookCalls += 1;
248 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
249 while (context->phase !=
static_cast<size_t>(2) && Time::getTicks() < deadline) {
252 if (context->phase !=
static_cast<size_t>(2)) {
253 context->failures += 1;
258 AtomicReopenContext* context = g_AtomicReopenContext;
259 if (!context || handler != &context->handler) {
263 context->drainHookCalls += 1;
267 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
268 while (context->phase !=
static_cast<size_t>(3) && Time::getTicks() < deadline) {
271 if (context->phase !=
static_cast<size_t>(3)) {
272 context->failures += 1;
277int atomicReopenRemover(
void* parameter) {
278 AtomicReopenContext* context =
reinterpret_cast<AtomicReopenContext*
>(parameter);
279 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
280 while (context->phase !=
static_cast<size_t>(1) && Time::getTicks() < deadline) {
283 if (context->phase !=
static_cast<size_t>(1)) {
284 context->failures += 1;
290 if (!context->registry->unregisterHandler(&context->handler)) {
291 context->atomicRemovalRejected += 1;
294 context->atomicRemovalReturned += 1;
298bool atomicReopenPreservesSelfRemoval() {
299 constexpr const char* Test =
"pagefault-atomic-reopen-self-removal";
301 AtomicReopenContext context(registry);
303 &context,
nullptr,
false,
true);
304 context.remover->setName(
"hosted atomic pagefault remover");
306 g_AtomicReopenContext = &context;
307 PageFaultHandler::setHandlerPinHook(atomicReopenPinHook);
308 PageFaultHandler::setAtomicDrainHook(atomicReopenDrainHook);
310 const bool dispatched = registry->dispatchHandlerForTest(&context.handler);
311 const bool removerJoined = context.remover->join();
312 PageFaultHandler::setAtomicDrainHook(
nullptr);
313 PageFaultHandler::setHandlerPinHook(
nullptr);
314 g_AtomicReopenContext =
nullptr;
316 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
317 const bool hazardsDrained = registry->activeDispatchCountForTest(&context.handler) == 0;
322 passed &= check(registered && dispatched && removerJoined,
323 "the atomic-removal workers did not complete", Test);
324 passed &= check(context.failures == 0 && context.pinHookCalls == 1 &&
325 context.drainHookCalls == 1 && context.handlerCalls == 1,
326 "the atomic reopen interleaving was not reached", Test);
327 passed &= check(context.selfRemovalRejected == 1 && context.atomicRemovalReturned == 1 &&
328 context.atomicRemovalRejected == 1,
329 "atomic reopen erased or replaced callback self-removal", Test);
330 passed &= check(lateDispatchRejected && hazardsDrained,
331 "the self-removal publication remained enabled after callback return", Test);
332 passed &= check(reused && cleaned,
"the retired slot was not reusable", Test);
335 "HOSTED-WAIT-TEST: PASS "
336 "pagefault-atomic-reopen-self-removal");
341struct NestedDispatchContext;
345 explicit OuterNestedHandler(NestedDispatchContext& context) : m_Context(context) {}
347 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
350 NestedDispatchContext& m_Context;
355 explicit InnerNestedHandler(NestedDispatchContext& context) : m_Context(context) {}
357 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
360 NestedDispatchContext& m_Context;
363struct NestedDispatchContext {
365 : registry(registry),
370 selfRemovalRejected(0) {
371 for (
size_t& entry : order) {
376 void record(
size_t value) {
377 if (orderCount < 4) {
378 order[orderCount++] = value;
383 OuterNestedHandler outer;
384 InnerNestedHandler inner;
387 size_t innerDispatches;
388 size_t selfRemovalRejected;
391bool OuterNestedHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
393 if (m_Context.registry->dispatchHandlerForTest(&m_Context.inner)) {
394 ++m_Context.innerDispatches;
400bool InnerNestedHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
402 if (!m_Context.registry->unregisterHandler(
this)) {
403 ++m_Context.selfRemovalRejected;
409bool nestedDispatchRemoval() {
410 constexpr const char* Test =
"pagefault-nested-dispatch";
412 NestedDispatchContext context(registry);
413 const bool outerRegistered = registry->
registerHandler(&context.outer);
414 const bool innerRegistered = registry->
registerHandler(&context.inner);
415 const bool dispatched = registry->dispatchHandlerForTest(&context.outer);
416 const bool innerLateDispatchRejected = !registry->dispatchHandlerForTest(&context.inner);
417 const bool hazardsDrained = registry->activeDispatchCountForTest(&context.outer) == 0 &&
418 registry->activeDispatchCountForTest(&context.inner) == 0;
419 const bool outerCleaned = outerRegistered && registry->
unregisterHandler(&context.outer);
421 const bool innerCleaned = innerReused && registry->
unregisterHandler(&context.inner);
424 passed &= check(outerRegistered && innerRegistered && dispatched,
425 "the nested handlers could not be registered and dispatched", Test);
426 passed &= check(context.innerDispatches == 1 && context.selfRemovalRejected == 1 &&
427 context.orderCount == 4 && context.order[0] == 1 && context.order[1] == 2 &&
428 context.order[2] == 3 && context.order[3] == 4,
429 "nested dispatch did not unwind in strict LIFO order", Test);
430 passed &= check(innerLateDispatchRejected && hazardsDrained,
431 "nested self-removal did not retire after the final hazard", Test);
432 passed &= check(outerCleaned && innerReused && innerCleaned,
433 "nested handlers did not leave reusable registry slots", Test);
435 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-nested-dispatch");
440struct HandlerLifetimeContext;
441HandlerLifetimeContext* g_HandlerLifetimeContext =
nullptr;
445 explicit LifetimeHandler(HandlerLifetimeContext& context) : m_Context(context) {}
447 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
450 HandlerLifetimeContext& m_Context;
453struct HandlerLifetimeContext {
455 : registry(registry),
462 hookObservedDrain(0),
464 callbacksAfterReturn(0),
465 unregisterReturned(0),
466 unregisterSucceeded(0),
467 competitorReturned(0),
468 competitorRejected(0),
469 selfRemovalDuringDrain(0),
473 LifetimeHandler handler;
490bool LifetimeHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
491 m_Context.handlerCalls += 1;
492 if (m_Context.unregisterReturned) {
493 m_Context.callbacksAfterReturn += 1;
495 if (m_Context.phase ==
static_cast<size_t>(3) && !m_Context.registry->unregisterHandler(
this)) {
496 m_Context.selfRemovalDuringDrain += 1;
503 uintptr_t debugAddress = 0;
504 return thread && thread->
getWaitDebugInfo(wait) && wait.queue && wait.channelOwner &&
505 wait.channelValue && wait.queued && wait.reason == WaitQueue::WakeReason::Waiting &&
506 thread->
getDebugState(debugAddress) == Thread::CallbackDrain &&
507 debugAddress ==
reinterpret_cast<uintptr_t
>(handler);
513 : m_Registry(registry), calls(0), rejectionSeen(0) {}
515 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override {
517 if (!m_Registry->unregisterHandler(
this)) {
529 HandlerLifetimeContext* context = g_HandlerLifetimeContext;
530 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
534 context->hookCalls += 1;
535 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
536 while (Time::getTicks() < deadline) {
537 if (context->phase ==
static_cast<size_t>(2) &&
538 hasCallbackDrainWait(context->remover, &context->handler)) {
544 if (context->phase ==
static_cast<size_t>(2) &&
545 hasCallbackDrainWait(context->remover, &context->handler) && !context->unregisterReturned) {
546 context->hookObservedDrain += 1;
547 context->competitorStart = 1;
548 while (!context->competitorReturned && Time::getTicks() < deadline) {
551 if (!context->competitorReturned) {
552 context->failures += 1;
556 context->failures += 1;
561int unregisterPinnedHandler(
void* parameter) {
562 HandlerLifetimeContext* context =
reinterpret_cast<HandlerLifetimeContext*
>(parameter);
563 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
564 while (context->phase !=
static_cast<size_t>(1) && Time::getTicks() < deadline) {
568 if (context->phase !=
static_cast<size_t>(1)) {
569 context->failures += 1;
574 if (context->registry->unregisterHandler(&context->handler)) {
575 context->unregisterSucceeded += 1;
577 context->unregisterReturned += 1;
582int competingUnregister(
void* parameter) {
583 HandlerLifetimeContext* context =
reinterpret_cast<HandlerLifetimeContext*
>(parameter);
584 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
585 while (!context->competitorStart && Time::getTicks() < deadline) {
589 if (!context->competitorStart) {
590 context->failures += 1;
594 if (!context->registry->unregisterHandler(&context->handler)) {
595 context->competitorRejected += 1;
597 context->competitorReturned += 1;
601bool handlerLifetimeBarrier() {
603 HandlerLifetimeContext context(registry);
605 &context,
nullptr,
false,
true);
606 context.remover->setName(
"hosted pagefault-handler remover");
608 &context,
nullptr,
false,
true);
609 context.competitor->setName(
"hosted pagefault-handler competitor");
611 g_HandlerLifetimeContext = &context;
612 PageFaultHandler::setHandlerPinHook(handlerPinHook);
614 const bool duplicateRejected = !registry->
registerHandler(&context.handler);
615 const bool dispatched = registry->dispatchHandlerForTest(&context.handler);
616 const bool joined = context.remover->join();
617 const bool competitorJoined = context.competitor->join();
618 PageFaultHandler::setHandlerPinHook(
nullptr);
619 g_HandlerLifetimeContext =
nullptr;
621 if (!context.unregisterSucceeded) {
625 const size_t callsAtUnregisterReturn = context.handlerCalls;
626 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
627 const bool unregisterBoundaryHeld = lateDispatchRejected &&
628 context.handlerCalls == callsAtUnregisterReturn &&
629 context.callbacksAfterReturn == 0;
632 const bool redispatched = registry->dispatchHandlerForTest(&context.handler);
633 const bool recleaned = reregistered && registry->
unregisterHandler(&context.handler);
635 SelfRemovingHandler selfRemoving(registry);
637 const bool selfDispatched = registry->dispatchHandlerForTest(&selfRemoving);
638 const bool selfLateDispatchRejected = !registry->dispatchHandlerForTest(&selfRemoving);
639 const bool selfReregistered = registry->
registerHandler(&selfRemoving);
640 const bool selfCleanup = selfReregistered && registry->
unregisterHandler(&selfRemoving);
643 passed &= check(registered && duplicateRejected && dispatched,
644 "registration, duplicate rejection, or initial dispatch failed");
645 passed &= check(joined && competitorJoined && context.failures == 0,
646 "the concurrent unregister workers did not finish cleanly");
647 passed &= check(context.hookCalls == 1 && context.hookObservedDrain == 1,
648 "unregister did not wait for the callback pin");
649 passed &= check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
650 context.competitorReturned == 1 && context.competitorRejected == 1 &&
651 context.selfRemovalDuringDrain == 1,
652 "draining self-removal was lost or a second drainer was admitted");
653 passed &= check(unregisterBoundaryHeld,
"a callback began after unregisterHandler returned");
654 passed &= check(reregistered && redispatched && recleaned &&
655 context.handlerCalls == callsAtUnregisterReturn + 1,
656 "a drained handler could not be registered and dispatched again");
657 passed &= check(selfRegistered && selfDispatched && selfRemoving.rejectionSeen == 1 &&
658 selfRemoving.calls == 1 && selfLateDispatchRejected,
659 "self-unregister did not reject its own drain and retire on return");
660 passed &= check(selfReregistered && selfCleanup,
661 "deferred self-removal did not release its registry slot");
664 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-handler-waitqueue-drain");
665 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-handler-lifetime");
670struct CopyOnWriteRaceContext {
672 : addressSpace(addressSpace),
687CopyOnWriteRaceContext* g_CopyOnWriteRaceContext =
nullptr;
689void copyOnWritePreCommit(
void* address) {
690 CopyOnWriteRaceContext* context = g_CopyOnWriteRaceContext;
691 if (!context || context->address != address) {
695 context->arrivals += 1;
696 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
697 while (!context->release && Time::getTicks() < deadline) {
700 if (!context->release) {
701 context->failures += 1;
705int resolveCopyOnWrite(
void* parameter) {
706 CopyOnWriteRaceContext* context =
reinterpret_cast<CopyOnWriteRaceContext*
>(parameter);
707 if (context->addressSpace->handleCopyOnWriteFault(context->address,
true)) {
708 context->successes += 1;
712 context->failures += 1;
716bool concurrentCopyOnWriteResolution() {
717 constexpr const char* Test =
"pagefault-cow-atomic-publish";
722 void* address =
reinterpret_cast<void*
>(addressSpace.
getDynamicStart() + (16 * pageSize));
723 while (addressSpace.
isMapped(address)) {
724 address = adjust_pointer(address, pageSize);
727 const physical_uintptr_t original = physicalMemory.
allocatePage();
728 physicalMemory.
pin(original);
729 physicalMemory.
pin(original);
733 return check(
false,
"the copy-on-write fixture could not be mapped", Test);
736 uint8_t* bytes =
reinterpret_cast<uint8_t*
>(address);
737 for (
size_t i = 0; i < pageSize; ++i) {
738 bytes[i] =
static_cast<uint8_t
>((i * 37U + 11U) & 0xFFU);
742 CopyOnWriteRaceContext context(&addressSpace, address);
744 nullptr,
false,
true,
true);
746 &context,
nullptr,
false,
true,
true);
747 first->setName(
"hosted first copy-on-write resolver");
748 second->setName(
"hosted second copy-on-write resolver");
750 g_CopyOnWriteRaceContext = &context;
751 VirtualAddressSpace::setCopyOnWritePreCommitHookForTest(copyOnWritePreCommit);
752 const bool firstStarted = first->
start();
753 const bool secondStarted = second->
start();
755 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
756 while (context.arrivals !=
static_cast<size_t>(2) && Time::getTicks() < deadline) {
760 physical_uintptr_t preCommitPhysical = 0;
761 size_t preCommitFlags = 0;
762 addressSpace.
getMapping(address, preCommitPhysical, preCommitFlags);
763 const size_t preCommitReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
768 VirtualAddressSpace::setCopyOnWritePreCommitHookForTest(
nullptr);
769 g_CopyOnWriteRaceContext =
nullptr;
771 physical_uintptr_t finalPhysical = 0;
772 size_t finalFlags = 0;
773 addressSpace.
getMapping(address, finalPhysical, finalFlags);
774 bool contentPreserved =
true;
775 for (
size_t i = 0; i < pageSize; ++i) {
776 const uint8_t expected =
static_cast<uint8_t
>((i * 37U + 11U) & 0xFFU);
777 if (bytes[i] != expected) {
778 contentPreserved =
false;
783 const size_t oldReferencesAfter = PhysicalMemoryManager::pageReferenceCountForTest(original);
784 const size_t replacementReferences =
785 PhysicalMemoryManager::pageReferenceCountForTest(finalPhysical);
788 passed &= check(firstStarted && secondStarted && context.arrivals == 2,
789 "both resolvers did not reach the pre-commit window", Test);
793 "a replacement became visible before race revalidation", Test);
794 passed &= check(preCommitReferences == 2,
795 "the pre-commit window changed the shared-page references", Test);
796 passed &= check(firstJoined && secondJoined && context.successes == 2 && context.failures == 0,
797 "the winner or stale-fault resolver did not complete", Test);
800 "the winning replacement was not published writable", Test);
801 passed &= check(contentPreserved,
"the replacement exposed incomplete page contents", Test);
802 passed &= check(oldReferencesAfter == 1 && replacementReferences == 1,
803 "the race did not retire exactly one shared-page reference", Test);
807 physical_uintptr_t privilegedPhysical = 0;
808 size_t privilegedFlags = 0;
809 addressSpace.
getMapping(address, privilegedPhysical, privilegedFlags);
810 passed &= check(userPrivilegeRejected && privilegedPhysical == finalPhysical &&
814 "a user fault accepted a supervisor-only writable mapping as stale", Test);
816 addressSpace.
unmap(address);
817 physicalMemory.
freePage(finalPhysical);
819 const size_t finalOldReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
820 const size_t finalReplacementReferences =
821 PhysicalMemoryManager::pageReferenceCountForTest(finalPhysical);
822 passed &= check(finalOldReferences == 0 && finalReplacementReferences == 0,
823 "the test mappings did not release both physical pages", Test);
826 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-cow-atomic-publish");
831bool hostedAddressSpaceCloneSemantics() {
832 constexpr const char* Test =
"pagefault-hosted-clone-cow";
836 void* address =
reinterpret_cast<void*
>(originalSpace.
getDynamicStart() + (24 * pageSize));
837 while (originalSpace.
isMapped(address)) {
838 address = adjust_pointer(address, pageSize);
845 Process* childProcess =
nullptr;
846 const physical_uintptr_t original = physicalMemory.
allocatePage();
851 delete sourceProcess;
852 return check(
false,
"the private-clone fixture could not be mapped", Test);
856 volatile uint8_t* bytes =
reinterpret_cast<volatile uint8_t*
>(address);
858 bytes[pageSize - 1] = 0x7A;
859 childProcess =
new Process(sourceProcess,
true);
862 physical_uintptr_t sourceBeforePhysical = 0;
863 physical_uintptr_t childBeforePhysical = 0;
864 size_t sourceBeforeFlags = 0;
865 size_t childBeforeFlags = 0;
866 source->
getMapping(address, sourceBeforePhysical, sourceBeforeFlags);
867 child->
getMapping(address, childBeforePhysical, childBeforeFlags);
868 const size_t sharedReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
870 passed &= check(sourceBeforePhysical == original && childBeforePhysical == original &&
875 "a private clone did not downgrade both mappings", Test);
876 passed &= check(sharedReferences == 2,
"the private clone did not retain both page owners", Test);
879 if (sourceResolved) {
882 physical_uintptr_t sourceAfterPhysical = 0;
883 size_t sourceAfterFlags = 0;
884 source->
getMapping(address, sourceAfterPhysical, sourceAfterFlags);
885 passed &= check(sourceResolved && sourceAfterPhysical != original &&
888 bytes[pageSize - 1] == 0x7A &&
889 PhysicalMemoryManager::pageReferenceCountForTest(original) == 1,
890 "the parent write did not split from the child page", Test);
893 passed &= check(bytes[0] == 0x31 && bytes[pageSize - 1] == 0x7A,
894 "the parent write changed the child view", Test);
900 physical_uintptr_t childAfterPhysical = 0;
901 size_t childAfterFlags = 0;
902 child->
getMapping(address, childAfterPhysical, childAfterFlags);
903 passed &= check(childResolved && childAfterPhysical != original &&
904 childAfterPhysical != sourceAfterPhysical &&
907 bytes[pageSize - 1] == 0x7A &&
908 PhysicalMemoryManager::pageReferenceCountForTest(original) == 0,
909 "the child write did not publish an independent page", Test);
912 passed &= check(bytes[0] == 0xA5 && bytes[pageSize - 1] == 0x7A,
913 "the child write changed the parent view", Test);
917 delete sourceProcess;
918 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(original) == 0 &&
919 PhysicalMemoryManager::pageReferenceCountForTest(sourceAfterPhysical) == 0 &&
920 PhysicalMemoryManager::pageReferenceCountForTest(childAfterPhysical) == 0,
921 "the private clone leaked a physical page", Test);
923 constexpr const char* WritableAliasTest =
"pagefault-hosted-clone-writable-alias";
924 sourceProcess =
new Process(kernelProcess,
true);
927 childProcess =
nullptr;
928 const physical_uintptr_t sharedPhysical = physicalMemory.
allocatePage();
930 physicalMemory.
freePage(sharedPhysical);
931 delete sourceProcess;
932 return check(
false,
"the writable-alias fixture could not be mapped", WritableAliasTest) &&
938 childProcess =
new Process(sourceProcess,
false);
941 physical_uintptr_t sourceSharedPhysical = 0;
942 physical_uintptr_t childSharedPhysical = 0;
943 size_t sourceSharedFlags = 0;
944 size_t childSharedFlags = 0;
945 source->
getMapping(address, sourceSharedPhysical, sourceSharedFlags);
946 child->
getMapping(address, childSharedPhysical, childSharedFlags);
948 check(sourceSharedPhysical == sharedPhysical && childSharedPhysical == sharedPhysical &&
953 PhysicalMemoryManager::pageReferenceCountForTest(sharedPhysical) == 2,
954 "clone(false) changed a fresh writable mapping's alias semantics", WritableAliasTest);
958 passed &= check(bytes[0] == 0x48,
"the child did not observe the parent's aliased write",
962 passed &= check(bytes[0] == 0x84,
"the parent did not observe the child's aliased write",
967 delete sourceProcess;
968 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(sharedPhysical) == 0,
969 "the writable alias leaked its physical page", WritableAliasTest);
972 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-hosted-clone-cow");
973 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-hosted-clone-writable-alias");
978struct AbandonedDispatchContext;
979AbandonedDispatchContext* g_AbandonedDispatchContext =
nullptr;
983 explicit AbandonedDispatchHandler(AbandonedDispatchContext& context) : m_Context(context) {}
985 bool trap(InterruptState&, uintptr_t,
bool,
bool)
override;
988 AbandonedDispatchContext& m_Context;
991struct AbandonedDispatchContext {
993 : registry(registry),
999 hookObservedDrain(0),
1001 dispatchReturned(0),
1002 unregisterReturned(0),
1003 unregisterSucceeded(0),
1007 AbandonedDispatchHandler handler;
1020bool AbandonedDispatchHandler::trap(InterruptState&, uintptr_t,
bool,
bool) {
1021 m_Context.handlerCalls += 1;
1026 AbandonedDispatchContext* context = g_AbandonedDispatchContext;
1027 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
1031 context->hookCalls += 1;
1032 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1033 while (Time::getTicks() < deadline) {
1034 if (context->phase ==
static_cast<size_t>(2) &&
1035 hasCallbackDrainWait(context->remover, &context->handler)) {
1036 context->hookObservedDrain += 1;
1038 PerProcessorScheduler::StackDiscardReason::HostedRegression);
1046 context->failures += 1;
1048 PerProcessorScheduler::StackDiscardReason::HostedRegression);
1051int unregisterAbandonedDispatch(
void* parameter) {
1052 AbandonedDispatchContext* context =
reinterpret_cast<AbandonedDispatchContext*
>(parameter);
1053 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1054 while (context->phase !=
static_cast<size_t>(1) && Time::getTicks() < deadline) {
1058 if (context->phase !=
static_cast<size_t>(1)) {
1059 context->failures += 1;
1064 if (context->registry->unregisterHandler(&context->handler)) {
1065 context->unregisterSucceeded += 1;
1067 context->unregisterReturned += 1;
1071int dispatchThenAbandon(
void* parameter) {
1072 AbandonedDispatchContext* context =
reinterpret_cast<AbandonedDispatchContext*
>(parameter);
1073 context->registry->dispatchHandlerForTest(&context->handler);
1074 context->dispatchReturned += 1;
1078bool abandonedDispatchCleanup() {
1080 AbandonedDispatchContext context(registry);
1082 unregisterAbandonedDispatch, &context,
nullptr,
false,
true);
1083 context.remover->setName(
"hosted abandoned pagefault remover");
1085 &context,
nullptr,
false,
true,
true);
1086 context.dispatcher->setName(
"hosted abandoned pagefault dispatch");
1088 g_AbandonedDispatchContext = &context;
1089 PageFaultHandler::setHandlerPinHook(abandonedDispatchPinHook);
1091 const bool started = context.dispatcher->start();
1092 const bool dispatcherJoined = context.dispatcher->join();
1093 const bool removerJoined = context.remover->join();
1094 PageFaultHandler::setHandlerPinHook(
nullptr);
1095 g_AbandonedDispatchContext =
nullptr;
1097 if (!context.unregisterSucceeded) {
1100 const size_t activeDispatches = registry->activeDispatchCountForTest(&context.handler);
1101 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
1104 check(registered && started && dispatcherJoined && removerJoined,
1105 "the abandoned-dispatch workers did not complete") &&
1106 check(context.failures == 0 && context.hookCalls == 1 && context.hookObservedDrain == 1,
1107 "the forced abandonment window was not reached") &&
1108 check(context.handlerCalls == 0 && context.dispatchReturned == 0,
1109 "the abandoned fault stack returned through normal dispatch") &&
1110 check(activeDispatches == 0,
"stack abandonment leaked an active callback hazard") &&
1111 check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
1112 lateDispatchRejected,
1113 "state cleanup did not release the pinned callback exactly once");
1115 NOTICE(
"HOSTED-WAIT-TEST: PASS pagefault-abandoned-dispatch");
1121bool runHostedPageFaultRegressions() {
1122 bool passed = lockIndependentDispatch();
1123 passed &= admissionCloseRevalidation();
1124 passed &= atomicReopenPreservesSelfRemoval();
1125 passed &= nestedDispatchRemoval();
1126 passed &= handlerLifetimeBarrier();
1127 passed &= concurrentCopyOnWriteResolution();
1128 passed &= hostedAddressSpaceCloneSemantics();
1129 passed &= abandonedDispatchCleanup();
virtual bool trap(InterruptState &state, uintptr_t address, bool bIsWrite, bool bWasPresent)=0
EXPORTED_PUBLIC bool unregisterHandler(MemoryTrapHandler *pHandler)
EXPORTED_PUBLIC bool registerHandler(MemoryTrapHandler *pHandler)
static PageFaultHandler & instance()
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() PURE
virtual void freePage(physical_uintptr_t page)=0
virtual void pin(physical_uintptr_t page)=0
VirtualAddressSpace * getAddressSpace()
static bool getInterrupts()
static ProcessorInformation & information()
static void switchAddressSpace(VirtualAddressSpace &AddressSpace)
static void setInterrupts(bool bEnable)
static Scheduler & instance()
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
virtual void setFlags(void *virtualAddress, size_t newFlags)=0
static const size_t CopyOnWrite
virtual uintptr_t getDynamicStart() const
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool isMapped(void *virtualAddress)=0
virtual bool handleCopyOnWriteFault(void *virtualAddress, bool userMode)=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 unmap(void *virtualAddress)=0