淺聊一下 C#程式的 記憶體映射文件 玩法

来源:https://www.cnblogs.com/huangxincheng/archive/2023/06/13/17478410.html
-Advertisement-
Play Games

## 一:背景 ### 1. 講故事 前段時間訓練營里有朋友問 `記憶體映射文件` 是怎麼玩的?說實話這東西理論我相信很多朋友都知道,就是將文件映射到進程的虛擬地址,說起來很容易,那如何讓大家眼見為實呢?可能會難倒很多人,所以這篇我以自己的認知嘗試讓大家眼見為實。 ## 二:如何眼見為實 ### 1. ...


一:背景

1. 講故事

前段時間訓練營里有朋友問 記憶體映射文件 是怎麼玩的?說實話這東西理論我相信很多朋友都知道,就是將文件映射到進程的虛擬地址,說起來很容易,那如何讓大家眼見為實呢?可能會難倒很多人,所以這篇我以自己的認知嘗試讓大家眼見為實。

二:如何眼見為實

1. 我想象的文件映射

在任何討論之前,記憶體文件映射大概像下麵這樣,多個進程可以完全View一個文件,也可以 View 文件的一部分到進程的虛擬地址中,畫個圖大概像下麵這樣。

但仔細一想,這裡還有很多的小細節,比如:

疑問1:到底是映射文件還是映射磁碟的物理地址 ?

疑問2:既然是後備存儲,那是不是每次修改虛擬地址都要刷硬碟 ?

疑問3:記憶體頁是4k為一個單位,文件大小不是 4k 整數倍怎麼辦 ?

這三個疑問我相信很多朋友或多或少都會遇到,這裡我簡單解答一下,後面再用 windbg 驗證。

  1. 嚴格來說是 硬碟物理地址

  2. 文件所處的硬碟地址為後備存儲這個不假,但這裡有個小細節,對虛擬地址的讀寫涉及到 記憶體頁 概念,如果訪問的虛擬地址所在的物理地址不在 物理記憶體 中,就會引發缺頁中斷,操作系統會將 磁碟上的 4k 頁粒度灌入到 物理記憶體 中,同樣的道理,如果修改了虛擬地址,那麼物理記憶體頁就是臟數據,會在後續的某個時刻刷新到 硬碟 上,產生磁碟 IO。

總的來說:從磁碟到物理記憶體(記憶體條) 之間的記憶體頁的換入換出都是一種按需的 懶載入懶寫入行為,稍後我們用 windbg 驗證下。

  1. 記憶體的管理採用的是記憶體頁的方式,如果 View 大於 文件Size,那麼文件會擴容到 4k 對齊,這樣方便對文件追加寫入。

綜合上面的三點信息,圖就可以畫的再詳細一點了,比如下麵這樣:

熟悉記憶體管理的朋友應該知道,我們程式的 exe 和 dll 就是用 記憶體映射文件 的方式載入到虛擬地址中的,所以就拿它開刀吧。

2. 一段測試代碼

為了方便演示,上一段簡單的的測試代碼,觀察 ConsoleApp1.exe 的映射方式。


        static void Main(string[] args)
        {
            Console.WriteLine($"當前時間:{DateTime.Now}, 程式啟動!");
            Console.ReadLine();
        }

接下來用 windbg 啟動 ConsoleApp1.exe 兩次,結合詳細分解圖,我們觀察下這兩個進程的虛擬地址所映射的記憶體條物理地址是否一致?

  1. 實例1

ModLoad: 00007ff6`bfe00000 00007ff6`bfe2a000   apphost.exe
ModLoad: 00007ff9`b1450000 00007ff9`b1648000   ntdll.dll
...

0:008> lmvm apphost
Browse full module list
start             end                 module name
00007ff6`bfe00000 00007ff6`bfe2a000   apphost  C (private pdb symbols)  c:\mysymbols\apphost.pdb\1643A9EB126F4FE184548E9CC1B740B71\apphost.pdb
    Loaded symbol image file: D:\net7\ConsoleApplication1\ConsoleApp1\bin\Debug\net6.0\ConsoleApp1.exe
    Image path: apphost.exe
    Image name: apphost.exe
    ...

