内存管理的主要目的:提高内存利用率,减少内存碎片,提高内存分配的速度.
最大的可分配单元是chunk
,大小为2MB.每个chunk分配了512个page
,page的大小为4KB.
zend的page_size与Linux一般的page_size大小相等.
zend内部使用了mmap分配内存,不足一内存页的mmap强制以Linux的page_size进行对齐.
mmap可以指定这块内存在不同进程之间的处理方式,是共享还是写时拷贝.
相较于malloc分配方式,提供了文件到内存映射的功能.malloc分配不是基于page_size对齐的.
#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024) /* 2 MB */
#define ZEND_MM_PAGE_SIZE (4 * 1024) /* 4 KB */
#define ZEND_MM_PAGES (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE) /* 512 */
每个chunk中的4KB用于zend_mm_chunk结构体分配.
内存对其的意义:根据给定的内存地址快速定位在申请内存的位置.
实际上再申请的时候为了保证内存对齐,会多申请一块内存,然后在这块内存找到对齐的地址,除这个地址+申请的地址大小外的内存都将要被释放.
zend提供了以下三种计算对齐的方式.
#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \
(((size_t)(size)) & ((alignment) - 1)) //得出基于对齐的起始地址的offset
#define ZEND_MM_ALIGNED_BASE(size, alignment) \
(((size_t)(size)) & ~((alignment) - 1)) //得出对齐的起始地址
#define ZEND_MM_SIZE_TO_NUM(size, alignment) \
(((size_t)(size) + ((alignment) - 1)) / (alignment)) //根据size,alignment得出需分配的个数
zend_mm_heap:
全局变量alloc_globals.mm_heap指向zend_mm_heap数据结构
struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
int use_custom_heap;
#endif
#if ZEND_MM_STORAGE
zend_mm_storage *storage;
#endif
#if ZEND_MM_STAT
size_t size; /* current memory usage */
size_t peak; /* peak memory usage */
#endif
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
size_t real_size; /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
size_t real_peak; /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
size_t limit; /* memory limit */
int overflow; /* memory overflow flag */
#endif
zend_mm_huge_list *huge_list; /* list of huge allocated blocks */
zend_mm_chunk *main_chunk;
zend_mm_chunk *cached_chunks; /* list of unused chunks */
int chunks_count; /* number of alocated chunks */
int peak_chunks_count; /* peak number of allocated chunks for current request */
int cached_chunks_count; /* number of cached chunks */
double avg_chunks_count; /* average number of chunks allocated per request */
#if ZEND_MM_CUSTOM
union {
struct {
void *(*_malloc)(size_t);
void (*_free)(void*);
void *(*_realloc)(void*, size_t);
} std;
struct {
void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
} debug;
} custom_heap;
#endif
};
chunk数据结构:
struct _zend_mm_chunk {
zend_mm_heap *heap;//AG()里的mm_heap地址
zend_mm_chunk *next;//下一个trunk
zend_mm_chunk *prev;//之前的trunk
int free_pages; /* number of free pages */
int free_tail; /* number of free pages at the end of chunk
最后一块连续可用的page*/
int num; //当前chunk的序号
char reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)];
zend_mm_heap heap_slot; /* 只用于mainchunk used only in main chunk */
zend_mm_page_map free_map; /* 空闲页的位图512 bits or 64 bytes */
zend_mm_page_info map[ZEND_MM_PAGES]; /* 存储每个页的使用信息,高两位代表使用内存的类型,低十位区分是否连续的页 2 KB = 512 * 4 */
};
如之前所述,内存分配分为三种不同的范围.
分配
1.申请size需要根据page_size进行对齐
2.对齐后的size再根据chunk_size大小进行对齐
3.将内存挂载到alloc_global.mm_heap->huge_list上
释放: 从huge_list链表中删除,调用munmap释放.
large分配是page分配的整数倍.
1.遍历双向链表alloc_global.mm_heap->main_trunk
2.如果free_pages小于要申请的页的个数回到1.
3.根据zend_mm_chunk->free_map查找最优连续page(连续page个数最少,连续page编号最少).
4.如果查找可分配的页则返回对应的地址,并将map[page_num]标记为large内存
5.如果chunk都没有可分配内存,就新申请一个chunk,在进行分配.
释放:
将zend_mm_chunk->free_map[page_num],zend_mm_chunk->map[page_num]置为0.
然后修改free_pages.如果pages都释放,那么释放chunk.
small类型共分为30种不同的大小.
mm_heap->free_slot数组结构存着每个slot类型链表的首地址.
1.根据申请的内存查找对应的规格表
2.根据规格表中的num,如果mm_heap->free_slot[num]为空则继续下一步,如果不为空返回对应的地址,并从mm_heap->free_slot[num]指向链表的首地址删除
3.申请的规格表中对应的页数(bin_pages[bin_num])并更新mm_chunk->map[page_num]标识位为small内存.第一个页需要设置map[page_num](位于map的24bit-16bit位段)设置free_slot个数.接下的连续页的标志位给予顺序标志(位于map的24bit-16bit位段).
释放时:
直接插入mm_heap->free_slot当中.
依次遍历mm_heap->free_slot.
根据slot的地址执行上述的查找的宏,找到对应的chunk地址以及当前slot所在的page_num,读取map[page_num].
如果是slot连续申请的规格表中对应的页数(bin_pages[bin_num]),那么位于第一页的map的24bit-16bit位段,会用于存储这几个连续页的free_slot个数.其余的页的map的24bit-16bit位段用存储连续页的页号.
如果map中的free_slot个数+1等于规格表中(bin_elements[num])存在的元素个数,那么删除heap->free_slot[num]链表中该slot.
chunk的遍历每个page,如果是small内存的第一页(map[page_num]&0x80000000),取24-16位判断free_slot个数等于这个规格表规定的slot个数.释放这几个连续的页.
循环遍历chunk链表如果chunk->free_pages = 511那么就释放这个chunk.