Windows核心开发以及内存映射文件

10 篇文章 0 订阅
8 篇文章 0 订阅

引言

这里主要记录在开发中用到的Windows的一些API。以防忘记。如果是大神请直接出门右拐,如果想更详细学习直接google,这里偷懒看的百度百科。

Windows API

Windows API中的一些类/结构体

源码:winnt.h

  1. LARGE_INTEGER
typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart; // unsigned long
        LONG HighPart; // long
    } u;
    LONGLONG QuadPart; // long long, mostly used
} LARGE_INTEGER;
  1. SYSTEM_INFO
typedef struct _SYSTEM_INFO {
    union {
        DWORD dwOemId;          // Obsolete field...do not use
        struct {
            WORD wProcessorArchitecture;
            WORD wReserved;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
    DWORD dwPageSize;
    LPVOID lpMinimumApplicationAddress;
    LPVOID lpMaximumApplicationAddress;
    DWORD_PTR dwActiveProcessorMask;
    DWORD dwNumberOfProcessors;
    DWORD dwProcessorType;
    DWORD dwAllocationGranularity;
    WORD wProcessorLevel;
    WORD wProcessorRevision;
} SYSTEM_INFO, *LPSYSTEM_INFO;
系统信息&内存状态
// system info // 系统信息
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
DWORD processCoreNum = sys_info.dwNumberOfProcessors;
// virtual memory space's granularity
DWORD allocation_granularity = sys_info.dwAllocationGranularity; // 分配粒度
LPVOID min_address = sys_info.lpMinimumApplicationAddress; // 用户区最小值
LPVOID max_address = sys_info.lpMaximumApplicationAddress; // 用户区最大值
// processor info
DWORD ProcessorType = sys_info.dwProcessorType;
WORD ProcessorLevel = sys_info.wProcessorLevel;
WORD ProcessorRevision = sys_info.wProcessorRevision;
// memory status // 内存初始状态
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
std::cout << "内存繁忙程度=" << memStatus.dwMemoryLoad << std::endl;
std::cout << "总物理内存=" << memStatus.dwTotalPhys << std::endl;
std::cout << "可用物理内存=" << memStatus.dwAvailPhys << std::endl;
std::cout << "总页文件=" << memStatus.dwTotalPageFile << std::endl;
std::cout << "可用页文件=" << memStatus.dwAvailPageFile << std::endl;
std::cout << "总进程空间=" << memStatus.dwTotalVirtual << std::endl;
std::cout << "可用进程空间=" << memStatus.dwAvailVirtual << std::endl;
查看报错信息

在使用windows api的一些函数之后可以调用GetLastError

  • 该函数返回调用线程最近的错误代码值,错误代码以单线程为基础来维护的,多线程不重写各自的错误代码值。

例如:〖1132〗-指定的基址或文件偏移量没有适当对齐。
〖487〗-试图访问无效的地址。

内存映射

是什么

内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于 磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。(摘自百度百科)

为什么

实现文件映射主要是为了用于读写大文件,减少大文件读写的时间。实际测试下来使用内存映射的方式读取一个大文件(e.g. 664MB),读取时间可以减少约30%(16.579s-9.855s)。

怎么做

这里仅介绍如何使用内存映射的方式读取文件,写文件需要修改部分参数,读者动手尝试下。
详见代码中的注释,如下:包含头文件为:#include <Windows.h>
核心函数有:

  1. CreateFile
  2. CreateFileMapping
  3. MapViewOfFile
  • 核心:倒数2和3参数需要注意,dwFileOffsetLow和dwFileOffsetHigh必须反映一个偏移距离,它由系统的内存分配精度决定。例如,假设系统的内存精度是64KB(即最小分配单位是64KB),则这些值必须是64KB的整数倍。大多数应用程序都简单的用零从文件的起始处开始映射。这里要着重注意这一点,如果由于某种情况不能从开头开始映射,那么需要找到对应allocation granularity最小整数倍的位置,然后才能开始映射,否则会导致文件一致被占用,调用CloseHandle之后而产生内存泄漏。
  • If you want to view a portion of the file that does not start at the beginning of the file, you must create a file mapping object. This object is the size of the portion of the file you want to view plus the offset into the file. For example, if you want to view the 1 kilobyte (1K) that begins 131,072 bytes (128K) into the file, you must create a file mapping object of at least 132,096 bytes (129K) in size. The view starts 131,072 bytes (128K) into the file and extend for at least 1,024 bytes. This example assumes a file allocation granularity of 64K.
  • File allocation granularity affects where a map view can start. A map view must start at an offset into the file that is a multiple of the file allocation granularity. So the data you want to view may be the file offset modulo the allocation granularity into the view. The size of the view is the offset of the data modulo the allocation granularity, plus the size of the data that you want to examine.
  1. UnmapViewOfFile
  1. CloseHandle

代码流程:关注流程,不保证能运行

// 调用CreateFile()生成一个映射文件,具体每个形参的含义详见
HANDLE file_handle = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == file_handle)
	LOG(ERROR) << "Failed to create a mmapping file.";
// 获取待处理文件的大小,因为不可能直接映射同等大小的空间,必须将原始文件切碎成固定长度大小的小片,分别处理,最后一段剩多少是多少。
LARGE_INTEGER file_sz;
GetFileSizeEx(file_handle, &file_sz);
// 建立文件映射
HANDLE mapping_handle = CreateFileMapping(file_handle, NULL, PAGE_READONLY, 0, 0, NULL);
// error check
if (NULL == mapping_handle)
	// error code e.g. ERROR_FILE_INVALID,ERROR_INVALID_HANDLE,ERROR_ALREADY_EXISTS
	LOG(ERROR) << "Mapping file failed, error code: " << GetLastError();
// system info
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
// 获取当前系统的粒度,映射文件时设置的偏移量和系统粒度相关
DWORD allocation_granularity = sys_info.dwAllocationGranularity;
// 由前述可知,MapViewOfFile可以从文件中特定的某一位置开始读取,并不需要全部从头开始,而实现这一功能的秘密就是其倒数2和3个形参。这里先将cur_offset_size初始化为0,然后每次固定映射EACH_SIZE进行处理,最后剩下多少是多少。这样就可以不用将全部的数据都读进来,分批分块逐个击破。
LARGE_INTEGER cur_offset_size;
cur_offset_size.QuadPart = 0;
const uint32_t EACH_SIZE = allocation_granularity * 1000;
// while loop untill the whole file is loaded
while (true)
{
	if (cur_offset_size.QuadPart + EACH_SIZE < file_sz.QuadPart)
	{
		char *pFile = nullptr;
		// MapViewOfFile return a start pointer position pointing to the file mmapping memory space (memory size is EACH_SIZE or rest_sz)
		// mmapping will divided into several subpieces, subpieces length is rest_sz or EACH_SIZE
		pFile = (char*)MapViewOfFile(mapping_handle, FILE_MAP_READ, cur_offset_size.HighPart, cur_offset_size.LowPart, EACH_SIZE);
		// the pFile is the address behind cur_offset_size and the memory size is EACH_SIZE
		// 将pFile传入自定义函数中进行处理;
		void 自定义函数(pFile);
		// move back to the base address
		UnmapViewOfFile(pFile - EACH_SIZE);
		// 每个循环就要更新一下当前的位置。
		cur_offset_size.QuadPart += EACH_SIZE;
	}
	else // the last piece
	{
		uint32_t rest_sz = file_sz.QuadPart - cur_offset_size.QuadPart;
		pFile = (char*)MapViewOfFile(mapping_handle, FILE_MAP_READ, cur_offset_size.HighPart, cur_offset_size.LowPart, rest_sz);
		// 将pFile传入自定义函数中进行处理;
		UnmapViewOfFile(pFile - rest_count * pt_length);
		break;
	}
}
// 处理完成之后就可以关闭文件映射了。
CloseHandle(mapping_handle);
CloseHandle(file_handle);

例如自定义函数可以是:读取其中的n个float的内容到一个数组中。

void doAction(char * pFile, int pts_num) {
	int size_float3 = sizeof(float) * 3;
	float *data = new float[pts_num * 3];
	memcpy(data, pFile, pts_num * size_float3);
	delete[]data;
}
实际测试
  • 上述介绍的通过分块的方法一点点读取文件时,分块的大小 E A C H _ S I Z E {EACH\_SIZE} EACH_SIZE对最终的时间运行时间几乎无影响,但是也不能取太大,结合自身系统情况来;
  • 读取大文件时,除了采用内存映射的方式,其他的操作,例如内存开辟大小,次数,写入效率这些都会影响最终的耗时,因此单靠内存映射,效率提升有上限。

TODO

  • 内存映射的方式写文件
  • 多线程并行处理同一个文件

References

  1. Ref1
  2. Ref2
  3. Ref3
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值