0:008> ~
   0  Id: 232c.4abc Suspend: 1 Teb: 0000000e`7b1a5000 Unfrozen

  1. 實例2

ModLoad: 00007ff6`bfe00000 00007ff6`bfe2a000   apphost.exe
ModLoad: 00007ff9`b1450000 00007ff9`b1648000   ntdll.dll
...

0:008> ~
   0  Id: 60e8.3e3c Suspend: 1 Teb: 000000da`ab498000 Unfrozen
   1  Id: 60e8.53b0 Suspend: 1 Teb: 000000da`ab49a000 Unfrozen

這裡要提醒一下的是在 Windows 平臺上 ConsoleApp1.exe 已經成了一個引導程式,通過 lmvm 可以看到它其實是 apphost.exe

兩個實例都開起來後,可以看到 apphost.exe 在各自進程的虛擬地址都一樣,那他們的物理地址是否也一樣呢? 要尋找答案,接下來我們到 Windows 內核態去挖一挖。


lkd> !process 0 0 ConsoleApp1.exe

PROCESS ffff838bd84c9080
    SessionId: 8  Cid: 232c    Peb: e7b1a4000  ParentCid: 0b14
FreezeCount 2
    DirBase: 3468cf000  ObjectTable: ffff938feae02900  HandleCount: 172.
    Image: ConsoleApp1.exe

PROCESS ffff838bef157080
    SessionId: 8  Cid: 60e8    Peb: daab497000  ParentCid: 4804
FreezeCount 2
    DirBase: 3552f3000  ObjectTable: ffff938fe8f7ec40  HandleCount: 166.
    Image: ConsoleApp1.exe

從卦中看,Cid: 232c 是我們的實例1, Cid: 60e8 是我們的實例2,接下來用 windbg 提供的 !vtop 命令觀察 apphost.exe 的首地址對應的物理地址。


// ----  實例1 -----
lkd> !vtop 3468cf000 00007ff6bfe00000
Amd64VtoP: Virt 00007ff6bfe00000, pagedir 00000003468cf000
Amd64VtoP: PML4E 00000003468cf7f8
Amd64VtoP: PDPE 00000001138dbed0
Amd64VtoP: PDE 00000002153dcff8
Amd64VtoP: PTE 000000024dadd000
Amd64VtoP: Mapped phys 00000002271c2000
Virtual address 7ff6bfe00000 translates to physical address 2271c2000.

//----  實例2 -----

lkd> !vtop 3552f3000 00007ff6bfe00000
Amd64VtoP: Virt 00007ff6bfe00000, pagedir 00000003552f3000
Amd64VtoP: PML4E 00000003552f37f8
Amd64VtoP: PDPE 00000002db7ffed0
Amd64VtoP: PDE 0000000208100ff8
Amd64VtoP: PTE 000000033de01000
Amd64VtoP: Mapped phys 00000002271c2000
Virtual address 7ff6bfe00000 translates to physical address 2271c2000.

從卦中看,實例1 和 實例2 的 虛擬地址 映射的 物理地址 是相同的 2271c2000。這也很好的解釋了那張圖。

有朋友可能會有疑問,能否看下 2271c2000 這個 物理地址 的內容? 這當然是可以的,用 windbg 的 !da 就好了。


lkd> !db 2271c2000
#2271c2000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
#2271c2010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
#2271c2020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2271c2030 00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00 ................
#2271c2040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
#2271c2050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
#2271c2060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS 
#2271c2070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

從卦中看,物理地址上有一段 This program cannot be run in DOS mode,這不就是經典的 PE 文件哈,如果不相信可以用 WinHex 打開 ConsoleApp1.exe 即可,截圖如下:

最後就是內核中的 記憶體管理器 會將 物理地址 與 磁碟地址 進行打通,實現懶載入和懶寫入。

3. 如何自定義實現

Image 雖然是一個快捷的觀察記憶體文件映射方式,那如果自己能實現一個就更有意思了,比如下麵對 1.txt 進行文件映射,在 C# 中有一個快捷類 MemoryMappedFile 實現了 win32api 的封裝,參考代碼如下:


    internal class Program
    {
        static void Main(string[] args)
        {
            int capaticy = 1024; //1k

            using (var mmf = MemoryMappedFile.CreateFromFile(@"C:\1.txt", FileMode.OpenOrCreate,
                                                            "testmapfile",
                                                             capaticy,
                                                             MemoryMappedFileAccess.ReadWrite))
            {
                var viewAccessor = mmf.CreateViewAccessor(0, capaticy);

                while (true)
                {
                    Console.WriteLine("請輸入你要寫入的內容: ");

                    string input = Console.ReadLine();

                    viewAccessor.WriteArray(0, input.ToArray(), 0, input.Length);
                }
            }
        }
    }

接下來用 windbg 附加一下,觀察 1.txt 是不是被 MappedFile 上了,同時做的修改有沒有更新到物理磁碟上。


0:006> !address

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
...
+  31a0000  31a1000     1000 MEM_MAPPED  MEM_COMMIT  PAGE_READWRITE                     MappedFile "\Device\HarddiskVolume3\1.txt"
...

0:006> du 31a0000
031a0000  "helloworld!"

從卦中可以看到,雖然 1.txt 最大的 View 區間是 1k,但提交的記憶體頁還是按照最小粒度 4k 給的。

三:總結

這篇我們就簡單的淺聊一下,如果這塊是知識盲區的朋友應該會有一點幫助,希望沒有帶偏大家,更多的細節期待大家挖掘!

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #爬取豆瓣Top250圖書數據 項目的實現步驟 1.項目結構 2.獲取網頁數據 3.提取網頁中的關鍵信息 4.保存數據 **1.項目結構** ![image](https://img2023.cnblogs.com/blog/3047082/202306/3047082-20230613170853 ...
  • # Go 語言之 sqlx 庫使用 ## 一、sqlx 庫安裝與連接 ### sqlx 介紹 sqlx is a library which provides a set of extensions on go's standard `database/sql` library. The sqlx ...
  • 有時候,需要將一系列字典存儲在列表中,或將列表作為值存儲在字典中,這稱為**嵌套**。我們可以在列表中嵌套字典、在字典中嵌套列表、在字典中嵌套字典。 # 1.列表嵌套字典 我們可以把一個人的信息放在字典中,但是多個人的信息我們無法放在同一個字典中,所以就需要字典列表。 其語法格式: [字典1,字典2 ...
  • #搭建springboot環境(idea環境) 實現步驟: 1.基礎環境配置 2.maven配置 3.編寫第一個程式helloworld(可能有兩個小問題) 4.運行(jar包運行,命令行運行) 一.基礎環境配置 進入idea,點擊file->new->project,在彈出的頁面上,選擇sprin ...
  • Spring的Bean生命周期包括以下步驟: 1、實例化(Instantiation):當Spring容器接收到創建Bean的請求時,它會先實例化Bean對象。這個過程可以通過構造函數、工廠方法或者反序列化等方式完成; 2、屬性賦值(Populate Properties):在實例化Bean對象後, ...
  • ## 教程簡介 Axure RP是一款專業的快速原型設計工具。Axure(發音:Ack-sure),代表美國Axure公司;RP則是Rapid Prototyping(快速原型)的縮寫。 Axure RP是美國Axure Software Solution公司旗艦產品,是一個專業的快速原型設計工具, ...
  • ## 教程簡介 AWS Lambda 是AWS在2014年推出的「無伺服器」(Serverless)計算服務,用戶無需管理伺服器,可以更專註自己業務。由於上手簡單,而且真正利用了雲的優勢,Lambda快速成為了一項明星服務。 Lambda 在可用性高的計算基礎設施上運行您的代碼,執行計算資源的所有管 ...
  • 經過前幾篇文章的講解,初步瞭解ASP.NET Core MVC項目創建,啟動運行,以及命名約定,創建控制器,視圖,模型,接收參數,傳遞數據ViewData,ViewBag,路由,頁面佈局,wwwroot和客戶端庫,Razor語法,EnityFrameworkCore與資料庫,HttpContext,... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...