C# MemoryMappedFile 会扩展吗?

Posted

技术标签:

【中文标题】C# MemoryMappedFile 会扩展吗?【英文标题】:Will a C# MemoryMappedFile expand? 【发布时间】:2015-02-08 08:43:35 【问题描述】:

我正在使用MemoryMappedFile 在两个进程之间交换数据。所以我在两个进程中都创建/打开了这样的文件:

MemoryMappedFile m_MemoryMappedFile = MemoryMappedFile.CreateOrOpen("Demo", 8);

文件访问本身在两个进程中都受到全局互斥锁的保护。现在,当我将数据写入大于定义的 8 字节长度的文件时,我确实 NOT 得到一个异常。

var random = new Random();
var testData = new byte[55];
random.NextBytes(testData);
using (var contentAccessor = m_MemoryMappedFile.CreateViewStream())

    contentAccessor.Write(testData, 0, testData.Length);

所以也许我在这里弄错了,但我想如果我创建一个具有指定容量(在我的情况下为 8 个字节)的非持久内存映射文件,则不允许写入超过 8 个字节的数据?或者我是否用上面的调用破坏了内存?任何解释都会很棒?

【问题讨论】:

您创建了一个 8 字节长的 MMF。然后在那里写 55 个字节......所以这显然是一个问题。分配的内存不是自扩展的。视图也不是你和 MMF 之间的视图。 我不确定这是不是你的情况,但是在 CreateOrOpen 和 CreateViewStream 调用的 Native API 中搜索你会偶然发现 MSDN 中关于 CreateFileMapping 函数的评论:如果应用程序指定了大小对于大于磁盘上实际命名文件大小的文件映射对象,并且如果页面保护允许写访问(即,flProtect 参数指定 PAGE_READWRITE 或 PAGE_EXECUTE_READWRITE),则磁盘上的文件将增加以匹配指定的文件映射对象的大小 是的,我认为也有问题,但它似乎有效,我在另一个应用程序中看到了 55 个字节,这让我感到奇怪。对于@Steve 的文档内容,我创建了一个非持久内存映射文件,所以我不确定这是否也适用于这里?真是可惜,MSDN 文档在这方面做得这么差。 @FranzGsell 是的,文档有点模糊,但请查看MemoryMappedFile的源代码 【参考方案1】:

这在the documentation for CreateViewStream()中特别提到:

要创建内存映射文件的完整视图,请将 size 参数指定为 0(零)。如果这样做,视图的大小可能会大于磁盘上源文件的大小。这是因为视图是以系统页面为单位提供的,并且视图的大小会向上舍入到下一个系统页面大小。

确实四舍五入到页面大小。最好的办法是使用允许您设置视图大小的方法重载:

using (var contentAccessor = m_MemoryMappedFile.CreateViewStream(0, 8))

    contentAccessor.Write(testData, 0, testData.Length);

【讨论】:

是的,我知道我可以创建一个只有 8 个字节容量的视图流。问题是为什么我上面的示例有效并且我没有遇到异常。另一个应用程序能够看到 55 个字节。所以我预计会出现错误? 再次阅读引用的 MSDN 备注。如果您不知道“系统页面”是什么意思,那么您可以提出相关问题。 是的@HansPassant 看来我并不真正了解底层机制。当视图四舍五入到下一个系统页面大小时,这是否意味着如果我写入的字节数超过下一个系统页面大小我会得到一个异常,或者视图是否再次扩展为另一个系统页面大小? 你得到一个例外。用var testData = new byte[4097];看,一页4096字节。【参考方案2】:

tl;dr:是的 - C# MemoryMappedFile 将扩展 - 但只会扩展到 Environment.SystemPageSize 的下一个最大倍数(typically 4,096 字节)。一旦其大小超过该倍数,就会引发异常。


我遇到这个问题是因为我正在寻找使用MemoryMappedFile 的方法来解决MemoryStream(其支持字段is a byte array)仅限于int.MaxValue 值的事实。

我看到了与您在超出指定容量的情况下可以写入/读取数据的行为相同的行为,并注意到the documentation for MemoryMappedFile.CreateOrOpen(string mapName, long capacity) 明确表示(强调我的):

capacity

Int64

分配给内存映射文件的最大大小(以字节为单位)。

这显然与我们看到的不符。

作为Hans Passantpoints out,视图流的大小实际上是Environment.SystemPageSize 的倍数,根据the Wikipedia page for Page Size 传统上的固定值是4,096 字节。

