将内存映射的数据块读取到结构中

Reading a memory mapped block of data into a structure

本文关键字:读取 结构 数据 内存 映射      更新时间:2023-10-16

我今天一直在VC++2008上玩内存映射,但我仍然不完全理解如何使用它,也不完全理解它是否适合我的目的。我的目标是快速读取一个非常大的二进制文件。

我有一个结构:

typedef struct _data
{
int number;
char character[512];
float *entries;
}Data;

它被多次写入一个文件中。"entries"变量是一个浮点小数数组。在写完这个文件(10000个数据结构,每个"entries"数组为90000个浮点)后,我尝试用以下函数对这个文件进行内存映射,这样我可以更快地读取数据。到目前为止,我拥有的是:

void readDataMmap(char *fname,      //name of file containing my data
int arraySize,    //number of values in struct Data
int entrySize)    //number of values in each "entries" array
{
//Read and mem map the file
HANDLE hFile = INVALID_HANDLE_VALUE;
HANDLE hMapFile;
char* pBuf;
int fd = open(fname, O_RDONLY);
if(fd == -1){
printf("Error: read failed");
exit(-1);
}
hFile = CreateFile((TCHAR*)fname, 
GENERIC_READ,          // open for reading 
0,                     // do not share 
NULL,                  // default security 
OPEN_EXISTING,         // existing file only 
FILE_ATTRIBUTE_NORMAL, // normal file 
NULL);                 // no template
if (hFile == INVALID_HANDLE_VALUE) 
{ 
printf("First CreateFile failed"));
return (1);
} 
hMapFile = CreateFileMapping(hFile,
NULL,                    // default security
PAGE_READWRITE,
0,                       // max. object size
0,                    // buffer size
NULL);                 // name of mapping object
if(hMapFile == ERROR_FILE_INVALID){
printf("File Mapping failed");
return(2);
}
pBuf = (char*) MapViewOfFile(hMapFile,   // handle to map object
FILE_MAP_READ, // read/write permission
0,
0,
0);         //Was NULL, 0 should represent full file bytesToMap size
if (pBuf == NULL)
{
printf("Could not map view of filen");
CloseHandle(hMapFile);
return 1;
}
//Allocate data structure
Data *inData = new Data[arraySize];
for(int i = 0; i<arraySize; i++)inData[i].entries = new float[entrySize];
int pos = 0;
for(int i = 0; i < arraySize; i++)
{
//This is where I'm not sure what to do with the memory block
}
}

在函数结束时,在映射内存并返回一个指向内存块"pBuf"开头的指针后,我不知道该怎么做才能将这个内存块读回我的数据结构中。因此,最终我想将这个内存块转移回我的10000个数据结构项的数组中。当然,我可能完全错了。。。

处理内存映射文件实际上与处理任何其他类型的内存指针没有什么不同。内存映射文件只是一个数据块,您可以从任何使用相同名称的进程中读取和写入这些数据。

我假设你想将文件加载到内存映射中,然后在那里随意读取和更新它,并以某个规则或已知的间隔将其转储到文件中,对吧?如果是这样的话,只需从文件中读取数据并将数据复制到内存映射指针即可。稍后,您可以从映射中读取数据,并将其投射到与内存对齐的结构中,然后随意使用您的结构。

如果我是你,我可能会创建一些辅助方法,比如

data ReadData(void *ptr)

void WriteData(data *ptrToData, void *ptr)

其中*ptr是内存映射地址,*ptrToData是指向要写入内存的数据结构的指针。实际上,在这一点上,它的内存是否映射并不重要,如果你想从加载到本地内存的文件中读取,你也可以这样做。

您可以像读取/写入任何其他块数据一样,使用memcpy将数据从源复制到目标,还可以使用指针算术来推进数据中的位置。不要担心"内存映射",它只是一个指向内存的指针,你可以这样对待它。

此外,由于您将要处理直接内存指针,您不需要将每个元素逐一写入映射文件,因此可以像一样在一批中写入所有元素

memcpy(mapPointer, data->entries, sizeof(float)*number)

它将float*条目大小从data->entries复制到映射指针起始地址。显然,你可以随心所欲地复制它,无论你想在哪里复制,这只是一个例子。看见http://www.devx.com/tips/Tip/13291.

