MemoryMappedFile 在 Mono in Linux 的開發筆記


前言

MemoryMappedFile(簡稱MMF)類是.NET中對內存映射文件進行操作的類,內存映射文件是非常高效的本地IO方案,由操作系統提供內存與IO文件之間的映射轉換,對內存映射文件的更改由操作系統自動與物理文件進行高效的數據交換。在大文件處理中一般都需要使用到它,同時它也被用來做高效的進程間通訊的底層技術。

正因為它是如此的高效和便捷,所以在服務器程序開發中被廣泛使用到。譬如,我們實現的基於Socket網絡通訊程序中,在發送大數據時,需要對數據進行拆包組包的操作,這就往往需要對未接收完全的數據包進行緩存,在這個的場景中最好是使用MMF手動來對通訊包數據的緩存管理,倘若直接把這些數據放在.NET內置的集合、列表或字典中,那很可能會把.NET托管內存撐爆的。

當我把基於Socket的通訊類庫代碼運行在Mono on Linux中的時候,發現其使用到的MMF代碼運行時異常了,本文就是對這一問題的處理過程的記錄。

問題說明

Mono on Linux

我的代碼是通過指定文件路徑的方式創建MMF的,譬如文件路徑為:/tmp/Zongsoft.Communication.Net#1.cache,在Windows中運行的很正常,但是在Mono on Linux中發生運行時異常:

Unhandled Exception:
System.IO.FileNotFoundException: 沒有那個文件或目錄 ---> Mono.Unix.UnixIOException: 沒有那個文件或目錄 [ENOENT].
    at Mono.Unix.UnixMarshal.ThrowExceptionForLastError () [0x00000] in :0
    at System.IO.MemoryMappedFiles.MemoryMapImpl.Open (System.String path, FileMode mode, Int64 capacity, MemoryMappedFileAccess access) [0x00000] in :0
    at System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile (System.String path, FileMode mode, System.String mapName, Int64 capacity, MemoryMappedFileAccess access) [0x00000] in :0

難道在Mono中,需要先創建MMF對應的物理文件?好吧,那我就手動在臨時文件夾下創建一個指定名稱的空文件后,再來跑一遍,結果報了這個:

Unhandled Exception:
System.ArgumentException: capacity
    at System.IO.MemoryMappedFiles.MemoryMapImpl.Open (System.String path, FileMode mode, Int64 capacity, MemoryMappedFileAccess access) [0x00000] in :0
    at System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile (System.String path, FileMode mode, System.String mapName, Int64 capacity, MemoryMappedFileAccess access) [0x00000] in :0

難道是Mono對通過文件路徑來創建MMF支持不力?好吧,那就試試通過文件流的方式來創建MMF吧,結果還是不行:

Unhandled Exception:
System.ArgumentException: capacity
at System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile (System.IO.FileStream fileStream, System.String mapName, Int64 capacity, MemoryMappedFileAccess access, System.IO.MemoryMappedFiles.MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability inheritability, Boolean leaveOpen) [0x00000] in :0

看來,應該是跟capacity與物理文件的大小不匹配所致,好吧,那就在創建完文件流后,再使用文件流的SetLength來指定一個長度后,再通過該特定長度的文件流來創建MMF吧,結果果然創建成功!這說明在Mono中的創建MMF時,傳入的目標文件必須是已經存在,且該文件長度不能為零。

由於剛才那個文件流的長度正好與創建MMF時capacity參數值相同,那么接下來我再來測試下,當文件流的長度與創建MMF時指定的capacity數值不同時,分別會發生什么情況。

1、文件流的長度大於創建MMF時capacity,成功,並且MMF創建后不會改變對應文件流的大小;

2、文件流的長度小於創建MMF時capacity:

Unhandled Exception:
System.ArgumentException: capacity
    at System.IO.MemoryMappedFiles.MemoryMappedFile.CreateFromFile (System.IO.FileStream fileStream, System.String mapName, Int64 capacity, MemoryMappedFileAccess access, System.IO.MemoryMappedFiles.MemoryMappedFileSecurity memoryMappedFileSecurity, HandleInheritability inheritability, Boolean leaveOpen) [0x00000] in :0

.NET on Windows

再簡單說明下MemoryMappedFile類在.NET Windows平台中的創建行為:

  1. 通過文件路徑的方式,如果對應的文件不存在則自動創建一個對應指定capacity大小的文件。
  2. 通過文件流的方式,如果對應文件流的大小小於capacity則成功;如果對應文件流大小大於capacity則失敗,提示指定的文件流大小太小。

Mono 源碼簡查

通過查看Mono-3.0.12的源碼,發現MMF類的如下代碼:

public static MemoryMappedFile CreateNew(string mapName, long capacity, MemoryMappedFileAccess access,
        MemoryMappedFileOptions options, MemoryMappedFileSecurity memoryMappedFileSecurity,
        HandleInheritability inheritability)
{
    return CreateFromFile(mapName, FileMode.CreateNew, mapName, capacity, access);
}

public static MemoryMappedFile CreateOrOpen(string mapName, long capacity, MemoryMappedFileAccess access)
{
    return CreateFromFile(mapName, FileMode.OpenOrCreate, mapName, capacity, access);
}

可見它們是都是通過CreateFromFile方法來處理的,並且將mapName作為文件路徑來使用,所以,調用這兩個方法要留心了,因為它與.NET(Windows)平台的實現是完全不同的,除此以外的其他創建或打開方法未實現,請勿使用!代碼如下:

public static MemoryMappedFile OpenExisting(string mapName, MemoryMappedFileRights desiredAccessRights, HandleInheritability inheritability)
{
    throw new NotImplementedException();
}

總結

綜上所述,為了讓代碼能夠在Linux和Windows平台都正常運行,建議統一使用

MemoryMappedFile. CreateFromFile(
    FileStream fileStream,
    String mapName,
    Int64 capacity,
    MemoryMappedFileAccess access,
    System.IO.MemoryMappedFiles.MemoryMappedFileSecurity memoryMappedFileSecurity,
    HandleInheritability inheritability,
    Boolean leaveOpen
)

方法來創建MMF,並且在調用前確保指定的文件流大小與capacity參數值相同。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM