月度归档:2010年11月

PHP源码阅读笔记三十一:PHP内存池中的堆(heap)层基础

PHP源码阅读笔记三十一:PHP内存池中的堆(heap)层基础

【概述】
PHP的内存管理器是分层(hierarchical)的。这个管理器共有三层:存储层(storage)、堆(heap)层和 emalloc/efree 层。在PHP源码阅读笔记三十:PHP内存池中的存储层中介绍了存储层,存储层通过 malloc()、mmap() 等函数向系统真正的申请内存,并通过 free() 函数释放所申请的内存。存储层通常申请的内存块都比较大,这里申请的内存大并不是指storage层结构所需要的内存大,只是堆层通过调用存储层的分配方法时,其以段的格式申请的内存比较大,存储层的作用是将内存分配的方式对堆层透明化。
在存储层之上就是今天我们要了解的堆层。堆层一个调度层,它与上面的emalloc/efree层交互,将通过存储层申请到的大块内存,进行拆分,按需提供。在堆层中有其一套内存的调度策略,这个整个PHP内存分配管理的核心区域。

以下的所有分享都是基于ZEND_DEBUG未打开的情况。
首先看下堆层所涉及到的结构:
【结构】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 
/* 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_CACHE
	unsigned int        cached;	/* 已缓存大小 */
	zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];	/* 缓存数组/
#endif
	zend_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()]

2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
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的访存次数;第二个就是还要保持存储空间的效率足够高。

1
2
3
4
5
6
7
8
# 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_SIZE			ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_block))
#define ZEND_MM_ALIGNED_FREE_HEADER_SIZE	ZEND_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)

如下所示代码为块的定位

1
2
3
4
5
6
#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_MASK		ZEND_MM_LONG_CONST(0x3)

当前块的下一个元素,即为当前块的头位置加上整个块(去掉了类型的长度)的长度。
当前块的上一个元素,即为当前块的头位置减去前一个块(去掉了类型的长度)的长度。
关于前一个块的长度,在块的初始化时设置为当前块的大小与块类型的或操作的结果。

PHP源码阅读笔记三十:PHP内存池中的存储层

PHP源码阅读笔记三十:PHP内存池中的存储层

【概述】
PHP的内存管理器是分层(hierarchical)的。这个管理器共有三层:存储层(storage)、堆(heap)层和 emalloc/efree 层。存储层通过 malloc()、mmap() 等函数向系统真正的申请内存,并通过 free() 函数释放所申请的内存。存储层通常申请的内存块都比较大,这里申请的内存大并不是指storage层结构所需要的内存大,只是堆层通过调用存储层的分配方法时,其以段的格式申请的内存比较大,存储层的作用是将内存分配的方式对堆层透明化。

首先看storage层的结构:
【结构】

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/* Heaps with user defined storage */
typedef struct _zend_mm_storage zend_mm_storage;
 
typedef struct _zend_mm_segment {
    size_t    size;
    struct _zend_mm_segment *next_segment;
} zend_mm_segment;
 
typedef struct _zend_mm_mem_handlers {
    const char *name;
    zend_mm_storage* (*init)(void *params);    //    初始化函数
    void (*dtor)(zend_mm_storage *storage);    //    析构函数
    void (*compact)(zend_mm_storage *storage);
    zend_mm_segment* (*_alloc)(zend_mm_storage *storage, size_t size);    //    内存分配函数
    zend_mm_segment* (*_realloc)(zend_mm_storage *storage, zend_mm_segment *ptr, size_t size);    //    重新分配内存函数
    void (*_free)(zend_mm_storage *storage, zend_mm_segment *ptr);    //    释放内存函数
} zend_mm_mem_handlers;
 
struct _zend_mm_storage {
    const zend_mm_mem_handlers *handlers;    //    处理函数集
    void *data;
};

