The Pedigree Project 0.1
MemoryMappedFile-remap.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/Process.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/Vector.h"
9
10#include "File.h"
11#include "MemoryMappedFile.h"
12
13bool MemoryMappedFile::backingRangeValid(uintptr_t source, size_t length) const {
14 return source >= m_Address && source - m_Address <= ~size_t(0) - m_Offset &&
15 length <= ~size_t(0) - (m_Offset + (source - m_Address));
16}
17
18MemoryMappedObject* MemoryMappedFile::stageSlice(uintptr_t source, size_t sourceLength,
19 uintptr_t destination, size_t destinationLength) {
20 if (!backingRangeValid(source, destinationLength))
21 return nullptr;
22 auto* result = new MemoryMappedFile(
23 destination, destinationLength, m_Offset + (source - m_Address), m_pBacking, m_bCopyOnWrite,
24 m_Permissions, m_MaximumPermissions, m_Attachment, m_Origin, m_ExecutableUse);
25 if (!result)
26 return nullptr;
27 result->m_OwnerProcess = m_OwnerProcess;
28 result->m_OwnsMappings = false;
29 result->m_LockMode = m_LockMode;
30 if (sourceLength == destinationLength && source < m_Address + m_Length &&
31 sourceLength > m_Address + m_Length - source)
32 result->m_Length = m_Address + m_Length - source;
33 const size_t preserved = sourceLength < destinationLength ? sourceLength : destinationLength;
34 for (auto it = m_Mappings.count() ? m_Mappings.begin() : m_Mappings.end(); it != m_Mappings.end();
35 ++it) {
36 if (it.key() >= source && it.key() - source < preserved &&
37 !result->m_Mappings.tryInsert(destination + (it.key() - source), it.value())) {
38 delete result;
39 return nullptr;
40 }
41 }
42 return result;
43}
44
45namespace {
47using Status = MemoryMapManager::VmStatus;
48using PageStatus = VirtualAddressSpace::RemapStatus;
49
50uintptr_t roundedEnd(MemoryMappedObject* object, size_t pageMask) {
51 return (object->address() + object->length() + pageMask) & ~pageMask;
52}
53bool overlaps(uintptr_t a, size_t aLength, uintptr_t b, size_t bLength) {
54 return a < b + bLength && b < a + aLength;
55}
56MemoryAllocator* allocatorFor(Snapshot& snapshot, VirtualAddressSpace& space, uintptr_t base,
57 size_t length) {
58 if (length > ~uintptr_t(0) - base)
59 return nullptr;
60 const uintptr_t end = base + length;
61 if (space.getDynamicStart() && base >= space.getDynamicStart() && end <= space.getDynamicEnd())
62 return &snapshot.dynamic;
63 if (base >= space.getUserStart() && end <= space.getUserReservedStart())
64 return &snapshot.normal;
65 return nullptr;
66}
67bool allocateAnywhere(Snapshot& snapshot, size_t length, size_t pageMask, uintptr_t& address) {
68 MemoryAllocator* allocators[] = {&snapshot.dynamic, &snapshot.normal};
69 for (MemoryAllocator* allocator : allocators) {
70 uintptr_t allocation = 0;
71 if (!allocator->allocate(length + pageMask, allocation))
72 continue;
73 address = (allocation + pageMask) & ~pageMask;
74 if (address != allocation && !allocator->tryFree(allocation, address - allocation))
75 return false;
76 const uintptr_t tail = address + length;
77 if (tail < allocation + length + pageMask &&
78 !allocator->tryFree(tail, allocation + length + pageMask - tail))
79 return false;
80 return true;
81 }
82 return false;
83}
84Status translate(PageStatus status) {
85 switch (status) {
86 case PageStatus::Success:
87 return Status::Success;
88 case PageStatus::InvalidRange:
89 return Status::InvalidRange;
90 case PageStatus::Unsupported:
91 return Status::Unsupported;
92 default:
93 return Status::NoMemory;
94 }
95}
96
97struct MetadataPlan {
98 List<MemoryMappedObject*> replacement;
101 bool committed = false;
102 ~MetadataPlan() {
103 for (auto* object : committed ? retired : staged)
104 delete object;
105 }
106 bool appendSlice(MemoryMappedObject* owner, uintptr_t from, size_t length, uintptr_t destination,
107 size_t newLength) {
108 MemoryMappedObject* object = owner->stageSlice(from, length, destination, newLength);
109 if (!object)
110 return false;
111 staged.pushBack(object);
112 return replacement.tryPushBack(object);
113 }
114};
115} // namespace
116
117MemoryMapManager::VmStatus MemoryMapManager::remap(const RemapRequest& request, uintptr_t& result) {
118 OperationGuard operation(*this);
119 const size_t pageSize = PhysicalMemoryManager::getPageSize();
120 const size_t pageMask = pageSize - 1;
121 if (!request.oldLength)
122 return request.mayMove ? VmStatus::Unsupported : VmStatus::InvalidRange;
123 if (!request.newLength || ((request.source | request.oldLength | request.newLength) & pageMask) ||
124 request.oldLength > ~uintptr_t(0) - request.source ||
125 request.newLength > ~size_t(0) - pageMask ||
126 (request.fixed &&
127 (!request.mayMove || (request.destination & pageMask) ||
128 request.newLength > ~uintptr_t(0) - request.destination ||
129 overlaps(request.source, request.oldLength, request.destination, request.newLength))))
130 return VmStatus::InvalidRange;
131 VirtualAddressSpace& space = Processor::information().getVirtualAddressSpace();
132 Process* process = Processor::information().getCurrentThread()->getParent();
133 MmObjectList* objects = m_MmObjectLists.lookup(&space);
134 MemoryMappedObject* source = nullptr;
135 if (objects) {
136 for (auto* object : *objects) {
137 if (object->address() <= request.source && request.source < roundedEnd(object, pageMask) &&
138 request.oldLength <= roundedEnd(object, pageMask) - request.source) {
139 source = object;
140 break;
141 }
142 }
143 }
144 if (!source)
145 return !contains(request.source, request.oldLength) &&
146 space.isMapped(reinterpret_cast<void*>(request.source))
147 ? VmStatus::Unsupported
148 : VmStatus::Unmapped;
149 if (source->backingFile() && source->backingFile()->isDirectPhysicalMapping())
150 return VmStatus::Unsupported;
151 if (!source->backingRangeValid(request.source, request.newLength))
152 return VmStatus::InvalidRange;
153 auto attachment = source->m_Attachment;
154 if (attachment) {
155 if (request.source != source->address() ||
156 request.oldLength != roundedEnd(source, pageMask) - source->address() ||
157 request.newLength > request.oldLength)
158 return VmStatus::Unsupported;
159 for (auto* object : *objects)
160 if (object != source && object->m_Attachment.get() == attachment.get())
161 return VmStatus::Unsupported;
162 }
163 if (!request.fixed && request.oldLength == request.newLength) {
164 result = request.source;
165 return VmStatus::Success;
166 }
167 // Bound both leaf preparation and the worst-case tracking/list metadata.
168 // The additional object cap includes pooled list storage for all fragments.
169 constexpr size_t MaximumObjects = 4096;
170 if (request.oldLength / pageSize > VirtualAddressSpace::MaximumRemapPages ||
171 request.newLength / pageSize > VirtualAddressSpace::MaximumRemapPages ||
172 objects->count() > MaximumObjects)
173 return VmStatus::NoMemory;
174 for (size_t attempt = 0; attempt < 32; ++attempt) {
175 Snapshot snapshot;
176 if (!process->snapshotUserReservations(snapshot))
177 return VmStatus::NoMemory;
178 uintptr_t destination = request.fixed ? request.destination : request.source;
179 if (!request.fixed && request.newLength > request.oldLength) {
180 const uintptr_t extension = request.source + request.oldLength;
181 MemoryAllocator* allocator =
182 allocatorFor(snapshot, space, extension, request.newLength - request.oldLength);
183 const bool inPlace =
184 extension == roundedEnd(source, pageMask) && allocator &&
185 allocator->allocateSpecific(extension, request.newLength - request.oldLength);
186 if (!inPlace) {
187 if (!request.mayMove ||
188 !allocateAnywhere(snapshot, request.newLength, pageMask, destination))
189 return VmStatus::NoMemory;
190 }
191 }
192 MemoryAllocator* destinationAllocator =
193 allocatorFor(snapshot, space, destination, request.newLength);
194 if (!destinationAllocator)
195 return VmStatus::InvalidRange;
196
198 MetadataPlan metadata;
199 if (!victims.tryReserve(objects->count()) || !metadata.retired.tryReserve(objects->count()) ||
200 !metadata.staged.tryReserve(objects->count() * 3 + 1))
201 return VmStatus::NoMemory;
202 size_t metadataPages = request.newLength / pageSize;
203 for (auto* object : *objects) {
204 const size_t length = roundedEnd(object, pageMask) - object->address();
205 const bool victim = destination != request.source &&
206 overlaps(destination, request.newLength, object->address(), length);
207 if (victim) {
208 if (!request.fixed)
209 return VmStatus::NoMemory;
210 if (object->backingFile() && object->backingFile()->isDirectPhysicalMapping())
211 return VmStatus::Unsupported;
212 const uintptr_t first = object->address() > destination ? object->address() : destination;
213 const uintptr_t last = object->address() + length < destination + request.newLength
214 ? object->address() + length
215 : destination + request.newLength;
216 victims.pushBack({first, last - first});
217 }
218 if (object == source || victim) {
219 if (length / pageSize > VirtualAddressSpace::MaximumRemapPages - metadataPages)
220 return VmStatus::NoMemory;
221 metadataPages += length / pageSize;
222 metadata.retired.pushBack(object);
223 }
224 }
225 auto* account = space.memoryLockAccount();
226 auto charge = account ? account->charge() : MemoryLockCharge{};
227 if (account) {
228 size_t removed = 0;
229 if (source->m_LockMode != MemoryLockMode::None)
230 removed += request.oldLength / pageSize;
231 for (auto* object : metadata.retired) {
232 if (object == source || object->m_LockMode == MemoryLockMode::None)
233 continue;
234 const uintptr_t first = object->address() > destination ? object->address() : destination;
235 const uintptr_t end = roundedEnd(object, pageMask);
236 const uintptr_t last =
237 end < destination + request.newLength ? end : destination + request.newLength;
238 removed += (last - first) / pageSize;
239 }
240 assert(removed <= charge.managedPages);
241 charge.managedPages -= removed;
242 if (source->m_LockMode != MemoryLockMode::None) {
243 const size_t added = request.newLength / pageSize;
244 if (added > ~size_t(0) - charge.managedPages)
245 return VmStatus::NoMemory;
246 charge.managedPages += added;
247 if (charge.rawPages > ~size_t(0) - charge.managedPages ||
248 (request.newLength > request.oldLength &&
249 !account->permitsTotalPages(charge.managedPages + charge.rawPages,
250 process->getEffectiveUserId() == 0)))
251 return VmStatus::LockLimit;
252 }
253 }
254 if (request.fixed) {
255 uintptr_t cursor = destination;
256 const uintptr_t end = destination + request.newLength;
257 while (cursor < end) {
258 uintptr_t coveredEnd = cursor, next = end;
259 for (const auto& victim : victims) {
260 if (victim.base <= cursor && cursor < victim.base + victim.length)
261 coveredEnd = victim.base + victim.length;
262 else if (victim.base > cursor && victim.base < next)
263 next = victim.base;
264 }
265 if (coveredEnd > cursor)
266 cursor = coveredEnd < end ? coveredEnd : end;
267 else {
268 if (!destinationAllocator->allocateSpecific(cursor, next - cursor))
269 return VmStatus::NoMemory;
270 cursor = next;
271 }
272 }
273 }
274 uintptr_t released = request.source;
275 size_t releasedLength = request.oldLength;
276 if (destination == request.source) {
277 released += request.newLength;
278 releasedLength =
279 request.oldLength > request.newLength ? request.oldLength - request.newLength : 0;
280 }
281 if (releasedLength) {
282 auto* allocator = allocatorFor(snapshot, space, released, releasedLength);
283 if (!allocator || !allocator->tryFree(released, releasedLength))
284 return VmStatus::NoMemory;
285 }
286 for (auto* object : *objects) {
287 bool replaced = false;
288 for (auto* retired : metadata.retired)
289 replaced = replaced || retired == object;
290 if (!replaced) {
291 if (!metadata.replacement.tryPushBack(object))
292 return VmStatus::NoMemory;
293 continue;
294 }
296 size_t cutCount = 0;
297 if (object == source)
298 cuts[cutCount++] = {request.source, request.oldLength};
299 if (destination != request.source)
300 cuts[cutCount++] = {destination, request.newLength};
301 if (cutCount == 2 && cuts[1].base < cuts[0].base) {
302 const auto first = cuts[0];
303 cuts[0] = cuts[1];
304 cuts[1] = first;
305 }
306 uintptr_t cursor = object->address();
307 const uintptr_t end = roundedEnd(object, pageMask);
308 for (size_t i = 0; i < cutCount; ++i) {
309 const uintptr_t first = cuts[i].base > cursor ? cuts[i].base : cursor;
310 const uintptr_t last =
311 cuts[i].base + cuts[i].length < end ? cuts[i].base + cuts[i].length : end;
312 if (first >= end || last <= cursor)
313 continue;
314 if (first > cursor &&
315 !metadata.appendSlice(object, cursor, first - cursor, cursor, first - cursor))
316 return VmStatus::NoMemory;
317 cursor = last;
318 }
319 if (cursor < end && !metadata.appendSlice(object, cursor, end - cursor, cursor, end - cursor))
320 return VmStatus::NoMemory;
321 }
322 if (!metadata.appendSlice(source, request.source, request.oldLength, destination,
323 request.newLength))
324 return VmStatus::NoMemory;
325 // A complete replacement list owns its own nodes; publication replaces an
326 // existing tree value without transferring nodes between ObjectPools.
327 auto* replacement = new MmObjectList;
328 if (!replacement)
329 return VmStatus::NoMemory;
330 for (auto* object : metadata.replacement) {
331 if (!replacement->tryPushBack(object)) {
332 delete replacement;
333 return VmStatus::NoMemory;
334 }
335 }
337 request.source, destination, request.oldLength,
338 request.newLength, request.fixed, victims.count() ? &victims[0] : nullptr,
339 victims.count()};
341 PageStatus status = space.prepareRemap(pages, prepared);
342 if (status == PageStatus::Success) {
343 struct Admission {
344 Process* process;
345 Snapshot* snapshot;
346 } admission{process, &snapshot};
347 status = prepared.get()->commit(
348 [](void* opaque) {
349 auto* state = static_cast<Admission*>(opaque);
350 return state->process->commitUserReservations(state->snapshot->generation,
351 *state->snapshot);
352 },
353 &admission);
354 }
355 if (status != PageStatus::Success) {
356 delete replacement;
357 if (status == PageStatus::Retry)
358 continue;
359 return translate(status);
360 }
361 for (auto* object : metadata.retired)
362 object->setMappingOwnership(false);
363 for (auto* object : metadata.staged)
364 object->setMappingOwnership(true);
365 {
367 assert(m_MmObjectLists.contains(&space));
368 m_MmObjectLists.insert(&space, replacement);
369 }
370 if (attachment)
371 attachment->relocate(destination);
372 metadata.committed = true;
373 for (size_t i = 0; i < prepared.get()->detachedPageCount(); ++i) {
374 const auto& page = prepared.get()->detachedPages()[i];
375 for (auto* owner : metadata.retired) {
376 if (owner->address() <= page.address && page.address < roundedEnd(owner, pageMask)) {
377 owner->releaseDetachedPage(page.address, page);
378 break;
379 }
380 }
381 }
382 delete objects;
383 if (account)
384 account->publish(charge, account->futureMode());
385 if (source->m_LockMode == MemoryLockMode::Eager && request.newLength > request.oldLength)
386 populateMemory(space, destination + request.oldLength, request.newLength - request.oldLength);
387 result = destination;
388 return VmStatus::Success;
389 }
390 return VmStatus::NoMemory;
391}
Memory-mapped file interface.
Definition List.h:61
Tree< VirtualAddressSpace *, MmObjectList * > m_MmObjectLists
bool contains(uintptr_t base, size_t length)
Tree< uintptr_t, physical_uintptr_t > m_Mappings
MemoryMappedObject * stageSlice(uintptr_t source, size_t sourceLength, uintptr_t destination, size_t destinationLength) override
size_t length() const
uintptr_t address() const
Process * getParent()
Definition Process.h:567
static ProcessorInformation & information()
bool allocateSpecific(T address, T length)
Definition RangeList.h:363
Iterator begin()
Definition Tree.h:402
Iterator end()
Definition Tree.h:427
size_t count() const
Definition Tree.h:142
A vector / dynamic array.
Definition Vector.h:33
virtual uintptr_t getUserReservedStart() const =0
virtual bool isMapped(void *virtualAddress)=0
virtual uintptr_t getUserStart() const =0
virtual RemapStatus prepareRemap(const PageRemapRequest &request, UniquePointer< PreparedPageRemap > &plan)
#define assert(x)
Definition assert.h:39
bool tryPushBack(const T &value)
Definition List.h:246
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270