Skip to content

Commit fda9bae

Browse files
committed
update zendmm
1 parent 336032b commit fda9bae

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

5/zend_alloc.md

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,56 @@ static void *zend_mm_alloc_huge(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_D
151151
return ptr;
152152
}
153153
```
154-
huge的分配过程还是比较简单的。
154+
huge的分配实际就是分配多个chunk,chunk的分配也是large、small内存分配的基础,它是ZendMM向系统申请内存的唯一粒度。在申请chunk内存时有一个关键操作,那就是将内存地址对齐到ZEND_MM_CHUNK_SIZE,也就是说申请的chunk地址都是ZEND_MM_CHUNK_SIZE的整数倍,注意:这里说的内存对齐值并不是系统的字节对齐值,所以需要在申请后自己调整下。ZendMM的处理方法是:先按实际要申请的内存大小申请一次,如果系统分配的地址恰好是ZEND_MM_CHUNK_SIZE的整数倍那么就不需要调整了,直接返回使用;如果不是ZEND_MM_CHUNK_SIZE的整数倍,ZendMM会把这块内存释放掉,然后按照"实际要申请的内存大小+ZEND_MM_CHUNK_SIZE"的大小重新申请一块内存,多申请的ZEND_MM_CHUNK_SIZE大小的内存是用来调整的,ZendMM会从系统分配的地址向后偏移到ZEND_MM_CHUNK_SIZE的整数倍位置,调整完以后会把多余的内存再释放掉,如下图所示,虚线部分为alignment大小的内容,灰色部分为申请的内容大小,系统返回的地址为ptr1,而实际使用的内存是从ptr2开始的。
155+
156+
![](../img/chunk_alloc.png)
157+
158+
下面看下chunk的具体分配过程:
159+
```c
160+
//size为申请内存的大小,alignment为内存对齐值,一般为ZEND_MM_CHUNK_SIZE
161+
static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment)
162+
{
163+
//向系统申请size大小的内存
164+
void *ptr = zend_mm_mmap(size);
165+
if (ptr == NULL) {
166+
return NULL;
167+
} else if (ZEND_MM_ALIGNED_OFFSET(ptr, alignment) == 0) {//判断申请的内存是否为alignment的整数倍
168+
//是的话直接返回
169+
return ptr;
170+
}else{
171+
//申请的内存不是按照alignment对齐的,注意这里的alignment并不是系统的字节对齐值
172+
size_t offset;
173+
174+
//将申请的内存释放掉重新申请
175+
zend_mm_munmap(ptr, size);
176+
//重新申请一块内存,这里会多申请一块内存,用于截取到alignment的整数倍,可以忽略REAL_PAGE_SIZE
177+
ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE);
178+
//offset为ptr距离上一个alignment对齐内存位置的大小,注意不能往前移,因为前面的内存都是分配了的
179+
offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment);
180+
if (offset != 0) {
181+
offset = alignment - offset;
182+
zend_mm_munmap(ptr, offset);
183+
//偏移ptr,对齐到alignment
184+
ptr = (char*)ptr + offset;
185+
alignment -= offset;
186+
}
187+
if (alignment > REAL_PAGE_SIZE) {
188+
zend_mm_munmap((char*)ptr + size, alignment - REAL_PAGE_SIZE);
189+
}
190+
return ptr;
191+
}
192+
}
193+
```
194+
这个过程中用到了一个宏:
195+
```c
196+
#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \
197+
(((size_t)(size)) & ((alignment) - 1))
198+
```
199+
这个宏的作用是计算按alignment对齐的内存地址距离上一个alignment整数倍内存地址的大小,alignment必须为2的n次方,比如一段n*alignment大小的内存,ptr为其中一个位置,那么就可以通过位运算计算得到ptr所属内存块的offset:
200+
201+
![](../img/align.png)
202+
203+
这个位运算是因为alignment为2^n,所以可以通过alignment取到最低位的位置,也就是相对上一个整数倍alignment的offset,实际如果不用运算的话可以通过:`offset = (ptr/alignment取整)*alignment - ptr`得到,这个更容易理解些。
155204
156205
#### 5.1.3.2 Large分配
157206
大于3/4的page_size(4KB)且小于等于511个page_size的内存申请,也就是一个chunk的大小够用(之所以是511个page而不是512个是因为第一个page始终被chunk结构占用),__如果申请多个page的话 分配的时候这些page都是连续的__ 。
@@ -352,14 +401,17 @@ ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_OR
352401
353402
static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
354403
{
355-
size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); //根据内存地址及对齐值判断内存地址偏移量是否为0,是的话只有huge情况符合,page、slot分配出的内存地址偏移量一定是>=ZEND_MM_CHUNK_SIZE的,因为第一页始终被chunk自身结构占用,不可能分配出去
404+
//根据内存地址及对齐值判断内存地址偏移量是否为0,是的话只有huge情况符合,page、slot分配出的内存地>址偏移量一定是>=ZEND_MM_CHUNK_SIZE的,因为第一页始终被chunk自身结构占用,不可能分配出去
405+
//offset就是ptr距离当前chunk起始位置的偏移量
406+
size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE);
356407
357408
if (UNEXPECTED(page_offset == 0)) {
358409
if (ptr != NULL) {
359410
//释放huge内存,从huge_list中删除
360411
zend_mm_free_huge(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
361412
}
362413
} else { //page或slot,根据chunk->map[]值判断当前page的分配类型
414+
//根据ptr获取chunk的起始位置
363415
zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE);
364416
int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE);
365417
zend_mm_page_info info = chunk->map[page_num];
@@ -377,6 +429,8 @@ static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr Z
377429
}
378430
}
379431
```
432+
释放的内存地址可能是chunk中间的任意位置,因为chunk分配时是按照ZEND_MM_CHUNK_SIZE对齐的,也就是chunk的起始内存地址一定是ZEND_MM_CHUNK_SIZE的整数倍,所以可以根据chunk上的任意位置知道chunk的起始位置。
433+
380434
释放page的过程有一个地方值得注意,如果释放后发现当前chunk所有page都已经被释放则可能会释放所在chunk,还记得heap->cached_chunks吗?内存池会维持一定的chunk数,每次释放并不会直接销毁而是加入到cached_chunks中,这样下次申请chunk时直接就用了,同时为了防止占用过多内存,cached_chunks会根据每次request请求计算的chunk使用均值保证其维持在一定范围内。
381435

382436
每次request请求结束会对内存池进行一次清理,检查cache的chunk数是否超过均值,超过的话就进行清理,具体的操作:`zend_mm_shutdown`,这里不再展开。

img/align.png

5.87 KB
Loading

img/chunk_alloc.png

3.72 KB
Loading

0 commit comments

Comments
 (0)