2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/machine/Disk.h"
4#include "pedigree/kernel/process/Process.h"
5#include "pedigree/kernel/process/Scheduler.h"
6#include "pedigree/kernel/process/Thread.h"
7#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
8#include "pedigree/kernel/processor/Processor.h"
9#include "pedigree/kernel/processor/ProcessorInformation.h"
10#include "pedigree/kernel/utilities/utility.h"
12#include "modules/system/vfs/File.h"
14#include "modules/system/vfs/VFS.h"
16#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS && THREADS
18constexpr size_t PageSize = 4096, DataPages = 16;
19bool check(
bool value,
const char* detail) {
21 ERROR(
"SWAP-VM-TEST: FAIL " << detail);
24class DeferredPagingProcess final :
public Process {
26 explicit DeferredPagingProcess(
Process* parent) :
Process(DeferredPublication(), parent) {}
27 ~DeferredPagingProcess()
override {
30 void publishForTest() {
39 RecordingSwapDisk() : bytes(new unsigned char[(DataPages + 1) * PageSize]) {
43 bytes[1028] = DataPages;
44 MemoryCopy(bytes + PageSize - 10,
"SWAPSPACE2", 10);
49 ~RecordingSwapDisk()
override {
53 size_t getSize()
const override {
54 return (DataPages + 1) * PageSize;
56 bool pin(uint64_t)
override {
59 void unpin(uint64_t)
override {}
61 out = bytes ? this :
nullptr;
62 return bytes ? PagingStatus::Success : PagingStatus::NoMemory;
64 PagingStatus transfer(PagingOperation operation, uint64_t offset,
void* data)
override {
65 if (operation == PagingOperation::Flush) {
67 return failFlush ? PagingStatus::IoError : PagingStatus::Success;
69 if (offset >
getSize() - PageSize || (offset & (PageSize - 1)))
70 return PagingStatus::Invalid;
71 if (operation == PagingOperation::Read) {
72 if (readsBeforeFailure == 0)
73 return PagingStatus::IoError;
74 if (readsBeforeFailure > 0)
77 MemoryCopy(data, bytes + offset, PageSize);
81 return PagingStatus::IoError;
82 MemoryCopy(bytes + offset, data, PageSize);
84 return PagingStatus::Success;
86 void release()
override {
90 bool failWrite =
false, failFlush =
false;
91 ssize_t readsBeforeFailure = -1;
92 size_t reads = 0, writes = 0, flushes = 0, releases = 0;
94int swapWorker(
void* opaque) {
95 bool& passed = *
static_cast<bool*
>(opaque);
99 RecordingSwapDisk disk;
100 File emptyFile(
String(
"swap-empty-backing"), 0, 0, 0, 1,
nullptr, 0,
nullptr);
102 const uint32_t endpoint = disk.endpointId();
104 if (!check(disk.bytes && endpoint && SwapStore::instance().prepareAtBoot(),
"fixture setup"))
106 disk.bytes[PageSize - 10] = 0;
107 if (!check(manager.activateSwap(endpoint) == SwapStatus::Invalid,
"unformatted rejected"))
109 disk.bytes[PageSize - 10] =
'S';
110 disk.bytes[1032] = 1;
111 if (!check(manager.activateSwap(endpoint) == SwapStatus::Unsupported,
"bad-page list rejected"))
113 disk.bytes[1032] = 0;
115 if (!check(disk.acquireUse(loan) && manager.activateSwap(endpoint) == SwapStatus::Busy,
116 "ordinary ownership excludes activation"))
119 if (!check(manager.activateSwap(endpoint) == SwapStatus::Success &&
120 manager.activateSwap(endpoint) == SwapStatus::Busy && !disk.acquireUse(loan),
121 "activation exclusivity"))
123 if (!check(VFS::unloadAdmission(
false) == Module::UnloadAdmission::Busy &&
124 VFS::unloadAdmission(
true) == Module::UnloadAdmission::KeepMapped,
125 "active paging retains VFS service"))
127 if (!check(manager.mapAnon(base, 3 * PageSize,
128 MemoryMappedObject::Read | MemoryMappedObject::Write) !=
nullptr,
131 for (
size_t n = 0; n < 3; ++n) {
132 if (!check(manager.faultIn(base + n * PageSize,
true),
"initial population"))
134 ByteSet(
reinterpret_cast<void*
>(base + n * PageSize), 0x40 + n, PageSize);
137 MemoryMappedObject::Read | MemoryMappedObject::Write);
138 if (!check(!unenrolled.supportsPageOut(space, base),
"unenrolled owner stays resident"))
140 const auto before = memory.memorySnapshot();
141 disk.failWrite =
true;
142 if (!check(manager.pageOutRange(base, PageSize) == SwapStatus::IoError &&
143 space.isMapped(
reinterpret_cast<void*
>(base)) &&
144 *
reinterpret_cast<unsigned char*
>(base) == 0x40 &&
145 !manager.swapSnapshot().usedPages,
146 "failed write retains RAM and releases slot"))
148 disk.failWrite =
false;
149 disk.failFlush =
true;
150 if (!check(manager.pageOutRange(base, PageSize) == SwapStatus::IoError &&
151 space.isMapped(
reinterpret_cast<void*
>(base)) &&
152 !manager.swapSnapshot().usedPages,
153 "failed flush retains RAM"))
155 disk.failFlush =
false;
156 if (!check(manager.compact() && !space.isMapped(
reinterpret_cast<void*
>(base)) &&
157 manager.swapSnapshot().usedPages == 1 &&
158 memory.memorySnapshot().freePages > before.freePages,
159 "real compactor releases RAM"))
161 disk.readsBeforeFailure = 0;
162 if (!check(!manager.faultIn(base,
false) && !space.isMapped(
reinterpret_cast<void*
>(base)) &&
163 manager.swapSnapshot().usedPages == 1,
164 "failed read retains slot"))
166 disk.readsBeforeFailure = -1;
167 if (!check(manager.faultIn(base,
false) && *
reinterpret_cast<unsigned char*
>(base) == 0x40 &&
168 !manager.swapSnapshot().usedPages,
169 "read retry restores contents"))
171 NOTICE(
"SWAP-VM-TEST: PASS write-flush-read-retry-physical-release");
172 if (!check(manager.pageOutRange(base, 3 * PageSize) == SwapStatus::Success &&
173 manager.swapSnapshot().usedPages == 3,
174 "drain preparation"))
176 disk.readsBeforeFailure = 1;
177 if (!check(manager.deactivateSwap(endpoint) == SwapStatus::IoError &&
178 manager.swapSnapshot().active && manager.swapSnapshot().usedPages == 2 &&
179 space.isMapped(
reinterpret_cast<void*
>(base)) &&
180 *
reinterpret_cast<unsigned char*
>(base) == 0x40,
181 "partial I/O drain remains active"))
183 disk.readsBeforeFailure = -1;
184 PhysicalMemoryManager::setTryAllocationFailureForTest(0);
185 const auto exhausted = manager.deactivateSwap(endpoint);
186 PhysicalMemoryManager::setTryAllocationFailureForTest(-1);
187 if (!check(exhausted == SwapStatus::NoMemory && manager.swapSnapshot().usedPages == 2 &&
188 manager.swapSnapshot().active,
189 "OOM drain retains remaining references"))
191 disk.failFlush =
true;
192 if (!check(manager.deactivateSwap(endpoint) == SwapStatus::IoError &&
193 manager.swapSnapshot().active && !manager.swapSnapshot().usedPages,
194 "final flush failure retains empty channel"))
196 disk.failFlush =
false;
197 if (!check(manager.deactivateSwap(endpoint) == SwapStatus::Success &&
198 !manager.swapSnapshot().active && disk.releases == 3 &&
199 *
reinterpret_cast<unsigned char*
>(base + 2 * PageSize) == 0x42,
200 "drain retry releases channel"))
202 NOTICE(
"SWAP-VM-TEST: PASS partial-drain-io-oom-retry");
203 if (!check(manager.activateSwap(endpoint) == SwapStatus::Success &&
204 manager.pageOutRange(base, 3 * PageSize) == SwapStatus::Success,
205 "clone preparation"))
211 if (!check(child !=
nullptr,
"child address space"))
213 AnonymousMemoryMap::setCloneFailureForTest(1);
214 const bool cloned = manager.clone(child);
215 AnonymousMemoryMap::setCloneFailureForTest(-1);
216 const bool rollback = !cloned && manager.swapSnapshot().usedPages == 3;
222 if (!check(rollback,
"failed clone reference rollback"))
225 if (!check(manager.deactivateSwap(endpoint) == SwapStatus::Success,
226 "clone failure did not retain references"))
228 if (!check(manager.activateSwap(endpoint) == SwapStatus::Success &&
229 manager.pageOutRange(base, 3 * PageSize) == SwapStatus::Success,
230 "foreign owner preparation"))
235 auto* target =
new DeferredPagingProcess(parent);
236 if (!check(target !=
nullptr,
"unpublished target"))
238 bool owned = manager.clone(target);
239 owned = check(owned && manager.deactivateSwap(endpoint) == SwapStatus::Busy &&
240 manager.swapSnapshot().active,
241 "unpublished owner blocks drain") &&
243 target->publishForTest();
245 owned = check(manager.deactivateSwap(endpoint) == SwapStatus::Success,
246 "published foreign owner restores");
249 owned = check(*
reinterpret_cast<unsigned char*
>(base + 2 * PageSize) == 0x42,
250 "foreign restored contents");
252 owned = check(manager.activateSwap(endpoint) == SwapStatus::Success &&
253 manager.pageOutRange(base, PageSize) == SwapStatus::Success,
254 "foreign owner pageout");
256 target->closeForTest();
258 owned = check(manager.deactivateSwap(endpoint) == SwapStatus::Busy &&
259 manager.swapSnapshot().usedPages == 1,
260 "closed owner retains slot");
265 if (manager.swapSnapshot().active)
266 owned = check(manager.deactivateSwap(endpoint) == SwapStatus::Success,
267 "retired owner releases drain") &&
272 NOTICE(
"SWAP-VM-TEST: PASS foreign-owner-publication-retirement");
273 if (!check(VFS::unloadAdmission(
false) == Module::UnloadAdmission::Busy &&
274 VFS::unloadAdmission(
true) == Module::UnloadAdmission::KeepMapped,
275 "registered mapping service remains after drain"))
277 NOTICE(
"SWAP-VM-TEST: PASS clone-metadata-failure-retirement");
278 NOTICE(
"SWAP-VM-TEST: PASS vfs-service-retention");
279 using Resolution = MemoryMapManager::FaultResolution;
280 if (!check(manager.activateSwap(endpoint) == SwapStatus::Success &&
281 manager.pageOutRange(base, PageSize) == SwapStatus::Success &&
282 manager.setPermissions(base, 3 * PageSize, MemoryMappedObject::Read),
283 "ordinary fault resolver preparation"))
285 const size_t reads = disk.reads;
286 if (!check(manager.resolveUserFault(base,
true,
false,
false) == Resolution::Unhandled &&
287 manager.resolveUserFault(base,
false,
false,
true) == Resolution::Unhandled &&
288 manager.setPermissions(base, 3 * PageSize, MemoryMappedObject::None) &&
289 manager.resolveUserFault(base,
false,
false,
false) == Resolution::Unhandled &&
290 disk.reads == reads && manager.swapSnapshot().usedPages == 1 &&
291 !space.isMapped(
reinterpret_cast<void*
>(base)),
292 "readonly NX and inaccessible faults retain slot without I/O"))
294 if (!check(manager.setPermissions(base, 3 * PageSize, MemoryMappedObject::Read),
295 "resolver read permission restored"))
297 disk.readsBeforeFailure = 0;
298 if (!check(manager.resolveUserFault(base,
false,
false,
false) == Resolution::Unhandled &&
299 manager.swapSnapshot().usedPages == 1 &&
300 !space.isMapped(
reinterpret_cast<void*
>(base)),
301 "resolver I/O failure retains backing"))
303 disk.readsBeforeFailure = -1;
304 if (!check(manager.resolveUserFault(base,
false,
false,
false) == Resolution::Resolved &&
305 *
reinterpret_cast<unsigned char*
>(base) == 0x40 &&
306 !manager.swapSnapshot().usedPages &&
307 manager.resolveUserFault(base,
true,
true,
false) == Resolution::Unhandled,
308 "resolver retry restores contents and preserves write protection"))
310 if (!check(manager.setPermissions(base, 3 * PageSize,
311 MemoryMappedObject::Read | MemoryMappedObject::Write) &&
312 manager.pageOutRange(base, PageSize) == SwapStatus::Success &&
313 manager.setPermissions(base, 3 * PageSize, MemoryMappedObject::Exec) &&
314 manager.resolveUserFault(base,
false,
false,
true) == Resolution::Resolved &&
315 !manager.swapSnapshot().usedPages,
316 "permitted fetch restores execute-only swapped page"))
318 const bool splitMappingCreated =
319 manager.setPermissions(base + PageSize, PageSize,
320 MemoryMappedObject::Read | MemoryMappedObject::Write) == 1;
321 if (!check(splitMappingCreated &&
322 manager.pageOutRange(base + PageSize, PageSize) == SwapStatus::Success &&
323 manager.resolveUserFault(base + PageSize,
false,
false,
false) ==
324 Resolution::Resolved &&
325 *
reinterpret_cast<unsigned char*
>(base + PageSize) == 0x41 &&
326 !manager.swapSnapshot().usedPages,
327 "split mapping fault resolver reuses selected object"))
331 manager.mapFile(&emptyFile, empty, PageSize, MemoryMappedObject::Read) &&
332 manager.resolveUserFault(empty,
false,
false,
false) == Resolution::BackingFault &&
333 manager.resolveUserFault(empty,
true,
false,
false) == Resolution::Unhandled &&
334 !space.isMapped(
reinterpret_cast<void*
>(empty)) &&
335 manager.deactivateSwap(endpoint) == SwapStatus::Success,
336 "EOF classified for SIGBUS after logical permission checks"))
338 NOTICE(
"SWAP-VM-TEST: PASS ordinary-fault-retry-permissions-EOF");
341 PhysicalMemoryManager::setTryAllocationFailureForTest(-1);
342 AnonymousMemoryMap::setCloneFailureForTest(-1);
343 disk.failWrite = disk.failFlush =
false;
344 disk.readsBeforeFailure = -1;
346 if (manager.swapSnapshot().active)
348 check(manager.deactivateSwap(endpoint) == SwapStatus::Success,
"fixture drain") && passed;
352bool runSwapRegressions() {
353 NOTICE(
"SWAP-VM-TEST: BEGIN");
355 if (!check(process !=
nullptr,
"isolated process"))
358 auto*
worker =
new Thread(process, swapWorker, &passed,
nullptr,
false,
true,
true);
360 const bool joined = started &&
worker->joinForCompletion();
363 if (!joined && started)
364 return check(
false,
"worker completion");
366 passed = check(started && joined && passed,
"isolated worker result");
368 NOTICE(
"SWAP-VM-TEST: END PASS");
Memory-mapped file interface.
virtual size_t getSize() const
Gets the size of the disk.
virtual void unpin(uint64_t location)=0
virtual PagingStatus preparePagingTransport(PagingTransport *&transport)
virtual MUST_USE_RESULT bool pin(uint64_t location)=0
Pins a cache page.
static MemoryMapManager & instance()
static PhysicalMemoryManager & instance()
VirtualAddressSpace * getAddressSpace()
void prepareForDestruction()
static ProcessorInformation & information()
static void switchAddressSpace(VirtualAddressSpace &AddressSpace)
static Scheduler & instance()