内存的分配方式,调用的函数是_zend_mm_storage结构中的处理函数集,而内存是以段的形式表现的。
【4种内存方案】
PHP在存储层共有4种内存分配方案: malloc,win32,mmap_anon,mmap_zero默认使用malloc分配内存,如果设置了ZEND_WIN32宏,则为windows版本,调用HeapAlloc分配内存,剩下两种内存方案为匿名内存映射,并且PHP的内存方案可以通过设置变量来修改。
官方说明如下:
The Zend MM can be tweaked using ZEND_MM_MEM_TYPE and ZEND_MM_SEG_SIZE environment
variables. Default values are “malloc” and “256K”. Dependent on target system you
can also use “mmap_anon”, “mmap_zero” and “win32″ storage managers.

在代码中,对于这4种内存分配方案,分别对应实现了zend_mm_mem_handlers中的各个处理函数。配合代码的简单说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 使用mmap内存映射函数分配内存 写入时拷贝的私有映射,并且匿名映射,映射区不与任何文件关联。*/
# define ZEND_MM_MEM_MMAP_ANON_DSC {"mmap_anon", zend_mm_mem_dummy_init, zend_mm_mem_dummy_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_mmap_anon_alloc, zend_mm_mem_mmap_realloc, zend_mm_mem_mmap_free}

/* 使用mmap内存映射函数分配内存 写入时拷贝的私有映射,并且映射到/dev/zero。*/
# define ZEND_MM_MEM_MMAP_ZERO_DSC {"mmap_zero", zend_mm_mem_mmap_zero_init, zend_mm_mem_mmap_zero_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_mmap_zero_alloc, zend_mm_mem_mmap_realloc, zend_mm_mem_mmap_free}

/* 使用HeapAlloc分配内存 windows版本 关于这点,注释中写的是VirtualAlloc() to allocate memory,实际在程序中使用的是HeapAlloc*/
# define ZEND_MM_MEM_WIN32_DSC {"win32", zend_mm_mem_win32_init, zend_mm_mem_win32_dtor, zend_mm_mem_win32_compact, zend_mm_mem_win32_alloc, zend_mm_mem_win32_realloc, zend_mm_mem_win32_free}

/* 使用malloc分配内存 默认为此种分配 如果有加ZEND_WIN32宏,则使用win32的分配方案*/
# define ZEND_MM_MEM_MALLOC_DSC {"malloc", zend_mm_mem_dummy_init, zend_mm_mem_dummy_dtor, zend_mm_mem_dummy_compact, zend_mm_mem_malloc_alloc, zend_mm_mem_malloc_realloc, zend_mm_mem_malloc_free}

static const zend_mm_mem_handlers mem_handlers[] = {
#ifdef HAVE_MEM_WIN32
    ZEND_MM_MEM_WIN32_DSC,
#endif
#ifdef HAVE_MEM_MALLOC
    ZEND_MM_MEM_MALLOC_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ANON
    ZEND_MM_MEM_MMAP_ANON_DSC,
#endif
#ifdef HAVE_MEM_MMAP_ZERO
    ZEND_MM_MEM_MMAP_ZERO_DSC,
#endif
    {NULL, NULL, NULL, NULL, NULL, NULL}
};

【关于匿名内存映射的优点】
mmem_zero方案:
(SVR 4 ) /dev/zero Memory Mapping
1. 可以将伪设备 “/dev/zero” 作为参数传递给 mmap 而创建一个映射区。/dev/zero 的特殊在于,对于该设备文件所有的读操作都返回值为 0 的指定长度的字节流 ,任何写入的内容都被丢弃。我们的兴趣在于用它来创建映射区,用 /dev/zero 创建的映射区,其内容被初始为 0 。
2. 使用 /dev/zero 的优点在于,mmap创建映射区时,不需要一个时间存在的文件,伪文件 /dev/zero 就足够了。缺点是只能用在相关进程间。相对于相关进程间的通信,使用线程间通信效率要更高一些。不管使用那种技术,对共享数据的访问都需要进行同步。

mmem_anon方案:
(4.4 BSD) Anonymous Memory Mapping
1. 匿名内存映射 与 使用 /dev/zero 类型,都不需要真实的文件。要使用匿名映射之需要向 mmap 传入 MAP_ANON 标志,并且 fd 参数 置为 -1 。
2. 所谓匿名,指的是映射区并没有通过 fd 与 文件路径名相关联。匿名内存映射用在有血缘关系的进程间。

