PHP源码阅读笔记三十五:PHP中的SESSION实现之多种存储方式

PHP源码阅读笔记三十五:PHP中的SESSION实现之多种存储方式
源码版本:php5.3.1
环境:VS2008

在php.ini中,可以看到配置项session.save_handler = files
默认情况下,php.ini 中设置的 SESSION 保存方式是 files(session.save_handler = files),即使用读写文件的方式保存 SESSION 数据,而 SESSION 文件保存的目录由 session.save_path 指定,文件名以 sess_ 为前缀,后跟 SESSION ID,如:sess_sf26st2tfamqbarvnkfo2aftf2。文件中的数据即是序列化之后的 SESSION 数据了。如果访问量大,可能产生的 SESSION 文件会比较多,这时可以设置分级目录进行 SESSION 文件的保存,效率会提高很多,设置方法为:session.save_path=”N;/save_path”,N 为分级的级数,save_path 为开始目录。当写入 SESSION 数据的时候,PHP 会获取到客户端的 SESSION_ID,然后根据这个 SESSION ID 到指定的 SESSION 文件保存目录中找到相应的 SESSION 文件,不存在则创建之,最后将数据序列化之后写入文件。读取 SESSION 数据是也是类似的操作流程,对读出来的数据需要进行解序列化,生成相应的 SESSION 变量。如果需要对session文件设置mode,设置方法为:session.save_path = “N;MODE;/path”

同样我们可以使用session_set_save_handler函数设置session的处理方法。
bool session_set_save_handler ( callback $open , callback $close , callback $read , callback $write , callback $destroy , callback $gc )
那么此时可能需要思考一些问题,对于save_handler的设置是如何实现的?在内部存在怎样的结构存储这些处理方法?不同的存储方式之间是如何替换的?如果以一个扩展的方法添加,则需要实现哪些方法?这些方法在哪里添加到系统中的?

【结构】
/ext/session/php_session.h文件

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#define PS_OPEN_ARGS void **mod_data, const char *save_path, const char *session_name TSRMLS_DC
#define PS_CLOSE_ARGS void **mod_data TSRMLS_DC
#define PS_READ_ARGS void **mod_data, const char *key, char **val, int *vallen TSRMLS_DC
#define PS_WRITE_ARGS void **mod_data, const char *key, const char *val, const int vallen TSRMLS_DC
#define PS_DESTROY_ARGS void **mod_data, const char *key TSRMLS_DC
#define PS_GC_ARGS void **mod_data, int maxlifetime, int *nrdels TSRMLS_DC
#define PS_CREATE_SID_ARGS void **mod_data, int *newlen TSRMLS_DC
 
/* default create id function */
PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS);
 
typedef struct ps_module_struct {
	const char *s_name;	// session存储方式的名字 如默认的files
	int (*s_open)(PS_OPEN_ARGS);
	int (*s_close)(PS_CLOSE_ARGS);
	int (*s_read)(PS_READ_ARGS);
	int (*s_write)(PS_WRITE_ARGS);
	int (*s_destroy)(PS_DESTROY_ARGS);
	int (*s_gc)(PS_GC_ARGS);
	char *(*s_create_sid)(PS_CREATE_SID_ARGS);
} ps_module;

第43到52行 定义ps_module,用于存放整个php_session结构,通过方法名应该可以很容易的识别出其作用意图。
【以扩展方式支持session】
如果一个扩展模块需要支持session,则在其PHP_MINIT_FUNCTION方法中调用php_session_register_module方法。
如在/ext/sqlite/sqlite.c 1395行,其调用了php_session_register_module(ps_sqlite_ptr);,说明在此扩展模块初始化时已经将其作为一个session的存储方式加载到内存中了。并且在此扩展中有sess_sqlite.c文件专门处理关于session中的应该定义的各方法,其分别以宏PS_OPEN_FUNC、PS_CLOSE_FUNC、PS_READ_FUNC、PS_WRITE_FUNC、PS_DESTROY_FUNC、PS_GC_FUNC、PS_CREATE_SID_FUNC等给出。这些宏的定义如下:

