xref: /reactos/ntoskrnl/mm/marea.c (revision 3051eb0e)
1 /*
2  * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  *
19  * PROJECT:         ReactOS kernel
20  * FILE:            ntoskrnl/mm/marea.c
21  * PURPOSE:         Implements memory areas
22  *
23  * PROGRAMMERS:     Rex Jolliff
24  *                  David Welch
25  *                  Eric Kohl
26  *                  Philip Susi
27  *                  Casper Hornstrup
28  *                  Eric Kohl
29  *                  Ge van Geldorp
30  *                  Royce Mitchell III
31  *                  Aleksey Bragin
32  *                  Jason Filby
33  *                  Thomas Weidenmueller
34  *                  Gunnar Andre' Dalsnes
35  *                  Mike Nordell
36  *                  Alex Ionescu
37  *                  Filip Navara
38  *                  Herve Poussineau
39  *                  Steven Edwards
40  */
41 
42 /* INCLUDES *****************************************************************/
43 
44 #include <ntoskrnl.h>
45 #define NDEBUG
46 #include <cache/section/newmm.h>
47 #include <debug.h>
48 
49 #include "ARM3/miarm.h"
50 
51 MEMORY_AREA MiStaticMemoryAreas[MI_STATIC_MEMORY_AREAS];
52 ULONG MiStaticMemoryAreaCount;
53 
54 MM_AVL_TABLE MiRosKernelVadRoot;
55 BOOLEAN MiRosKernelVadRootInitialized;
56 
57 /* FUNCTIONS *****************************************************************/
58 
59 PMEMORY_AREA NTAPI
60 MmLocateMemoryAreaByAddress(
61     PMMSUPPORT AddressSpace,
62     PVOID Address_)
63 {
64     ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
65     PEPROCESS Process;
66     PMM_AVL_TABLE Table;
67     PMMADDRESS_NODE Node;
68     PMEMORY_AREA MemoryArea;
69     TABLE_SEARCH_RESULT Result;
70     PMMVAD_LONG Vad;
71 
72     Process = MmGetAddressSpaceOwner(AddressSpace);
73     Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
74 
75     Result = MiCheckForConflictingNode(StartVpn, StartVpn, Table, &Node);
76     if (Result != TableFoundNode)
77     {
78         return NULL;
79     }
80 
81     Vad = (PMMVAD_LONG)Node;
82     if (Vad->u.VadFlags.Spare == 0)
83     {
84         /* Check if this is VM VAD */
85         if (Vad->ControlArea == NULL)
86         {
87             /* We store the reactos MEMORY_AREA here */
88             MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
89         }
90         else
91         {
92             /* This is a section VAD. Store the MAREA here for now */
93             MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
94         }
95     }
96     else
97     {
98         MemoryArea = (PMEMORY_AREA)Node;
99     }
100 
101     return MemoryArea;
102 }
103 
104 PMEMORY_AREA
105 NTAPI
106 MmLocateMemoryAreaByRegion(
107     PMMSUPPORT AddressSpace,
108     PVOID Address_,
109     ULONG_PTR Length)
110 {
111     ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
112     ULONG_PTR EndVpn = ((ULONG_PTR)Address_ + Length - 1) / PAGE_SIZE;
113     PEPROCESS Process;
114     PMM_AVL_TABLE Table;
115     PMMADDRESS_NODE Node;
116     PMEMORY_AREA MemoryArea;
117     TABLE_SEARCH_RESULT Result;
118     PMMVAD_LONG Vad;
119 
120     Process = MmGetAddressSpaceOwner(AddressSpace);
121     Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
122 
123     Result = MiCheckForConflictingNode(StartVpn, EndVpn, Table, &Node);
124     if (Result != TableFoundNode)
125     {
126         return NULL;
127     }
128 
129     Vad = (PMMVAD_LONG)Node;
130     if (Vad->u.VadFlags.Spare == 0)
131     {
132         /* Check if this is VM VAD */
133         if (Vad->ControlArea == NULL)
134         {
135             /* We store the reactos MEMORY_AREA here */
136             MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
137         }
138         else
139         {
140             /* This is a section VAD. Store the MAREA here for now */
141             MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
142         }
143     }
144     else
145     {
146         MemoryArea = (PMEMORY_AREA)Node;
147     }
148 
149     ASSERT(MemoryArea != NULL);
150     return MemoryArea;
151 }
152 
153 VOID
154 NTAPI
155 MiInsertVad(IN PMMVAD Vad,
156             IN PMM_AVL_TABLE VadRoot);
157 
158 ULONG
159 NTAPI
160 MiMakeProtectionMask(
161     IN ULONG Protect
162 );
163 
164 
165 static VOID
166 MmInsertMemoryArea(
167     PMMSUPPORT AddressSpace,
168     PMEMORY_AREA marea)
169 {
170     PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
171 
172     marea->VadNode.u.VadFlags.Spare = 1;
173     marea->VadNode.u.VadFlags.Protection = MiMakeProtectionMask(marea->Protect);
174 
175     /* Build a lame VAD if this is a user-space allocation */
176     if (marea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT)
177     {
178         ASSERT(Process != NULL);
179         if (marea->Type != MEMORY_AREA_OWNED_BY_ARM3)
180         {
181             ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW || marea->Type == MEMORY_AREA_CACHE);
182 
183             /* Insert the VAD */
184             MiLockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
185             MiInsertVad(&marea->VadNode, &Process->VadRoot);
186             MiUnlockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
187             marea->Vad = &marea->VadNode;
188         }
189     }
190     else
191     {
192         ASSERT(Process == NULL);
193 
194         if (!MiRosKernelVadRootInitialized)
195         {
196             MiRosKernelVadRoot.BalancedRoot.u1.Parent = &MiRosKernelVadRoot.BalancedRoot;
197             MiRosKernelVadRoot.Unused = 1;
198             MiRosKernelVadRootInitialized = TRUE;
199         }
200 
201         /* Insert the VAD */
202         MiLockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
203         MiInsertVad(&marea->VadNode, &MiRosKernelVadRoot);
204         MiUnlockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
205         marea->Vad = NULL;
206     }
207 }
208 
209 PVOID NTAPI
210 MmFindGap(
211     PMMSUPPORT AddressSpace,
212     ULONG_PTR Length,
213     ULONG_PTR Granularity,
214     BOOLEAN TopDown)
215 {
216     PEPROCESS Process;
217     PMM_AVL_TABLE VadRoot;
218     TABLE_SEARCH_RESULT Result;
219     PMMADDRESS_NODE Parent;
220     ULONG_PTR StartingAddress, HighestAddress;
221 
222     Process = MmGetAddressSpaceOwner(AddressSpace);
223     VadRoot = Process ? &Process->VadRoot : &MiRosKernelVadRoot;
224     if (TopDown)
225     {
226         /* Find an address top-down */
227         HighestAddress = Process ? (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS : (LONG_PTR)-1;
228         Result = MiFindEmptyAddressRangeDownTree(Length,
229                                                  HighestAddress,
230                                                  Granularity,
231                                                  VadRoot,
232                                                  &StartingAddress,
233                                                  &Parent);
234     }
235     else
236     {
237         Result = MiFindEmptyAddressRangeInTree(Length,
238                                                Granularity,
239                                                VadRoot,
240                                                &Parent,
241                                                &StartingAddress);
242     }
243 
244     if (Result == TableFoundNode)
245     {
246         return NULL;
247     }
248 
249     return (PVOID)StartingAddress;
250 }
251 
252 VOID
253 NTAPI
254 MiRemoveNode(IN PMMADDRESS_NODE Node,
255              IN PMM_AVL_TABLE Table);
256 
257 
258 /**
259  * @name MmFreeMemoryArea
260  *
261  * Free an existing memory area.
262  *
263  * @param AddressSpace
264  *        Address space to free the area from.
265  * @param MemoryArea
266  *        Memory area we're about to free.
267  * @param FreePage
268  *        Callback function for each freed page.
269  * @param FreePageContext
270  *        Context passed to the callback function.
271  *
272  * @return Status
273  *
274  * @remarks Lock the address space before calling this function.
275  */
276 
277 NTSTATUS NTAPI
278 MmFreeMemoryArea(
279     PMMSUPPORT AddressSpace,
280     PMEMORY_AREA MemoryArea,
281     PMM_FREE_PAGE_FUNC FreePage,
282     PVOID FreePageContext)
283 {
284     ULONG_PTR Address;
285     PVOID EndAddress;
286 
287     /* Make sure we own the address space lock! */
288     ASSERT(CONTAINING_RECORD(AddressSpace, EPROCESS, Vm)->AddressCreationLock.Owner == KeGetCurrentThread());
289 
290     /* Check magic */
291     ASSERT(MemoryArea->Magic == 'erAM');
292 
293     if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
294     {
295         PEPROCESS CurrentProcess = PsGetCurrentProcess();
296         PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
297 
298         if (Process != NULL &&
299                 Process != CurrentProcess)
300         {
301             KeAttachProcess(&Process->Pcb);
302         }
303 
304         EndAddress = MM_ROUND_UP(MA_GetEndingAddress(MemoryArea), PAGE_SIZE);
305         for (Address = MA_GetStartingAddress(MemoryArea);
306                 Address < (ULONG_PTR)EndAddress;
307                 Address += PAGE_SIZE)
308         {
309             BOOLEAN Dirty = FALSE;
310             SWAPENTRY SwapEntry = 0;
311             PFN_NUMBER Page = 0;
312 
313             if (MmIsPageSwapEntry(Process, (PVOID)Address))
314             {
315                 MmDeletePageFileMapping(Process, (PVOID)Address, &SwapEntry);
316             }
317             else
318             {
319                 MmDeleteVirtualMapping(Process, (PVOID)Address, &Dirty, &Page);
320             }
321             if (FreePage != NULL)
322             {
323                 FreePage(FreePageContext, MemoryArea, (PVOID)Address,
324                          Page, SwapEntry, (BOOLEAN)Dirty);
325             }
326 #if (_MI_PAGING_LEVELS == 2)
327             /* Remove page table reference */
328             ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
329             if ((SwapEntry || Page) && ((PVOID)Address < MmSystemRangeStart))
330             {
331                 ASSERT(AddressSpace != MmGetKernelAddressSpace());
332                 if (MiQueryPageTableReferences((PVOID)Address) == 0)
333                 {
334                     /* No PTE relies on this PDE. Release it */
335                     KIRQL OldIrql = MiAcquirePfnLock();
336                     PMMPDE PointerPde = MiAddressToPde(Address);
337                     ASSERT(PointerPde->u.Hard.Valid == 1);
338                     MiDeletePte(PointerPde, MiPdeToPte(PointerPde), Process, NULL);
339                     ASSERT(PointerPde->u.Hard.Valid == 0);
340                     MiReleasePfnLock(OldIrql);
341                 }
342             }
343 #endif
344         }
345 
346         if (Process != NULL &&
347                 Process != CurrentProcess)
348         {
349             KeDetachProcess();
350         }
351 
352         //if (MemoryArea->VadNode.StartingVpn < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT
353         if (MemoryArea->Vad)
354         {
355             ASSERT(MemoryArea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT);
356             ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW || MemoryArea->Type == MEMORY_AREA_CACHE);
357 
358             /* MmCleanProcessAddressSpace might have removed it (and this would be MmDeleteProcessAdressSpace) */
359             ASSERT(MemoryArea->VadNode.u.VadFlags.Spare != 0);
360             if (((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare == 1)
361             {
362                 MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &Process->VadRoot);
363             }
364 
365             MemoryArea->Vad = NULL;
366         }
367         else
368         {
369             MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &MiRosKernelVadRoot);
370         }
371     }
372 
373 #if DBG
374     MemoryArea->Magic = 'daeD';
375 #endif
376     ExFreePoolWithTag(MemoryArea, TAG_MAREA);
377 
378     DPRINT("MmFreeMemoryArea() succeeded\n");
379 
380     return STATUS_SUCCESS;
381 }
382 
383 /**
384  * @name MmCreateMemoryArea
385  *
386  * Create a memory area.
387  *
388  * @param AddressSpace
389  *        Address space to create the area in.
390  * @param Type
391  *        Type of the memory area.
392  * @param BaseAddress
393  *        Base address for the memory area we're about the create. On
394  *        input it contains either 0 (auto-assign address) or preferred
395  *        address. On output it contains the starting address of the
396  *        newly created area.
397  * @param Length
398  *        Length of the area to allocate.
399  * @param Attributes
400  *        Protection attributes for the memory area.
401  * @param Result
402  *        Receives a pointer to the memory area on successful exit.
403  *
404  * @return Status
405  *
406  * @remarks Lock the address space before calling this function.
407  */
408 
409 NTSTATUS NTAPI
410 MmCreateMemoryArea(PMMSUPPORT AddressSpace,
411                    ULONG Type,
412                    PVOID *BaseAddress,
413                    ULONG_PTR Length,
414                    ULONG Protect,
415                    PMEMORY_AREA *Result,
416                    ULONG AllocationFlags,
417                    ULONG Granularity)
418 {
419     ULONG_PTR tmpLength;
420     PMEMORY_AREA MemoryArea;
421     ULONG_PTR EndingAddress;
422 
423     DPRINT("MmCreateMemoryArea(Type 0x%lx, BaseAddress %p, "
424            "*BaseAddress %p, Length %p, AllocationFlags %x, "
425            "Result %p)\n",
426            Type, BaseAddress, *BaseAddress, Length, AllocationFlags,
427            Result);
428 
429     /* Is this a static memory area? */
430     if (Type & MEMORY_AREA_STATIC)
431     {
432         /* Use the static array instead of the pool */
433         ASSERT(MiStaticMemoryAreaCount < MI_STATIC_MEMORY_AREAS);
434         MemoryArea = &MiStaticMemoryAreas[MiStaticMemoryAreaCount++];
435     }
436     else
437     {
438         /* Allocate the memory area from nonpaged pool */
439         MemoryArea = ExAllocatePoolWithTag(NonPagedPool,
440                                            sizeof(MEMORY_AREA),
441                                            TAG_MAREA);
442     }
443 
444     if (!MemoryArea)
445     {
446         DPRINT1("Not enough memory.\n");
447         return STATUS_NO_MEMORY;
448     }
449 
450     RtlZeroMemory(MemoryArea, sizeof(MEMORY_AREA));
451     MemoryArea->Type = Type & ~MEMORY_AREA_STATIC;
452     MemoryArea->Protect = Protect;
453     MemoryArea->Flags = AllocationFlags;
454     MemoryArea->Magic = 'erAM';
455     MemoryArea->DeleteInProgress = FALSE;
456 
457     if (*BaseAddress == 0)
458     {
459         tmpLength = (ULONG_PTR)MM_ROUND_UP(Length, PAGE_SIZE);
460         *BaseAddress = MmFindGap(AddressSpace,
461                                  tmpLength,
462                                  Granularity,
463                                  (AllocationFlags & MEM_TOP_DOWN) == MEM_TOP_DOWN);
464         if ((*BaseAddress) == 0)
465         {
466             DPRINT("No suitable gap\n");
467             if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
468             return STATUS_NO_MEMORY;
469         }
470 
471         MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
472         MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
473         MmInsertMemoryArea(AddressSpace, MemoryArea);
474     }
475     else
476     {
477         EndingAddress = ((ULONG_PTR)*BaseAddress + Length - 1) | (PAGE_SIZE - 1);
478         *BaseAddress = ALIGN_DOWN_POINTER_BY(*BaseAddress, Granularity);
479         tmpLength = EndingAddress + 1 - (ULONG_PTR)*BaseAddress;
480 
481         if (!MmGetAddressSpaceOwner(AddressSpace) && *BaseAddress < MmSystemRangeStart)
482         {
483             ASSERT(FALSE);
484             if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
485             return STATUS_ACCESS_VIOLATION;
486         }
487 
488         if (MmGetAddressSpaceOwner(AddressSpace) &&
489                 (ULONG_PTR)(*BaseAddress) + tmpLength > (ULONG_PTR)MmSystemRangeStart)
490         {
491             DPRINT("Memory area for user mode address space exceeds MmSystemRangeStart\n");
492             if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
493             return STATUS_ACCESS_VIOLATION;
494         }
495 
496         /* No need to check ARM3 owned memory areas, the range MUST be free */
497         if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
498         {
499             if (MmLocateMemoryAreaByRegion(AddressSpace,
500                                            *BaseAddress,
501                                            tmpLength) != NULL)
502             {
503                 DPRINT("Memory area already occupied\n");
504                 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
505                 return STATUS_CONFLICTING_ADDRESSES;
506             }
507         }
508 
509         MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
510         MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
511         MmInsertMemoryArea(AddressSpace, MemoryArea);
512     }
513 
514     *Result = MemoryArea;
515 
516     DPRINT("MmCreateMemoryArea() succeeded (%p)\n", *BaseAddress);
517     return STATUS_SUCCESS;
518 }
519 
520 VOID
521 NTAPI
522 MiRosCleanupMemoryArea(
523     PEPROCESS Process,
524     PMMVAD Vad)
525 {
526     PMEMORY_AREA MemoryArea;
527     PVOID BaseAddress;
528     NTSTATUS Status;
529 
530     /* We must be called from MmCleanupAddressSpace and nowhere else!
531        Make sure things are as expected... */
532     ASSERT(Process == PsGetCurrentProcess());
533     ASSERT(Process->VmDeleted == TRUE);
534     ASSERT(((PsGetCurrentThread()->ThreadsProcess == Process) &&
535             (Process->ActiveThreads == 1)) ||
536            (Process->ActiveThreads == 0));
537 
538     /* We are in cleanup, we don't need to synchronize */
539     MmUnlockAddressSpace(&Process->Vm);
540 
541     MemoryArea = (PMEMORY_AREA)Vad;
542     BaseAddress = (PVOID)MA_GetStartingAddress(MemoryArea);
543 
544     if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
545     {
546         Status = MiRosUnmapViewOfSection(Process, BaseAddress, Process->ProcessExiting);
547     }
548     else if (MemoryArea->Type == MEMORY_AREA_CACHE)
549     {
550         Status = MmUnmapViewOfCacheSegment(&Process->Vm, BaseAddress);
551     }
552     else
553     {
554         /* There shouldn't be anything else! */
555         ASSERT(FALSE);
556     }
557 
558     /* Make sure this worked! */
559     ASSERT(NT_SUCCESS(Status));
560 
561     /* Lock the address space again */
562     MmLockAddressSpace(&Process->Vm);
563 }
564 
565 VOID
566 NTAPI
567 MmDeleteProcessAddressSpace2(IN PEPROCESS Process);
568 
569 NTSTATUS
570 NTAPI
571 MmDeleteProcessAddressSpace(PEPROCESS Process)
572 {
573 #ifndef _M_AMD64
574     KIRQL OldIrql;
575 #endif
576 
577     DPRINT("MmDeleteProcessAddressSpace(Process %p (%s))\n", Process,
578            Process->ImageFileName);
579 
580 #ifndef _M_AMD64
581     OldIrql = MiAcquireExpansionLock();
582     RemoveEntryList(&Process->MmProcessLinks);
583     MiReleaseExpansionLock(OldIrql);
584 #endif
585     MmLockAddressSpace(&Process->Vm);
586 
587     /* There should not be any memory areas left! */
588     ASSERT(Process->Vm.WorkingSetExpansionLinks.Flink == NULL);
589 
590 #if (_MI_PAGING_LEVELS == 2)
591     {
592         KIRQL OldIrql;
593         PVOID Address;
594         PMMPDE pointerPde;
595 
596         /* Attach to Process */
597         KeAttachProcess(&Process->Pcb);
598 
599         /* Acquire PFN lock */
600         OldIrql = MiAcquirePfnLock();
601 
602         for (Address = MI_LOWEST_VAD_ADDRESS;
603              Address < MM_HIGHEST_VAD_ADDRESS;
604              Address = (PVOID)((ULONG_PTR)Address + (PTE_PER_PAGE * PAGE_SIZE)))
605         {
606             /* At this point all references should be dead */
607             if (MiQueryPageTableReferences(Address) != 0)
608             {
609                 DPRINT1("Process %p, Address %p, UsedPageTableEntries %lu\n",
610                         Process,
611                         Address,
612                         MiQueryPageTableReferences(Address));
613                 ASSERT(MiQueryPageTableReferences(Address) == 0);
614             }
615 
616             pointerPde = MiAddressToPde(Address);
617             /* Unlike in ARM3, we don't necesarrily free the PDE page as soon as reference reaches 0,
618              * so we must clean up a bit when process closes */
619             if (pointerPde->u.Hard.Valid)
620                 MiDeletePte(pointerPde, MiPdeToPte(pointerPde), Process, NULL);
621             ASSERT(pointerPde->u.Hard.Valid == 0);
622         }
623 
624         /* Release lock */
625         MiReleasePfnLock(OldIrql);
626 
627         /* Detach */
628         KeDetachProcess();
629     }
630 #endif
631 
632     MmUnlockAddressSpace(&Process->Vm);
633 
634     DPRINT("Finished MmDeleteProcessAddressSpace()\n");
635     MmDeleteProcessAddressSpace2(Process);
636     return(STATUS_SUCCESS);
637 }
638 
639 /* EOF */
640