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
|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: MemoryMappedView
**
** Purpose: Internal class representing MemoryMappedFile view
**
** Date: February 7, 2007
**
===========================================================*/
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
namespace System.IO.MemoryMappedFiles {
internal class MemoryMappedView : IDisposable {
private SafeMemoryMappedViewHandle m_viewHandle;
private Int64 m_pointerOffset;
private Int64 m_size;
private MemoryMappedFileAccess m_access;
// These control the retry behaviour when lock violation errors occur during Flush:
private const Int32 MaxFlushWaits = 15; // must be <=30
private const Int32 MaxFlushRetriesPerWait = 20;
[System.Security.SecurityCritical]
private unsafe MemoryMappedView(SafeMemoryMappedViewHandle viewHandle, Int64 pointerOffset,
Int64 size, MemoryMappedFileAccess access) {
m_viewHandle = viewHandle;
m_pointerOffset = pointerOffset;
m_size = size;
m_access = access;
}
internal SafeMemoryMappedViewHandle ViewHandle {
[System.Security.SecurityCritical]
get {
return m_viewHandle;
}
}
internal Int64 PointerOffset {
get {
return m_pointerOffset;
}
}
internal Int64 Size {
get {
return m_size;
}
}
internal MemoryMappedFileAccess Access {
get {
return m_access;
}
}
// Callers must demand unmanaged code first
[System.Security.SecurityCritical]
internal unsafe static MemoryMappedView CreateView(SafeMemoryMappedFileHandle memMappedFileHandle,
MemoryMappedFileAccess access, Int64 offset, Int64 size) {
// MapViewOfFile can only create views that start at a multiple of the system memory allocation
// granularity. We decided to hide this restriction form the user by creating larger views than the
// user requested and hiding the parts that the user did not request. extraMemNeeded is the amount of
// extra memory we allocate before the start of the requested view. MapViewOfFile will also round the
// capacity of the view to the nearest multiple of the system page size. Once again, we hide this
// from the user by preventing them from writing to any memory that they did not request.
ulong extraMemNeeded = (ulong)offset % (ulong)MemoryMappedFile.GetSystemPageAllocationGranularity();
// newOffset takes into account the fact that we have some extra memory allocated before the requested view
ulong newOffset = (ulong)offset - extraMemNeeded;
Debug.Assert(newOffset >= 0, "newOffset = (offset - extraMemNeeded) < 0");
// determine size to pass to MapViewOfFile
ulong nativeSize;
if (size != MemoryMappedFile.DefaultSize) {
nativeSize = (ulong)size + (ulong)extraMemNeeded;
}
else {
nativeSize = 0;
}
if (IntPtr.Size == 4 && nativeSize > UInt32.MaxValue) {
throw new ArgumentOutOfRangeException("size", SR.GetString(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed));
}
// if request is >= than total virtual, then MapViewOfFile will fail with meaningless error message
// "the parameter is incorrect"; this provides better error message in advance
UnsafeNativeMethods.MEMORYSTATUSEX memStatus = new UnsafeNativeMethods.MEMORYSTATUSEX();
bool result = UnsafeNativeMethods.GlobalMemoryStatusEx(ref memStatus);
ulong totalVirtual = memStatus.ullTotalVirtual;
if (nativeSize >= totalVirtual) {
throw new IOException(SR.GetString(SR.IO_NotEnoughMemory));
}
// split the Int64 into two ints
uint offsetLow = (uint)(newOffset & 0x00000000FFFFFFFFL);
uint offsetHigh = (uint)(newOffset >> 32);
// create the view
SafeMemoryMappedViewHandle viewHandle = UnsafeNativeMethods.MapViewOfFile(memMappedFileHandle,
MemoryMappedFile.GetFileMapAccess(access), offsetHigh, offsetLow, new UIntPtr(nativeSize));
if (viewHandle.IsInvalid) {
__Error.WinIOError(Marshal.GetLastWin32Error(), String.Empty);
}
// Query the view for its size and allocation type
UnsafeNativeMethods.MEMORY_BASIC_INFORMATION viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo));
ulong viewSize = (ulong)viewInfo.RegionSize;
// Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
// OR check if the allocated view size is smaller than the expected native size
// If multiple overlapping views are created over the file mapping object, the pages in a given region
// could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
// views created on a mapping object backed by same file.
// In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
// but more pages may need to be committed in the region.
// This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
// and size of the region of pages with matching attributes starting from base address.
// VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
if (((viewInfo.State & UnsafeNativeMethods.MEM_RESERVE) != 0) || (viewSize < nativeSize)) {
ulong allocSize = (nativeSize == 0) ? viewSize : nativeSize;
IntPtr tempHandle = UnsafeNativeMethods.VirtualAlloc(viewHandle, (UIntPtr)allocSize, UnsafeNativeMethods.MEM_COMMIT,
MemoryMappedFile.GetPageAccess(access));
int lastError = Marshal.GetLastWin32Error();
// The following is commented out for backward compatibility.
// Previously releases failed to check for this error so introducing this check
// could cause new/different exceptions in existing code paths.
// if (tempHandle == IntPtr.Zero) {
// __Error.WinIOError(lastError, String.Empty);
// }
// again query the view for its new size
viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo));
viewSize = (ulong)viewInfo.RegionSize;
}
// if the user specified DefaultSize as the size, we need to get the actual size
if (size == MemoryMappedFile.DefaultSize) {
size = (Int64)(viewSize - extraMemNeeded);
}
else {
Debug.Assert(viewSize >= (ulong)size, "viewSize < size");
}
viewHandle.Initialize((ulong)size + extraMemNeeded);
MemoryMappedView mmv = new MemoryMappedView(viewHandle, (long)extraMemNeeded, size, access);
return mmv;
}
// Flushes the changes such that they are in sync with the FileStream bits (ones obtained
// with the win32 ReadFile and WriteFile functions). Need to call FileStream's Flush to
// flush to the disk.
// NOTE: This will flush all bytes before and after the view up until an offset that is a multiple
// of SystemPageSize.
[System.Security.SecurityCritical]
public void Flush(IntPtr capacity) {
if (m_viewHandle != null) {
unsafe {
byte* firstPagePtr = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
m_viewHandle.AcquirePointer(ref firstPagePtr);
bool success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
if (success)
return; // This will visit the finally block.
// It is a known issue within the NTFS transaction log system that
// causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
// [http://bugcheck/bugs/Windows8Bugs/152862].
// As a workaround, we catch this particular error and retry the flush operation
// a few milliseconds later. If it does not work, we give it a few more tries with
// increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
// this strategy successfully flushed the view after no more than 3 retries.
Int32 error = Marshal.GetLastWin32Error();
bool canRetry = (!success && error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
for (Int32 w = 0; canRetry && w < MaxFlushWaits; w++) {
Int32 pause = (1 << w); // MaxFlushRetries should never be over 30
Thread.Sleep(pause);
for (Int32 r = 0; canRetry && r < MaxFlushRetriesPerWait; r++) {
success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
if (success)
return; // This will visit the finally block.
Thread.Sleep(0);
error = Marshal.GetLastWin32Error();
canRetry = (error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
}
}
// We got too here, so there was no success:
__Error.WinIOError(error, String.Empty);
}
finally {
if (firstPagePtr != null) {
m_viewHandle.ReleasePointer();
}
}
}
}
}
[System.Security.SecurityCritical]
protected virtual void Dispose(bool disposing) {
if (m_viewHandle != null && !m_viewHandle.IsClosed) {
m_viewHandle.Dispose();
}
}
[System.Security.SecurityCritical]
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
internal bool IsClosed {
[SecuritySafeCritical]
get {
return (m_viewHandle == null || m_viewHandle.IsClosed);
}
}
}
}
|