57
58
59
60
61
62
63
#define PS_OPEN_FUNC(x) 	int ps_open_##x(PS_OPEN_ARGS)
#define PS_CLOSE_FUNC(x) 	int ps_close_##x(PS_CLOSE_ARGS)
#define PS_READ_FUNC(x) 	int ps_read_##x(PS_READ_ARGS)
#define PS_WRITE_FUNC(x) 	int ps_write_##x(PS_WRITE_ARGS)
#define PS_DESTROY_FUNC(x) 	int ps_delete_##x(PS_DESTROY_ARGS)
#define PS_GC_FUNC(x) 		int ps_gc_##x(PS_GC_ARGS)
#define PS_CREATE_SID_FUNC(x)	char *ps_create_sid_##x(PS_CREATE_SID_ARGS)

php_session_register_module方法的实现在/ext/session/session.c 1021行开始。如下:

1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
#define MAX_MODULES 10
#define PREDEFINED_MODULES 2
 
static ps_module *ps_modules[MAX_MODULES + 1] = {
	ps_files_ptr,
	ps_user_ptr
};
 
PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */
{
	int ret = -1;
	int i;
 
	for (i = 0; i < MAX_MODULES; i++) {
		if (!ps_modules[i]) {
			ps_modules[i] = ptr;
			ret = 0;
			break;
		}
	}
	return ret;
}
/* }}} */

从上面的代码我们可能看到:PHP中的存储方式只能有10种,如果需要更多的方式,则需要修改MAX_MODULES的值,重新编译PHP。
存储方式的添加顺序与对应的扩展模块加载顺序有关。

【关于session_set_save_handler】
此函数用于将session的存储方式设置为用户自定义的函数,此函数的各参数对应ps_module结构的各个部分,并且会是新session.save_handler配置为user
在将用户定义的函数设置为session的存储方式时会提前判断这些用户函数是否可用。如果存在一个函数不可用,则警告并退出,表示设置失败。

【关于各方法的调用】
对于各存储方式定义的函数以类似于PS(mod)->s_open的方式调用。
对于用户自定义的方法,通过PS(mod_user_names)保存函数名,在mod_user.c中以类似于retval = ps_call_handler(PSF(open), 2, args TSRMLS_CC);的方式调用。其中#define PSF(a) PS(mod_user_names).name.ps_##a

到此处理我们可以回答之前的问题。
1、save_handler的设置是如何实现的?
在php.ini中设置session.save_handler,在请求初始化时(PHP_RINIT_FUNCTION)查找对应的ps_module,将其赋值到全局变量PS(mod),后续都是通过PS(mod)来处理session的存储操作,如果用户使用了session_set_save_handler方法设置存储方式,则此时的存储方式为user,通过user的模块方法中转调用用户指定的方法
2、在内部存在怎样的结构存储这些处理方法?
此问题可以参见上面关于结构的说明,ps_module
3、不同的存储方式之间是如何替换的?
可以在php.ini中设置session.save_handler修改,或者使用session_set_save_handler使用用户自定义的函数
4、如果以一个扩展的方法添加,则需要实现哪些方法?
需要实现PS_OPEN_FUNC、PS_CLOSE_FUNC、PS_READ_FUNC、PS_WRITE_FUNC、PS_DESTROY_FUNC、PS_GC_FUNC、PS_CREATE_SID_FUNC等宏,这些分别对应ps_module的各个方法
5、这些方法在哪里添加到系统中的?
通过php_session_register_module函数在模块初始化时加载

PHP源码阅读笔记三十四:PHP5.3新增加的垃圾回收机制(Garbage Collection)

PHP源码阅读笔记三十四:PHP5.3新增加的垃圾回收机制(Garbage Collection)
在之前的文章 PHP源码阅读笔记三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础 中有介绍了垃圾回收机制的一些基础知识。今天我们看看其初始化,添加到垃圾缓冲区和垃圾回收的过程。
官方说明文档请猛击Garbage Collection
中文版地址:http://docs.php.net/manual/zh/features.gc.php
【初始化】
在zend/zend_gc.c 121行有函数gc_init实现了gc的初始化,其代码如下:

