浅聊一下 C#程序的 内存映射文件 玩法

news2024/11/15 11:53:00

一:背景

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 给的。

三:总结

这篇我们就简单的浅聊一下,如果这块是知识盲区的朋友应该会有一点帮助,希望没有带偏大家,更多的细节期待大家挖掘!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642900.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

耗时3个月,线下访谈30+ csdn大佬,规划出了我的云原生学习路线

前言 大家好,我是沐风晓月,最近线下拜访不少云原生方向的大佬和csdn其他方向的大佬,受益匪浅。 于是在 5月23日,我定下来自己的目标: 我的目标: 可以说,这个世代给予的机遇,让我…

WWDC苹果高管脱口秀,Mike Rockwell透露Vision Pro开发的秘密

WWDC2023之后例行的“The Talk Show”脱口秀如期展开,深入讨论了WWDC上的一切。本次脱口秀分别谈及Mac游戏、Vision Pro以及操作系统和AI,由John Gruber主持,嘉宾包括:苹果技术发展部副总裁Mike Rockwell、以及Greg Joswiak、John…

上线客流人数统计系统实现资源的最大化利用

在流量管理方面,智慧客流采集系统的应用可以帮助商家实现资源的最大化利用。通过对客流量数据的分析,商家可以准确把握客流量变化规律,进而制定出最优化的资源配置方案。 AI客流视觉监控 一、某汽车4S店智慧客流采集系统案例展示 以某汽车4S…

人民大学中外合作办学_人大女王金融硕士——国际顶尖的金融硕士学位等你来拿

进入金融圈后,你会发现学历是筛选手段之一,之后的晋升更是看重学历。一个金融学士学位可以让你跨过门槛进入到金融行业,想要得到更好的发展,就需要从知识和证书等多方面进行提升了。辞职去攻读硕士学位不太现实,幸好遇…

vsftpd安装配置,Linux系统配置FTP服务器教程(CentOS 7)

vsftpd(very secure FTP daemon)是Linux下的一款小巧轻快、安全易用的FTP服务器软件。今天飞飞将和你分享Linux系统安装并配置vsftpd,搭建FTP环境。 先让我们来认识下FTP! FTP(File Transfer Protocol)是一…

C语言-运算符

1 运算符的概念以及分类 运算符的概念 用运算符将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C表达式运算对象包括常量、变量、函数等 运算符的分类 1、双目运算符:即参加运算的操作数有两个 例&…

系统移植 网络环境配置和TFTP服务器搭建

目录 网络环境配置 1. 配置windows环境下协议 2. 配置linux环境下的网络 3. 开发板 4. Ping 不通解决办法 搭建TFTP服务器环境搭建 》1.验证tftp服务器环境是否搭建 》2.若是未安装,执行下面命令 》3.修改tftp服务器配置文件 》4.在ubuntu下创建工作目录并…

华为OD机试真题 JavaScript 实现【日志采集系统】【2023Q1 100分】

一、题目描述 日志采集是运维系统的的核心组件。日志是按行生成,每行记做一条,由采集系统分批上报。 如果上报太频繁,会对服务端造成压力;如果上报太晚,会降低用户的体验;如果一次上报的条数太多&#xf…

ffplay播放http-mp4视频提示 “stream 1, offset 0x1f85: partial file“错误

解决办法就是将非交织视频 重新 remux. ffmpeg -i a.mp4 -c copy -movflags faststart a1.mp4 -movflags faststart : 这个是将moov移动到 mp4头部,和remux没有关系. 这样处理也是通过HTTP 流式播放必须要做的一步.

Linux防火墙学习笔记11

iptables扩展动作:TARGET EXTENSION: filter: -j ACCEPT -j DROP -j REJECT -j LOG nat: -j SNAT: 源地址转换 -j MASQUERADE: 转换源地址(伪装) -j DNAT: 目的地址转换及端口 -j REDIRECT: 转换目标端口 本机端…

Java后端入职第六天,Nginx搭建负载均衡+反向代理

一:需求背景 Nginx相信大家应该不陌生吧!是一个高性能web服务器,可以做负载均衡或者是反向代理,比如,把静态页面直接部署到到html,实现动静分离,一个服务器部署多个服务,缓解服务压力,等等,都可以利用Nginx实现。 1.负载均衡 这个理解比较简单,其实就是压力分摊,…

Aspose.Pdf使用教程:获取PDF文档中的所有注释

Aspose.PDF 是一款高级PDF处理API,可以在跨平台应用程序中轻松生成,修改,转换,呈现,保护和打印文档。无需使用Adobe Acrobat。此外,API提供压缩选项,表创建和处理,图形和图像功能&am…

小马赠书【第7期】清华出版社 IT BOOK 多得活动(送书5本)

本期 敬 之 共精心挑选了 15 本 IT 相关书籍,包含 前端、后端、数据分析、人工智能、python、低代码 等各个领域。关于如何参与等具体活动信息请看活动详情页,以下是 15 本 IT 书籍介绍: 活动详情页:小马赠书【第7期】 1. C《C 高…

Django高级扩展之静态文件和ajax使用

目录 使用静态文件 创建静态文件目录 Settings.py配置 创建css文件 路由与视图 设置路由 视图 模板使用静态文件 调用css 调用js 调用图片 优化路径 加载标签 修改文件路径 修改图片路径 修改js路径 修改css路径 使用Ajax 应用场景 示例 路由 视图 模板…

Koordinator 最佳实践系列:精细化 CPU 编排

作者:乔普、申信 介绍 在云原生环境中,集群提供者常常将不同类型的工作负载部署在同一个集群中,利用不同业务的不同峰值效果,实现资源分时复用,避免资源浪费。然而,不同类型负载之间混合部署常常会导致资…

字符集和java的编码与解码

一、ASCII和GBK字符集 计算机存储一个英文字符需要一个字节。 ASCII字符集,包括128(0000000B~1111111B)个数据,存储英文字母和字符,对于欧美国家够用。 例如,存储字符’a’,查询ASCII得到为97&a…

Java阶段四Day03

Java阶段四Day03 文章目录 Java阶段四Day03数据处理基本流程代码编写顺序开发DAO层,添加的依赖项配置数据源任务拆解内容管理MySQL中的数据类型和Java属性的类型对照关于MyBatis PlusMyBatis Plus的基本使用关于MyBatis Plus的使用建议汇总如下自动更新时间 关于Pro…

一种不停服的数据迁移方案

一、前言 好的方案是一步步演进出来的。当前最优的系统方案,可能在下一个月、三个月或半年后,就会遇到瓶颈,需要调整自身以便适应新的业务场景。系统的演进就是一个快进版的人类进化史。 我之前负责的一个系统,一开始基本没啥数…

46 最佳实践-性能最佳实践-内存大页

文章目录 46 最佳实践-性能最佳实践-内存大页46.1 概述46.2 操作指导 46 最佳实践-性能最佳实践-内存大页 46.1 概述 相比传统的4K内存分页,openEuler也支持2MB/1GB的大内存分页。内存大页可以有效减少TLB miss,显著提升内存访问密集型业务的性能。ope…

证券行业异构系统众多,微服务和网格如何全都要

在携手网易数帆取得中间件云原生化的创新成果之后,安信证券已在谋划大规模微服务化的布局,以确保信息系统架构走在现代金融科技的前列,支撑业务在未来数智金融竞争中把握主动权。 架构未动,思想先行。安信证券近日在内部组织了一…