The Pedigree Project 0.1
MemoryMappedAnonymous.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Process.h"
3#include "pedigree/kernel/process/Scheduler.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include "MemoryMappedFile.h"
11
12namespace {
13size_t anonymousFlags(size_t flags, MemoryMappedObject::Permissions permissions) {
16 if (!permissions)
18 if (permissions & MemoryMappedObject::Exec)
20 if (!(permissions & MemoryMappedObject::Write))
24 return flags;
25}
26bool admitOwner(Process* identity, VirtualAddressSpace& space, Scheduler::ProcessLease& owner) {
28 owner->getAddressSpace() == &space;
29}
30void accountPages(Process& owner, ssize_t virtualPages, ssize_t physicalPages) {
31#if X64 && !HOSTED
32 // Existing PMM operations charge the executor. Transfer only committed
33 // data and new user-table ownership; failed allocations balance themselves.
34 auto* thread = Processor::information().getCurrentThread();
35 auto* caller = thread ? thread->getParent() : nullptr;
36 if (caller != &owner) {
37 if (caller)
38 caller->trackPages(0, -physicalPages, 0);
39 owner.trackPages(virtualPages, physicalPages, 0);
40 } else
41 owner.trackPages(virtualPages, 0, 0);
42#endif
43}
44PopulationStatus populationStatus(SwapStatus status) {
45 return status == SwapStatus::Success ? PopulationStatus::Success
46 : status == SwapStatus::NoMemory ? PopulationStatus::NoMemory
47 : PopulationStatus::IoError;
48}
49} // namespace
50
51#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
52namespace {
53ssize_t anonymousCloneFailure = -1;
54}
55void AnonymousMemoryMap::setCloneFailureForTest(ssize_t after) {
57 anonymousCloneFailure = after;
58}
59#endif
60AnonymousMemoryMap::~AnonymousMemoryMap() {
62 if (!m_OwnsMappings)
63 return;
64 uintptr_t cursor = 0, address;
65 Page page;
66 while (m_Mappings.lowerBound(cursor, address, page)) {
67 SwapStore::instance().release(page.slot);
68 cursor = address + 1;
69 }
70}
74 if (!result)
75 return nullptr;
76 result->m_OwnerProcess = m_OwnerProcess;
77 result->m_MaximumPermissions = m_MaximumPermissions;
78 uintptr_t cursor = 0, address;
79 Page page;
80 while (m_Mappings.lowerBound(cursor, address, page)) {
81#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
82 if (!anonymousCloneFailure) {
83 delete result;
84 return nullptr;
85 }
86 if (anonymousCloneFailure > 0)
87 --anonymousCloneFailure;
88#endif
89 if (!result->m_Mappings.tryInsert(page.address, page)) {
90 delete result;
91 return nullptr;
92 }
93 if (page.slot && !SwapStore::instance().retain(page.slot)) {
94 result->m_Mappings.remove(page.address);
95 delete result;
96 return nullptr;
97 }
98 cursor = address + 1;
99 }
100 return result;
101}
102MemoryMappedObject* AnonymousMemoryMap::stageSlice(uintptr_t source, size_t sourceLength,
103 uintptr_t destination,
104 size_t destinationLength) {
106 auto* result = new AnonymousMemoryMap(destination, destinationLength, m_Permissions);
107 if (!result)
108 return nullptr;
109 result->m_OwnerProcess = m_OwnerProcess;
110 result->m_OwnsMappings = false;
111 result->m_LockMode = m_LockMode;
112 if (sourceLength == destinationLength && source < m_Address + m_Length &&
113 sourceLength > m_Address + m_Length - source)
114 result->m_Length = m_Address + m_Length - source;
115 result->m_bCopyOnWrite = m_bCopyOnWrite;
116 result->m_MaximumPermissions = m_MaximumPermissions;
117 result->m_Attachment = m_Attachment;
118 const size_t preserved = sourceLength < destinationLength ? sourceLength : destinationLength;
119 uintptr_t cursor = source, address;
120 Page page;
121 while (m_Mappings.lowerBound(cursor, address, page) && address - source < preserved) {
122 Page moved = page;
123 moved.address = destination + page.address - source;
124 if (!result->m_Mappings.tryInsert(moved.address, moved)) {
125 delete result;
126 return nullptr;
127 }
128 cursor = address + 1;
129 }
130 return result;
131}
134 if (at <= m_Address || at >= m_Address + m_Length)
135 return nullptr;
136 auto* result = static_cast<AnonymousMemoryMap*>(
137 stageSlice(at, m_Address + m_Length - at, at, m_Address + m_Length - at));
138 if (!result)
139 return nullptr;
140 uintptr_t address;
141 Page page;
142 while (m_Mappings.lowerBound(at, address, page))
143 m_Mappings.remove(address);
144 m_Length = at - m_Address;
145 result->m_OwnsMappings = m_OwnsMappings;
146 return result;
147}
148void AnonymousMemoryMap::releaseDetachedPage(uintptr_t oldAddress,
151 Page* tracked = m_Mappings.find(oldAddress);
152 if (tracked) {
153 SwapStore::instance().release(tracked->slot);
154 m_Mappings.remove(oldAddress);
155 }
156 if (page.mapped)
158}
159void AnonymousMemoryMap::discardRange(VirtualAddressSpace& space, uintptr_t base, size_t length) {
160#if PEDIGREE_BENCHMARK_VM_DIAGNOSTICS
161 size_t trackedPages = 0, mappedPages = 0;
162#endif
163 uintptr_t address;
164 Page tracked;
165 while (m_Mappings.lowerBound(base, address, tracked) && address - base < length) {
166#if PEDIGREE_BENCHMARK_VM_DIAGNOSTICS
167 ++trackedPages;
168#endif
170 page.mapped = space.detachMapping(reinterpret_cast<void*>(address), page.physical, page.flags);
171#if PEDIGREE_BENCHMARK_VM_DIAGNOSTICS
172 if (page.mapped)
173 ++mappedPages;
174#endif
175 SwapStore::instance().release(tracked.slot);
176 m_Mappings.remove(address);
177 if (page.mapped)
179 }
180#if PEDIGREE_BENCHMARK_VM_DIAGNOSTICS
181 Process* process = Processor::information().getCurrentThread()->getParent();
182 process->recordBenchmarkVmCounter(Process::VmDiscardTrackedPages, trackedPages);
183 process->recordBenchmarkVmCounter(Process::VmDiscardMappedPages, mappedPages);
184#endif
185}
186bool AnonymousMemoryMap::remove(size_t length) {
188 const size_t mask = PhysicalMemoryManager::getPageSize() - 1;
189 length = (length + mask) & ~mask;
190 if (length >= m_Length) {
191 unmapUnlocked();
192 return true;
193 }
194 auto& space = Processor::information().getVirtualAddressSpace();
195 discardRange(space, m_Address, length);
196 m_Address += length;
197 m_Length -= length;
198 return false;
199}
202 auto& space = Processor::information().getVirtualAddressSpace();
203 uintptr_t cursor = 0, address;
204 Page tracked;
205 while (m_Mappings.lowerBound(cursor, address, tracked)) {
206 cursor = address + 1;
207 Page& page = *m_Mappings.find(address);
208 if (page.slot || !space.isMapped(reinterpret_cast<void*>(page.address)))
209 continue;
210 physical_uintptr_t physical;
211 size_t flags;
212 space.getMapping(reinterpret_cast<void*>(page.address), physical, flags);
213 space.setFlags(reinterpret_cast<void*>(page.address), anonymousFlags(flags, permissions));
214 page.pagingBlocked = false;
215 }
217}
222void AnonymousMemoryMap::unmapUnlocked() {
223 auto& space = Processor::information().getVirtualAddressSpace();
224 const size_t mask = PhysicalMemoryManager::getPageSize() - 1;
225 discardRange(space, m_Address, (m_Length + mask) & ~mask);
226}
227SwapStatus AnonymousMemoryMap::restorePage(VirtualAddressSpace& space, Page& page) {
228 auto* address = reinterpret_cast<void*>(page.address);
229 if (!page.slot && !page.pagingBlocked)
230 return SwapStatus::Success;
232 if (!admitOwner(m_OwnerProcess, space, owner))
233 return SwapStatus::Busy;
234 if (!page.slot) {
235 physical_uintptr_t physical;
236 size_t flags;
237 if (!space.isMapped(address))
238 return SwapStatus::Invalid;
239 space.getMapping(address, physical, flags);
240 if (!space.trySetFlags(address, anonymousFlags(flags, m_Permissions)))
241 return SwapStatus::IoError;
242 page.pagingBlocked = false;
243 return SwapStatus::Success;
244 }
245 auto& memory = PhysicalMemoryManager::instance();
246 const auto physical = memory.tryAllocatePage();
247 if (!physical)
248 return SwapStatus::NoMemory;
249 SwapStatus status = SwapStore::instance().readPage(page.slot, physical);
250 size_t committedTables = 0;
251 if (status == SwapStatus::Success &&
252 !space.tryMapUserPage(physical, address, anonymousFlags(0, m_Permissions), &committedTables))
253 status = SwapStatus::NoMemory;
254 if (status != SwapStatus::Success) {
255 memory.freePage(physical);
256 return status;
257 }
258 accountPages(*owner.get(), 1, 1 + static_cast<ssize_t>(committedTables));
259 SwapStore::instance().release(page.slot);
260 page.pagingBlocked = false;
261 return SwapStatus::Success;
262}
263bool AnonymousMemoryMap::trap(VirtualAddressSpace& space, uintptr_t address, bool write,
264 PopulationStatus* population) {
266 if (population)
267 *population = PopulationStatus::NoMemory;
268 const size_t bytes = PhysicalMemoryManager::getPageSize();
269 address &= ~(bytes - 1);
270 if ((write && !(m_Permissions & Write)) || (!write && !(m_Permissions & Read) && !population))
271 return false;
272 Page* entry = m_Mappings.find(address);
273 if (entry && (entry->slot || entry->pagingBlocked)) {
274 const auto status = restorePage(space, *entry);
275 if (population)
276 *population = populationStatus(status);
277 return status == SwapStatus::Success;
278 }
280 if (!admitOwner(m_OwnerProcess, space, owner))
281 return false;
282 auto* virtualAddress = reinterpret_cast<void*>(address);
283 const size_t execute = (m_Permissions & Exec) ? VirtualAddressSpace::Execute : 0;
284 auto& memory = PhysicalMemoryManager::instance();
285 if (!write) {
286 if (space.isMapped(virtualAddress))
287 return false;
288 memory.pin(m_Zero);
289 if (!space.map(m_Zero, virtualAddress, VirtualAddressSpace::Shared | execute)) {
290 memory.freePage(m_Zero);
291 return false;
292 }
293 if (!entry && !m_Mappings.tryInsert(address, Page{address, {}, false})) {
294 space.unmap(virtualAddress);
295 memory.freePage(m_Zero);
296 return false;
297 }
298 } else {
299#if X64 || HOSTED
300 const auto physical = memory.tryAllocatePage();
301#else
302 const auto physical = memory.allocatePage();
303#endif
304 if (!physical)
305 return false;
306 if (!initialisePhysicalPage(physical)) {
307 memory.freePage(physical);
308 return false;
309 }
310 // Reserve tracking before replacing a zero-page mapping. The existing
311 // address entry is reused, so allocation failure cannot orphan the PTE.
312 if (!entry && !m_Mappings.tryInsert(address, Page{address, {}, false})) {
313 memory.freePage(physical);
314 return false;
315 }
316 if (space.isMapped(virtualAddress)) {
317 physical_uintptr_t old;
318 size_t flags;
319 space.getMapping(virtualAddress, old, flags);
320 if (old != m_Zero) {
321 memory.freePage(physical);
322 return false;
323 }
324 space.unmap(virtualAddress);
325 memory.freePage(old);
326 }
327#if X64 || HOSTED
328 size_t committedTables = 0;
329 const bool mapped = space.tryMapUserPage(
330 physical, virtualAddress, VirtualAddressSpace::Write | execute, &committedTables);
331#else
332 const bool mapped = space.map(physical, virtualAddress, VirtualAddressSpace::Write | execute);
333#endif
334 if (!mapped) {
335 memory.freePage(physical);
336 // The retained empty entry still belongs to this object and may retry.
337 return false;
338 }
339#if X64 || HOSTED
340 accountPages(*owner.get(), 1, 1 + static_cast<ssize_t>(committedTables));
341#endif
342 }
343 if (population)
344 *population = PopulationStatus::Success;
345 return true;
346}
348 if (!m_OwnerProcess || !m_OwnsMappings || m_LockMode != MemoryLockMode::None || !m_bCopyOnWrite ||
349 m_Attachment)
350 return false;
351 Page* page = m_Mappings.find(address);
352 if (page) {
353 if (page->slot)
354 return true;
355 if (page->pagingBlocked)
356 return false;
357 auto* at = reinterpret_cast<void*>(address);
358 if (!space.isMapped(at))
359 return true;
360 physical_uintptr_t physical;
361 size_t flags;
362 space.getMapping(at, physical, flags);
363 if (physical == m_Zero)
364 return !(flags & VirtualAddressSpace::NoAccess);
365 return (flags & VirtualAddressSpace::Write) &&
369 }
370 return !space.isMapped(reinterpret_cast<void*>(address));
371}
372SwapStatus AnonymousMemoryMap::pageOutAt(VirtualAddressSpace& space, uintptr_t address,
373 bool& released) {
374 released = false;
375 if (!MemoryMapManager::instance().operationOwnedByCurrentExecution() ||
376 !supportsPageOut(space, address))
377 return SwapStatus::Unsupported;
379 if (!admitOwner(m_OwnerProcess, space, owner))
380 return SwapStatus::Busy;
381 Page* page = m_Mappings.find(address);
382 auto* at = reinterpret_cast<void*>(address);
383 if (page && !page->slot && space.isMapped(at)) {
384 physical_uintptr_t physical;
385 size_t flags;
386 space.getMapping(at, physical, flags);
387 if (!space.trySetFlags(at, flags | VirtualAddressSpace::NoAccess))
388 return SwapStatus::IoError;
389 page->pagingBlocked = true;
390 SwapReference slot;
391 const bool zero = physical == m_Zero;
392 const auto status =
393 zero ? SwapStatus::Success : SwapStore::instance().writePage(physical, slot);
394 if (status == SwapStatus::Success && space.tryDetachUserPage(at, physical)) {
395 page->slot = slot;
396 page->pagingBlocked = false;
398 accountPages(*owner.get(), -1, zero ? 0 : -1);
399 released = !zero;
400 return SwapStatus::Success;
401 }
402 SwapStore::instance().release(slot);
403 if (space.trySetFlags(at, flags))
404 page->pagingBlocked = false;
405 return status == SwapStatus::Success ? SwapStatus::IoError : status;
406 }
407 return SwapStatus::Success;
408}
409bool AnonymousMemoryMap::pageOut(VirtualAddressSpace& space) {
410 if (!MemoryMapManager::instance().operationOwnedByCurrentExecution() ||
411 !SwapStore::instance().snapshot().active)
412 return false;
413 uintptr_t cursor = 0, address;
414 Page page;
415 while (m_Mappings.lowerBound(cursor, address, page)) {
416 cursor = address + 1;
417 if (!supportsPageOut(space, address))
418 continue;
419 bool released = false;
420 if (pageOutAt(space, address, released) != SwapStatus::Success)
421 return false;
422 if (released)
423 return true;
424 }
425 return false;
426}
427SwapStatus AnonymousMemoryMap::restoreAll(VirtualAddressSpace& space) {
428 uintptr_t cursor = 0, address;
429 Page page;
430 while (m_Mappings.lowerBound(cursor, address, page)) {
431 const auto status = restorePage(space, *m_Mappings.find(address));
432 if (status != SwapStatus::Success)
433 return status;
434 cursor = address + 1;
435 }
436 return SwapStatus::Success;
437}
438
439PopulationStatus AnonymousMemoryMap::prepareResidentAccess(VirtualAddressSpace& space,
440 uintptr_t address) {
441 auto restoreResidentPage = [&]() {
442 Page* page = m_Mappings.find(address);
443 if (page && page->pagingBlocked)
444 return populationStatus(restorePage(space, *page));
445 return PopulationStatus::Success;
446 };
447 // User copies already hold the manager operation gate; avoid rebuilding the
448 // interrupt and termination deferral scopes for every resident page.
449 if (MemoryMapManager::instance().operationOwnedByCurrentExecution())
450 return restoreResidentPage();
451
453 return restoreResidentPage();
454}
Memory-mapped file interface.
virtual void unmap() override
virtual void setPermissions(MemoryMappedObject::Permissions perms) override
bool supportsPageOut(VirtualAddressSpace &space, uintptr_t address) override
virtual bool remove(size_t length) override
virtual bool trap(VirtualAddressSpace &space, uintptr_t address, bool bWrite, PopulationStatus *population=nullptr) override
virtual MemoryMappedObject * split(uintptr_t at) override
virtual MemoryMappedObject * clone() override
MemoryMappedObject * stageSlice(uintptr_t source, size_t sourceLength, uintptr_t destination, size_t destinationLength) override
static MemoryMapManager & instance()
size_t length() const
uintptr_t address() const
Permissions permissions() const
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
Process * getParent()
Definition Process.h:568
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:264
virtual void setFlags(void *virtualAddress, size_t newFlags)=0
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool isMapped(void *virtualAddress)=0
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
virtual bool detachMapping(void *virtualAddress, physical_uintptr_t &physical, size_t &flags, size_t requiredFlags=0)
virtual MUST_USE_RESULT bool trySetFlags(void *virtualAddress, size_t newFlags)
virtual void unmap(void *virtualAddress)=0