用类似的方法读回数据,但你想明确地将内存地址复制到一个已知的位置,所以想象一下将你的结构扁平化。代替

data:
int
char * -> points to some address
float * -> points to some address

当你的指针指向其他地方的其他内存时,复制像这样的内存

data:
int 
char * -> copy of original ptr
float * -> copy of original ptr
512 values of char array 
number of values of float array

因此,通过这种方式,您可以将内存映射中的数据"重新序列化"到本地。记住,数组只是指向内存的指针。内存在对象中不必是顺序的,因为它可以在其他时间分配。您需要确保将指针指向的实际数据复制到内存映射。一种常见的方法是将对象直接写入内存映射,然后使用所有扁平数组跟随对象。读回时,您首先读取对象,然后将指针增加sizeof(object)并读取下一个数组,然后将指示器再次增加arraysize等。

这里有一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct data{
int size;
char items[512];
float * dataPoints;
};
void writeToBuffer(data *input, char *buffer){
int sizeOfData = sizeof(data);
int dataPointsSize = sizeof(float) * input->size;
printf("size of data %dn", sizeOfData);
memcpy(buffer, input, sizeOfData);
printf("pointer to dataPoints of original %xn", input->dataPoints);
memcpy(buffer + sizeOfData, input->dataPoints, dataPointsSize);
}
void readFromBuffer(data *target, char * buffer){
memcpy(target, buffer, sizeof(data));
printf("pointer to datapoints of copy %x, same as originaln", target->dataPoints);

// give ourselves a new array
target->dataPoints =  (float *)malloc(target->size * sizeof(float));
// do a deep copy, since we just copied the same pointer from 
// the previous data into our local
memcpy(target->dataPoints, buffer + sizeof(data), target->size * sizeof(float));
printf("pointer to datapoints of copy %x, now it's own copyn", target->dataPoints);
}
int main(int argc, char* argv[])
{
data test;
for(unsigned int i=0;i<512;i++){
test.items[i] = i;
}
test.size = 10;
// create an array and populate the data
test.dataPoints = new float[test.size];
for(unsigned int i=0;i<test.size;i++){
test.dataPoints[i] = (float)i * (1000.0);
}
// print it out for demosntration
for(unsigned int i=0;i<test.size;i++){
printf("data point value %d: %fn", i, test.dataPoints[i]);
}
// create a memory buffer. this is no different than the shared memory
char * memBuffer = (char*)malloc(sizeof(data) + 512 + sizeof(float) * test.size + 200);
// create a target we'll load values into
data test2;
// write the original out to the memory buffer
writeToBuffer(&test, memBuffer);
// read from the memory buffer into the target
readFromBuffer(&test2, memBuffer);
// print for demonstration
printf("copy number %dn", test2.size);
for(int i=0;i<test2.size;i++){
printf("tcopy value %d: %fn", i, test2.dataPoints[i]);
}
// memory cleanup
delete memBuffer;
delete [] test.dataPoints;
return 0;
}

在将数据从结构写入内存时,您可能还需要了解数据对齐情况。检查打包结构的工作,C++结构对齐问题,以及数据结构对齐。

如果在读取时不提前知道数据的大小,则应将数据的大小写入内存映射开头的已知位置,以备将来使用。

无论如何,为了解决在这里使用它是否正确的问题,我认为它是正确的

内存映射文件的主要好处是提高I/O性能,尤其是在大文件上使用时。。。内存映射过程由虚拟内存管理器处理,它是负责处理页面文件的同一个子系统。内存映射文件一次加载一整页到内存中。页面大小由操作系统选择以获得最大性能。由于页面文件管理是虚拟内存系统中最关键的元素之一,因此将文件的页面大小的部分加载到物理内存中通常是一项高度优化的系统功能。

您将把整个文件加载到虚拟内存中,然后操作系统可以根据需要在内存中为您分页文件,从而创建一种"延迟加载"机制。

也就是说,内存映射是共享的,所以如果它跨越了进程边界,你会想用一个命名的互斥体来同步它们,这样就不会覆盖进程之间的数据。