【问题标题】:Reading a memory mapped block of data into a structure将内存映射的数据块读入结构
【发布时间】:2012-10-08 23:20:01
【问题描述】:

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

我有一个结构:

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

它被多次写入一个文件。 “条目”变量是浮点小数数组。写完这个文件(10000 个数据结构,每个“条目”数组是 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 file\n");
      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 个数据结构条目的数组中。当然,我这样做可能完全错了……

【问题讨论】:

  • 将指针写入文件通常没有意义。文件内容的实际格式是什么?
  • @Harry Johnston:文件内容是二进制数据结构集
  • 所以实际的浮点数不在文件中的任何地方?你能告诉我们用于编写文件的代码吗?
  • 不要尝试在类/结构中使用memcpy,因为允许编译器在成员之间添加填充,这会搞砸事情。还要记住多字节数量的字节序。更有理由从缓冲区/内存中单独分配结构成员。

标签: c++ windows memory-mapping


【解决方案1】:

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

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

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

data ReadData(void *ptr)

void WriteData(data *ptrToData, void *ptr)

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

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

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

一样将它们全部写入一批

memcpy(mapPointer, data-&gt;entries, sizeof(float)*number)

它将浮点*条目大小从data-&gt;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 %d\n", sizeOfData);

    memcpy(buffer, input, sizeOfData);

    printf("pointer to dataPoints of original %x\n", 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 original\n", 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 copy\n", 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: %f\n", 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 %d\n", test2.size);
    for(int i=0;i<test2.size;i++){
        printf("\tcopy value %d: %f\n", i, test2.dataPoints[i]);
    }

    // memory cleanup

    delete memBuffer;
    delete [] test.dataPoints;

    return 0;
}

在将数据从结构写入内存时,您可能还想了解数据对齐。检查working with packing structuresC++ struct alignment questiondata structure alignment

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

无论如何,为了解决它是否有权在这里使用它的事实,我认为它是。来自wikipedia

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

您要将整个内容加载到虚拟内存中,然后操作系统可以根据需要为您将文件分页进出内存,从而创建“延迟加载”机制。

总而言之,内存映射是共享的,因此如果它跨进程边界,您需要将它们与命名互斥锁同步,这样您就不会覆盖进程之间的数据。

【讨论】:

  • 使用memcpy后如何将内存块的地址指针前移?
  • 指向内存块的指针总是指向起始地址。要访问任何以后的点,您只需执行ptr+number 之类的操作,这将为您提供来自ptr 的指针number 字节。查看一些关于指针算术的文章:cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/pointer.html
  • foboi 我更新了我的答案,以帮助解决您对内存映射指针的担忧。希望这会有所帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
相关资源
最近更新 更多