windows核心编程---内存映射文件

-处理文件
1.打开–读取–关闭。
2.打开–缓存算法来读取/写入不同部分。
3.内存映射文件。

-内存映射文件
允许开发人员预订一块地址空间区域并给区域调拨物理存储器。
内存映射文件的物理存储器来自磁盘上已有的文件,而非页交换文件。

文件映射主要用于:
1.系统用内存映射文件载入并运行.exe和dll文件。
2.用内存映射文件来访问磁盘上的数据文件。
3.用内存映射文件在同一台机器的不同进程间共享数据。

-映射到内存的可执行文件和DLL
线程调CreateProcess时:
1.确定可执行文件位置。
2.创建进程内核对象。
3.创建进程私有地址空间。
4.预订地址空间容纳exe文件,预订区域基地址已在exe文件中指定。
5.对地址空间区域标注,表明其后备物理存储器来自磁盘exe文件。
6.访问exe文件的一个段,包含要导入dll路径。
7.依次载入每个dll,对每个dll载入后也放入它的一个包含要导入dll路径的段,并依次载入。步骤类似4,5.
8.执行exe文件的启动代码。

对内存映射文件的访问涉及的换页,缓存,高速缓存由系统负责处理。

-无法在Dll指定的基地址处预订区域
1.Dll包含重定位信息,对dll进行重定位。
2.dll不包含重定位信息,则,载入失败。

-同一可执行文件或dll的多个实例,不会共享静态数据
exe和dll文件的内容被分成段,代码一段,数据一段。段对齐到页面大小整数倍。
应用试图写入内存映射文件时,
系统为应用试图写入页面分配以页交换文件为后备存储器的物理内存。
复制页面内容,写。

系统创建进程时,检查文件映射所有页面。对可写的数据页(即,具备写时复制标记页,立即从页交换文件调拨存储器。)

-在同一个可执行文件或DLL的多个实例间共享静态数据
每个exe和dll文件,可划为多个段。
DumpBin可用来查看exe或dll文件的段信息。

SECTION HEADER #1
.text name
11A70 virtual size
1000 virtual address
12000 size of raw data
1000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line number
0 number of relocations
0 number of line numbers
60000020 flags
code
execute read


SECTION HEADER #6
.reloc name
26D virtual size
17000 virtual address
1000 size of raw data
17000 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
42000040 flags
Initialized data
discardable
read only

Summary
1000 .data
1000 .didat
1000 .idata
1000 .rdata
1000 .reloc
12000 .text

段名解释:
.bss 未初始化数据
.CRT 只读的C运行时数据
.data 已初始化数据
.debug 调试信息
.didata 延迟导入名字表
.edata 导出名字表
.idata 导入名字表
.rdata 只读的运行时数据
.reloc 重定位表信息
.rsrc 资源
.text .exe或Dll代码
.textbss 增量链接选项时,由C++编译器生成。
.tls 线程本地存储
.xdata 异常处理表

编译时,可用以下创建自己的段:

#pragma data_seg("sectionname")

例:
#pragma data_seg("shared")
LONG g_lInstanceCount = 0;// 编译器只会把已初始化数据放入自定义段
LONG g_lTest; // 不会放入自定义段
#pragma data_seg()// 停止把数据放入自定义段。
__declspec(allocate("shared")) int c;// 显示把初始化或未初始化数据放入指定段。

1.为了共享exe或dll中变量:
1.把变量放入单独段中。
2.链接器中:
/SECTION:name, attributes

#pragma comment(linker, "/SECTION:shared, RWS")

2.多应用协同时,通信

// 定义一个新的整个系统内唯一的窗口消息
// 成功时,返回消息id,范围[0xC000, 0xFFFF]
// 失败,0
// 两个应用用相同参数,得到相同返回值。适用于多个应用处理同一消息。
// 对处理一个窗口类内的私有消息,可使用[WM_USER, 0x7FFF]范围内任何整数,这些消息私有于窗口类而非应用。
UINT WINAPI RegisterWindowMessage(
  _In_ LPCTSTR lpString
);

-volatile
向编译器说明,被修饰变量每次被使用时,均需要从变量的物理存储器取值,不要假定变量在内存中。

-映射到内存的数据文件
例:
颠倒文件内容。
1.双缓存
分配两块大小相同缓存。(缓存对应的物理存储器为内存)
分别取文件首尾大小相同两块,放入缓存。
对缓存内容逆序,逆序后分别写入文件。
如此重复。

没有额外占据磁盘空间。
没有占据过多内存空间。