121
122
123
124
125
126
127
128
ZEND_API void gc_init(TSRMLS_D)
{
	if (GC_G(buf) == NULL && GC_G(gc_enabled)) {
		GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);
		GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
		gc_reset(TSRMLS_C);
	}
}

第123行判断是否为空和是否开启了gc,如果都为真,则转124行
第124行是直接调用malloc分配了10000个gc_root_buffer内存。
第125行将全局变量last_unused设置为gc缓冲区的结束位置。
第126行重置整个垃圾收集机制,其代码从zend/zend_gc.c 88行开始,如下:

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
ZEND_API void gc_reset(TSRMLS_D)
{
	GC_G(gc_runs) = 0;
	GC_G(collected) = 0;
 
#if GC_BENCH
	GC_G(root_buf_length) = 0;
	GC_G(root_buf_peak) = 0;
	GC_G(zval_possible_root) = 0;
	GC_G(zobj_possible_root) = 0;
	GC_G(zval_buffered) = 0;
	GC_G(zobj_buffered) = 0;
	GC_G(zval_remove_from_buffer) = 0;
	GC_G(zobj_remove_from_buffer) = 0;
	GC_G(zval_marked_grey) = 0;
	GC_G(zobj_marked_grey) = 0;
#endif
 
	GC_G(roots).next = &GC_G(roots);
	GC_G(roots).prev = &GC_G(roots);
 
	if (GC_G(buf)) {
		GC_G(unused) = NULL;
		GC_G(first_unused) = GC_G(buf);
 
		GC_G(zval_to_free) = NULL;
	} else {
		GC_G(unused) = NULL;
		GC_G(first_unused) = NULL;
		GC_G(last_unused) = NULL;
	}
}

第90~91行 设置gc运行的次数统计(gc_runs)和gc中垃圾的个数(collected)为0。
第106~107行 设置双向链表头结点的上一个结点和下一个结点指向自己。

关于gc_enabled,默认情况下是开启的,可以在php.ini中配置。
其实现代码在zend/zend.c 93行 如下:

93
STD_ZEND_INI_BOOLEAN("zend.enable_gc",	"1",	ZEND_INI_ALL,	OnUpdateGCEnabled,   gc_enabled, zend_gc_globals,        gc_globals)

初始化调用在zend/zend.c 79 行

93
94
95
96
97
98
99
100
101
102
static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */
{
	OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
 
	if (GC_G(gc_enabled)) {
		gc_init(TSRMLS_C);
	}
 
	return SUCCESS;
}

【添加到垃圾缓冲区】
跟踪PHP的源码 zend/zend_execute_API.c 424行
[_zval_ptr_dtor] -> [GC_ZVAL_CHECK_POSSIBLE_ROOT()] -> [gc_zval_check_possible_root()] -> [gc_zval_possible_root()]
其中在gc_zval_check_possible_root()函数中,仅对数组和对象执行垃圾回收操作