【win32方案中堆内存分配的声明】
windows API
函数HeapAlloc声明如下:

3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
WINBASEAPI
__out_opt
HANDLE
WINAPI
HeapCreate(
	__in DWORD flOptions,
	__in SIZE_T dwInitialSize,
	__in SIZE_T dwMaximumSize
	);
 
WINBASEAPI
BOOL
WINAPI
HeapDestroy(
	__in HANDLE hHeap
	);
 
WINBASEAPI
__bcount(dwBytes)
LPVOID
WINAPI
HeapAlloc(
	__in HANDLE hHeap,
	__in DWORD dwFlags,
	__in SIZE_T dwBytes
	);
 
 
WINBASEAPI
BOOL
WINAPI
HeapFree(
	__inout HANDLE hHeap,
	__in	DWORD dwFlags,
	__deref LPVOID lpMem
	);
 
WINBASEAPI
SIZE_T
WINAPI
HeapSize(
	__in HANDLE hHeap,
	__in DWORD dwFlags,
	__in LPCVOID lpMem
	);

hHeap是进程堆内存开始位置。
dwFlags是分配堆内存的标志。
dwBytes是分配堆内存的大小。

【初始化】
在zend_mm_startup启动时,程序会根据配置设置内存分配方案和段分配大小,如下所示代码:

1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
ZEND_API zend_mm_heap *zend_mm_startup(void)
{
    int i;
    size_t seg_size;
    char *mem_type = getenv("ZEND_MM_MEM_TYPE");
    char *tmp;
    const zend_mm_mem_handlers *handlers;
    zend_mm_heap *heap;
 
    if (mem_type == NULL) {
   	 i = 0;
    } else {
   	 for (i = 0; mem_handlers[i].name; i++) {
   		 if (strcmp(mem_handlers[i].name, mem_type) == 0) {
   			 break;
   		 }
   	 }
   	 if (!mem_handlers[i].name) {
   		 fprintf(stderr, "Wrong or unsupported zend_mm storage type '%s'\n", mem_type);
   		 fprintf(stderr, "  supported types:\n");
   		 for (i = 0; mem_handlers[i].name; i++) {
   			 fprintf(stderr, "	'%s'\n", mem_handlers[i].name);
   		 }
   		 exit(255);
   	 }
    }
    handlers = &mem_handlers[i];
 
    tmp = getenv("ZEND_MM_SEG_SIZE");
    if (tmp) {
   	 seg_size = zend_atoi(tmp, 0);
   	 if (zend_mm_low_bit(seg_size) != zend_mm_high_bit(seg_size)) {
   		 fprintf(stderr, "ZEND_MM_SEG_SIZE must be a power of two\n");
   		 exit(255);
   	 } else if (seg_size < ZEND_MM_ALIGNED_SEGMENT_SIZE + ZEND_MM_ALIGNED_HEADER_SIZE) {
   		 fprintf(stderr, "ZEND_MM_SEG_SIZE is too small\n");
   		 exit(255);
   	 }
    } else {
   	 seg_size = ZEND_MM_SEG_SIZE;
    }
 
    //....代码省略
}

第1121~1138行 遍历整个mem_handlers数组,确认内存分配方案,如果没有设置ZEND_MM_MEM_TYPE变量,默认使用malloc方案,如果是windows(即ZEND_WIN32),则默认使用win32方案,如果设置了ZEND_MM_MEM_TYPE变量,则采用设置的方案。

第1140~1152行 确认段分配大小,如果设置了ZEND_MM_SEG_SIZE变量,则使用设置的大小,此处会判断所设置的大小是否满足2的倍数,并且大于或等于ZEND_MM_ALIGNED_SEGMENT_SIZE + ZEND_MM_ALIGNED_HEADER_SIZE;如果没有设置没使用默认的ZEND_MM_SEG_SIZE

