因为最近项目中需要实现一个临时数据高速存储,所以最近对内存映射文件做了一下了解,写出来与大家分享一下,因为个人水平有限也许会有这样那样的问题也恳请大家指正。
mmap是linux内存映射文件,是将文件映射成为内存地址空间的一种方式,其实,方法很简单。
memfd = open(MEMFILE, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR);
memd = mmap(NULL, (sizeof(Type)) * size, PROT_WRITE | PROT_READ,
MAP_SHARED, memfd, 0);
我们的程序现在拥有了一定的地址空间,代码中也获得了一个指向首地址的指针,那我们该怎么使用他呢?其实,大家想怎么用就怎么用,不过我们还是采用一定的方法将这些地址空间管理起来,定义一下结构体来对内存进行结构化管理:
/**
* \struct NodeHeader
* \brief 节点头
*/
struct NodeHeader {
unsigned int size; /**< 内存尺寸 */
bool isActive; /**< 是否使用 */
unsigned int refCount; /**< 引用数量 */
NodeHeader *next;
};
/**
* \struct Node
* \brief 节点
*/
struct Node {
NodeHeader header; /**< 节点头 */
char *data; /**< 数据 */
};
/**
* \struct NodeList
* \brief 节点列表
*/
struct NodeList {
NodeList *next;
unsigned int size; /**< 列表中节点的尺寸 */
NodeHeader *header; /**< 节点 */
};
/**
* \struct NodeContext
* \brief 节点列表目录
*/
struct NodeContext {
bool isInit; /**< 是否已经初始化*/
unsigned int refCount; /**<引用计数器 */
NodeList *list; /**< 节点列表头 */
};
注意,以上结构体本身没有定义实际存储数据的空间,而是通过Node的data指针来指向数据的空间。我一般会在Node之后紧接着根据size大小的数据空间,在分配时直接将指针向下移动相应的数值就可以。这是malloc函数实现中brk ()函数要做的事情,我们自己来做来模拟实现其分配过程。
初始化节点很简单:
Node* allocNode(NodeList *list)
{
Node *node = (Node *)nodeMem;
node->header.size = list->size;
if(!nodeContext->isInit) {
node->header.refCount = 0;
node->header.isActive = false;
}
node->data = nodeMem + sizeof(Node);
nodeMem = nodeMem + sizeof(Node) + list->size; //注意,这里直接将指针移动过去一定的值来为实际数据保留相应的空间
return node;
}
我们经过一系列初始化过程将这些连续的地址空间进行了分割,这样我们就可以通过自定义的接口来访问这些内存块了。不过提醒大家的是由于进程间映射的地址值不同,所以我们把地址一个放到了映射文件中,会造成地址访问的越界。所以以上基于指针的实现只能够用于进程内,不过大家完全可以只在映射文件中保存数据,通过不同进程中相同的内存结构来访问。还有一种方式就是模拟cpu变址寻址和Linux底层虚拟内存来实现,这样既可以拥有了结构化的数据,又拥有了指针访问的灵活性。
那我们如何获取数据呢?大家注意到我上面使用的都是POD数据,也就是说他们都是不会经过C++编译器优化的(如果用在C中就更不存在这个问题了),这里我们直接offset来实现由数据获得节点:
Node* getNode(char *data)
{
return (Node *)(&(*data) - sizeof(NodeHeader));
}
上面的所有的过程都是简单的对内存区域的移动,记得有人说过“编程不过就是做一些加法或者将内存中的数据从一个地方搬到另一个地方”。