gc_zval_possible_root函数的代码如下:

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC)
{
	if (UNEXPECTED(GC_G(free_list) != NULL &&
	               GC_ZVAL_ADDRESS(zv) != NULL &&
		           GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
		           (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
		            GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
		/* The given zval is a garbage that is going to be deleted by
		 * currently running GC */
		return;
	}
 
	if (zv->type == IS_OBJECT) {
		GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
		return;
	}
 
	GC_BENCH_INC(zval_possible_root);
 
	if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
		GC_ZVAL_SET_PURPLE(zv);
 
		if (!GC_ZVAL_ADDRESS(zv)) {
			gc_root_buffer *newRoot = GC_G(unused);
 
			if (newRoot) {
				GC_G(unused) = newRoot->prev;
			} else if (GC_G(first_unused) != GC_G(last_unused)) {
				newRoot = GC_G(first_unused);
				GC_G(first_unused)++;
			} else {
				if (!GC_G(gc_enabled)) {
					GC_ZVAL_SET_BLACK(zv);
					return;
				}
				zv->refcount__gc++;
				gc_collect_cycles(TSRMLS_C);
				zv->refcount__gc--;
				newRoot = GC_G(unused);
				if (!newRoot) {
					return;
				}
				GC_ZVAL_SET_PURPLE(zv);
				GC_G(unused) = newRoot->prev;
			}
 
			newRoot->next = GC_G(roots).next;
			newRoot->prev = &GC_G(roots);
			GC_G(roots).next->prev = newRoot;
			GC_G(roots).next = newRoot;
 
			GC_ZVAL_SET_ADDRESS(zv, newRoot);
 
			newRoot->handle = 0;
			newRoot->u.pz = zv;
 
			GC_BENCH_INC(zval_buffered);
			GC_BENCH_INC(root_buf_length);
			GC_BENCH_PEAK(root_buf_peak, root_buf_length);
		}
	}
}

第132~140行 检查zval结点信息是否已经放入到结点缓冲区,如果已经放入到结点缓冲区,则直接返回,这样可以优化其性能

第142~145行 处理对象结点,直接返回,不再执行后面的操作

第149行 判断结点是否已经被标记为紫色,如果为紫色则不再添加到结点缓冲区,此处在于保证一个结点只执行一次添加到缓冲区的操作。

第150行 将结点的颜色标记为紫色,表示此结点已经添加到缓冲区,下次不用再做添加

第153~157行 找出新的结点的位置,如果缓冲区满了,则执行垃圾回收操作。

第176~184行 将新的结点添加到缓冲区所在的双向链表。

【垃圾回收过程】
在gc_zval_possible_root函数中,当缓冲区满时,程序调用gc_collect_cycles函数,执行垃圾回收操作。从zend/zend_gc.c文件615行开始,其实现代码如下:

615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
ZEND_API int gc_collect_cycles(TSRMLS_D)
{
	int count = 0;
 
	if (GC_G(roots).next != &GC_G(roots)) {
		zval_gc_info *p, *q, *orig_free_list, *orig_next_to_free;
 
		if (GC_G(gc_active)) {
			return 0;
		}
		GC_G(gc_runs)++;
		GC_G(zval_to_free) = FREE_LIST_END;
		GC_G(gc_active) = 1;
		gc_mark_roots(TSRMLS_C);
		gc_scan_roots(TSRMLS_C);
		gc_collect_roots(TSRMLS_C);
 
		orig_free_list = GC_G(free_list);
		orig_next_to_free = GC_G(next_to_free);
		p = GC_G(free_list) = GC_G(zval_to_free);
		GC_G(zval_to_free) = NULL;
		GC_G(gc_active) = 0;
 
		/* First call destructors */
		while (p != FREE_LIST_END) {
			if (Z_TYPE(p->z) == IS_OBJECT) {
				if (EG(objects_store).object_buckets &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0 &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor &&
					!EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called) {
 
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called = 1;
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount++;
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor(EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.object, Z_OBJ_HANDLE(p->z) TSRMLS_CC);
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount--;
				}
			}
			count++;
			p = p->u.next;
		}
 
		/* Destroy zvals */
		p = GC_G(free_list);
		while (p != FREE_LIST_END) {
			GC_G(next_to_free) = p->u.next;
			if (Z_TYPE(p->z) == IS_OBJECT) {
				if (EG(objects_store).object_buckets &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0) {
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount = 1;
					Z_TYPE(p->z) = IS_NULL;
					zend_objects_store_del_ref_by_handle_ex(Z_OBJ_HANDLE(p->z), Z_OBJ_HT(p->z) TSRMLS_CC);
				}
			} else if (Z_TYPE(p->z) == IS_ARRAY) {
				Z_TYPE(p->z) = IS_NULL;
				zend_hash_destroy(Z_ARRVAL(p->z));
				FREE_HASHTABLE(Z_ARRVAL(p->z));
			} else {
				zval_dtor(&p->z);
				Z_TYPE(p->z) = IS_NULL;
			}
			p = GC_G(next_to_free);
		}
 
		/* Free zvals */
		p = GC_G(free_list);
		while (p != FREE_LIST_END) {
			q = p->u.next;
			FREE_ZVAL_EX(&p->z);
			p = q;
		}
		GC_G(collected) += count;
		GC_G(free_list) = orig_free_list;
		GC_G(next_to_free) = orig_next_to_free;
	}
 
	return count;
}

第619行 判断缓冲区是否为空,如果为空则不会执行垃圾回收操作
第622行 判断垃圾回收操作是否正则进行,如果正在进行,则直接返回
第625~627行 将垃圾回收操作次数加1,初始化空闲列表,设置gc_active为1表示垃圾回归正在进行
第628行 此处为其官方文档中算法的步骤 B ,算法使用深度优先搜索查找所有可能的根,找到后将每个变量容器中的引用计数减1″,为确保不会对同一个变量容器减两次”1″,用灰色标记已减过1的。
第629行 这是算法的步骤 C ,算法再一次对每个根节点使用深度优先搜索,检查每个变量容器的引用计数。如果引用计数是 0 ,变量容器用白色来标记。如果引用次数大于0,则恢复在这个点上使用深度优先搜索而将引用计数减1的操作(即引用计数加1),然后将它们重新用黑色标记。
第630行 算法的最后一步 D ,算法遍历根缓冲区以从那里删除变量容器根(zval roots),同时,检查是否有在上一步中被白色标记的变量容器。每个被白色标记的变量容器都被清除。
在[gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ]中我们可以看到,对于白色标记的结点会被添加到全局变量zval_to_free列表中。此列表在后面的操作中有用到。
第632~633行 将全局变量free_list和next_to_free存放在相对应当的临时变量中,在最后会恢复到此时的状态。
第634~635行 初始化需要清除的列表,清空将要清空的zval列表并且将垃圾收集的操作状态为不激活状态。
第639~655行 第一次调用析构函数,并统计清除的变量个数
第657~678行 清除变量
第682~686行 释放内存
第687~689行 处理垃圾个数统计,恢复free_list和next_to_free变量

PHP源码阅读笔记三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础

PHP源码阅读笔记三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础
PHP5.3中新增加了垃圾回收机制,据说很先进,据说引诱了我去看看其先进的实现。
官方说明文档请猛击Garbage Collection
中文版地址:http://docs.php.net/manual/zh/features.gc.php
【垃圾回收机制的嵌入方式】
zend_gc.h文件在zend.h的749行被引用:#include “zend_gc.h”
从而替换覆盖了在237行引用的zend_alloc.h文件中的ALLOC_ZVAL等宏
zend/zend_gc.h文件的202行开始

202
203
204
205
206
207
208
/* The following macroses override macroses from zend_alloc.h */
#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z) 							\
	do {								\
		(z) = (zval*)emalloc(sizeof(zval_gc_info));		\
		GC_ZVAL_INIT(z);					\
	} while (0)

ALLOC_ZVAL宏在zend_alloc.h中的定义是分配一个zval结构的内存空间。新的ALLOC_ZVAL宏分配了一个zval_gc_info结构的宏。zval_gc_info的结构如下:
zend/zend_gc.h文件的91行开始:

91
92
93
94
95
96
97
typedef struct _zval_gc_info {
	zval z;
	union {
		gc_root_buffer       *buffered;
		struct _zval_gc_info *next;
	} u;
} zval_gc_info;

zval_gc_info的第一个成员为zval结构,这就确保其和以zval变量分配的内存的开始对齐,从而在zval_gc_info类型指针的强制转换时,其可以作为zval使用。关于gc_root_buffer等将在后面的结构和实现时介绍,它定义的PHP垃圾回收机制的缓存结构。GC_ZVAL_INIT用来初始化替代了zval的zval_gc_info,它会把zval_gc_info中的成员u的buffered字段设置成NULL,此字段仅在将其放入垃圾回收缓冲区时才会有值,否则会一直是NULL。
由于PHP中所有的变量都是以zval变量的形式存在,这里以zval_gc_info替换zval,从而成功实现垃圾收集机制在原有系统中的集成。
这个有点面向对象中多态的感觉。

【垃圾回收机制的存储方式】
结点结构:

81
82
83
84
85
86
87
88
89
typedef struct _gc_root_buffer {
	struct _gc_root_buffer   *prev;		/* double-linked list               */
	struct _gc_root_buffer   *next;
	zend_object_handle        handle;	/* must be 0 for zval               */
	union {
		zval                 *pz;
		zend_object_handlers *handlers;
	} u;
} gc_root_buffer;

很明显(见注释,虽然PHP中的注释很少,但是有些纯粹是纠结的注释),这是一个双向链表。
在联合体中的pz变量很明显就是之前定义的多态的zval_gc_info结构,于是其在链表中的当前结点指针可以通过((zval_gc_info*)(pz))->u.buffered获取,不过在看其源码中有多处使用到这个调用方式,为何不另起一个宏呢?难道是怕宏太多,不是啊,PHP就是以宏多著称,比这个宏嵌套多的宏海了去了。不懂。另外handle等结构是特别针对对象变量的。

缓冲区是话在全局变量中的,和其它模块的全局变量一样,gc也有其自己的全局变量访问宏 GC_G(v),同样对于全局变量访问宏在是否ZTS下有不同的实现。
在zend_gc.h中定义的全局变量如下:

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
typedef struct _zend_gc_globals {
	zend_bool         gc_enabled;	/* 是否开启垃圾收集机制 */
	zend_bool         gc_active;	/* 是否正在进行 */
 
	gc_root_buffer   *buf;				/* 预分配的缓冲区数组,默认为10000(preallocated arrays of buffers)   */
	gc_root_buffer    roots;			/* 列表的根结点(list of possible roots of cycles) */
	gc_root_buffer   *unused;			/* 没有使用过的缓冲区列表(list of unused buffers)           */
	gc_root_buffer   *first_unused;		/* 指向第一个没有使用过的缓冲区结点(pointer to first unused buffer)   */
	gc_root_buffer   *last_unused;		/* 指向最后一个没有使用过的缓冲区结点,此处为标记结束用(pointer to last unused buffer)    */
 
	zval_gc_info     *zval_to_free;		/* 将要释放的zval变量的临时列表(temporaryt list of zvals to free) */
	zval_gc_info     *free_list;		/* 临时变量,需要释放的列表开头 */
	zval_gc_info     *next_to_free;		/* 临时变量,下一个将要释放的变量位置*/
 
	zend_uint gc_runs;	/* gc运行的次数统计 */
	zend_uint collected;    /* gc中垃圾的个数 */
 
	// 省略...

【垃圾回收机制中的颜色标记】

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#define GC_COLOR  0x03
 
#define GC_BLACK  0x00
#define GC_WHITE  0x01
#define GC_GREY   0x02
#define GC_PURPLE 0x03
 
#define GC_ADDRESS(v) \
	((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR))
#define GC_SET_ADDRESS(v, a) \
	(v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)(a))))
