为了方便一些对时间不敏感的大数据块的存放,尤其是在JNI环境中避免系统OOM,我打算给予MMAP做一套虚拟内存工具:
首先来想一下申请连续块时的各种情况,方便编写仿malloc函数,各种增删情况如图:
每个块开头用一个long来保存块总大小,寻找空间时见到这个标记即可立即跳过相应区域,如果有多个空间块则会因为连续踩到块大小标记产生连续跳跃,而当没有踩到该标记时——即内容为0时,逐地址遍历是否满足空间要求(一个long + 用户要求的大小)的地址内容都为0,是的话则使用该空间,起始地址用long记录占用空间大小,然后偏移一个long大小给用户作为数据保存区。这种逻辑在内存地址中间的空间块被移除时依然有效,如果间隙位置足够则使用间隙位置,不够的话自然会产生跳跃重新在跳跃后寻找足够的空间的地址。
数据结构设计:
块:[块大小标记 long][用户指定大小内存块]
操作时:
malloc:用户传入需要的大小,例如8个字节,函数内将大小需求加大一个sizeof(long)增大一个long的占用空间在开头,用于标记占用量,返回时不返回块开头,而是块开头+sizeof(long)偏移到“用户制定大小内存块”的开头给用户自由发挥。
free:用户传入“用户指定大小内存块”的首地址,然后地址减去sizeof(long)得到真正的数据结构开头,使用“块大小标记”了解整块结构的实际占用空间,然后把整块空间从头到尾清0
(但实际上这样有个漏洞,用户超容量操作时容量引发虚拟内存泄露,这个我暂时认为是无法解决的,用物理内存时也一样……只能使用时规范些)
具体实现代码如下,此为代码草稿,不是正式版,用于思想展示:
/**@author 陈杰柱 for unit/linux,用于映射外存文件
* 作为内存指针,并模拟malloc和free的操作,来模拟内存条,
* 作为虚拟内存使用,可以同时使用多个虚拟内存条**/
#include "stdio.h"
#include "stdlib.h"
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
/* 虚拟内存条
@param fileMapAddress 内存条起始地址
@param size 内存条大小 */
typedef struct memoryUnit
{
void *fileMapAddress;
unsigned long fileHandle;
size_t size;
} MemoryUnit;
MemoryUnit *createVirtualMemory(char *filePath, size_t size, int createFile);
void *fileMapMalloc(MemoryUnit *mem, size_t size);
int fileMapObjectSize(void *p);
int fileMapFree(void *p);
int destroyVirtualMemory(MemoryUnit *m);
/* 创造虚拟内存条
@param filePath 虚拟内存条在外存的地址
@param size 设定内存条大小
@return 返回内存条对象 */
MemoryUnit *createVirtualMemory(char *filePath, size_t size, int createFile)
{
if(createFile > 0)
{
//创建制定大小的虚拟内存文件
if (creat(filePath, 0755) < 0)
{
printf("create file %s failure!\n", filePath);
return NULL;
}
else
{
printf("create file %s successed!\n", filePath);
}
}
unsigned long f = open(filePath, O_RDWR);
if (lseek(f, size - 1, SEEK_SET) < 0)
{
return NULL;
}
else
{
char data[1] = {0};
write(f, &data, 1);
lseek(f, 0, SEEK_SET);
printf("open file %s successed!\n", filePath);
//创建内存映射
//map the file start position as a FAKE memory address.
MemoryUnit *unit = (MemoryUnit *)malloc(sizeof(MemoryUnit));
unit->fileHandle = f;
unit->fileMapAddress = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, f, 0);
unit->size = size;
if (unit->fileMapAddress == MAP_FAILED) {
free(unit);
return NULL;
}
return unit;
}
}
/* 从虚拟内存条中获取指定大小的连续空间
@param mem 虚拟内存条
@param size 需要分配多大空间
@return 返回连续空间地址 */
void* fileMapMalloc(MemoryUnit *mem, size_t size)
{
long i = 0, count = 0;
void *p = NULL;
size += sizeof(long); //留一个long的位置记录该块的容量
//寻找合适的空块
for (i = 0; i < mem->size; i++)
{
if (*(unsigned char *)(mem->fileMapAddress + i) == 0)
{
count++;
if (count == size)
{
p = mem->fileMapAddress + (i + 1 - size); //找到足够大小的块,减去刚刚遍历越过的位置得到起始地址
*(long *)p = size; //记录真正大小
p += sizeof(long); //掩盖块大小记录标记
break;
}
}
else
{
i += *(long *)(mem->fileMapAddress + i) - 1; //每一个块的头部都是占用空间量,所以直接跳过。块与块之间的空位如果足够使用的话会形成新的块
count = 0;
}
}
return p;
}
/* 已分配空间大小统计
@param p 虚拟内存条分配的连续空间地址
@return 占用虚拟内存条的空间 */
int fileMapObjectSize(void *p)
{
long size = *(long *)(p - sizeof(long));
printf("This fileMapObject's size is %ld\n", size);
return size;
}
/* 销毁已分配空间
@param p 虚拟内存条分配的连续空间地址
@return 是否成功 */
int fileMapFree(void *p)
{
memset(p - sizeof(long), 0, fileMapObjectSize(p));
return 1;
}
int destroyVirtualMemory(MemoryUnit *m)
{
munmap(m->fileMapAddress, m->size);
close(m->fileHandle);
free(m);
return 1;
}
编写测试程序:
int main()
{
MemoryUnit *m = createVirtualMemory("./shit.txt", 1024 * 1024 * 300);
//测试1
int *p = (int *)fileMapMalloc(m, sizeof(int) * 5);
p[0] = 1;
p[1] = 999;
p[4] = 0xFFFFFFFF;
int i;
for (i = 0; i < 5; i++)
{
printf("%d\n", p[i]);
}
//测试2
char *p2 = (char *)fileMapMalloc(m, sizeof(char) * 6);
strcpy(p2, "hello");
printf("%s\n", p2);
//释放测试
for (int i = 0; i < 60; i++)
{
printf("%d ", *(unsigned char *)(m->fileMapAddress + i));
}
printf("\n");
fileMapFree(p);
//测试3
p = (int *)fileMapMalloc(m, sizeof(int) * 3);
p[0] = 1;
p[1] = 999;
p[2] = 0xFFFFFFFF;
for (i = 0; i < 3; i++)
{
printf("%d\n", p[i]);
}
p = fileMapMalloc(m, 3);
memset(p, 255, 3);
for (int i = 0; i < 60; i++)
{
printf("%d ", *(unsigned char *)(m->fileMapAddress + i));
}
printf("\n");
//释放测试2
fileMapFree(p2);
for (int i = 0; i < 60; i++)
{
printf("%d ", *(unsigned char *)(m->fileMapAddress + i));
}
printf("\n");
destroyVirtualMemory(m);
}
测试结果:
create file ./shit.txt successed!
open file ./shit.txt successed!
1
999
0
0
-1
hello
28 0 0 0 0 0 0 0 1 0 0 0 231 3 0 0 0 0 0 0 0 0 0 0 255 255 255 255 14 0 0 0 0 0 0 0 104 101 108 108 111 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
This fileMapObject's size is 28
1
999
-1
20 0 0 0 0 0 0 0 1 0 0 0 231 3 0 0 255 255 255 255 0 0 0 0 0 0 0 0 14 0 0 0 0 0 0 0 104 101 108 108 111 0 11 0 0 0 0 0 0 0 255 255 255 0 0 0 0 0 0 0
This fileMapObject's size is 14
20 0 0 0 0 0 0 0 1 0 0 0 231 3 0 0 255 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 11 0 0 0 0 0 0 0 255 255 255 0 0 0 0 0 0 0
可以看到虚拟内存的使用情况符合规划的情况,实验成功。