概述
这是源码php7系列的第二篇文章,主要介绍变量的机制和内存的管理,我相信学习源码是对代码整体提升的有效手段,话不多说,开始吧!
变量实现
1. 解密zval
zval 底层结构:
struct_zval_struct {
zend_value value; //8个字节
union u1; //4个字节
union u2; //4个字节
}
对于vue来说是一个联合体,zval一共16个字节,u1 4个字节,u2四个字节,value结构体如下:
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
虽然PHP属于弱类型语言,但是在底层实现中还是要区分类型的,因为类型里有天然的长度,类型引势内存的长度。
底层做了很多类型转化的处理,让我们不用关心php的类型和长度,这也是php开发高效的原因之一。
变量知识点:
- value、u1、u2都是联合体,在底层是要区分类型的
- u2里面有个重要的变量next,next会在数组中解决冲突使用
2.写时复制(Copy On Write)
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
zend\_refcounted\_h 作用是string类型的引用计数的结构体,h是字符串对应的hash值,它后面会用到数组里,len代表字符串的长度,char是字符串的值,因为C言语中字符串遇到\0就会自动结束,二进制是不安全的,所以php加上了长度。
$value1 = 'stark';
$value2 = $value1;
$value2 = 'zcc';
php的写时复制是这样发生的,如果把value2,两个变量指向的是同一个物理内存地址,存在硬盘上的某一个块里,也许地址是0x7fff5e01c00,当$value2赋值新的值时,zend\_refcounted\_h引用计数减一,zcc存入新的地址。可以看我之前的文章。
3.字符串的引用类型
struct _zend_reference {
zend_refcounted_h gc;
zval val;
};
可以跟着代码执行一下,看看你心里的预期和实际打印出的值是否一致
$a = 'hello';
$b = &$a;
var_dump($a,$b);
$b = 'stark';
var_dump($a,$b);
unset($b);
var_dump($a,$b);
执行结果:
[root@dd2065d03db8 code]# /usr/local/php7.1.0/bin/php refer.php
string(5) "hello"
string(5) "hello"
string(5) "stark"
string(5) "stark"
string(5) "stark"
NULL
源码中的数组HashTable
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
nTableMask是计算数组的索引值,\*arData存储数组里的key=>value的键值对,nNumUsed表示已经使用的空间,nNumOfElements真正的元素个数,nTableSize是arData的大小,nTableSize默认大小是8字节,内存不够每次扩容都x2,以此类推。
内存管理
在malloc申请内存时声明了size大小,但是回收时没有传size,怎么做到准确释放size大小内存的呢?
void *ptr=malloc(size);
free(ptr);
php7内存接口
void *ptr=_emalloc(size);
_efree(ptr);
1.Small内存的管理
内存的基本概念:chunk、page、各种规格的内存。
- chunk: 2MB 大小的内存
- page :4KB大小的内存
#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 */
内存规格
- 内存预分配:使用mmap分配chunk
内存分类:
- 1.Small(30种规格) (size <= 3KB)
- 2.Large (3KB < size <= 2MB-4KB)
- 3.Huge(size > 2MB-4KB)
2. Chunk的内存对齐
关于chunk对齐的算法
/**********/
/* Chunks */
/**********/
static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment)
{
void *ptr = zend_mm_mmap(size);
if (ptr == NULL) {
return NULL;
} else if (ZEND_MM_ALIGNED_OFFSET(ptr, alignment) == 0) {
#ifdef MADV_HUGEPAGE
madvise(ptr, size, MADV_HUGEPAGE);
#endif
return ptr;
} else {
size_t offset;
/* chunk has to be aligned */
zend_mm_munmap(ptr, size);
ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE);
#ifdef _WIN32
offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment);
zend_mm_munmap(ptr, size + alignment - REAL_PAGE_SIZE);
ptr = zend_mm_mmap_fixed((void*)((char*)ptr + (alignment - offset)), size);
offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment);
if (offset != 0) {
zend_mm_munmap(ptr, size);
return NULL;
}
return ptr;
#else
offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment);
if (offset != 0) {
offset = alignment - offset;
zend_mm_munmap(ptr, offset);
ptr = (char*)ptr + offset;
alignment -= offset;
}
if (alignment > REAL_PAGE_SIZE) {
zend_mm_munmap((char*)ptr + size, alignment - REAL_PAGE_SIZE);
}
# ifdef MADV_HUGEPAGE
madvise(ptr, size, MADV_HUGEPAGE);
# endif
#endif
return ptr;
}
}