【参考资料】

http://blog.chinaunix.net/u1/38994/showart_2061729.html

【附录】
功能描述:
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime
在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。

用法:
#include
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *start, size_t length);

参数:
start:映射区的开始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE //这个标志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE //兼容标志,被忽略。
MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
offset:被映射对象内容的起点。

返回说明:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区

PHP源码阅读笔记二十九:接口的继承

PHP源码阅读笔记二十九:接口的继承
在之前有看过PHP源码中类的继承,今天我们看下PHP中的接口继承是如何实现的。
同样我们从CachingIterator类开始查找接口的继承实现。
CachingIterator extends IteratorIterator implements OuterIterator , Traversable , Iterator , ArrayAccess , Countable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* ArrayAccess接口的继承实现 */
REGISTER_SPL_IMPLEMENTS(CachingIterator, ArrayAccess);
 
//    ext/spl/spl_functions.h 41行
#define REGISTER_SPL_IMPLEMENTS(class_name, interface_name) \
	zend_class_implements(spl_ce_ ## class_name TSRMLS_CC, 1, spl_ce_ ## interface_name);
 
//	zend/zend_API.c 2218行
ZEND_API void zend_class_implements(zend_class_entry *class_entry TSRMLS_DC, int num_interfaces, ...) /* {{{ */
{
	zend_class_entry *interface_entry;
	va_list interface_list;
	va_start(interface_list, num_interfaces);
 
	while (num_interfaces--) {
		interface_entry = va_arg(interface_list, zend_class_entry *);
		zend_do_implement_interface(class_entry, interface_entry TSRMLS_CC);
	}
 
	va_end(interface_list);
}
/* }}} */
 
 
 
//	zend/zend_complie.c 2887行
ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC) /* {{{ */
{
	zend_uint i, ignore = 0;
	zend_uint current_iface_num = ce->num_interfaces;
	zend_uint parent_iface_num  = ce->parent ? ce->parent->num_interfaces : 0;
 
	for (i = 0; i < ce->num_interfaces; i++) {
		if (ce->interfaces[i] == NULL) {
			memmove(ce->interfaces + i, ce->interfaces + i + 1, sizeof(zend_class_entry*) * (--ce->num_interfaces - i));
			i--;
		} else if (ce->interfaces[i] == iface) {	/* 已存在此接口 */
			if (i < parent_iface_num) {
				ignore = 1;
			} else {
				zend_error(E_COMPILE_ERROR, "Class %s cannot implement previously implemented interface %s", ce->name, iface->name);
			}
		}
	}
	if (!ignore) {
		if (ce->num_interfaces >= current_iface_num) {
			if (ce->type == ZEND_INTERNAL_CLASS) {
				ce->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num));
			} else {
				ce->interfaces = (zend_class_entry **) erealloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num));
			}
		}
		ce->interfaces[ce->num_interfaces++] = iface;	//	接口数加1,将接口加入接口列表中
 
		/* 合并接口中的常量列表和方法列表 */
		zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref, sizeof(zval *), (merge_checker_func_t) do_inherit_constant_check, iface);
		zend_hash_merge_ex(&ce->function_table, &iface->function_table, (copy_ctor_func_t) do_inherit_method, sizeof(zend_function), (merge_checker_func_t) do_inherit_method_check, ce);
 
		do_implement_interface(ce, iface TSRMLS_CC);
		zend_do_inherit_interfaces(ce, iface TSRMLS_CC);
	}
}

从zend_do_implement_interface函数的合并接口中的常量列表和方法列表操作,我们可以猜想这也许是接口中不能有变量可以有常量的原因之一吧
在接口继承的过程中有对当前类的接口中是否存在同样接口的判断操作,如果已经存在了同样的接口,则此接口将不会继承。
如下所示php代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
interface FooInterface {
	public function method1();
}
 
class Base implements FooInterface {
	public function method1() {
		echo 'ss';
	}
}
 
class Foo extends Base implements FooInterface {
 
}
 
$foo = new Foo();
$foo->method1();