PHP 扩展开发的文章,我均已更新至《TIPI》(下面的博文可能已经过时,以 TIPI 上的内容为准)。
1. 注册资源类型
1.0 添加的资源释放函数static void zmk_file_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}
注意到传入的参数类型为zend_rsrc_list_entry *,也就是说资源的结构体为zend_rsrc_list_entry。其定义为typedef struct _zend_rsrc_list_entry {
void *ptr;
int type;
int refcount;
} zend_rsrc_list_entry;
文件位置:Zend/zend_list.h
1.1 注册类型
然后我们在PHP_MINIT_FUNCTION()里执行了le_zmk_file = zend_register_list_destructors_ex(zmk_file_dtor, NULL, RESOURCE_TYPE_ZMK_FILE, module_number);
资源类型注册函数ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
{
zend_rsrc_list_dtors_entry lde;
lde.list_dtor = NULL;/* old style destructors 这里直接设置 NULL 表示不再使用了*/
lde.plist_dtor = NULL;/* old style destructors */
lde.list_dtor_ex = ld;/* new style destructors */
lde.plist_dtor_ex = pld;/* new style destructors */
lde.module_number = module_number;
lde.resource_id = list_destructors.nNextFreeElement;
lde.type = ZEND_RESOURCE_LIST_TYPE_EX;
lde.type_name = type_name;
if (zend_hash_next_index_insert(&list_destructors, (void *) &lde, sizeof(zend_rsrc_list_dtors_entry), NULL)==FAILURE) {
return FAILURE;
}
return list_destructors.nNextFreeElement-1;
}
list_destructors是一个全局静态HashTable,资源类型注册时,实际是将一个zend_rsrc_list_dtors_entry存放入list_destructors的arBuckets中,该结构体中包含的该种资源释放函数指针、持久资源的释放函数指针,资源类型名称,该资源在 hashtable 中的索引依据 (resource_id)等。
而这里的resource_id则是该函数的返回值,所以后面我们在解析该类型变量时,都需要将resource_id带上。
2. 资源的初始化
2.1 调用 ZEND_REGISTER_RESOURCE 执行注册
然后在file_open里面执行了ZEND_REGISTER_RESOURCE(return_value, fp, le_zmk_file);
fp是打开文件的指针,le_zmk_file就是上面注册资源类型返回的resource_id。
而ZEND_REGISTER_RESOURCE宏实际调用的是下面的函数:ZEND_API int zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type)
{
int rsrc_id;
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);
if (rsrc_result) {
rsrc_result->value.lval = rsrc_id;
rsrc_result->type = IS_RESOURCE;
}
return rsrc_id;
}ZEND_API int zend_list_insert(void *ptr, int type)
{
int index;
zend_rsrc_list_entry le;
TSRMLS_FETCH();
le.ptr=ptr;
le.type=type;
le.refcount=1;
index = zend_hash_next_free_element(&EG(regular_list));
zend_hash_index_update(&EG(regular_list), index, (void *) &le, sizeof(zend_rsrc_list_entry), NULL);
return index;
}
也就是把一个zend_rsrc_list_entry的实体放入了EG(regular_list)表中。
前两步我绘制了一张图来表示
3. 使用资源
3.1 通过 ZEND_FETCH_RESOURCE 解析还原资源本质
例如代码中的ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, filehandle_id, RESOURCE_TYPE_ZMK_FILE, le_zmk_file);
目的是将传入的filehandle转换为原始的fp指针。
源码分析#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type) \
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 1, resource_type); \
ZEND_VERIFY_RESOURCE(rsrc);
我们发现在解析资源的最后还做了资源验证。我们先说资源的解析,下面我简化了一些本次执行不相关的代码,使得逻辑更加清晰简单//zend_fetch_resource(&filehandle TSRMLS_DC, -1, RESOURCE_TYPE_ZMK_FILE, NULL, 1, le_zmk_file);
ZEND_API void *zend_fetch_resource(zval **passed_id TSRMLS_DC, int default_id, char *resource_type_name, int *found_resource_type, int num_resource_types, ...)
{
int id;
int actual_resource_type;
void *resource;
va_list resource_types;
int i;
char *space;
char *class_name;
if (default_id==-1) { /* use id */
id = (*passed_id)->value.lval;
} else {
id = default_id;
}
resource = zend_list_find(id, &actual_resource_type);
va_start(resource_types, num_resource_types);
for (i=0; i
if (actual_resource_type == va_arg(resource_types, int)) {
va_end(resource_types);
if (found_resource_type) {
*found_resource_type = actual_resource_type;
}
return resource;
}
}
va_end(resource_types);
return NULL;
}
该函数是支持可变参数的,所以最后传入了num_resource_types和...,这里只传入了一个resource_type,见函数上方的注释代码,而关于可变参数的原理,可以参考http://mengkang.net/678.htmlZEND_API void *_zend_list_find(int id, int *type TSRMLS_DC)
{
zend_rsrc_list_entry *le;
if (zend_hash_index_find(&EG(regular_list), id, (void **) &le)==SUCCESS) {
*type = le->type;
return le->ptr;
} else {
*type = -1;
return NULL;
}
}
返回了le->ptr也就是原始的File *指针,用图表示(省略部分参数的传递,请配合代码理解)
4. 删除资源
4.1 通过 zend_list_delete 删除资源
从上篇的例子中我们在file_close()中调用了zend_list_delete来完成资源的删除。分析其源码执行的过程#define zend_list_delete(id) _zend_list_delete(id TSRMLS_CC)ZEND_API int _zend_list_delete(int id TSRMLS_DC)
{
zend_rsrc_list_entry *le;
if (zend_hash_index_find(&EG(regular_list), id, (void **) &le)==SUCCESS) {
if (--le->refcount<=0) {
return zend_hash_index_del(&EG(regular_list), id);
} else {
return SUCCESS;
}
} else {
return FAILURE;
}
}
把引用计数(refcount)减一,然后检查refcount是否到0了,如果到0,就真正的从EG(regular_list)列表删除。
那么上文中说到的,最初注册的资源的释放回调函数是如何执行的呢?
原来是通过zend_hash_init初始化了EG(regular_list)的pDestructorint zend_init_rsrc_list(TSRMLS_D)
{
if (zend_hash_init(&EG(regular_list), 0, NULL, list_entry_destructor, 0)==SUCCESS) {
EG(regular_list).nNextFreeElement=1; /* we don't want resource id 0 */
return SUCCESS;
} else {
return FAILURE;
}
}
唯一调用zend_hash_init的地方为Zend/zend_compile.c里的init_compilervoid list_entry_destructor(void *ptr)
{
zend_rsrc_list_entry *le = (zend_rsrc_list_entry *) ptr;
zend_rsrc_list_dtors_entry *ld;
TSRMLS_FETCH();
if (zend_hash_index_find(&list_destructors, le->type, (void **) &ld)==SUCCESS) {
switch (ld->type) {
case ZEND_RESOURCE_LIST_TYPE_STD:
if (ld->list_dtor) {
(ld->list_dtor)(le->ptr);
}
break;
case ZEND_RESOURCE_LIST_TYPE_EX:
if (ld->list_dtor_ex) {
ld->list_dtor_ex(le TSRMLS_CC);
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
} else {
zend_error(E_WARNING,"Unknown list entry type in request shutdown (%d)", le->type);
}
}
而list_entry_destructor的作用就是接受zend_rsrc_list_entry指针,然后根据其类型(也就是我们定义的le_zmk_file)在list_destructors中找到其对应的zend_rsrc_list_dtors_entry,然后对传入的资源执行释放函数,例如我们这里的ld->list_dtor_ex(le TSRMLS_CC)。
而在zend_hash_index_del宏对应的删除函数中则正好执行了ht->pDestructor(p->pData)。ZEND_API int zend_hash_del_key_or_index(HashTable *ht, const char *arKey, uint nKeyLength, ulong h, int flag)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
if (flag == HASH_DEL_KEY) {
h = zend_inline_hash_func(arKey, nKeyLength);
}
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h)
&& (p->nKeyLength == nKeyLength)
&& ((p->nKeyLength == 0) /* Numeric index (short circuits the memcmp() check) */
|| !memcmp(p->arKey, arKey, nKeyLength))) { /* String index */
...
if (ht->pDestructor) {
ht->pDestructor(p->pData);
}
...
ht->nNumOfElements--;
return SUCCESS;
}
p = p->pNext;
}
return FAILURE;
}