文章目录
- 0. 概要
- 1. mmap基础
- 2. 为什么使用内存池?
- 3. 使用示例
- 4. 设计考虑
- 5. 实现步骤
0. 概要
c语言基于mmap实现的内存池模块
仓库地址: https://gitee.com/liudegui/mem_allocator_c
1. mmap基础
mmap
系统调用在进程的虚拟地址空间和某个文件对象或匿名存储之间建立映射关系。当使用mmap
进行内存映射时,操作系统会创建一个映射,使得对这段内存的访问就像对文件的读写一样。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:建议的映射起始地址(通常设为NULL,由系统决定)。length
:映射区域的长度。prot
:期望的内存保护标志(如PROT_READ, PROT_WRITE等)。flags
:影响映射区域的各种特性(如MAP_SHARED, MAP_PRIVATE等)。fd
:被映射对象的文件描述符,或者使用-1
来表示匿名映射。offset
:文件映射的起始位置。
2. 为什么使用内存池?
- 提高效率:内存池可以减少向操作系统申请和释放内存的次数,从而提高内存分配的效率。
- 减少碎片:内存池可以有效地减少内存碎片,提高内存利用率。
- 便于管理:内存池可以将内存的申请、释放等操作集中管理,方便程序员进行内存调试和维护。
3. 使用示例
下面是一个基于C语言的内存池实现示例:
- 头文件mem_allocator_c.h
/***Manager usage of vm mapped to /dev/mem 0x1000000~0x1dfffff*When create a MemPool, it's pool_ should be alloc use this class.*It will first alloc firstly try to alloc from memory map to /dev/mem.*If there is not enough memory in /dev/mem 0x1000000~0x1dfffff, it*will try to call calloc() func of stdlib to alloc memory.*/#ifndef UTILS_MEM_ALLOCATOR_C_H_
#define UTILS_MEM_ALLOCATOR_C_H_#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <stddef.h>#define MEM_ALLOC_DEFAULT 1
#define MEM_ALLOC_MAP 1
#define MEM_FD_INIT -1//=====================================================================
// MemMapMemAllocator
// Use MemMap to map /dev/mem 0x1000000-0x1dffefff into virtual memory.
// Use a linked table to store ranges in this mapping space. A range
// may be free or in-use.
//=====================================================================typedef struct MemAllocator MemAllocator;
typedef struct MemRange MemRange;
typedef struct MMapParams MMapParams;
typedef struct MemAllocDelegate MemAllocDelegate;#define MMAP_BASE_ADDR 0x10000000
#define MMAP_HIGH_ADDR 0x1DFFEFFFstruct MemRange {int8_t* offset;int8_t* end;uint32_t size;bool is_dirty;MemRange* next;MemRange* pre;
};struct MMapParams {MemRange* range_list;int32_t mem_fd;int8_t* map_base;uint32_t page_size;uint32_t base_addr;uint32_t high_addr;
};struct MemAllocator {int8_t* (*calloc)(MemAllocator* allocator, uint32_t nmemb, uint32_t size);void (*free)(MemAllocator* allocator, int8_t* buffer);char allocator_name[32];MMapParams* mmap_params;MemAllocator* next;
};struct MemAllocDelegate{MemAllocator* allocator;
};void init_mem_delegate(MemAllocDelegate* delegate, uint32_t base_addr, uint32_t high_addr);
int8_t* mem_delegate_calloc(MemAllocDelegate* delegate, uint32_t nmemb, uint32_t size);
void mem_delegate_free(MemAllocDelegate* delegate, int8_t* buffer);
void destroy_mem_delegate(MemAllocDelegate* delegate);#endif // UTILS_MEM_ALLOCATOR_C_H_
- 实现代码见: https://gitee.com/liudegui/mem_allocator_c
#include "mem_allocator_c.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>int main() {printf("hello world");MemAllocDelegate delegate;init_mem_delegate(&delegate, 0, 0);int8_t* p_addr = mem_delegate_calloc(&delegate, 10, 10);const char* const str = "hello world";memcpy(p_addr, str, strlen(str) + 1);printf("%s\n", p_addr);mem_delegate_free(&delegate, p_addr);destroy_mem_delegate(&delegate);return 0;
}
在这个示例中,我们首先定义了一个MemAllocDelegate
结构体,用于存储内存池的信息。然后,我们通过init_mem_delegate
函数初始化内存池,指定其大小和对齐要求。接下来,我们使用mem_delegate_calloc
函数从内存池中分配一块内存,并将字符串"hello world"
复制到这块内存中。最后,我们使用mem_delegate_free
函数释放这块内存,并通过destroy_mem_delegate
函数销毁内存池。
4. 设计考虑
在设计基于mmap
的内存池时,需要考虑以下关键因素:
- 内存映射大小 :确定内存映射的大小是至关重要的。太小可能导致频繁地创建新的映射,而太大则可能造成内存浪费。
- 内存对齐 :确保内存分配满足特定硬件或软件的对齐要求。
- 内存保护 :设置合理的内存保护标志以避免越界访问。
- 内存清理 :在程序结束时,确保所有映射都被正确地释放。
- 错误处理 :正确处理
mmap
调用可能出现的错误,例如内存不足。 - 性能监控 :跟踪内存分配和释放的性能,以优化内存池的行为。
5. 实现步骤
- 初始化 :
- 定义内存池结构体,包含必要的字段如
mmap_params
、MMapMemAllocator
等。 - 初始化内存映射参数,包括页大小、映射大小等。
- 调用
mmap
创建初始内存映射区域。
- 定义内存池结构体,包含必要的字段如
- 内存分配 :
- 实现内存分配逻辑,根据请求的大小找到第一个合适的空闲区域。
- 更新内存池的数据结构以反映分配情况。
- 返回分配的内存区域的指针。
- 内存释放 :
- 根据提供的指针找到对应的内存区域。
- 更新内存池的数据结构以标记该区域为可用。
- 如果有必要,合并相邻的空闲区域以减少碎片。
- 销毁 :
- 遍历内存池的所有映射区域,调用
munmap
释放每个映射。 - 清理内存池的数据结构。
- 遍历内存池的所有映射区域,调用