为了方便一些对时间不敏感的大数据块的存放,尤其是在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
#include
#include
#include
#include
#include /* 虚拟内存条
@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
【C语言|一种基于linux mmap特性的应用层虚拟内存工具的编写】可以看到虚拟内存的使用情况符合规划的情况,实验成功。
推荐阅读
- C语言学习|第十一届蓝桥杯省赛 大学B组 C/C++ 第一场
- 【C】题目|【C语言】题集 of ⑥
- 单片机|自学单片机好找工作吗(会单片机能找什么工作?)
- 单片机|keil把源代码生成lib的方法
- c语言|一文搞懂栈(stack)、堆(heap)、单片机裸机内存管理malloc
- c语言|C语言初期学习遇到的特殊点 【三子棋详解】【初学者福音,详细总结,复习能手】
- 笔记|C语言数据结构——二叉树的顺序存储和二叉树的遍历
- 个人理解|【C语言基础之类型转换】
- c语言|【C语言】自定义类型 结构体 枚举 联合
- 学习分享|【C语言函数基础】