2.内存映射文件。
使用后,对文件内容的操作涉及的读内容到内存,写内存中内容到文件均有系统内部完成。对我们而言,是透明的。类似于整个文件在内存中的操作。

-使用内存映射文件:
1.创建或打开一个文件内核对象。

HANDLE WINAPI CreateFile(
  _In_     LPCTSTR               lpFileName,
 // 对内存映射文件,必须以只读或读写打开
  _In_     DWORD                 dwDesiredAccess,
  _In_     DWORD                 dwShareMode,
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _In_     DWORD                 dwCreationDisposition,
  _In_     DWORD                 dwFlagsAndAttributes,
  _In_opt_ HANDLE                hTemplateFile
);

2.创建一个文件映射内核对象。
包含,文件大小,访问方式。

// 直接物理存储器为内存。对虚拟地址的访问最终都再内存中获取写入数据。
// 创建一个内存映射文件相当于先预订一块地址空间区域,再给区域调拨后备物理存储器。
// 创建一个文件映射对象的时候,系统不会预订一块地址空间区域并把文件映射到该区域。
// 但,此时,系统须知道应给后备物理存储器的页面指定何种保护属性。
// 
HANDLE WINAPI CreateFileMapping(
  _In_     HANDLE                hFile,
  _In_opt_ LPSECURITY_ATTRIBUTES lpAttributes,
  // 如调用CreateFileMapping并传入PAGE_READWRITE,系统会检查,保证磁盘上对应文件不小于指定大小。
  _In_     DWORD                 flProtect,
  // 内存映射文件的最大大小
  // 两者都为0,使用hFile对应文件大小。
  // 文件可以很大,超过4GB。但虚拟地址空间中用户可用分区一般仅仅为2GB,如何在小虚拟地址空间操纵大于其大小的文件。
  _In_     DWORD                 dwMaximumSizeHigh,
  _In_     DWORD                 dwMaximumSizeLow,
  // 文件映射对象名称
  _In_opt_ LPCTSTR               lpName
);

3.把文件映射内核对象的部分或全部映射到进程的地址空间。

// 为文件的数据预订一块地址空间区域,并以文件的数据作为后备物理存储器调拨给此区域。
// 返回预订+调拨区域的基地址
LPVOID WINAPI MapViewOfFile(
  _In_ HANDLE hFileMappingObject,
// FILE_MAP_COPY被指定时,系统从页交换文件中调拨dwNumberOfBytesToMap
大小的物理存储器。
 // 对文件映射视图,不写入时,页交换文件中被调拨的物理存储器不会被使用。
 // 写入时,把写入页内容复制到对应的页交换文件页,再把此页映射到进程地址空间。修改映射表。页交换文件页不再需要写时复制。
  _In_ DWORD  dwDesiredAccess,
 // 文件中被映射到进程地址空间的部分。又称文件视图。
 // 文件偏移量须为分配粒度整数倍。
  _In_ DWORD  dwFileOffsetHigh,
  _In_ DWORD  dwFileOffsetLow,
  // 大小。
  _In_ SIZE_T dwNumberOfBytesToMap
);
// NUMA机器上
HANDLE CreateFileMappingNuma(
HANDLE hFile,
PSECURITY_ATTRIBUTE psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName,
// 显示指定在那个NUMA节点分配内存
DWORD dwPreferredNumaNode
);

PVOID MapViewOfFileExNuma(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
LPVOID lpBaseAddress,
DWORD dwPreferredNumaNode
);

-清理
1.从进程地址空间中取消对文件映射内核对象的映射。

// pvBaseAddress:区域基地址,须和MapViewOfFile的返回值相同。
// 取消调拨 + 释放预订区域
// 会使写时复制页对应的页交换页被释放,使修改丢失。
BOOL UnmapViewOfFile(PVOID pvBaseAddress);

// 强制系统把部分或全部修改过的数据写回磁盘中
BOOL FlushViewOfFile(
// pvAddress和dwNumberOfBytesToFlush确定的区域应该被视图包含。
// pvAddress和pvAddress+dwNumberOfBytesToFlush所在页均会被写回。
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush
);

2.关闭文件映射内核对象。
3.关闭文件内核对象。

CloseHandle

系统在MapViewOfFile时,会增加文件对象,文件映射对象的引用计数。UnmapViewOfFile时,会减少。

-用内存映射文件来处理大文件
内存映射视图来完成

-内存映射文件和一致性
同一个文件映射对象的多个视图内数据一致。

-给内存映射文件指定基地址

