周梦康 发表于 2016-03-01 9321 次浏览 标签 : PHP 扩展开发

PHP 扩展开发的文章,我均已更新至《TIPI》(下面的博文可能已经过时,以 TIPI 上的内容为准)。

首先回顾上节的知识点 http://mengkang.net/681.html 然后梳理最后整理的四步操作。

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_destructorsarBuckets中,该结构体中包含的该种资源释放函数指针、持久资源的释放函数指针,资源类型名称,该资源在 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)表中。

前两步我绘制了一张图来表示

PHP5 使用资源包裹第三方扩展源码解读

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<num_resource_types; 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.html

ZEND_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)pDestructor

int 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_compiler

void 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;
}

评论列表

回复 梦康 2016-03-02 02:02:34
终于写完了,好晚。