#define GC_GET_COLOR(v) \
	(((zend_uintptr_t)(v)) & GC_COLOR)
#define GC_SET_COLOR(v, c) \
	(v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & ~GC_COLOR) | (c)))
#define GC_SET_BLACK(v) \
	(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR))
#define GC_SET_PURPLE(v) \
	(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) | GC_PURPLE))

在PHP的内存管理中我们也有看到类似的以最后位作为某种类型的标记方式。
这里以内存分配的最后两位作为整个结构的颜色标记。其中
白色表示垃圾
紫色表示已放入缓冲区
灰色表示已经进行了一次refcount的减一操作
黑色是默认颜色,正常

【zval定义的改变】
PHP3.0版本 在zend/zend.h文件中,其定义如下:

316
317
318
319
320
321
322
struct _zval_struct {
	/* Variable information */
	zvalue_value value;		/* value */
	zend_uint refcount__gc;
	zend_uchar type;	/* active type */
	zend_uchar is_ref__gc;
};

在php3.0之前的版本,如php5.2.9版本,在zend/zend.h文件中,其定义如下:

307
308
309
310
311
312
313
struct _zval_struct {
	/* Variable information */
	zvalue_value value;		/* value */
	zend_uint refcount;
	zend_uchar type;	/* active type */
	zend_uchar is_ref;
};