本文内容
- 进程、视图和管理内存
- 使用内存映射文件编程
- 示例
内存映射文件包含虚拟内存中文件的内容。 借助文件和内存空间之间的这种映射,应用(包括多个进程)可以直接对内存执行读取和写入操作,从而修改文件。 可以使用托管代码访问内存映射文件,就像本机 Windows 函数访问内存映射文件(如管理内存映射文件中所述)一样。
内存映射文件分为两种类型:
-
持久化内存映射文件
持久化文件是与磁盘上的源文件相关联的内存映射文件。 当最后一个进程处理完文件时,数据保存到磁盘上的源文件中。 此类内存映射文件适用于处理非常大的源文件。
-
非持久化内存映射文件
非持久化文件是不与磁盘上的文件相关联的内存映射文件。 当最后一个进程处理完文件时,数据会丢失,且文件被垃圾回收器回收。 此类文件适合创建共享内存,以进行进程内通信 (IPC)。
1、进程、视图和管理内存
可以跨多个进程共享内存映射文件。 进程可以映射到相同的内存映射文件,只需使用文件创建进程分配的通用名称即可。
必须创建整个或部分内存映射文件的视图,才能使用内存映射文件。 还可以为内存映射文件的同一部分创建多个视图,从而创建并发内存。 若要让两个视图一直处于并发状态,必须通过同一个内存映射文件创建它们。
如果文件大于可用于内存映射的应用程序逻辑内存空间(在 32 位计算机中为 2 GB),可能也有必要使用多个视图。
视图分为以下两种类型:流访问视图和随机访问视图。 使用流访问视图,可以顺序访问文件;建议对非持久化文件和 IPC 使用这种类型。 随机访问视图是处理持久化文件的首选类型。
由于内存映射文件是通过操作系统的内存管理程序进行访问,因此文件会被自动分区到很多页面,并根据需要进行访问。 无需自行处理内存管理。
下图展示了多个进程如何同时对同一个内存映射文件有多个重叠视图。
下图显示了内存映射文件的多个重叠视图:
2、使用内存映射文件编程
下表列出了与使用内存映射文件对象及其成员相关的指南。
任务 | 要使用的方法或属性 |
---|---|
从磁盘上的文件获取表示持久化内存映射文件的 MemoryMappedFile 对象。 | MemoryMappedFile.CreateFromFile 方法。 |
获取表示非持久化内存映射文件的 MemoryMappedFile 对象(未与磁盘上的文件关联)。 | MemoryMappedFile.CreateNew 方法。 - 或 - MemoryMappedFile.CreateOrOpen 方法。 |
获取现有内存映射文件(持久化或非持久化)的 MemoryMappedFile 对象。 | MemoryMappedFile.OpenExisting 方法。 |
获取内存映射文件的顺序访问视图的 UnmanagedMemoryStream 对象。 | MemoryMappedFile.CreateViewStream 方法。 |
获取内存映射文件的随机访问视图的 UnmanagedMemoryAccessor 对象。 | MemoryMappedFile.CreateViewAccessor 方法。 |
获取要与非托管代码结合使用的 SafeMemoryMappedViewHandle 对象。 | MemoryMappedFile.SafeMemoryMappedFileHandle 属性。 - 或 - MemoryMappedViewAccessor.SafeMemoryMappedViewHandle 属性。 - 或 - MemoryMappedViewStream.SafeMemoryMappedViewHandle 属性。 |
将内存分配一直延迟到视图创建完成(仅限非持久化文件)。 (若要确定当前系统页面大小,请使用 Environment.SystemPageSize 属性。) | 值为 MemoryMappedFileOptions.DelayAllocatePages 的 CreateNew 方法。 - 或 - 将 MemoryMappedFileOptions 枚举用作参数的 CreateOrOpen 方法。 |
2.1 安全性
可以在创建内存映射文件时应用访问权限,具体操作是运行以下需要将 MemoryMappedFileAccess 枚举用作参数的方法:
-
MemoryMappedFile.CreateFromFile
-
MemoryMappedFile.CreateNew
-
MemoryMappedFile.CreateOrOpen
若要指定打开现有内存映射文件所需的访问权限,可以运行需要将 MemoryMappedFileRights 用作参数的 OpenExisting 方法。
另外,还可以添加包含预定义访问规则的 MemoryMappedFileSecurity 对象。
若要将新的或更改后的访问规则应用于内存映射文件,请使用 SetAccessControl 方法。 若要从现有文件检索访问或审核规则,请使用 GetAccessControl 方法。
3、示例
3.1 持久化内存映射文件
CreateFromFile 方法通过磁盘上的现有文件创建内存映射文件。
下面的示例为极大文件的一部分创建内存映射视图,并控制其中一部分。
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
long offset = 0x10000000; // 256 megabytes
long length = 0x20000000; // 512 megabytes
// Create the memory-mapped file.
using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data", FileMode.Open,"ImgA"))
{
// Create a random access view, from the 256th megabyte (the offset)
// to the 768th megabyte (the offset plus length).
using (var accessor = mmf.CreateViewAccessor(offset, length))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;
// Make changes to the view.
for (long i = 0; i < length; i += colorSize)
{
accessor.Read(i, out color);
color.Brighten(10);
accessor.Write(i, ref color);
}
}
}
}
}
public struct MyColor
{
public short Red;
public short Green;
public short Blue;
public short Alpha;
// Make the view brighter.
public void Brighten(short value)
{
Red = (short)Math.Min(short.MaxValue, (int)Red + value);
Green = (short)Math.Min(short.MaxValue, (int)Green + value);
Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
}
}
下面的示例为另一个进程打开相同的内存映射文件。
using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
// Assumes another process has created the memory-mapped file.
using (var mmf = MemoryMappedFile.OpenExisting("ImgA"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;
// Make changes to the view.
for (long i = 0; i < 1500000; i += colorSize)
{
accessor.Read(i, out color);
color.Brighten(20);
accessor.Write(i, ref color);
}
}
}
}
}
public struct MyColor
{
public short Red;
public short Green;
public short Blue;
public short Alpha;
// Make the view brigher.
public void Brighten(short value)
{
Red = (short)Math.Min(short.MaxValue, (int)Red + value);
Green = (short)Math.Min(short.MaxValue, (int)Green + value);
Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
}
}
3.2 非持久化内存映射文件
CreateNew 和 CreateOrOpen 方法创建未映射到磁盘上现有文件的内存映射文件。
下面的示例包含三个独立进程(控制台应用),以将布尔值写入内存映射文件。 各操作按下面的顺序发生:
-
Process A
创建内存映射文件,并向其中写入值。 -
Process B
打开内存映射文件,并向其中写入值。 -
Process C
打开内存映射文件,并向其中写入值。 -
Process A
读取并显示内存映射文件中的值。 -
在
Process A
处理完内存映射文件后,此文件立即被垃圾回收器回收。
若要运行此示例,请按照以下步骤操作:
-
编译应用并打开三个命令提示符窗口。
-
在第一个命令提示符窗口中,运行
Process A
。 -
在第二个命令提示符窗口中,运行
Process B
。 -
返回到
Process A
,再按 Enter。 -
在第三个命令提示符窗口中,运行
Process C
。 -
返回到
Process A
,再按 Enter。
Process A
的输出如下所示:
Start Process B and press ENTER to continue.
Start Process C and press ENTER to continue.
Process A says: True
Process B says: False
Process C says: True
Process A
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process A:
static void Main(string[] args)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
Mutex mutex = new Mutex(true, "testmapmutex", out mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
Console.WriteLine("Start Process B and press ENTER to continue.");
Console.ReadLine();
Console.WriteLine("Start Process C and press ENTER to continue.");
Console.ReadLine();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryReader reader = new BinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
Console.WriteLine("Process C says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}
}
}
Process B
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process B:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream(1, 0))
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();
}
}
catch (FileNotFoundException)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first.");
}
}
}
Process C
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
class Program
{
// Process C:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream(2, 0))
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
}
}
catch (FileNotFoundException)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B.");
}
}
}