这篇文章主要介绍了关于浅谈php源码三十一:php内存池中的堆(heap)层基础,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下
浅谈php源码三十一:php内存池中的堆(heap)层基础
【概述】
php的内存管理器是分层(hierarchical)的。这个管理器共有三层:存储层(storage)、堆(heap)层和 emalloc/efree 层。在php源码阅读笔记三十:php内存池中的存储层中介绍了存储层,存储层通过 malloc()、mmap() 等函数向系统真正的申请内存,并通过 free() 函数释放所申请的内存。存储层通常申请的内存块都比较大,这里申请的内存大并不是指storage层结构所需要的内存大,只是堆层通过调用存储层的分配方法时,其以段的格式申请的内存比较大,存储层的作用是将内存分配的方式对堆层透明化。
在存储层之上就是今天我们要了解的堆层。堆层一个调度层,它与上面的emalloc/efree层交互,将通过存储层申请到的大块内存,进行拆分,按需提供。在堆层中有其一套内存的调度策略,这个整个php内存分配管理的核心区域。
以下的所有分享都是基于zend_debug未打开的情况。
首先看下堆层所涉及到的结构:
【结构】
/* mm block type */typedef struct _zend_mm_block_info {size_t _size;/* block的大小*/size_t _prev;/* 计算前一个块有用到*/} zend_mm_block_info; typedef struct _zend_mm_block {zend_mm_block_info info;} zend_mm_block; typedef struct _zend_mm_small_free_block {/* 双向链表 */zend_mm_block_info info;struct _zend_mm_free_block *prev_free_block;/* 前一个块 */struct _zend_mm_free_block *next_free_block;/* 后一个块 */} zend_mm_small_free_block;/* 小的空闲块*/ typedef struct _zend_mm_free_block {/* 双向链表 + 树结构 */zend_mm_block_info info;struct _zend_mm_free_block *prev_free_block;/* 前一个块 */struct _zend_mm_free_block *next_free_block;/* 后一个块 */ struct _zend_mm_free_block **parent;/* 父结点 */struct _zend_mm_free_block *child[2];/* 两个子结点*/} zend_mm_free_block; struct _zend_mm_heap {int use_zend_alloc;/* 是否使用zend内存管理器 */void *(*_malloc)(size_t);/* 内存分配函数*/void (*_free)(void*);/* 内存释放函数*/void *(*_realloc)(void*, size_t);size_t free_bitmap;/* 小块空闲内存标识 */size_t large_free_bitmap; /* 大块空闲内存标识*/size_t block_size;/* 一次内存分配的段大小,即zend_mm_seg_size指定的大小,默认为zend_mm_seg_size (256 * 1024)*/size_t compact_size;/* 压缩操作边界值,为zend_mm_compact指定大小,默认为 2 * 1024 * 1024*/zend_mm_segment *segments_list;/* 段指针列表 */zend_mm_storage *storage;/* 所调用的存储层 */size_t real_size;/* 堆的真实大小 */size_t real_peak;/* 堆真实大小的峰值 */size_t limit;/* 堆的内存边界 */size_t size;/* 堆大小 */size_t peak;/* 堆大小的峰值*/size_t reserve_size;/* 备用堆大小*/void *reserve;/* 备用堆 */int overflow;/* 内存溢出数*/int internal;#if zend_mm_cacheunsigned int cached;/* 已缓存大小 */zend_mm_free_block *cache[zend_mm_num_buckets];/* 缓存数组/#endifzend_mm_free_block *free_buckets[zend_mm_num_buckets*2];/* 小块空闲内存数组 */zend_mm_free_block *large_free_buckets[zend_mm_num_buckets];/* 大块空闲内存数组*/zend_mm_free_block *rest_buckets[2];/* 剩余内存数组 */ };
对于heap结构中的内存操作函数,如果use_zend_alloc为否,则使用malloc-type 内存分配,此时所有的操作就不经过堆层中的内存管理,直接采用malloc等函数。
compact_size的大小默认为 2 * 1024 * 1024(2m),如果有设置变量zend_mm_compact则为此指定大小,如果内存的峰值超过这个值,则会调用storage的compact函数,只是这个函数现在的实现为空,可能在后续的版本中添加。
reserve_size为备用堆的大小,默认情况下为zend_mm_reserve_size,其大小为(8*1024)
*reserve为备用堆,其大小为reserve_size,其用作内存溢出时报告错误用。
【关于use_zend_alloc】
环境变量 use_zend_alloc 可用于允许在运行时选择 malloc 或 emalloc 内存分配。使用 malloc-type 内存分配将允许外部调试器观察内存使用情况,而 emalloc 分配将使用 zend 内存管理器抽象,要求进行内部调试。
[zend_startup() -> start_memory_manager() -> alloc_globals_ctor()]
static void alloc_globals_ctor(zend_alloc_globals *alloc_globals tsrmls_dc){char *tmp;alloc_globals->mm_heap = zend_mm_startup(); tmp = getenv("use_zend_alloc");if (tmp) {alloc_globals->mm_heap->use_zend_alloc = zend_atoi(tmp, 0);if (!alloc_globals->mm_heap->use_zend_alloc) {/* 如果不使用zend的内存管理器,同直接使用malloc函数*/alloc_globals->mm_heap->_malloc = malloc;alloc_globals->mm_heap->_free = free;alloc_globals->mm_heap->_realloc = realloc;}}}
【初始化】
[zend_mm_startup()]
初始化storage层的分配方案,初始化段大小,压缩边界值,并调用zend_mm_startup_ex()初始化堆层。
[zend_mm_startup() -> zend_mm_startup_ex()]
【内存对齐】
在php的内存分配中使用了内存对齐,内存对齐计算显然有两个目标:一是减少cpu的访存次数;第二个就是还要保持存储空间的效率足够高。
# define zend_mm_alignment 8 #define zend_mm_alignment_mask ~(zend_mm_alignment-1) #define zend_mm_aligned_size(size)(((size) + zend_mm_alignment - 1) & zend_mm_alignment_mask) #define zend_mm_aligned_header_sizezend_mm_aligned_size(sizeof(zend_mm_block)) #define zend_mm_aligned_free_header_sizezend_mm_aligned_size(sizeof(zend_mm_small_free_block))
php在分配块的内存中,用到内存对齐,如果所需要的内存的大小的低三位不为0(不能为8整除),则将低三位加上7,并~7进行与操作,即对于大小不是8的整数倍的内存大小补全到可以被8整除。
在win32机器上,一些宏对应的数值大小为:
zend_mm_min_size=8
zend_mm_max_small_size=272
zend_mm_aligned_header_size=8
zend_mm_aligned_free_header_size=16
zend_mm_min_alloc_block_size=8
zend_mm_aligned_min_header_size=16
zend_mm_aligned_segment_size=8
如果要分配一个大小为9个字节的块,则其实际分配的大小为zend_mm_aligned_size(9 + 8)=24
【块的定位】
所分配的内存的右边的两位是用来标记内存的类型。
其大小的定义为#define zend_mm_type_mask zend_mm_long_const(0×3)
如下所示代码为块的定位
#define zend_mm_next_block(b)zend_mm_block_at(b, zend_mm_block_size(b)) #define zend_mm_prev_block(b)zend_mm_block_at(b, -(int)((b)->info._prev & ~zend_mm_type_mask)) #define zend_mm_block_at(blk, offset)((zend_mm_block *) (((char *) (blk))+(offset))) #define zend_mm_block_size(b)((b)->info._size & ~zend_mm_type_mask)#define zend_mm_type_maskzend_mm_long_const(0x3)
当前块的下一个元素,即为当前块的头位置加上整个块(去掉了类型的长度)的长度。
当前块的上一个元素,即为当前块的头位置减去前一个块(去掉了类型的长度)的长度。
关于前一个块的长度,在块的初始化时设置为当前块的大小与块类型的或操作的结果。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注!
相关推荐:
浅谈php源码三十:php内存池中的存储层
浅谈php源码二十九:关于接口的继承
浅谈php源码二十八:关于类结构和继承
以上就是浅谈php源码三十一:php内存池中的堆(heap)层基础的详细内容。