summaryrefslogtreecommitdiffstats
path: root/private/ntos/mm/wrtfault.c
blob: d2227d4b8ae2fe2a3b125cc1cf7910be4dd8ae31 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

   wrtfault.c

Abstract:

    This module contains the copy on write routine for memory management.

Author:

    Lou Perazzoli (loup) 10-Apr-1989

Revision History:

--*/

#include "mi.h"

NTSTATUS
FASTCALL
MiCopyOnWrite (
    IN PVOID FaultingAddress,
    IN PMMPTE PointerPte
    )

/*++

Routine Description:

    This routine performs a copy on write operation for the specified
    virtual address.

Arguments:

    FaultingAddress - Supplies the virtual address which caused the
                      fault.

    PointerPte - Supplies the pointer to the PTE which caused the
                 page fault.


Return Value:

    Returns the status of the fault handling operation.  Can be one of:
        - Success.
        - In-page Error.

Environment:

    Kernel mode, APC's disabled, Working set mutex held.

--*/

{
    MMPTE TempPte;
    ULONG PageFrameIndex;
    ULONG NewPageIndex;
    PULONG CopyTo;
    PULONG CopyFrom;
    KIRQL OldIrql;
    PMMPFN Pfn1;
//    PMMPTE PointerPde;
    PEPROCESS CurrentProcess;
    PMMCLONE_BLOCK CloneBlock;
    PMMCLONE_DESCRIPTOR CloneDescriptor;
    PVOID VirtualAddress;
    ULONG WorkingSetIndex;
    BOOLEAN FakeCopyOnWrite = FALSE;

    //
    // This is called from MmAccessFault, the PointerPte is valid
    // and the working set mutex ensures it cannot change state.
    //

#if DBG
    if (MmDebug & MM_DBG_WRITEFAULT) {
        DbgPrint("**copy on write Fault va %lx proc %lx thread %lx\n",
            (ULONG)FaultingAddress,
            (ULONG)PsGetCurrentProcess(), (ULONG)PsGetCurrentThread());
    }

    if (MmDebug & MM_DBG_PTE_UPDATE) {
        MiFormatPte(PointerPte);
    }
#endif //DBG

    ASSERT (PsGetCurrentProcess()->ForkInProgress == NULL);

    //
    // Capture the PTE contents to TempPte.
    //

    TempPte = *PointerPte;

    //
    // Check to see if this is a prototype PTE with copy on write
    // enabled.
    //

    if (TempPte.u.Hard.CopyOnWrite == 0) {

        //
        // This is a fork page which is being made private in order
        // to change the protection of the page.
        // Do not make the page writable.
        //

        FakeCopyOnWrite = TRUE;
    }

    PageFrameIndex = TempPte.u.Hard.PageFrameNumber;
    Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
    CurrentProcess = PsGetCurrentProcess();

    //
    // Acquire the PFN mutex.
    //

    VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
    WorkingSetIndex = MiLocateWsle (VirtualAddress, MmWorkingSetList,
                        Pfn1->u1.WsIndex);

    LOCK_PFN (OldIrql);

    //
    // The page must be copied into a new page.
    //

    //
    // If a fork operation is in progress and the faulting thread
    // is not the thread performning the fork operation, block until
    // the fork is completed.
    //

    if ((CurrentProcess->ForkInProgress != NULL) &&
        (CurrentProcess->ForkInProgress != PsGetCurrentThread())) {
        MiWaitForForkToComplete (CurrentProcess);
        UNLOCK_PFN (OldIrql);
        return STATUS_SUCCESS;
    }

    if (MiEnsureAvailablePageOrWait(CurrentProcess, NULL)) {

        //
        // A wait operation was performed to obtain an available
        // page and the working set mutex and pfn mutexes have
        // been released and various things may have changed for
        // the worse.  Rather than examine all the conditions again,
        // return and if things are still proper, the fault we
        // be taken again.
        //

        UNLOCK_PFN (OldIrql);
        return STATUS_SUCCESS;
    }

    //
    // Increment the number of private pages.
    //

    CurrentProcess->NumberOfPrivatePages += 1;

    MmInfoCounters.CopyOnWriteCount += 1;

    //
    // A page is being copied and made private, the global state of
    // the shared page needs to be updated at this point on certain
    // hardware.  This is done by ORing the dirty bit into the modify bit in
    // the PFN element.
    //

    MI_CAPTURE_DIRTY_BIT_TO_PFN (PointerPte, Pfn1);

    //
    // This must be a prototype PTE.  Perform the copy on write.
    //

#if DBG
    if (Pfn1->u3.e1.PrototypePte == 0) {
        DbgPrint("writefault - PTE indicates cow but not protopte\n");
        MiFormatPte(PointerPte);
        MiFormatPfn(Pfn1);
    }
#endif

    CloneBlock = (PMMCLONE_BLOCK)Pfn1->PteAddress;

    //
    // If the share count for the physical page is one, the reference
    // count is one, and the modified flag is clear the current page
    // can be stolen to satisfy the copy on write.
    //

#if 0
// COMMENTED OUT ****************************************************
// COMMENTED OUT ****************************************************
// COMMENTED OUT ****************************************************
    if ((Pfn1->u2.ShareCount == 1) && (Pfn1->u3.e2.ReferenceCount == 1)
            && (Pfn1->u3.e1.Modified == 0)) {

        //
        // Make this page a private page and return the prototype
        // PTE into its original contents.  The PFN database for
        // this page now points to this PTE.
        //

        //
        // Note that a page fault could occur referencing the prototype
        // PTE, so we map it into hyperspace to prevent a fault.
        //

        MiRestorePrototypePte (Pfn1);

        Pfn1->PteAddress = PointerPte;

        //
        // Get the protection for the page.
        //

        VirtualAddress = MiGetVirtualAddressMappedByPte (PointerPte);
        WorkingSetIndex = MiLocateWsle (VirtualAddress, MmWorkingSetList,
                            Pfn1->u1.WsIndex);

        ASSERT (WorkingSetIndex != WSLE_NULL_INDEX) {

        Pfn1->OriginalPte.u.Long = 0;
        Pfn1->OriginalPte.u.Soft.Protection =
                MI_MAKE_PROTECT_NOT_WRITE_COPY (
                                MmWsle[WorkingSetIndex].u1.e1.Protection);

        PointerPde = MiGetPteAddress(PointerPte);
        Pfn1->u3.e1.PrototypePte = 0;
        Pfn1->PteFrame = PointerPde->u.Hard.PageFrameNumber;

        if (!FakeCopyOnWrite) {

            //
            // If the page was Copy On Write and stolen or if the page was not
            // copy on write, update the PTE setting both the dirty bit and the
            // accessed bit. Note, that as this PTE is in the TB, the TB must
            // be flushed.
            //

            MI_SET_PTE_DIRTY (TempPte);
            TempPte.u.Hard.Write = 1;
            MI_SET_ACCESSED_IN_PTE (&TempPte, 1);
            TempPte.u.Hard.CopyOnWrite = 0;
            *PointerPte = TempPte;

            //
            // This is a copy on write operation, set the modify bit
            // in the PFN database and deallocate any page file space.
            //

            Pfn1->u3.e1.Modified = 1;

            if ((Pfn1->OriginalPte.u.Soft.Prototype == 0) &&
                (Pfn1->u3.e1.WriteInProgress == 0)) {

                //
                // This page is in page file format, deallocate the page
                // file space.
                //

                MiReleasePageFileSpace (Pfn1->OriginalPte);

                //
                // Change original PTE to indicate no page file space is
                // reserved, otherwise the space will be deallocated when
                // the PTE is deleted.
                //

                Pfn1->OriginalPte.u.Soft.PageFileHigh = 0;
            }
        }

        //
        // The TB entry must be flushed as the valid PTE with the dirty
        // bit clear has been fetched into the TB. If it isn't flushed,
        // another fault is generated as the dirty bit is not set in
        // the cached TB entry.
        //


        KeFillEntryTb ((PHARDWARE_PTE)PointerPte, FaultingAddress, TRUE);

        CloneDescriptor = MiLocateCloneAddress ((PVOID)CloneBlock);

        if (CloneDescriptor != (PMMCLONE_DESCRIPTOR)NULL) {

            //
            // Decrement the reference count for the clone block,
            // note that this could release and reacquire
            // the mutexes.
            //

            MiDecrementCloneBlockReference ( CloneDescriptor,
                                             CloneBlock,
                                             CurrentProcess );
        }

    ] else [

// ABOVE COMMENTED OUT ****************************************************
// ABOVE COMMENTED OUT ****************************************************
#endif

    //
    // Get a new page with the same color as this page.
    //

    NewPageIndex = MiRemoveAnyPage (
                    MI_GET_SECONDARY_COLOR (PageFrameIndex,
                                            Pfn1));
    MiInitializeCopyOnWritePfn (NewPageIndex, PointerPte, WorkingSetIndex);

    UNLOCK_PFN (OldIrql);

    CopyTo = (PULONG)MiMapPageInHyperSpace (NewPageIndex, &OldIrql);
    CopyFrom = (PULONG)MiGetVirtualAddressMappedByPte (PointerPte);

    RtlCopyMemory ( CopyTo, CopyFrom, PAGE_SIZE);

    MiUnmapPageInHyperSpace (OldIrql);

    if (!FakeCopyOnWrite) {

        //
        // If the page was really a copy on write page, make it
        // accessed, dirty and writable.  Also, clear the copy-on-write
        // bit in the PTE.
        //

        MI_SET_PTE_DIRTY (TempPte);
        TempPte.u.Hard.Write = 1;
        MI_SET_ACCESSED_IN_PTE (&TempPte, 1);
        TempPte.u.Hard.CopyOnWrite = 0;
        TempPte.u.Hard.PageFrameNumber = NewPageIndex;

    } else {

        //
        // The page was not really a copy on write, just change
        // the frame field of the PTE.
        //

        TempPte.u.Hard.PageFrameNumber = NewPageIndex;
    }

    //
    // If the modify bit is set in the PFN database for the
    // page, the data cache must be flushed.  This is due to the
    // fact that this process may have been cloned and the cache
    // still contains stale data destined for the page we are
    // going to remove.
    //

    ASSERT (TempPte.u.Hard.Valid == 1);

    LOCK_PFN (OldIrql);

    //
    // Flush the TB entry for this page.
    //

    KeFlushSingleTb (FaultingAddress,
                     TRUE,
                     FALSE,
                     (PHARDWARE_PTE)PointerPte,
                     TempPte.u.Flush);

    //
    // Decrement the share count for the page which was copied
    // as this pte no longer refers to it.
    //

    MiDecrementShareCount (PageFrameIndex);

    CloneDescriptor = MiLocateCloneAddress ((PVOID)CloneBlock);

    if (CloneDescriptor != (PMMCLONE_DESCRIPTOR)NULL) {

        //
        // Decrement the reference count for the clone block,
        // note that this could release and reacquire
        // the mutexes.
        //

        MiDecrementCloneBlockReference ( CloneDescriptor,
                                         CloneBlock,
                                         CurrentProcess );
    }

    UNLOCK_PFN (OldIrql);
    return STATUS_SUCCESS;
}