3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
8 ** Class: MemoryMappedView
10 ** Purpose: Internal class representing MemoryMappedFile view
12 ** Date: February 7, 2007
14 ===========================================================*/
16 using System.Diagnostics;
17 using System.Runtime.InteropServices;
18 using System.Runtime.CompilerServices;
19 using System.Security;
20 using System.Threading;
21 using Microsoft.Win32;
22 using Microsoft.Win32.SafeHandles;
24 namespace System.IO.MemoryMappedFiles {
26 internal class MemoryMappedView : IDisposable {
28 private SafeMemoryMappedViewHandle m_viewHandle;
29 private Int64 m_pointerOffset;
31 private MemoryMappedFileAccess m_access;
33 // These control the retry behaviour when lock violation errors occur during Flush:
34 private const Int32 MaxFlushWaits = 15; // must be <=30
35 private const Int32 MaxFlushRetriesPerWait = 20;
37 [System.Security.SecurityCritical]
38 private unsafe MemoryMappedView(SafeMemoryMappedViewHandle viewHandle, Int64 pointerOffset,
39 Int64 size, MemoryMappedFileAccess access) {
41 m_viewHandle = viewHandle;
42 m_pointerOffset = pointerOffset;
47 internal SafeMemoryMappedViewHandle ViewHandle {
48 [System.Security.SecurityCritical]
54 internal Int64 PointerOffset {
56 return m_pointerOffset;
66 internal MemoryMappedFileAccess Access {
72 // Callers must demand unmanaged code first
73 [System.Security.SecurityCritical]
74 internal unsafe static MemoryMappedView CreateView(SafeMemoryMappedFileHandle memMappedFileHandle,
75 MemoryMappedFileAccess access, Int64 offset, Int64 size) {
77 // MapViewOfFile can only create views that start at a multiple of the system memory allocation
78 // granularity. We decided to hide this restriction form the user by creating larger views than the
79 // user requested and hiding the parts that the user did not request. extraMemNeeded is the amount of
80 // extra memory we allocate before the start of the requested view. MapViewOfFile will also round the
81 // capacity of the view to the nearest multiple of the system page size. Once again, we hide this
82 // from the user by preventing them from writing to any memory that they did not request.
83 ulong extraMemNeeded = (ulong)offset % (ulong)MemoryMappedFile.GetSystemPageAllocationGranularity();
85 // newOffset takes into account the fact that we have some extra memory allocated before the requested view
86 ulong newOffset = (ulong)offset - extraMemNeeded;
87 Debug.Assert(newOffset >= 0, "newOffset = (offset - extraMemNeeded) < 0");
89 // determine size to pass to MapViewOfFile
91 if (size != MemoryMappedFile.DefaultSize) {
92 nativeSize = (ulong)size + (ulong)extraMemNeeded;
98 if (IntPtr.Size == 4 && nativeSize > UInt32.MaxValue) {
99 throw new ArgumentOutOfRangeException("size", SR.GetString(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed));
102 // if request is >= than total virtual, then MapViewOfFile will fail with meaningless error message
103 // "the parameter is incorrect"; this provides better error message in advance
104 UnsafeNativeMethods.MEMORYSTATUSEX memStatus = new UnsafeNativeMethods.MEMORYSTATUSEX();
105 bool result = UnsafeNativeMethods.GlobalMemoryStatusEx(ref memStatus);
106 ulong totalVirtual = memStatus.ullTotalVirtual;
107 if (nativeSize >= totalVirtual) {
108 throw new IOException(SR.GetString(SR.IO_NotEnoughMemory));
111 // split the Int64 into two ints
112 uint offsetLow = (uint)(newOffset & 0x00000000FFFFFFFFL);
113 uint offsetHigh = (uint)(newOffset >> 32);
116 SafeMemoryMappedViewHandle viewHandle = UnsafeNativeMethods.MapViewOfFile(memMappedFileHandle,
117 MemoryMappedFile.GetFileMapAccess(access), offsetHigh, offsetLow, new UIntPtr(nativeSize));
118 if (viewHandle.IsInvalid) {
119 __Error.WinIOError(Marshal.GetLastWin32Error(), String.Empty);
122 // Query the view for its size and allocation type
123 UnsafeNativeMethods.MEMORY_BASIC_INFORMATION viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
124 UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo));
125 ulong viewSize = (ulong)viewInfo.RegionSize;
128 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
129 // OR check if the allocated view size is smaller than the expected native size
130 // If multiple overlapping views are created over the file mapping object, the pages in a given region
131 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
132 // views created on a mapping object backed by same file.
133 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
134 // but more pages may need to be committed in the region.
135 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
136 // and size of the region of pages with matching attributes starting from base address.
137 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
138 if (((viewInfo.State & UnsafeNativeMethods.MEM_RESERVE) != 0) || (viewSize < nativeSize)) {
139 ulong allocSize = (nativeSize == 0) ? viewSize : nativeSize;
140 IntPtr tempHandle = UnsafeNativeMethods.VirtualAlloc(viewHandle, (UIntPtr)allocSize, UnsafeNativeMethods.MEM_COMMIT,
141 MemoryMappedFile.GetPageAccess(access));
142 int lastError = Marshal.GetLastWin32Error();
143 // The following is commented out for backward compatibility.
144 // Previously releases failed to check for this error so introducing this check
145 // could cause new/different exceptions in existing code paths.
146 // if (tempHandle == IntPtr.Zero) {
147 // __Error.WinIOError(lastError, String.Empty);
150 // again query the view for its new size
151 viewInfo = new UnsafeNativeMethods.MEMORY_BASIC_INFORMATION();
152 UnsafeNativeMethods.VirtualQuery(viewHandle, ref viewInfo, (IntPtr)Marshal.SizeOf(viewInfo));
153 viewSize = (ulong)viewInfo.RegionSize;
156 // if the user specified DefaultSize as the size, we need to get the actual size
157 if (size == MemoryMappedFile.DefaultSize) {
158 size = (Int64)(viewSize - extraMemNeeded);
161 Debug.Assert(viewSize >= (ulong)size, "viewSize < size");
164 viewHandle.Initialize((ulong)size + extraMemNeeded);
165 MemoryMappedView mmv = new MemoryMappedView(viewHandle, (long)extraMemNeeded, size, access);
170 // Flushes the changes such that they are in sync with the FileStream bits (ones obtained
171 // with the win32 ReadFile and WriteFile functions). Need to call FileStream's Flush to
172 // flush to the disk.
173 // NOTE: This will flush all bytes before and after the view up until an offset that is a multiple
174 // of SystemPageSize.
175 [System.Security.SecurityCritical]
176 public void Flush(IntPtr capacity) {
178 if (m_viewHandle != null) {
181 byte* firstPagePtr = null;
182 RuntimeHelpers.PrepareConstrainedRegions();
184 m_viewHandle.AcquirePointer(ref firstPagePtr);
186 bool success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
188 return; // This will visit the finally block.
190 // It is a known issue within the NTFS transaction log system that
191 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
192 // [http://bugcheck/bugs/Windows8Bugs/152862].
193 // As a workaround, we catch this particular error and retry the flush operation
194 // a few milliseconds later. If it does not work, we give it a few more tries with
195 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
196 // this strategy successfully flushed the view after no more than 3 retries.
198 Int32 error = Marshal.GetLastWin32Error();
199 bool canRetry = (!success && error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
201 for (Int32 w = 0; canRetry && w < MaxFlushWaits; w++) {
203 Int32 pause = (1 << w); // MaxFlushRetries should never be over 30
206 for (Int32 r = 0; canRetry && r < MaxFlushRetriesPerWait; r++) {
208 success = UnsafeNativeMethods.FlushViewOfFile(firstPagePtr, capacity);
210 return; // This will visit the finally block.
214 error = Marshal.GetLastWin32Error();
215 canRetry = (error == UnsafeNativeMethods.ERROR_LOCK_VIOLATION);
219 // We got too here, so there was no success:
220 __Error.WinIOError(error, String.Empty);
223 if (firstPagePtr != null) {
224 m_viewHandle.ReleasePointer();
233 [System.Security.SecurityCritical]
234 protected virtual void Dispose(bool disposing) {
236 if (m_viewHandle != null && !m_viewHandle.IsClosed) {
237 m_viewHandle.Dispose();
241 [System.Security.SecurityCritical]
242 public void Dispose() {
245 GC.SuppressFinalize(this);
248 internal bool IsClosed {
249 [SecuritySafeCritical]
251 return (m_viewHandle == null || m_viewHandle.IsClosed);