LPVOID WINAPI MapViewOfFileEx(
  _In_     HANDLE hFileMappingObject,
  _In_     DWORD  dwDesiredAccess,
  _In_     DWORD  dwFileOffsetHigh,
  _In_     DWORD  dwFileOffsetLow,
  _In_     SIZE_T dwNumberOfBytesToMap,
  // 建议系统把文件映射到指定的地址
  _In_opt_ LPVOID lpBaseAddress
);

-用内存映射文件在进程间共享数据
1.进程间共享数据的机制
COM,OLE,DDE,Windows消息,剪贴板,邮件槽,管道,套接字…。
windows中,同一台机器上共享数据最底层机制就是内存映射文件。

数据共享通过让多个进程映射同一个文件映射对象的视图来实现。同一文件映射对象的各个视图对应的各个内存页在内存中只有一份。

-以页交换文件为后备存储器的内存映射文件
背景:共享应用运行中创建的数据。
支持:让系统能创建以页交换文件为后备存储器的内存映射文件。
使用:
CreateFileMapping + 传入INVALID_HANDLE_VALUE。
MapViewOfFile
使用完毕,CloseHandle关闭文件映射对象句柄。所有句柄都关闭,系统会从页交换文件收回已调拨的物理存储器。

-稀疏调拨的内存映射文件
在以页交换文件为后备存储器来创建文件映射对象时,可给fdwProtect指定SEC_RESERVE或SEC_COMMIT标志。
SEC_RESERVE:创建的文件映射对象在使用MapViewOfFile(Ex)时,会预订一块地址空间区域,但不会给区域调拨任何物理存储器。

为了给共享区域调拨物理存储器,需调用:VirtualAlloc。
可以只调拨部分存储器给用MapViewOfFile(Ex)预订的区域。

内存映射文件通过SEC_RESERVE标志预订得到时,不能用VirtualFree来撤销调拨给它的存储器。

稀疏文件需要磁盘驱动器的支持

BOOL WINAPI GetVolumeInformation(
 // 根目录,如"C:\"
  _In_opt_  LPCTSTR lpRootPathName,
  // 用于接收扇区名,可选
  _Out_opt_ LPTSTR  lpVolumeNameBuffer,
  _In_      DWORD   nVolumeNameSize,
  // 用于接收扇区序列号,可选
  _Out_opt_ LPDWORD lpVolumeSerialNumber,
  // 单个文件名最大长,可选。
  _Out_opt_ LPDWORD lpMaximumComponentLength,
  // FILE_SUPPORTS_SPARSE_FILES  支持稀疏文件
  // ...
  _Out_opt_ LPDWORD lpFileSystemFlags,
  // 用于接收文件系统名,可选。
  _Out_opt_ LPTSTR  lpFileSystemNameBuffer,
  _In_      DWORD   nFileSystemNameSize
);

// 在调试时触发断言失败。
void WINAPI DebugBreak(void);

// 直接给指定的设备驱动发送控制码,让相应的设备执行相应的操作
BOOL WINAPI DeviceIoControl(
// 设备句柄。典型的如:卷,目录,文件,流
  _In_        HANDLE       hDevice,
  _In_        DWORD        dwIoControlCode,
  _In_opt_    LPVOID       lpInBuffer,
  _In_        DWORD        nInBufferSize,
  _Out_opt_   LPVOID       lpOutBuffer,
  _In_        DWORD        nOutBufferSize,
  // 同步I/O下,返回写入lpOutBuffer字节数。
  _Out_opt_   LPDWORD      lpBytesReturned,
  // 异步I/O
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);

指定文件为稀疏文件:
控制码:FSCTL_SET_SPARSE
在稀疏文件中,大范围0,不需要分配磁盘。
文件被写入时,将根据需要分配非0数据空间。
要读取指定位置数据时,需要给指定位置分配数据空间。

造成文件的实际大小(实际占用磁盘空间)小于其逻辑大小。

查询磁盘文件,物理磁盘分配情况:
控制码:FSCTL_QUERY_ALLOCATED_RANGES
输入参数:
FILE_ALLOCATED_RANGE_BUFFER,指定查询的文件区间。
输出:
FILE_ALLOCATED_RANGE_BUFFER数组,标识查询区间内各个已分配磁盘的子区间。

给磁盘文件指定区间内容清零
控制码:FSCTL_SET_ZERO_DATA
输入参数:
FILE_ZERO_DATA_INFORMATION,指定要清零的区间

// 在堆中分配内存&释放内存
LPVOID WINAPI HeapAlloc(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ SIZE_T dwBytes
);

BOOL WINAPI HeapFree(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ LPVOID lpMem
);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值