您可以通过检查MemoryMappedViewStream 上的一些属性来确认这一点,例如:

var pageSize = Environment.SystemPageSize;
Console.WriteLine($"System page size: pageSize bytes");
Console.WriteLine();

var capacity = 1;

Console.WriteLine($"Creating MemoryMappedFile with capacity of capacity byte(s)");
using ( var memoryMappedFile = MemoryMappedFile.CreateOrOpen("Demo", capacity) )
using ( var viewStream = memoryMappedFile.CreateViewStream() )

    Console.WriteLine($"View stream capacity: viewStream.Capacity bytes");
    Console.WriteLine($"View stream length: viewStream.Length bytes");
    Console.WriteLine();

    var randomBytes = new byte[pageSize];
    new Random().NextBytes(randomBytes);
    viewStream.Write(randomBytes, offset: 0, count: randomBytes.Length);

    Console.WriteLine($"randomBytes.Length bytes written to view stream");

    viewStream.Position = 0;
    for ( int index = 0; index < randomBytes.Length; index++ )
    
        var writtenByte = randomBytes[index];
        var readByte = viewStream.ReadByte();

        if ( readByte != writtenByte )
            throw new Exception($"Read byte at index index (readByte) was not the same as written (writtenByte)");
    

    Console.WriteLine($"randomBytes.Length bytes successfully read and verified from view stream");
    Console.WriteLine();

    // Attempt to write another byte (this should throw a NotSupportedException )
    try
    
        viewStream.WriteByte(0);
        throw new InvalidOperationException("An extra byte was written to the view stream when it should not have");
    
    catch ( NotSupportedException ex )
    
        Console.WriteLine($"Unable to write additional bytes to view stream:Environment.NewLine    ex.Message");
    

哪个输出:

System page size: 4096 bytes

Creating MemoryMappedFile with capacity of 1 byte(s)
View stream capacity: 4096 bytes
View stream length: 4096 bytes

4096 bytes written to view stream
4096 bytes successfully read and verified from view stream

Unable to write additional bytes to view stream:
    Unable to expand length of this stream beyond its capacity.

注意:C# System.IO.MemoryMappedFiles namespace 中的类型实际上是 Windows API 调用的 C# 包装器,例如CreateFileMappingWOpenFileMappingW,Microsoft 文档中有相当数量的文档。

例如,在Creating a File Mapping Object 它说(强调我的):

文件映射大小

文件映射对象的大小与被映射文件的大小无关。但是,如果文件映射对象大于文件,系统会在CreateFileMapping 返回之前展开文件。如果文件映射对象小于文件,系统只映射文件中指定的字节数。

CreateFileMappingdwMaximumSizeHighdwMaximumSizeLow 参数允许您指定要从文件映射的字节数:

当您不想改变文件的大小时(例如,映射只读文件时),调用CreateFileMapping 并为 dwMaximumSizeHighdwMaximumSizeLow。这样做会创建一个与文件大小完全相同的文件映射对象。 否则,您必须计算或估计完成文件的大小,因为文件映射对象的大小是静态的;一旦创建,它们的大小就不能增加或减少。

Managing Memory-Mapped Files 上面写着:

内存映射文件必须提供什么?

使用 MMF I/O 的一个优点是系统在 4K 数据页中为其执行所有数据传输。

这与the documentation for MemoryMappedFile.CreateViewStream 一致,它说(强调我的):

要创建内存映射文件的完整视图,请将 size 参数指定为 0(零)。如果这样做,视图的大小可能会大于磁盘上源文件的大小。这是因为视图是以系统页面为单位提供的,并且视图的大小会四舍五入到下一个系统页面大小。

换句话说,作为MemoryMappedFile 的实现细节(即它是Windows API 调用的包装器),传递给静态构造函数的capacity 有效地向上取整为系统页面大小的下一个最大倍数因为虚拟地址空间被分成页面(typically 4,096 字节)。


相关问题:

Increase the size of the memory mapped file How can I extend the length of a memory-mapped file? Windows: Resize shared memory How to dynamically expand a Memory Mapped File

【讨论】:

以上是关于C# MemoryMappedFile 会扩展吗?的主要内容,如果未能解决你的问题,请参考以下文章

与 MemoryMappedFile 并行读写

内存映射文件MemoryMappedFile使用

MemoryMappedFile的初级应用

MemoryMappedFile 或序列化,超大对象的速度

Dart 有像 c# 这样的扩展方法吗?

在 C# 中重载扩展方法,它有效吗?